schub 0.1.6 → 0.1.7

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 (87) hide show
  1. package/dist/index.js +1246 -783
  2. package/package.json +7 -5
  3. package/skills/create-tasks/SKILL.md +2 -0
  4. package/templates/create-proposal/proposal-template.md +2 -0
  5. package/templates/create-tasks/task-template.md +8 -5
  6. package/skills/update-roadmap/SKILL.md +0 -23
  7. package/src/commands/adr.test.ts +0 -63
  8. package/src/commands/adr.ts +0 -107
  9. package/src/commands/changes.test.ts +0 -286
  10. package/src/commands/changes.ts +0 -264
  11. package/src/commands/cookbook.test.ts +0 -62
  12. package/src/commands/cookbook.ts +0 -95
  13. package/src/commands/eject.test.ts +0 -74
  14. package/src/commands/eject.ts +0 -100
  15. package/src/commands/init.test.ts +0 -260
  16. package/src/commands/init.ts +0 -187
  17. package/src/commands/project.test.ts +0 -109
  18. package/src/commands/project.ts +0 -75
  19. package/src/commands/review.test.ts +0 -80
  20. package/src/commands/review.ts +0 -231
  21. package/src/commands/tasks-create.test.ts +0 -221
  22. package/src/commands/tasks-implement.test.ts +0 -253
  23. package/src/commands/tasks-implement.ts +0 -121
  24. package/src/commands/tasks-list.test.ts +0 -177
  25. package/src/commands/tasks-update.test.ts +0 -92
  26. package/src/commands/tasks.ts +0 -269
  27. package/src/features/changes/index.test.ts +0 -166
  28. package/src/features/changes/index.ts +0 -400
  29. package/src/features/frontmatter/index.test.ts +0 -19
  30. package/src/features/frontmatter/index.ts +0 -110
  31. package/src/features/project/index.ts +0 -69
  32. package/src/features/tasks/constants.ts +0 -35
  33. package/src/features/tasks/create.test.ts +0 -52
  34. package/src/features/tasks/create.ts +0 -168
  35. package/src/features/tasks/filesystem.test.ts +0 -345
  36. package/src/features/tasks/filesystem.ts +0 -348
  37. package/src/features/tasks/graph.ts +0 -106
  38. package/src/features/tasks/index.ts +0 -22
  39. package/src/features/tasks/sorting.ts +0 -14
  40. package/src/features/tasks/template.test.ts +0 -57
  41. package/src/features/tasks/template.ts +0 -19
  42. package/src/features/tasks/worktree.ts +0 -48
  43. package/src/features/templates/index.test.ts +0 -29
  44. package/src/features/templates/index.ts +0 -18
  45. package/src/index.test.ts +0 -59
  46. package/src/index.ts +0 -222
  47. package/src/init.test.ts +0 -43
  48. package/src/init.ts +0 -27
  49. package/src/opencode.test.ts +0 -221
  50. package/src/opencode.ts +0 -231
  51. package/src/schub-root.ts +0 -33
  52. package/src/tui/app.test.tsx +0 -483
  53. package/src/tui/app.tsx +0 -354
  54. package/src/tui/clipboard.ts +0 -5
  55. package/src/tui/components/change-list.tsx +0 -69
  56. package/src/tui/components/list-item.test.tsx +0 -68
  57. package/src/tui/components/list-item.tsx +0 -208
  58. package/src/tui/components/markdown-renderer.test.ts +0 -1
  59. package/src/tui/components/markdown-renderer.ts +0 -1
  60. package/src/tui/components/plan-view.test.tsx +0 -101
  61. package/src/tui/components/plan-view.tsx +0 -98
  62. package/src/tui/components/preview-page.test.tsx +0 -69
  63. package/src/tui/components/preview-page.tsx +0 -68
  64. package/src/tui/components/proposal-detail-view.test.tsx +0 -203
  65. package/src/tui/components/proposal-detail-view.tsx +0 -140
  66. package/src/tui/components/session-view.tsx +0 -102
  67. package/src/tui/components/status-color.ts +0 -17
  68. package/src/tui/components/status-show-all-tasks-view.tsx +0 -28
  69. package/src/tui/components/status-view-data.test.ts +0 -125
  70. package/src/tui/components/status-view-data.ts +0 -556
  71. package/src/tui/components/status-view-render.tsx +0 -126
  72. package/src/tui/components/status-view-session.test.tsx +0 -324
  73. package/src/tui/components/status-view.test.tsx +0 -1570
  74. package/src/tui/components/status-view.tsx +0 -481
  75. package/src/tui/components/task-list.tsx +0 -79
  76. package/src/tui/hooks/use-refresh-interval.ts +0 -29
  77. package/src/tui/hooks/use-status-view-input.ts +0 -320
  78. package/src/tui/index.ts +0 -16
  79. package/src/tui/prompts/confirm-prompt.tsx +0 -83
  80. package/src/tui/prompts/multi-select-prompt.tsx +0 -50
  81. package/src/tui/shared/detail-tasks.ts +0 -38
  82. package/src/tui/shared/input.test.ts +0 -25
  83. package/src/tui/shared/input.ts +0 -85
  84. package/src/tui/shared/selection.test.ts +0 -11
  85. package/src/tui/shared/selection.ts +0 -7
  86. package/src/tui/terminal.test.ts +0 -46
  87. package/src/tui/terminal.ts +0 -16
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ #!/usr/bin/env node
3
4
  import { createRequire } from "node:module";
4
5
  var __create = Object.create;
5
6
  var __getProtoOf = Object.getPrototypeOf;
@@ -31356,7 +31357,7 @@ var require_core = __commonJS((exports, module) => {
31356
31357
  return match && match.index === 0;
31357
31358
  }
31358
31359
  var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
31359
- function join15(regexps, separator = "|") {
31360
+ function join18(regexps, separator = "|") {
31360
31361
  let numCaptures = 0;
31361
31362
  return regexps.map((regex2) => {
31362
31363
  numCaptures += 1;
@@ -31639,7 +31640,7 @@ var require_core = __commonJS((exports, module) => {
31639
31640
  this.exec = () => null;
31640
31641
  }
31641
31642
  const terminators = this.regexes.map((el) => el[1]);
31642
- this.matcherRe = langRe(join15(terminators), true);
31643
+ this.matcherRe = langRe(join18(terminators), true);
31643
31644
  this.lastIndex = 0;
31644
31645
  }
31645
31646
  exec(s) {
@@ -89519,6 +89520,7 @@ var resolveTemplatePath = (schubDir, templatePath, bundledPath) => {
89519
89520
  // src/features/changes/index.ts
89520
89521
  var CHANGE_ID_PATTERN = /^(?:[Cc]\d+_)?[a-z0-9]+(?:-[a-z0-9]+)*$/;
89521
89522
  var SUMMARY_FALLBACK = "No summary provided.";
89523
+ var ARCHIVED_CHANGE_STATUSES = new Set(["archived", "rejected"]);
89522
89524
  var isDirectory2 = (path) => {
89523
89525
  try {
89524
89526
  return statSync5(path).isDirectory();
@@ -89533,6 +89535,7 @@ var readFrontmatterString = (data, key) => {
89533
89535
  }
89534
89536
  return "";
89535
89537
  };
89538
+ var normalizeStatusValue = (status) => status.trim().toLowerCase();
89536
89539
  var parseProposal = (content, changeId) => {
89537
89540
  const titleMatch = content.match(/^#\s+Proposal\s+-\s+(.*)$/m);
89538
89541
  const { data } = readFrontmatter(content);
@@ -89570,6 +89573,22 @@ var normalizeChangeId = (value) => {
89570
89573
  return trimmed;
89571
89574
  };
89572
89575
  var isValidChangeId = (value) => CHANGE_ID_PATTERN.test(value.trim());
89576
+ var resolveChangePaths = (schubDir, changeId) => {
89577
+ const changeDir = join4(schubDir, "changes", changeId);
89578
+ const proposalPath = join4(changeDir, "proposal.md");
89579
+ if (existsSync(proposalPath)) {
89580
+ return { changeDir, proposalPath, isArchived: false };
89581
+ }
89582
+ const archiveDir = join4(schubDir, "archive", "changes", changeId);
89583
+ const archiveProposalPath = join4(archiveDir, "proposal.md");
89584
+ if (existsSync(archiveProposalPath)) {
89585
+ return { changeDir: archiveDir, proposalPath: archiveProposalPath, isArchived: true };
89586
+ }
89587
+ throw new Error(`Required change files missing:
89588
+ - ${proposalPath}
89589
+ - ${archiveProposalPath}
89590
+ Create the change proposal before scaffolding docs.`);
89591
+ };
89573
89592
  var readChangeSummary = (schubDir, changeId) => {
89574
89593
  const trimmed = changeId.trim();
89575
89594
  if (!trimmed) {
@@ -89579,20 +89598,15 @@ var readChangeSummary = (schubDir, changeId) => {
89579
89598
  throw new Error(`Invalid change-id '${changeId}'. Use kebab-case or a C-prefixed id (e.g., C1_add-user-auth).`);
89580
89599
  }
89581
89600
  const normalized = normalizeChangeId(trimmed);
89582
- const changeDir = join4(schubDir, "changes", normalized);
89583
- const proposalPath = join4(changeDir, "proposal.md");
89584
- if (!existsSync(proposalPath)) {
89585
- throw new Error(`Required change files missing:
89586
- - ${proposalPath}
89587
- Create the change proposal before scaffolding docs.`);
89588
- }
89589
- const content = readFileSync4(proposalPath, "utf8");
89601
+ const resolved = resolveChangePaths(schubDir, normalized);
89602
+ const content = readFileSync4(resolved.proposalPath, "utf8");
89590
89603
  const parsed = parseProposal(content, normalized);
89591
89604
  return {
89592
89605
  changeId: normalized,
89593
89606
  changeTitle: parsed.title,
89594
- changeDir,
89595
- proposalPath
89607
+ changeDir: resolved.changeDir,
89608
+ proposalPath: resolved.proposalPath,
89609
+ isArchived: resolved.isArchived
89596
89610
  };
89597
89611
  };
89598
89612
  var readChangeDetail = (schubDir, changeId) => {
@@ -89607,6 +89621,7 @@ var readChangeDetail = (schubDir, changeId) => {
89607
89621
  summary: detail.summary
89608
89622
  };
89609
89623
  };
89624
+ var isArchivedStatus = (value) => ARCHIVED_CHANGE_STATUSES.has(normalizeStatusValue(value));
89610
89625
  var updateChangeStatus = (schubDir, changeId, status) => {
89611
89626
  const nextStatus = status.trim();
89612
89627
  if (!nextStatus) {
@@ -89621,6 +89636,38 @@ var updateChangeStatus = (schubDir, changeId, status) => {
89621
89636
  Add a 'status' field in frontmatter before updating status.`);
89622
89637
  }
89623
89638
  const updated = updateFrontmatterValue(content, "status", nextStatus);
89639
+ const shouldArchive = isArchivedStatus(nextStatus);
89640
+ const archiveRoot = join4(schubDir, "archive", "changes");
89641
+ const archivePath = join4(archiveRoot, summary.changeId);
89642
+ const activePath = join4(schubDir, "changes", summary.changeId);
89643
+ if (shouldArchive && !summary.isArchived) {
89644
+ if (existsSync(archivePath)) {
89645
+ throw new Error(`Archive already exists: ${archivePath}`);
89646
+ }
89647
+ mkdirSync(archiveRoot, { recursive: true });
89648
+ writeFileSync(summary.proposalPath, updated, "utf8");
89649
+ renameSync(summary.changeDir, archivePath);
89650
+ return {
89651
+ changeId: summary.changeId,
89652
+ proposalPath: join4(archivePath, "proposal.md"),
89653
+ previousStatus,
89654
+ status: nextStatus
89655
+ };
89656
+ }
89657
+ if (!shouldArchive && summary.isArchived) {
89658
+ if (existsSync(activePath)) {
89659
+ throw new Error(`Change already exists: ${activePath}`);
89660
+ }
89661
+ mkdirSync(join4(schubDir, "changes"), { recursive: true });
89662
+ writeFileSync(summary.proposalPath, updated, "utf8");
89663
+ renameSync(summary.changeDir, activePath);
89664
+ return {
89665
+ changeId: summary.changeId,
89666
+ proposalPath: join4(activePath, "proposal.md"),
89667
+ previousStatus,
89668
+ status: nextStatus
89669
+ };
89670
+ }
89624
89671
  writeFileSync(summary.proposalPath, updated, "utf8");
89625
89672
  return {
89626
89673
  changeId: summary.changeId,
@@ -89631,19 +89678,16 @@ Add a 'status' field in frontmatter before updating status.`);
89631
89678
  };
89632
89679
  var archiveChange = (schubDir, changeId) => {
89633
89680
  const summary = readChangeSummary(schubDir, changeId);
89634
- const archiveRoot = join4(schubDir, "archive", "changes");
89635
- const archivePath = join4(archiveRoot, summary.changeId);
89636
- if (existsSync(archivePath)) {
89681
+ const archivePath = join4(schubDir, "archive", "changes", summary.changeId);
89682
+ if (summary.isArchived) {
89637
89683
  throw new Error(`Archive already exists: ${archivePath}`);
89638
89684
  }
89639
- mkdirSync(archiveRoot, { recursive: true });
89640
- const updated = updateChangeStatus(schubDir, summary.changeId, "Archived");
89641
- renameSync(summary.changeDir, archivePath);
89685
+ const archived = updateChangeStatus(schubDir, summary.changeId, "Archived");
89642
89686
  return {
89643
- changeId: updated.changeId,
89644
- previousStatus: updated.previousStatus,
89645
- status: updated.status,
89646
- proposalPath: join4(archivePath, "proposal.md"),
89687
+ changeId: archived.changeId,
89688
+ previousStatus: archived.previousStatus,
89689
+ status: archived.status,
89690
+ proposalPath: archived.proposalPath,
89647
89691
  archivePath
89648
89692
  };
89649
89693
  };
@@ -89693,6 +89737,7 @@ var STATUS_GROUPS = [
89693
89737
  { label: "Accepted", match: "accepted" },
89694
89738
  { label: "Implementing", match: "implement" },
89695
89739
  { label: "Done", match: "done" },
89740
+ { label: "Rejected", match: "reject" },
89696
89741
  { label: "Archived", match: "archiv" }
89697
89742
  ];
89698
89743
  var normalizeStatusLabel = (status) => {
@@ -89938,8 +89983,54 @@ var runAdrCreate = (args, startDir) => {
89938
89983
  `);
89939
89984
  };
89940
89985
 
89986
+ // src/features/tasks/checklist.ts
89987
+ var CHECKLIST_PATTERN = /^(\s*-\s+\[)( |x|X)(\].*)$/;
89988
+ var toggleChecklistItem = (content, itemNumber) => {
89989
+ if (!Number.isInteger(itemNumber) || itemNumber < 1) {
89990
+ throw new Error("Checklist item number must be a positive integer.");
89991
+ }
89992
+ const lines = content.split(/\r?\n/);
89993
+ let inSteps = false;
89994
+ let sawSteps = false;
89995
+ let currentItem = 0;
89996
+ for (let index = 0;index < lines.length; index += 1) {
89997
+ const line = lines[index];
89998
+ const headingMatch = line.match(/^##\s+(.*)$/);
89999
+ if (headingMatch) {
90000
+ const heading = headingMatch[1].trim().toLowerCase();
90001
+ if (heading === "steps") {
90002
+ inSteps = true;
90003
+ sawSteps = true;
90004
+ } else if (inSteps) {
90005
+ break;
90006
+ }
90007
+ continue;
90008
+ }
90009
+ if (!inSteps) {
90010
+ continue;
90011
+ }
90012
+ const checklistMatch = line.match(CHECKLIST_PATTERN);
90013
+ if (!checklistMatch) {
90014
+ continue;
90015
+ }
90016
+ currentItem += 1;
90017
+ if (currentItem !== itemNumber) {
90018
+ continue;
90019
+ }
90020
+ const isChecked = checklistMatch[2].toLowerCase() === "x";
90021
+ const nextValue = isChecked ? " " : "x";
90022
+ lines[index] = `${checklistMatch[1]}${nextValue}${checklistMatch[3]}`;
90023
+ return lines.join(`
90024
+ `);
90025
+ }
90026
+ if (!sawSteps) {
90027
+ throw new Error("No Steps checklist found.");
90028
+ }
90029
+ throw new Error(`Checklist item ${itemNumber} not found.`);
90030
+ };
89941
90031
  // src/features/tasks/constants.ts
89942
- var TASK_STATUSES = ["backlog", "reviewed", "wip", "blocked", "done", "archived"];
90032
+ var TASK_STATUSES = ["backlog", "reviewed", "wip", "blocked", "done", "rejected", "archived"];
90033
+ var DEFAULT_TASK_STATUSES = ["backlog", "reviewed", "wip", "blocked", "done", "archived"];
89943
90034
  // src/features/tasks/create.ts
89944
90035
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
89945
90036
  import { join as join6 } from "node:path";
@@ -90182,7 +90273,7 @@ var parseTaskFile = (filePath, fallback) => {
90182
90273
  ...checklistCounts ?? {}
90183
90274
  };
90184
90275
  };
90185
- var loadTaskFiles = (schubDir, statuses = TASK_STATUSES) => {
90276
+ var loadTaskFiles = (schubDir, statuses = DEFAULT_TASK_STATUSES) => {
90186
90277
  const tasksRoot = join7(schubDir, "tasks");
90187
90278
  if (!existsSync4(tasksRoot) || !isDirectory3(tasksRoot)) {
90188
90279
  return [];
@@ -90220,7 +90311,7 @@ var loadTaskFiles = (schubDir, statuses = TASK_STATUSES) => {
90220
90311
  }
90221
90312
  return tasks;
90222
90313
  };
90223
- var listTasks = (schubDir, statuses = TASK_STATUSES) => {
90314
+ var listTasks = (schubDir, statuses = DEFAULT_TASK_STATUSES) => {
90224
90315
  const tasks = loadTaskFiles(schubDir, statuses).map((task) => ({
90225
90316
  id: task.id,
90226
90317
  title: task.title,
@@ -90232,7 +90323,7 @@ var listTasks = (schubDir, statuses = TASK_STATUSES) => {
90232
90323
  }));
90233
90324
  return tasks.sort(compareTasks);
90234
90325
  };
90235
- var loadTaskDependencies = (schubDir, statuses = TASK_STATUSES) => {
90326
+ var loadTaskDependencies = (schubDir, statuses = DEFAULT_TASK_STATUSES) => {
90236
90327
  const tasks = loadTaskFiles(schubDir, statuses).map((task) => {
90237
90328
  const dependsOn = task.parsedFile.dependsOn.sort(compareTaskIds);
90238
90329
  return {
@@ -90249,7 +90340,7 @@ var loadTaskDependencies = (schubDir, statuses = TASK_STATUSES) => {
90249
90340
  });
90250
90341
  return tasks.sort(compareTasks);
90251
90342
  };
90252
- var listTasksForChange = (schubDir, changeId, statuses = TASK_STATUSES) => {
90343
+ var listTasksForChange = (schubDir, changeId, statuses = DEFAULT_TASK_STATUSES) => {
90253
90344
  const normalizedChangeId = changeId.trim();
90254
90345
  const tasks = loadTaskFiles(schubDir, statuses).filter((task) => task.parsedFile.changeId === normalizedChangeId).map((task) => ({
90255
90346
  id: task.id,
@@ -90281,7 +90372,7 @@ var updateTaskStatuses = (schubDir, taskIds, status) => {
90281
90372
  };
90282
90373
  });
90283
90374
  };
90284
- var ACTIVE_TASK_STATUSES = TASK_STATUSES.filter((status) => status !== "archived");
90375
+ var ACTIVE_TASK_STATUSES = TASK_STATUSES.filter((status) => status !== "archived" && status !== "rejected");
90285
90376
  var assignTaskToWip = (schubDir, taskId) => {
90286
90377
  const normalizedId = taskId.trim().toUpperCase();
90287
90378
  if (!normalizedId) {
@@ -90339,43 +90430,6 @@ var archiveTasksForChange = (schubDir, changeId) => {
90339
90430
  });
90340
90431
  };
90341
90432
  // src/features/tasks/graph.ts
90342
- var compareText = (left2, right2) => left2.localeCompare(right2, undefined, { sensitivity: "base" });
90343
- var taskSortTitle = (task) => {
90344
- const title = trimTaskTitle(task.title);
90345
- return title || task.id;
90346
- };
90347
- var compareTaskTitles = (left2, right2) => {
90348
- const titleCompare = compareText(taskSortTitle(left2), taskSortTitle(right2));
90349
- if (titleCompare !== 0) {
90350
- return titleCompare;
90351
- }
90352
- return compareText(left2.id, right2.id);
90353
- };
90354
- var buildTaskGraph = (tasks) => {
90355
- const tasksById = new Map(tasks.map((task) => [task.id, task]));
90356
- const nodes = new Map;
90357
- for (const task of tasks) {
90358
- const uniqueDependencies = Array.from(new Set(task.dependsOn)).sort(compareTaskIds);
90359
- const dependencyIds = uniqueDependencies.filter((dependency) => tasksById.has(dependency));
90360
- nodes.set(task.id, {
90361
- ...task,
90362
- dependencyIds
90363
- });
90364
- }
90365
- const childrenById = new Map;
90366
- for (const node of nodes.values()) {
90367
- for (const dependencyId of node.dependencyIds) {
90368
- const siblings = childrenById.get(dependencyId) ?? [];
90369
- siblings.push(node);
90370
- childrenById.set(dependencyId, siblings);
90371
- }
90372
- }
90373
- for (const [id, children] of childrenById) {
90374
- childrenById.set(id, children.sort(compareTaskTitles));
90375
- }
90376
- const roots = Array.from(nodes.values()).filter((node) => node.dependencyIds.length === 0).sort(compareTaskTitles);
90377
- return { roots, childrenById };
90378
- };
90379
90433
  var trimTaskTitle = (title, maxLength = 40) => {
90380
90434
  const trimmed = title.trim();
90381
90435
  if (trimmed.length <= maxLength) {
@@ -90386,41 +90440,6 @@ var trimTaskTitle = (title, maxLength = 40) => {
90386
90440
  }
90387
90441
  return `${trimmed.slice(0, maxLength - 1).trimEnd()}…`;
90388
90442
  };
90389
- var renderTaskGraphLines = (graph) => {
90390
- const lines = [];
90391
- const renderedIds = new Set;
90392
- const renderNode = (node, prefix, isLast, isRoot) => {
90393
- const connector = isRoot ? "" : isLast ? "└─ " : "├─ ";
90394
- if (renderedIds.has(node.id)) {
90395
- lines.push({
90396
- text: `${prefix}${connector}${node.id} ↩`,
90397
- status: node.status,
90398
- isDuplicate: true,
90399
- checklistRemaining: node.checklistRemaining,
90400
- checklistTotal: node.checklistTotal
90401
- });
90402
- return;
90403
- }
90404
- renderedIds.add(node.id);
90405
- const title = trimTaskTitle(node.title);
90406
- lines.push({
90407
- text: `${prefix}${connector}${node.id} ${title}`,
90408
- status: node.status,
90409
- isDuplicate: false,
90410
- checklistRemaining: node.checklistRemaining,
90411
- checklistTotal: node.checklistTotal
90412
- });
90413
- const children = graph.childrenById.get(node.id) ?? [];
90414
- const nextPrefix = isRoot ? `${prefix} ` : `${prefix}${isLast ? " " : "│ "}`;
90415
- children.forEach((child, index) => {
90416
- renderNode(child, nextPrefix, index === children.length - 1, false);
90417
- });
90418
- };
90419
- graph.roots.forEach((root, index) => {
90420
- renderNode(root, "", index === graph.roots.length - 1, true);
90421
- });
90422
- return lines;
90423
- };
90424
90443
  // src/features/tasks/template.ts
90425
90444
  var TEMPLATE_MARKERS = [
90426
90445
  "{{CHANGE_ID}}",
@@ -90430,7 +90449,8 @@ var TEMPLATE_MARKERS = [
90430
90449
  "[no|yes]",
90431
90450
  "[What this task accomplishes, in one paragraph.]",
90432
90451
  "[Included work]",
90433
- "[Explicitly excluded work]"
90452
+ "[Explicitly excluded work]",
90453
+ '[Describe documentation updates required or state "No documentation updates required."]'
90434
90454
  ];
90435
90455
  var TEMPLATE_PATTERNS = [/\[Objective \d+ pass\/fail condition\]/, /^\s*-\s+\[\s\]\s+Step\s+\d+/m, /^\s*-\s+\.\.\./m];
90436
90456
  var hasUnimplementedTemplateTokens = (content) => {
@@ -90668,11 +90688,16 @@ var parseChangeArchiveOptions = (args) => {
90668
90688
  return options;
90669
90689
  };
90670
90690
  var parseChangeListOptions = (args) => {
90691
+ let showAll = false;
90671
90692
  const unknown = [];
90672
90693
  const rejectUnsupported = (flag) => {
90673
90694
  throw new Error(`Unsupported option: ${flag}.`);
90674
90695
  };
90675
90696
  for (const arg of args) {
90697
+ if (arg === "--show-all") {
90698
+ showAll = true;
90699
+ continue;
90700
+ }
90676
90701
  if (arg === "--schub-root" || arg === "--agent-root") {
90677
90702
  rejectUnsupported(arg);
90678
90703
  }
@@ -90687,6 +90712,37 @@ var parseChangeListOptions = (args) => {
90687
90712
  if (unknown.length > 0) {
90688
90713
  throw new Error(`Unknown option(s): ${unknown.join(", ")}`);
90689
90714
  }
90715
+ return { showAll };
90716
+ };
90717
+ var compareChangeIds = (left2, right2) => {
90718
+ const leftNumber = Number(left2.id.match(/\d+/)?.[0] ?? Number.POSITIVE_INFINITY);
90719
+ const rightNumber = Number(right2.id.match(/\d+/)?.[0] ?? Number.POSITIVE_INFINITY);
90720
+ if (leftNumber !== rightNumber) {
90721
+ return leftNumber - rightNumber;
90722
+ }
90723
+ return left2.id.localeCompare(right2.id);
90724
+ };
90725
+ var groupChangesByStatus = (changes) => {
90726
+ const grouped = new Map;
90727
+ for (const change of changes) {
90728
+ const existing = grouped.get(change.statusLabel);
90729
+ if (existing) {
90730
+ existing.items.push(change);
90731
+ continue;
90732
+ }
90733
+ grouped.set(change.statusLabel, { label: change.statusLabel, order: change.statusOrder, items: [change] });
90734
+ }
90735
+ const sorted = Array.from(grouped.values()).sort((left2, right2) => {
90736
+ const orderCompare = left2.order - right2.order;
90737
+ if (orderCompare !== 0) {
90738
+ return orderCompare;
90739
+ }
90740
+ return left2.label.localeCompare(right2.label);
90741
+ });
90742
+ for (const group of sorted) {
90743
+ group.items.sort(compareChangeIds);
90744
+ }
90745
+ return sorted;
90690
90746
  };
90691
90747
  var runChangesCreate = (args, startDir) => {
90692
90748
  const options = parseChangeCreateOptions(args);
@@ -90712,8 +90768,20 @@ var runChangesArchive = (args, startDir) => {
90712
90768
  `);
90713
90769
  };
90714
90770
  var runChangesList = (args, startDir) => {
90715
- parseChangeListOptions(args);
90771
+ const options = parseChangeListOptions(args);
90716
90772
  const schubDir = resolveChangeRoot(startDir);
90773
+ if (options.showAll) {
90774
+ const changes2 = listChangeOverview(schubDir);
90775
+ const groups = groupChangesByStatus(changes2);
90776
+ const lines2 = groups.flatMap((group) => [
90777
+ group.label,
90778
+ ...group.items.map((change) => `${change.id} ${change.title} (${change.statusLabel})`)
90779
+ ]);
90780
+ process.stdout.write(`${lines2.join(`
90781
+ `)}
90782
+ `);
90783
+ return;
90784
+ }
90717
90785
  const changes = listChanges(schubDir);
90718
90786
  const lines = changes.map((change) => `${change.id} ${change.title} (${change.status})`);
90719
90787
  process.stdout.write(`${lines.join(`
@@ -99534,12 +99602,153 @@ var runReviewComplete = (args, startDir) => {
99534
99602
  `);
99535
99603
  };
99536
99604
 
99605
+ // src/commands/tasks-check.ts
99606
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
99607
+ import { dirname as dirname10, join as join15 } from "node:path";
99608
+ var parseTaskCheckOptions = (args) => {
99609
+ let taskId;
99610
+ let itemValue;
99611
+ const unknown = [];
99612
+ const rejectUnsupported = (flag) => {
99613
+ throw new Error(`Unsupported option: ${flag}.`);
99614
+ };
99615
+ for (let index = 0;index < args.length; index += 1) {
99616
+ const arg = args[index];
99617
+ if (arg === "--id") {
99618
+ taskId = args[index + 1];
99619
+ if (!taskId) {
99620
+ throw new Error("Missing value for --id.");
99621
+ }
99622
+ index += 1;
99623
+ continue;
99624
+ }
99625
+ if (arg.startsWith("--id=")) {
99626
+ taskId = arg.slice("--id=".length);
99627
+ continue;
99628
+ }
99629
+ if (arg === "--item") {
99630
+ itemValue = args[index + 1];
99631
+ if (itemValue === undefined) {
99632
+ throw new Error("Missing value for --item.");
99633
+ }
99634
+ index += 1;
99635
+ continue;
99636
+ }
99637
+ if (arg.startsWith("--item=")) {
99638
+ itemValue = arg.slice("--item=".length);
99639
+ continue;
99640
+ }
99641
+ if (arg === "--schub-root" || arg === "--agent-root") {
99642
+ rejectUnsupported(arg);
99643
+ }
99644
+ if (arg.startsWith("--schub-root=")) {
99645
+ rejectUnsupported("--schub-root");
99646
+ }
99647
+ if (arg.startsWith("--agent-root=")) {
99648
+ rejectUnsupported("--agent-root");
99649
+ }
99650
+ unknown.push(arg);
99651
+ }
99652
+ if (unknown.length > 0) {
99653
+ throw new Error(`Unknown option(s): ${unknown.join(", ")}`);
99654
+ }
99655
+ if (!taskId) {
99656
+ throw new Error("Provide --id.");
99657
+ }
99658
+ if (itemValue === undefined || itemValue.trim() === "") {
99659
+ throw new Error("Provide --item.");
99660
+ }
99661
+ const item = Number(itemValue);
99662
+ if (!Number.isInteger(item) || item < 1) {
99663
+ throw new Error(`Invalid item '${itemValue}'. Use a positive integer.`);
99664
+ }
99665
+ const options = {
99666
+ taskId: taskId.trim().toUpperCase(),
99667
+ item
99668
+ };
99669
+ return options;
99670
+ };
99671
+ var findTaskById = (schubDir, taskId) => {
99672
+ const normalizedId = taskId.trim().toUpperCase();
99673
+ const tasks = listTasks(schubDir, TASK_STATUSES);
99674
+ const task = tasks.find((entry) => entry.id === normalizedId);
99675
+ if (!task) {
99676
+ throw new Error(`Task ${normalizedId} not found.`);
99677
+ }
99678
+ return task;
99679
+ };
99680
+ var runTasksCheck = (schubDir, args) => {
99681
+ if (!schubDir) {
99682
+ throw new Error("No .schub directory found.");
99683
+ }
99684
+ const options = parseTaskCheckOptions(args);
99685
+ const task = findTaskById(schubDir, options.taskId);
99686
+ const repoRoot = dirname10(schubDir);
99687
+ const taskPath = join15(repoRoot, task.path);
99688
+ const content = readFileSync12(taskPath, "utf8");
99689
+ const updated = toggleChecklistItem(content, options.item);
99690
+ writeFileSync7(taskPath, updated, "utf8");
99691
+ process.stdout.write(`[OK] Updated task ${task.id}: item ${options.item}
99692
+ `);
99693
+ };
99537
99694
  // src/commands/tasks-implement.ts
99538
- import { dirname as dirname10 } from "node:path";
99695
+ import { dirname as dirname11, join as join17 } from "node:path";
99696
+
99697
+ // src/features/context/prepare-implement-context.ts
99698
+ import { readdirSync as readdirSync7, readFileSync as readFileSync13 } from "node:fs";
99699
+ import { basename as basename5, join as join16 } from "node:path";
99700
+ var listMarkdownFiles = (root, relativeRoot = "") => {
99701
+ const entries = readdirSync7(join16(root, relativeRoot), { withFileTypes: true });
99702
+ const paths = [];
99703
+ for (const entry of entries) {
99704
+ if (entry.isDirectory()) {
99705
+ paths.push(...listMarkdownFiles(root, join16(relativeRoot, entry.name)));
99706
+ continue;
99707
+ }
99708
+ if (entry.isFile() && entry.name.endsWith(".md")) {
99709
+ paths.push(join16(relativeRoot, entry.name));
99710
+ }
99711
+ }
99712
+ return paths;
99713
+ };
99714
+ var readChangeMarkdown = (schubDir, changeId) => {
99715
+ const changeDir = join16(schubDir, "changes", changeId);
99716
+ const entries = listMarkdownFiles(changeDir);
99717
+ return entries.sort().map((relativePath) => {
99718
+ const filePath = join16(changeDir, relativePath);
99719
+ return {
99720
+ filename: basename5(relativePath),
99721
+ content: readFileSync13(filePath, "utf8")
99722
+ };
99723
+ });
99724
+ };
99725
+ var buildPrompt = (taskContent, changeMarkdown) => {
99726
+ const sections = [taskContent.trimEnd()];
99727
+ for (const entry of changeMarkdown) {
99728
+ const content = entry.content.trimEnd();
99729
+ sections.push(`# ${entry.filename}
99730
+
99731
+ ${content}`);
99732
+ }
99733
+ return sections.join(`
99734
+
99735
+ `);
99736
+ };
99737
+ var prepareImplementContext = (schubDir, taskPath) => {
99738
+ const taskContent = readFileSync13(taskPath, "utf8");
99739
+ const { data } = readFrontmatter(taskContent);
99740
+ const changeIdValue = data.change_id;
99741
+ const changeId = typeof changeIdValue === "string" ? changeIdValue.trim() : "";
99742
+ if (!changeId) {
99743
+ return taskContent.trimEnd();
99744
+ }
99745
+ const changeMarkdown = readChangeMarkdown(schubDir, changeId);
99746
+ return buildPrompt(taskContent, changeMarkdown);
99747
+ };
99539
99748
 
99540
99749
  // src/opencode.ts
99541
99750
  import { spawn, spawnSync as spawnSync3 } from "node:child_process";
99542
- import { basename as basename5 } from "node:path";
99751
+ import { basename as basename6 } from "node:path";
99543
99752
  var opencodeSpawnOptions = { stdio: "ignore", detached: true };
99544
99753
  var spawnOpencode = (command2, args, spawner, overrides) => {
99545
99754
  const child = spawner(command2, args, { ...opencodeSpawnOptions, ...overrides });
@@ -99556,7 +99765,7 @@ var runOpencodeCommand = (args) => {
99556
99765
  var buildSessionTitle = (repoRoot, label, id, detail) => {
99557
99766
  const trimmedDetail = detail?.trim();
99558
99767
  const suffix = trimmedDetail ? ` ${trimmedDetail}` : "";
99559
- return `(${basename5(repoRoot)}) ${label} ${id}${suffix}`;
99768
+ return `(${basename6(repoRoot)}) ${label} ${id}${suffix}`;
99560
99769
  };
99561
99770
  var formatChangeId = (value) => {
99562
99771
  const match = value.match(/^([Cc]\d+)_/);
@@ -99569,11 +99778,16 @@ var buildReviewCommand = (changeId, repoRoot, changeTitle) => {
99569
99778
  args: ["--prompt", `review ${changeId}`, "--title", title]
99570
99779
  };
99571
99780
  };
99572
- var buildImplementCommand = (taskId, repoRoot, taskTitle) => {
99573
- const title = buildSessionTitle(repoRoot, "Implement", taskId, taskTitle);
99781
+ var buildImplementCommand = (taskId, repoRoot, options = {}) => {
99782
+ const title = buildSessionTitle(repoRoot, "Implement", taskId, options.title);
99783
+ const args = ["run", "implement", taskId];
99784
+ if (typeof options.prompt === "string" && options.prompt.trim() !== "") {
99785
+ args.push("--prompt", options.prompt);
99786
+ }
99787
+ args.push("--title", title);
99574
99788
  return {
99575
99789
  command: "opencode",
99576
- args: ["run", "implement", taskId, "--title", title]
99790
+ args
99577
99791
  };
99578
99792
  };
99579
99793
  var buildCreateTasksCommand = (changeId, repoRoot) => {
@@ -99646,8 +99860,8 @@ var launchOpencodeReview = (changeId, repoRoot, overrides, spawner = spawn) => {
99646
99860
  const { command: command2, args } = buildReviewCommand(changeId, repoRoot);
99647
99861
  return spawnOpencode(command2, args, spawner, overrides);
99648
99862
  };
99649
- var launchOpencodeImplement = (taskId, repoRoot, taskTitle, overrides, spawner = spawn) => {
99650
- const { command: command2, args } = buildImplementCommand(taskId, repoRoot, taskTitle);
99863
+ var launchOpencodeImplement = (taskId, repoRoot, options, overrides, spawner = spawn) => {
99864
+ const { command: command2, args } = buildImplementCommand(taskId, repoRoot, options);
99651
99865
  return spawnOpencode(command2, args, spawner, overrides);
99652
99866
  };
99653
99867
  var launchOpencodeCreateTasks = (changeId, repoRoot, overrides, spawner = spawn) => {
@@ -99734,7 +99948,7 @@ var runTasksImplement = (schubDir, args) => {
99734
99948
  }
99735
99949
  const options = parseTaskImplementOptions(args);
99736
99950
  const assigned = assignTaskToWip(schubDir, options.taskId);
99737
- const repoRoot = dirname10(schubDir);
99951
+ const repoRoot = dirname11(schubDir);
99738
99952
  let launchRoot = repoRoot;
99739
99953
  if (options.mode === "worktree") {
99740
99954
  const worktree = createTaskWorktree({
@@ -99746,7 +99960,9 @@ var runTasksImplement = (schubDir, args) => {
99746
99960
  launchRoot = worktree.worktreePath;
99747
99961
  }
99748
99962
  }
99749
- launchOpencodeImplement(options.taskId, repoRoot, assigned.title, {
99963
+ const taskPath = join17(repoRoot, assigned.path);
99964
+ const prompt = prepareImplementContext(schubDir, taskPath);
99965
+ launchOpencodeImplement(options.taskId, repoRoot, { title: assigned.title, prompt }, {
99750
99966
  env: { ...process.env, SCHUB_CWD: launchRoot }
99751
99967
  });
99752
99968
  process.stdout.write(`[OK] Assigned task ${assigned.id}: ${assigned.status}
@@ -99756,7 +99972,7 @@ var runTasksImplement = (schubDir, args) => {
99756
99972
  // src/commands/tasks.ts
99757
99973
  var parseStatusFilter = (value) => {
99758
99974
  if (!value) {
99759
- return [...TASK_STATUSES];
99975
+ return [...DEFAULT_TASK_STATUSES];
99760
99976
  }
99761
99977
  const normalized = value.split(",").map((status) => status.trim().toLowerCase()).filter(Boolean);
99762
99978
  const allowed = new Set(TASK_STATUSES);
@@ -99867,7 +100083,7 @@ var parseTaskCreateOptions = (args) => {
99867
100083
  const options = { changeId, status, titles, overwrite };
99868
100084
  return options;
99869
100085
  };
99870
- var BACKLOG_UPDATE_STATUSES = ["reviewed", "archived"];
100086
+ var BACKLOG_UPDATE_STATUSES = ["reviewed", "rejected", "archived"];
99871
100087
  var parseTaskUpdateOptions = (args) => {
99872
100088
  let statusValue;
99873
100089
  const taskIds = [];
@@ -99921,7 +100137,7 @@ var parseTaskUpdateOptions = (args) => {
99921
100137
  }
99922
100138
  const normalizedStatus = statusValue.trim().toLowerCase();
99923
100139
  if (!BACKLOG_UPDATE_STATUSES.includes(normalizedStatus)) {
99924
- throw new Error(`Invalid status '${statusValue}'. Use reviewed or archived.`);
100140
+ throw new Error(`Invalid status '${statusValue}'. Use reviewed, rejected, or archived.`);
99925
100141
  }
99926
100142
  const normalizedIds = taskIds.map((id) => id.trim()).filter(Boolean);
99927
100143
  if (normalizedIds.length === 0) {
@@ -99973,35 +100189,37 @@ var runTasksCreate = (args, startDir) => {
99973
100189
  }
99974
100190
  };
99975
100191
  // src/tui/index.ts
99976
- var import_react63 = __toESM(require_react(), 1);
100192
+ var import_react62 = __toESM(require_react(), 1);
99977
100193
 
99978
100194
  // src/tui/app.tsx
99979
- import { readFileSync as readFileSync13 } from "node:fs";
100195
+ import { readFileSync as readFileSync15 } from "node:fs";
99980
100196
  import { homedir as homedir2 } from "node:os";
99981
- import { basename as basename6, dirname as dirname13, resolve as resolve12 } from "node:path";
99982
- var import_react62 = __toESM(require_react(), 1);
100197
+ import { basename as basename7, dirname as dirname14, resolve as resolve13 } from "node:path";
100198
+ var import_react61 = __toESM(require_react(), 1);
99983
100199
  // package.json
99984
100200
  var package_default = {
99985
100201
  name: "schub",
99986
- version: "0.1.6",
100202
+ version: "0.1.7",
99987
100203
  type: "module",
99988
100204
  bin: {
99989
- schub: "./src/index.ts"
100205
+ schub: "./dist/index.js"
99990
100206
  },
99991
100207
  files: [
99992
100208
  "dist",
99993
- "src",
99994
100209
  "skills",
99995
100210
  "templates"
99996
100211
  ],
99997
100212
  scripts: {
99998
100213
  schub: "bun ./src/index.ts",
99999
- prepublishOnly: "npm run build",
100000
- build: "bun build ./src/index.ts --outdir dist --target node",
100214
+ prepublishOnly: "bun run build",
100215
+ build: 'bun build ./src/index.ts --outfile dist/index.js --target node --banner "#!/usr/bin/env node"',
100001
100216
  lint: "bunx @biomejs/biome lint .",
100002
100217
  format: "bunx @biomejs/biome format --write .",
100003
100218
  test: "bun test"
100004
100219
  },
100220
+ engines: {
100221
+ node: ">=24"
100222
+ },
100005
100223
  dependencies: {
100006
100224
  "@inkjs/ui": "^2.0.0",
100007
100225
  chalk: "^5.6.2",
@@ -100028,108 +100246,240 @@ var copyToClipboard = (value) => {
100028
100246
  spawnSync4("pbcopy", [], { input: value });
100029
100247
  };
100030
100248
 
100031
- // src/tui/components/plan-view.tsx
100032
- var import_react59 = __toESM(require_react(), 1);
100033
-
100034
- // src/tui/hooks/use-refresh-interval.ts
100035
- var import_react58 = __toESM(require_react(), 1);
100036
- var useRefreshInterval = ({ enabled = true, refreshIntervalMs, onRefresh }) => {
100037
- const refreshRef = import_react58.default.useRef(onRefresh);
100038
- import_react58.default.useEffect(() => {
100039
- refreshRef.current = onRefresh;
100040
- }, [onRefresh]);
100041
- import_react58.default.useEffect(() => {
100042
- if (!enabled) {
100043
- return;
100044
- }
100045
- const interval = setInterval(() => {
100046
- refreshRef.current();
100047
- }, refreshIntervalMs);
100048
- return () => {
100049
- clearInterval(interval);
100050
- };
100051
- }, [enabled, refreshIntervalMs]);
100249
+ // src/tui/components/status-color.ts
100250
+ var STATUS_COLORS = {
100251
+ draft: "gray",
100252
+ "in-review": "yellow",
100253
+ accepted: "blue",
100254
+ "no-tasks": "gray",
100255
+ wip: "blue",
100256
+ done: "green",
100257
+ archived: "gray",
100258
+ backlog: "gray",
100259
+ reviewed: "yellow",
100260
+ ready: "yellow",
100261
+ blocked: "red",
100262
+ rejected: "red"
100263
+ };
100264
+ var statusColor = (status) => {
100265
+ return status ? STATUS_COLORS[status] : "gray";
100052
100266
  };
100053
100267
 
100054
- // src/tui/components/plan-view.tsx
100268
+ // src/tui/components/list-item.tsx
100055
100269
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
100056
- var PLAN_TASK_STATUSES = ["backlog", "reviewed", "wip", "blocked"];
100057
- var DEFAULT_REFRESH_INTERVAL_MS = 1000;
100058
- var buildPlanData = (schubDir) => {
100059
- if (!schubDir) {
100060
- return { visibleTasks: [], graphLines: [] };
100270
+ var TITLE_WIDTH = 40;
100271
+ var STATUS_LABELS = {
100272
+ draft: "DRAFT",
100273
+ "in-review": "IN REVIEW",
100274
+ accepted: "ACCEPTED",
100275
+ "no-tasks": "NO TASKS",
100276
+ wip: "WIP",
100277
+ done: "DONE",
100278
+ archived: "ARCHIVED",
100279
+ backlog: "BACKLOG",
100280
+ reviewed: "REVIEWED",
100281
+ ready: "READY",
100282
+ blocked: "BLOCKED",
100283
+ rejected: "REJECTED"
100284
+ };
100285
+ var CHANGE_STATUS_OPTIONS = [
100286
+ { label: "Draft", status: "Draft" },
100287
+ { label: "Pending Review", status: "Pending Review" },
100288
+ { label: "Accepted", status: "Accepted" },
100289
+ { label: "Implementing", status: "Implementing" },
100290
+ { label: "Done", status: "Done" },
100291
+ { label: "Rejected", status: "Rejected" },
100292
+ { label: "Archived", status: "Archived" }
100293
+ ];
100294
+ var CHANGE_STATUS_VALUE = {
100295
+ draft: "Draft",
100296
+ "in-review": "Pending Review",
100297
+ accepted: "Accepted",
100298
+ "no-tasks": "Accepted",
100299
+ wip: "Implementing",
100300
+ done: "Done",
100301
+ rejected: "Rejected",
100302
+ archived: "Archived"
100303
+ };
100304
+ var TASK_STATUS_OPTIONS = {
100305
+ backlog: [
100306
+ { label: "Reviewed", status: "reviewed" },
100307
+ { label: "Archive", status: "archived" }
100308
+ ],
100309
+ reviewed: [
100310
+ { label: "Backlog", status: "backlog" },
100311
+ { label: "Archive", status: "archived" }
100312
+ ]
100313
+ };
100314
+ var isTaskListItem = (item) => ("sourceStatus" in item);
100315
+ var buildChangeStatusOptions = (status) => {
100316
+ const currentStatus = CHANGE_STATUS_VALUE[status];
100317
+ return CHANGE_STATUS_OPTIONS.filter((option) => option.status !== currentStatus);
100318
+ };
100319
+ var buildTaskStatusOptions = (item) => {
100320
+ if (item.status === "draft") {
100321
+ return null;
100322
+ }
100323
+ if (item.sourceStatus === "backlog") {
100324
+ return TASK_STATUS_OPTIONS.backlog;
100061
100325
  }
100062
- const allTasks = loadTaskDependencies(schubDir);
100063
- const visibleTasks = allTasks.filter((task) => PLAN_TASK_STATUSES.includes(task.status));
100064
- if (visibleTasks.length === 0) {
100065
- return { visibleTasks, graphLines: [] };
100326
+ if (item.sourceStatus === "reviewed") {
100327
+ return TASK_STATUS_OPTIONS.reviewed;
100066
100328
  }
100067
- const graph = buildTaskGraph(visibleTasks);
100068
- const graphLines = renderTaskGraphLines(graph);
100069
- return { visibleTasks, graphLines };
100329
+ return null;
100070
100330
  };
100071
- function PlanView({ refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS, startDir }) {
100072
- const resolvedStartDir = startDir ?? process.env.SCHUB_CWD ?? process.cwd();
100073
- const schubDir = findSchubRoot(resolvedStartDir);
100074
- const [, setRefreshTick] = import_react59.default.useState(0);
100075
- const planData = buildPlanData(schubDir);
100076
- const refresh = () => {
100077
- setRefreshTick((current) => current + 1);
100331
+ var buildStatusModalState = (item) => {
100332
+ if (isTaskListItem(item)) {
100333
+ const options = buildTaskStatusOptions(item);
100334
+ if (!options) {
100335
+ return null;
100336
+ }
100337
+ return { itemId: item.id, itemKind: "task", selection: 0, options };
100338
+ }
100339
+ return {
100340
+ itemId: item.id,
100341
+ itemKind: "change",
100342
+ selection: 0,
100343
+ options: buildChangeStatusOptions(item.status)
100078
100344
  };
100079
- useRefreshInterval({ enabled: Boolean(schubDir), refreshIntervalMs, onRefresh: refresh });
100080
- if (!schubDir) {
100081
- return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100082
- flexDirection: "column",
100083
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100084
- color: "red",
100085
- children: "No .schub directory found."
100086
- }, undefined, false, undefined, this)
100087
- }, undefined, false, undefined, this);
100345
+ };
100346
+ var formatStatusLabel = (status) => STATUS_LABELS[status] ?? status.toUpperCase();
100347
+ var formatTitle = (value) => {
100348
+ const trimmed = value.trim();
100349
+ if (!trimmed) {
100350
+ return "";
100088
100351
  }
100089
- if (planData.visibleTasks.length === 0) {
100090
- return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100091
- flexDirection: "column",
100092
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100093
- color: "gray",
100094
- children: "No tasks found in .schub"
100095
- }, undefined, false, undefined, this)
100096
- }, undefined, false, undefined, this);
100352
+ if (trimmed.length <= TITLE_WIDTH) {
100353
+ return trimmed;
100354
+ }
100355
+ if (TITLE_WIDTH <= 1) {
100356
+ return "";
100097
100357
  }
100358
+ return `${trimmed.slice(0, TITLE_WIDTH - 1).trimEnd()}…`;
100359
+ };
100360
+ function ListItem({ id, status, title, selected, progress, secondaryId, pending }) {
100361
+ const statusLabel = formatStatusLabel(status);
100362
+ const formattedTitle = formatTitle(title);
100363
+ const showProgress = status === "wip" && progress && progress.total > 0;
100364
+ const progressValue = showProgress ? Math.min(100, Math.max(0, Math.round(progress.completed / progress.total * 100))) : 0;
100365
+ const showPending = Boolean(pending);
100366
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100367
+ marginLeft: 1,
100368
+ flexDirection: "row",
100369
+ children: [
100370
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100371
+ color: selected ? "blue" : "gray",
100372
+ children: selected ? "›" : " "
100373
+ }, undefined, false, undefined, this),
100374
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100375
+ flexDirection: "row",
100376
+ gap: 1,
100377
+ children: [
100378
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Badge, {
100379
+ color: statusColor(status),
100380
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100381
+ color: "darkGray",
100382
+ backgroundColor: statusColor(status),
100383
+ children: statusLabel
100384
+ }, undefined, false, undefined, this)
100385
+ }, undefined, false, undefined, this),
100386
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Badge, {
100387
+ color: "whiteBright",
100388
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100389
+ backgroundColor: "whiteBright",
100390
+ children: id
100391
+ }, undefined, false, undefined, this)
100392
+ }, undefined, false, undefined, this),
100393
+ secondaryId ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Badge, {
100394
+ color: "grey",
100395
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100396
+ backgroundColor: "grey",
100397
+ children: secondaryId
100398
+ }, undefined, false, undefined, this)
100399
+ }, undefined, false, undefined, this) : null,
100400
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100401
+ width: TITLE_WIDTH,
100402
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100403
+ color: "gray",
100404
+ bold: selected,
100405
+ children: formattedTitle
100406
+ }, undefined, false, undefined, this)
100407
+ }, undefined, false, undefined, this),
100408
+ showProgress ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100409
+ marginLeft: 1,
100410
+ width: 6,
100411
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(ProgressBar, {
100412
+ value: progressValue
100413
+ }, undefined, false, undefined, this)
100414
+ }, undefined, false, undefined, this) : null,
100415
+ showPending ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Spinner, {}, undefined, false, undefined, this) : null
100416
+ ]
100417
+ }, undefined, true, undefined, this)
100418
+ ]
100419
+ }, undefined, true, undefined, this);
100420
+ }
100421
+ var ListItemStatusModal = ({ statusModal }) => {
100098
100422
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100423
+ backgroundColor: "darkGray",
100099
100424
  flexDirection: "column",
100100
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100101
- flexDirection: "column",
100102
- children: [
100103
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100104
- marginBottom: 1,
100105
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100106
- bold: true,
100107
- color: "white",
100108
- children: "Dependency Plan"
100109
- }, undefined, false, undefined, this)
100110
- }, undefined, false, undefined, this),
100111
- planData.graphLines.map((line, index) => {
100112
- const hasProgress = line.status === "wip" && line.checklistTotal !== undefined && line.checklistTotal > 0 && line.checklistRemaining !== undefined;
100113
- const progressValue = hasProgress ? Math.round((line.checklistTotal - line.checklistRemaining) / line.checklistTotal * 100) : 0;
100425
+ paddingX: 2,
100426
+ paddingY: 1,
100427
+ children: [
100428
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100429
+ flexDirection: "column",
100430
+ marginBottom: 1,
100431
+ children: statusModal.options.map((option, index) => {
100432
+ const selected = index === statusModal.selection;
100114
100433
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100115
100434
  children: [
100116
100435
  /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100117
- color: "grey",
100118
- children: line.text
100436
+ color: selected ? "blue" : "gray",
100437
+ children: selected ? "›" : " "
100119
100438
  }, undefined, false, undefined, this),
100120
- hasProgress ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100121
- marginLeft: 1,
100122
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(ProgressBar, {
100123
- value: progressValue
100124
- }, undefined, false, undefined, this)
100125
- }, undefined, false, undefined, this) : null
100439
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100440
+ color: "white",
100441
+ children: [
100442
+ " ",
100443
+ option.label
100444
+ ]
100445
+ }, undefined, true, undefined, this)
100126
100446
  ]
100127
- }, `${line.text}-${index}`, true, undefined, this);
100447
+ }, option.status, true, undefined, this);
100128
100448
  })
100129
- ]
100130
- }, undefined, true, undefined, this)
100131
- }, undefined, false, undefined, this);
100132
- }
100449
+ }, undefined, false, undefined, this),
100450
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100451
+ alignItems: "flex-end",
100452
+ children: [
100453
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100454
+ marginRight: 2,
100455
+ children: [
100456
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100457
+ color: "white",
100458
+ children: "enter"
100459
+ }, undefined, false, undefined, this),
100460
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100461
+ color: "gray",
100462
+ children: " confirm"
100463
+ }, undefined, false, undefined, this)
100464
+ ]
100465
+ }, undefined, true, undefined, this),
100466
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
100467
+ children: [
100468
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100469
+ color: "white",
100470
+ children: "esc"
100471
+ }, undefined, false, undefined, this),
100472
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
100473
+ color: "gray",
100474
+ children: " cancel"
100475
+ }, undefined, false, undefined, this)
100476
+ ]
100477
+ }, undefined, true, undefined, this)
100478
+ ]
100479
+ }, undefined, true, undefined, this)
100480
+ ]
100481
+ }, undefined, true, undefined, this);
100482
+ };
100133
100483
 
100134
100484
  // ../../node_modules/.bun/marked@9.1.6/node_modules/marked/lib/marked.esm.js
100135
100485
  function _getDefaults() {
@@ -102656,7 +103006,7 @@ function sanitizeTab(tab2, fallbackTab) {
102656
103006
  }
102657
103007
 
102658
103008
  // src/tui/components/preview-page.tsx
102659
- var import_react60 = __toESM(require_react(), 1);
103009
+ var import_react58 = __toESM(require_react(), 1);
102660
103010
 
102661
103011
  // src/tui/shared/input.ts
102662
103012
  var normalizeTypedValue = (input, keyName2, keySequence) => {
@@ -102689,10 +103039,10 @@ var buildInputState = (input, key) => {
102689
103039
  const isEnter = key.return || key.enter || keyName2 === "return" || keyName2 === "enter" || keySequence === "\r" || keySequence === `
102690
103040
  ` || input === "\r" || input === `
102691
103041
  `;
102692
- const isDownArrow = key.downArrow || Boolean(keySequence?.includes("[B")) || input.includes("[B");
102693
- const isUpArrow = key.upArrow || Boolean(keySequence?.includes("[A")) || input.includes("[A");
102694
- const isLeftArrow = key.leftArrow || Boolean(keySequence?.includes("[D")) || input.includes("[D");
102695
- const isRightArrow = key.rightArrow || Boolean(keySequence?.includes("[C")) || input.includes("[C");
103042
+ const isDownArrow = key.downArrow || keyName2 === "down" || keyName2 === "downArrow" || Boolean(keySequence?.includes("[B")) || Boolean(keySequence?.includes("OB")) || input.includes("[B") || input.includes("OB");
103043
+ const isUpArrow = key.upArrow || keyName2 === "up" || keyName2 === "upArrow" || Boolean(keySequence?.includes("[A")) || Boolean(keySequence?.includes("OA")) || input.includes("[A") || input.includes("OA");
103044
+ const isLeftArrow = key.leftArrow || keyName2 === "left" || keyName2 === "leftArrow" || Boolean(keySequence?.includes("[D")) || Boolean(keySequence?.includes("OD")) || input.includes("[D") || input.includes("OD");
103045
+ const isRightArrow = key.rightArrow || keyName2 === "right" || keyName2 === "rightArrow" || Boolean(keySequence?.includes("[C")) || Boolean(keySequence?.includes("OC")) || input.includes("[C") || input.includes("OC");
102696
103046
  return {
102697
103047
  input,
102698
103048
  lowerInput,
@@ -102713,13 +103063,14 @@ var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
102713
103063
  var HEADER_HEIGHT = 2;
102714
103064
  function PreviewPage({ fileName, markdown, height, onClose }) {
102715
103065
  const { stdout } = use_stdout_default();
102716
- const lines = import_react60.default.useMemo(() => markdown.split(/\r?\n/), [markdown]);
103066
+ const { body } = import_react58.default.useMemo(() => readFrontmatter(markdown), [markdown]);
103067
+ const lines = import_react58.default.useMemo(() => body.split(/\r?\n/), [body]);
102717
103068
  const availableHeight = height ?? stdout.rows ?? 0;
102718
103069
  const visibleLineCount = Math.max(0, availableHeight - HEADER_HEIGHT);
102719
103070
  const maxOffset = visibleLineCount > 0 ? Math.max(0, lines.length - visibleLineCount) : 0;
102720
- const [scrollOffset, setScrollOffset] = import_react60.default.useState(0);
103071
+ const [scrollOffset, setScrollOffset] = import_react58.default.useState(0);
102721
103072
  const clampOffset = (value) => Math.min(maxOffset, Math.max(0, value));
102722
- import_react60.default.useEffect(() => {
103073
+ import_react58.default.useEffect(() => {
102723
103074
  setScrollOffset((current) => Math.min(maxOffset, Math.max(0, current)));
102724
103075
  }, [maxOffset]);
102725
103076
  use_input_default((input, key) => {
@@ -102739,7 +103090,7 @@ function PreviewPage({ fileName, markdown, height, onClose }) {
102739
103090
  const visibleLines = lines.slice(scrollOffset, scrollOffset + visibleLineCount);
102740
103091
  const visibleMarkdown = visibleLines.join(`
102741
103092
  `);
102742
- const renderedMarkdown = import_react60.default.useMemo(() => {
103093
+ const renderedMarkdown = import_react58.default.useMemo(() => {
102743
103094
  setOptions({ renderer: new marked_terminal_default });
102744
103095
  return parse(visibleMarkdown).trim();
102745
103096
  }, [visibleMarkdown]);
@@ -102772,21 +103123,22 @@ function PreviewPage({ fileName, markdown, height, onClose }) {
102772
103123
  }
102773
103124
 
102774
103125
  // src/tui/components/status-view-data.ts
102775
- import { readFileSync as readFileSync12 } from "node:fs";
102776
- import { dirname as dirname11, join as join15, normalize as normalize2, sep } from "node:path";
103126
+ import { readFileSync as readFileSync14 } from "node:fs";
103127
+ import { dirname as dirname12, join as join18, normalize as normalize2, sep } from "node:path";
102777
103128
  var TASK_STATUS_LABELS = {
102778
103129
  backlog: "Backlog",
102779
103130
  reviewed: "Reviewed",
102780
103131
  wip: "WIP",
102781
103132
  blocked: "Blocked",
102782
103133
  done: "Done",
103134
+ rejected: "Rejected",
102783
103135
  archived: "Archived"
102784
103136
  };
102785
103137
  var ACTIVE_TASK_STATUSES2 = ["blocked", "wip", "reviewed", "backlog"];
102786
103138
  var CHANGE_TASK_STATUSES = [...ACTIVE_TASK_STATUSES2, "done"];
102787
103139
  var READY_TO_IMPLEMENT_STATUSES = new Set(["reviewed"]);
102788
103140
  var AUTO_MARK_STATUSES = new Set(["accepted", "wip"]);
102789
- var compareText2 = (left2, right2) => left2.localeCompare(right2, undefined, { sensitivity: "base" });
103141
+ var compareText = (left2, right2) => left2.localeCompare(right2, undefined, { sensitivity: "base" });
102790
103142
  var idSortValue = (value) => {
102791
103143
  const match = value.match(/\d+/);
102792
103144
  return match ? Number(match[0]) : Number.POSITIVE_INFINITY;
@@ -102796,7 +103148,7 @@ var compareIds = (left2, right2) => {
102796
103148
  if (numberDiff !== 0) {
102797
103149
  return numberDiff;
102798
103150
  }
102799
- return compareText2(left2, right2);
103151
+ return compareText(left2, right2);
102800
103152
  };
102801
103153
  var taskSortName = (task) => {
102802
103154
  if (task.status === "blocked") {
@@ -102817,7 +103169,7 @@ var sortByTaskIdThenName = (left2, right2) => {
102817
103169
  if (idCompare !== 0) {
102818
103170
  return idCompare;
102819
103171
  }
102820
- return compareText2(taskSortName(left2), taskSortName(right2));
103172
+ return compareText(taskSortName(left2), taskSortName(right2));
102821
103173
  };
102822
103174
  var formatChangeId2 = (value) => {
102823
103175
  const match = value.match(/^([Cc]\d+)_/);
@@ -102839,7 +103191,7 @@ var sortByChangeIdThenName = (left2, right2) => {
102839
103191
  if (idCompare !== 0) {
102840
103192
  return idCompare;
102841
103193
  }
102842
- return compareText2(changeSortName(left2), changeSortName(right2));
103194
+ return compareText(changeSortName(left2), changeSortName(right2));
102843
103195
  };
102844
103196
  var buildChecklistProgress = (task) => {
102845
103197
  if (task.status !== "wip") {
@@ -102879,7 +103231,7 @@ var buildChangeTaskCounts = (tasks) => {
102879
103231
  return changeTaskCounts;
102880
103232
  };
102881
103233
  var hasDraftTokens = (repoRoot, task) => {
102882
- const content = readFileSync12(join15(repoRoot, task.path), "utf8");
103234
+ const content = readFileSync14(join18(repoRoot, task.path), "utf8");
102883
103235
  return hasUnimplementedTemplateTokens(content);
102884
103236
  };
102885
103237
  var isReadyTask = (task, tasksById) => {
@@ -102905,6 +103257,9 @@ var deriveChangeListStatus = (change, counts) => {
102905
103257
  if (normalized.includes("done")) {
102906
103258
  return "done";
102907
103259
  }
103260
+ if (normalized.includes("reject")) {
103261
+ return "rejected";
103262
+ }
102908
103263
  if (normalized.includes("review")) {
102909
103264
  return "in-review";
102910
103265
  }
@@ -102925,7 +103280,7 @@ var deriveChangeListStatus = (change, counts) => {
102925
103280
  }
102926
103281
  return "accepted";
102927
103282
  };
102928
- var groupChangesByStatus = (changes) => {
103283
+ var groupChangesByStatus2 = (changes) => {
102929
103284
  const grouped = new Map;
102930
103285
  for (const change of changes) {
102931
103286
  const key = `${change.statusOrder}-${change.statusLabel}`;
@@ -102942,7 +103297,7 @@ var groupChangesByStatus = (changes) => {
102942
103297
  if (orderCompare !== 0) {
102943
103298
  return orderCompare;
102944
103299
  }
102945
- return compareText2(left2.label, right2.label);
103300
+ return compareText(left2.label, right2.label);
102946
103301
  });
102947
103302
  for (const group of sorted) {
102948
103303
  group.items.sort(sortByChangeIdThenName);
@@ -102956,7 +103311,7 @@ var buildShowAllGroups = (schubDir) => {
102956
103311
  const allTasks = loadTaskDependencies(schubDir, CHANGE_TASK_STATUSES);
102957
103312
  const changeTaskCounts = buildChangeTaskCounts(allTasks);
102958
103313
  const changes = listChangeOverview(schubDir).map((change) => addChangeProgress(change, changeTaskCounts.get(change.id)));
102959
- return groupChangesByStatus(changes);
103314
+ return groupChangesByStatus2(changes);
102960
103315
  };
102961
103316
  var buildTaskStatusGroups = (tasks) => {
102962
103317
  const groups = new Map;
@@ -102984,13 +103339,13 @@ var buildShowAllTaskGroups = (schubDir) => {
102984
103339
  }
102985
103340
  return buildTaskStatusGroups(listTasks(schubDir, TASK_STATUSES));
102986
103341
  };
102987
- var buildTaskListItems = (schubDir, statuses = TASK_STATUSES) => {
103342
+ var buildTaskListItems = (schubDir, statuses = DEFAULT_TASK_STATUSES) => {
102988
103343
  if (!schubDir) {
102989
103344
  return [];
102990
103345
  }
102991
103346
  const allTasks = loadTaskDependencies(schubDir, TASK_STATUSES);
102992
103347
  const tasksById = new Map(allTasks.map((task) => [task.id, task]));
102993
- const repoRoot = dirname11(schubDir);
103348
+ const repoRoot = dirname12(schubDir);
102994
103349
  const allowed = new Set(statuses);
102995
103350
  return allTasks.filter((task) => allowed.has(task.status)).map((task) => ({
102996
103351
  id: task.id,
@@ -103133,236 +103488,8 @@ var buildStatusData = (schubDir) => {
103133
103488
  };
103134
103489
  };
103135
103490
 
103136
- // src/tui/components/status-color.ts
103137
- var STATUS_COLORS = {
103138
- draft: "gray",
103139
- "in-review": "yellow",
103140
- accepted: "blue",
103141
- "no-tasks": "gray",
103142
- wip: "blue",
103143
- done: "green",
103144
- archived: "gray",
103145
- backlog: "gray",
103146
- reviewed: "yellow",
103147
- ready: "yellow",
103148
- blocked: "red"
103149
- };
103150
- var statusColor = (status) => {
103151
- return status ? STATUS_COLORS[status] : "gray";
103152
- };
103153
-
103154
- // src/tui/components/list-item.tsx
103155
- var jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
103156
- var TITLE_WIDTH = 40;
103157
- var STATUS_LABELS = {
103158
- draft: "DRAFT",
103159
- "in-review": "IN REVIEW",
103160
- accepted: "ACCEPTED",
103161
- "no-tasks": "NO TASKS",
103162
- wip: "WIP",
103163
- done: "DONE",
103164
- archived: "ARCHIVED",
103165
- backlog: "BACKLOG",
103166
- reviewed: "REVIEWED",
103167
- ready: "READY",
103168
- blocked: "BLOCKED"
103169
- };
103170
- var CHANGE_STATUS_OPTIONS = [
103171
- { label: "Draft", status: "Draft" },
103172
- { label: "Pending Review", status: "Pending Review" },
103173
- { label: "Accepted", status: "Accepted" },
103174
- { label: "Implementing", status: "Implementing" },
103175
- { label: "Done", status: "Done" },
103176
- { label: "Archived", status: "Archived" }
103177
- ];
103178
- var CHANGE_STATUS_VALUE = {
103179
- draft: "Draft",
103180
- "in-review": "Pending Review",
103181
- accepted: "Accepted",
103182
- "no-tasks": "Accepted",
103183
- wip: "Implementing",
103184
- done: "Done",
103185
- archived: "Archived"
103186
- };
103187
- var TASK_STATUS_OPTIONS = {
103188
- backlog: [
103189
- { label: "Reviewed", status: "reviewed" },
103190
- { label: "Archive", status: "archived" }
103191
- ],
103192
- reviewed: [
103193
- { label: "Backlog", status: "backlog" },
103194
- { label: "Archive", status: "archived" }
103195
- ]
103196
- };
103197
- var isTaskListItem = (item) => ("sourceStatus" in item);
103198
- var buildChangeStatusOptions = (status) => {
103199
- const currentStatus = CHANGE_STATUS_VALUE[status];
103200
- return CHANGE_STATUS_OPTIONS.filter((option) => option.status !== currentStatus);
103201
- };
103202
- var buildTaskStatusOptions = (item) => {
103203
- if (item.sourceStatus === "backlog") {
103204
- return TASK_STATUS_OPTIONS.backlog;
103205
- }
103206
- if (item.sourceStatus === "reviewed") {
103207
- return TASK_STATUS_OPTIONS.reviewed;
103208
- }
103209
- return null;
103210
- };
103211
- var buildStatusModalState = (item) => {
103212
- if (isTaskListItem(item)) {
103213
- const options2 = buildTaskStatusOptions(item);
103214
- if (!options2) {
103215
- return null;
103216
- }
103217
- return { itemId: item.id, itemKind: "task", selection: 0, options: options2 };
103218
- }
103219
- return {
103220
- itemId: item.id,
103221
- itemKind: "change",
103222
- selection: 0,
103223
- options: buildChangeStatusOptions(item.status)
103224
- };
103225
- };
103226
- var formatStatusLabel = (status) => STATUS_LABELS[status] ?? status.toUpperCase();
103227
- var formatTitle = (value) => {
103228
- const trimmed = value.trim();
103229
- if (!trimmed) {
103230
- return "";
103231
- }
103232
- if (trimmed.length <= TITLE_WIDTH) {
103233
- return trimmed;
103234
- }
103235
- if (TITLE_WIDTH <= 1) {
103236
- return "…";
103237
- }
103238
- return `${trimmed.slice(0, TITLE_WIDTH - 1).trimEnd()}…`;
103239
- };
103240
- function ListItem({ id, status, title, selected, progress, secondaryId, pending }) {
103241
- const statusLabel = formatStatusLabel(status);
103242
- const formattedTitle = formatTitle(title);
103243
- const showProgress = status === "wip" && progress && progress.total > 0;
103244
- const progressValue = showProgress ? Math.min(100, Math.max(0, Math.round(progress.completed / progress.total * 100))) : 0;
103245
- const showPending = Boolean(pending);
103246
- return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103247
- marginLeft: 1,
103248
- flexDirection: "row",
103249
- children: [
103250
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103251
- color: selected ? "blue" : "gray",
103252
- children: selected ? "›" : " "
103253
- }, undefined, false, undefined, this),
103254
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103255
- flexDirection: "row",
103256
- gap: 1,
103257
- children: [
103258
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Badge, {
103259
- color: statusColor(status),
103260
- children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103261
- color: "darkGray",
103262
- backgroundColor: statusColor(status),
103263
- children: statusLabel
103264
- }, undefined, false, undefined, this)
103265
- }, undefined, false, undefined, this),
103266
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Badge, {
103267
- color: "whiteBright",
103268
- children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103269
- backgroundColor: "whiteBright",
103270
- children: id
103271
- }, undefined, false, undefined, this)
103272
- }, undefined, false, undefined, this),
103273
- secondaryId ? /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Badge, {
103274
- color: "grey",
103275
- children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103276
- backgroundColor: "grey",
103277
- children: secondaryId
103278
- }, undefined, false, undefined, this)
103279
- }, undefined, false, undefined, this) : null,
103280
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103281
- width: TITLE_WIDTH,
103282
- children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103283
- color: "gray",
103284
- bold: selected,
103285
- children: formattedTitle
103286
- }, undefined, false, undefined, this)
103287
- }, undefined, false, undefined, this),
103288
- showProgress ? /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103289
- marginLeft: 1,
103290
- width: 6,
103291
- children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(ProgressBar, {
103292
- value: progressValue
103293
- }, undefined, false, undefined, this)
103294
- }, undefined, false, undefined, this) : null,
103295
- showPending ? /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Spinner, {}, undefined, false, undefined, this) : null
103296
- ]
103297
- }, undefined, true, undefined, this)
103298
- ]
103299
- }, undefined, true, undefined, this);
103300
- }
103301
- var ListItemStatusModal = ({ statusModal }) => {
103302
- return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103303
- backgroundColor: "darkGray",
103304
- flexDirection: "column",
103305
- paddingX: 2,
103306
- paddingY: 1,
103307
- children: [
103308
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103309
- flexDirection: "column",
103310
- marginBottom: 1,
103311
- children: statusModal.options.map((option, index) => {
103312
- const selected = index === statusModal.selection;
103313
- return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103314
- children: [
103315
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103316
- color: selected ? "blue" : "gray",
103317
- children: selected ? "›" : " "
103318
- }, undefined, false, undefined, this),
103319
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103320
- color: "white",
103321
- children: [
103322
- " ",
103323
- option.label
103324
- ]
103325
- }, undefined, true, undefined, this)
103326
- ]
103327
- }, option.status, true, undefined, this);
103328
- })
103329
- }, undefined, false, undefined, this),
103330
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103331
- alignItems: "flex-end",
103332
- children: [
103333
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103334
- marginRight: 2,
103335
- children: [
103336
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103337
- color: "white",
103338
- children: "enter"
103339
- }, undefined, false, undefined, this),
103340
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103341
- color: "gray",
103342
- children: " confirm"
103343
- }, undefined, false, undefined, this)
103344
- ]
103345
- }, undefined, true, undefined, this),
103346
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103347
- children: [
103348
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103349
- color: "white",
103350
- children: "esc"
103351
- }, undefined, false, undefined, this),
103352
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103353
- color: "gray",
103354
- children: " cancel"
103355
- }, undefined, false, undefined, this)
103356
- ]
103357
- }, undefined, true, undefined, this)
103358
- ]
103359
- }, undefined, true, undefined, this)
103360
- ]
103361
- }, undefined, true, undefined, this);
103362
- };
103363
-
103364
103491
  // src/tui/components/task-list.tsx
103365
- var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
103492
+ var jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
103366
103493
  var EMPTY_PENDING_IDS = new Set;
103367
103494
  var resolveTaskTitle = (item) => {
103368
103495
  if (item.status === "blocked") {
@@ -103382,12 +103509,12 @@ function TaskList({
103382
103509
  if (!section2.emptyState) {
103383
103510
  return null;
103384
103511
  }
103385
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103512
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103386
103513
  flexDirection: "column",
103387
103514
  marginBottom: 1,
103388
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103515
+ children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103389
103516
  marginLeft: 1,
103390
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103517
+ children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
103391
103518
  color: "gray",
103392
103519
  children: section2.emptyState
103393
103520
  }, undefined, false, undefined, this)
@@ -103399,8 +103526,8 @@ function TaskList({
103399
103526
  const selected = selection === sectionStartIndex + index;
103400
103527
  const title = resolveTaskTitle(item);
103401
103528
  const changeBadge = item.changeId ? formatChangeId2(item.changeId) : undefined;
103402
- const pending = pendingIds.has(item.id);
103403
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ListItem, {
103529
+ const pending = pendingIds.has(item.id) || item.status === "draft";
103530
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(ListItem, {
103404
103531
  id: item.id,
103405
103532
  status: item.status,
103406
103533
  title,
@@ -103411,22 +103538,22 @@ function TaskList({
103411
103538
  }, item.id, false, undefined, this);
103412
103539
  });
103413
103540
  currentIndex += section2.items.length;
103414
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103541
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103415
103542
  flexDirection: "column",
103416
103543
  marginBottom: 1,
103417
103544
  children: rows
103418
103545
  }, section2.title, false, undefined, this);
103419
103546
  };
103420
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103547
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
103421
103548
  flexDirection: "column",
103422
103549
  children: sections.map((section2) => renderSection(section2))
103423
103550
  }, undefined, false, undefined, this);
103424
103551
  }
103425
103552
 
103426
103553
  // src/tui/components/proposal-detail-view.tsx
103427
- var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
103554
+ var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
103428
103555
  var SUMMARY_FALLBACK2 = "No summary provided.";
103429
- var compareText3 = (left2, right2) => left2.localeCompare(right2, undefined, { sensitivity: "base" });
103556
+ var compareText2 = (left2, right2) => left2.localeCompare(right2, undefined, { sensitivity: "base" });
103430
103557
  var idSortValue2 = (value) => {
103431
103558
  const match = value.match(/\d+/);
103432
103559
  return match ? Number(match[0]) : Number.POSITIVE_INFINITY;
@@ -103436,7 +103563,7 @@ var compareIds2 = (left2, right2) => {
103436
103563
  if (numberDiff !== 0) {
103437
103564
  return numberDiff;
103438
103565
  }
103439
- return compareText3(left2, right2);
103566
+ return compareText2(left2, right2);
103440
103567
  };
103441
103568
  var resolveTaskName = (task) => {
103442
103569
  if (task.status === "blocked") {
@@ -103452,7 +103579,7 @@ var sortTaskItems = (tasks) => tasks.sort((left2, right2) => {
103452
103579
  if (idCompare !== 0) {
103453
103580
  return idCompare;
103454
103581
  }
103455
- return compareText3(resolveTaskName(left2), resolveTaskName(right2));
103582
+ return compareText2(resolveTaskName(left2), resolveTaskName(right2));
103456
103583
  });
103457
103584
  var TASK_STATUS_LABELS2 = {
103458
103585
  backlog: "Backlog",
@@ -103460,6 +103587,7 @@ var TASK_STATUS_LABELS2 = {
103460
103587
  wip: "WIP",
103461
103588
  blocked: "Blocked",
103462
103589
  done: "Done",
103590
+ rejected: "Rejected",
103463
103591
  archived: "Archived"
103464
103592
  };
103465
103593
  var buildTaskSections = (tasks) => {
@@ -103485,28 +103613,28 @@ function ProposalDetailView({ changeId, selection = 0, startDir }) {
103485
103613
  const resolvedStartDir = startDir ?? process.env.SCHUB_CWD ?? process.cwd();
103486
103614
  const schubDir = findSchubRoot(resolvedStartDir);
103487
103615
  if (!schubDir) {
103488
- return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103616
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103489
103617
  flexDirection: "column",
103490
- children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103618
+ children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103491
103619
  color: "red",
103492
103620
  children: "No .schub directory found."
103493
103621
  }, undefined, false, undefined, this)
103494
103622
  }, undefined, false, undefined, this);
103495
103623
  }
103496
103624
  const detail = readChangeDetail(schubDir, changeId);
103497
- const tasks = buildTaskListItems(schubDir).filter((task) => task.changeId === detail.changeId);
103625
+ const tasks = buildTaskListItems(schubDir, TASK_STATUSES).filter((task) => task.changeId === detail.changeId);
103498
103626
  const summary = detail.summary.trim() || SUMMARY_FALLBACK2;
103499
103627
  const taskSections = buildTaskSections(tasks);
103500
- const renderMetadataRow = (label, value) => /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103628
+ const renderMetadataRow = (label, value) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103501
103629
  children: [
103502
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103630
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103503
103631
  color: "white",
103504
103632
  children: [
103505
103633
  label,
103506
103634
  ":"
103507
103635
  ]
103508
103636
  }, undefined, true, undefined, this),
103509
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103637
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103510
103638
  color: "gray",
103511
103639
  children: [
103512
103640
  " ",
@@ -103515,18 +103643,24 @@ function ProposalDetailView({ changeId, selection = 0, startDir }) {
103515
103643
  }, undefined, true, undefined, this)
103516
103644
  ]
103517
103645
  }, undefined, true, undefined, this);
103518
- return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103646
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103519
103647
  flexDirection: "column",
103520
103648
  children: [
103521
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103649
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103522
103650
  marginBottom: 1,
103523
- children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103524
- bold: true,
103525
- color: "white",
103526
- children: "Proposal Detail"
103527
- }, undefined, false, undefined, this)
103528
- }, undefined, false, undefined, this),
103529
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103651
+ children: [
103652
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103653
+ bold: true,
103654
+ color: "white",
103655
+ children: "Proposal Detail"
103656
+ }, undefined, false, undefined, this),
103657
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103658
+ color: "gray",
103659
+ children: " · esc to return"
103660
+ }, undefined, false, undefined, this)
103661
+ ]
103662
+ }, undefined, true, undefined, this),
103663
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103530
103664
  flexDirection: "column",
103531
103665
  marginBottom: 1,
103532
103666
  children: [
@@ -103536,41 +103670,41 @@ function ProposalDetailView({ changeId, selection = 0, startDir }) {
103536
103670
  renderMetadataRow("Input", detail.input)
103537
103671
  ]
103538
103672
  }, undefined, true, undefined, this),
103539
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103673
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103540
103674
  flexDirection: "column",
103541
103675
  marginBottom: 1,
103542
103676
  children: [
103543
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103677
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103544
103678
  color: "white",
103545
103679
  children: "Summary"
103546
103680
  }, undefined, false, undefined, this),
103547
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103681
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103548
103682
  marginLeft: 1,
103549
- children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103683
+ children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103550
103684
  color: "gray",
103551
103685
  children: summary
103552
103686
  }, undefined, false, undefined, this)
103553
103687
  }, undefined, false, undefined, this)
103554
103688
  ]
103555
103689
  }, undefined, true, undefined, this),
103556
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103690
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103557
103691
  flexDirection: "column",
103558
103692
  children: [
103559
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103693
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103560
103694
  marginBottom: 1,
103561
- children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103695
+ children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103562
103696
  bold: true,
103563
103697
  color: "white",
103564
103698
  children: "Tasks"
103565
103699
  }, undefined, false, undefined, this)
103566
103700
  }, undefined, false, undefined, this),
103567
- taskSections.length === 0 ? /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103701
+ taskSections.length === 0 ? /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
103568
103702
  marginLeft: 1,
103569
- children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103703
+ children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
103570
103704
  color: "gray",
103571
103705
  children: "No tasks found."
103572
103706
  }, undefined, false, undefined, this)
103573
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(TaskList, {
103707
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(TaskList, {
103574
103708
  selection,
103575
103709
  sections: taskSections
103576
103710
  }, undefined, false, undefined, this)
@@ -103581,28 +103715,28 @@ function ProposalDetailView({ changeId, selection = 0, startDir }) {
103581
103715
  }
103582
103716
 
103583
103717
  // src/tui/components/session-view.tsx
103584
- var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
103718
+ var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
103585
103719
  var EMPTY_MESSAGE = "No session selected.";
103586
103720
  var MESSAGE_FALLBACK = "No messages found.";
103587
103721
  var ERROR_FALLBACK = "Unable to load session.";
103588
103722
  function SessionView({ state }) {
103589
103723
  if (state.status === "error") {
103590
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103724
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103591
103725
  flexDirection: "column",
103592
103726
  children: [
103593
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103727
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103594
103728
  marginBottom: 1,
103595
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103729
+ children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103596
103730
  bold: true,
103597
103731
  color: "white",
103598
103732
  children: "Session Detail"
103599
103733
  }, undefined, false, undefined, this)
103600
103734
  }, undefined, false, undefined, this),
103601
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103735
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103602
103736
  color: "red",
103603
103737
  children: ERROR_FALLBACK
103604
103738
  }, undefined, false, undefined, this),
103605
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103739
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103606
103740
  color: "gray",
103607
103741
  children: state.message
103608
103742
  }, undefined, false, undefined, this)
@@ -103610,18 +103744,18 @@ function SessionView({ state }) {
103610
103744
  }, undefined, true, undefined, this);
103611
103745
  }
103612
103746
  if (state.status === "empty") {
103613
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103747
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103614
103748
  flexDirection: "column",
103615
103749
  children: [
103616
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103750
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103617
103751
  marginBottom: 1,
103618
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103752
+ children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103619
103753
  bold: true,
103620
103754
  color: "white",
103621
103755
  children: "Session Detail"
103622
103756
  }, undefined, false, undefined, this)
103623
103757
  }, undefined, false, undefined, this),
103624
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103758
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103625
103759
  color: "gray",
103626
103760
  children: EMPTY_MESSAGE
103627
103761
  }, undefined, false, undefined, this)
@@ -103630,16 +103764,16 @@ function SessionView({ state }) {
103630
103764
  }
103631
103765
  const { summary } = state;
103632
103766
  const messages = summary.messages;
103633
- const renderMetadataRow = (label, value) => /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103767
+ const renderMetadataRow = (label, value) => /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103634
103768
  children: [
103635
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103769
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103636
103770
  color: "white",
103637
103771
  children: [
103638
103772
  label,
103639
103773
  ":"
103640
103774
  ]
103641
103775
  }, undefined, true, undefined, this),
103642
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103776
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103643
103777
  color: "gray",
103644
103778
  children: [
103645
103779
  " ",
@@ -103648,14 +103782,14 @@ function SessionView({ state }) {
103648
103782
  }, undefined, true, undefined, this)
103649
103783
  ]
103650
103784
  }, undefined, true, undefined, this);
103651
- const renderMessage = (message, index) => /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103785
+ const renderMessage = (message, index) => /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103652
103786
  marginLeft: 1,
103653
103787
  children: [
103654
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103788
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103655
103789
  color: "white",
103656
103790
  children: message.role
103657
103791
  }, undefined, false, undefined, this),
103658
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103792
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103659
103793
  color: "gray",
103660
103794
  children: [
103661
103795
  " ",
@@ -103664,18 +103798,18 @@ function SessionView({ state }) {
103664
103798
  }, undefined, true, undefined, this)
103665
103799
  ]
103666
103800
  }, `${message.role}-${index}`, true, undefined, this);
103667
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103801
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103668
103802
  flexDirection: "column",
103669
103803
  children: [
103670
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103804
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103671
103805
  marginBottom: 1,
103672
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103806
+ children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103673
103807
  bold: true,
103674
103808
  color: "white",
103675
103809
  children: "Session Detail"
103676
103810
  }, undefined, false, undefined, this)
103677
103811
  }, undefined, false, undefined, this),
103678
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103812
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103679
103813
  flexDirection: "column",
103680
103814
  marginBottom: 1,
103681
103815
  children: [
@@ -103685,20 +103819,20 @@ function SessionView({ state }) {
103685
103819
  renderMetadataRow("Updated", summary.updatedAt)
103686
103820
  ]
103687
103821
  }, undefined, true, undefined, this),
103688
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103822
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103689
103823
  flexDirection: "column",
103690
103824
  children: [
103691
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103825
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103692
103826
  marginBottom: 1,
103693
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103827
+ children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103694
103828
  bold: true,
103695
103829
  color: "white",
103696
103830
  children: "Messages"
103697
103831
  }, undefined, false, undefined, this)
103698
103832
  }, undefined, false, undefined, this),
103699
- messages.length === 0 ? /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103833
+ messages.length === 0 ? /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
103700
103834
  marginLeft: 1,
103701
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103835
+ children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
103702
103836
  color: "gray",
103703
103837
  children: MESSAGE_FALLBACK
103704
103838
  }, undefined, false, undefined, this)
@@ -103710,8 +103844,98 @@ function SessionView({ state }) {
103710
103844
  }
103711
103845
 
103712
103846
  // src/tui/components/status-view.tsx
103713
- import { dirname as dirname12 } from "node:path";
103714
- var import_react61 = __toESM(require_react(), 1);
103847
+ import { dirname as dirname13, resolve as resolve12 } from "node:path";
103848
+ var import_react60 = __toESM(require_react(), 1);
103849
+
103850
+ // src/tui/hooks/use-refresh-interval.ts
103851
+ var import_react59 = __toESM(require_react(), 1);
103852
+ var useRefreshInterval = ({ enabled = true, refreshIntervalMs, onRefresh }) => {
103853
+ const refreshRef = import_react59.default.useRef(onRefresh);
103854
+ import_react59.default.useEffect(() => {
103855
+ refreshRef.current = onRefresh;
103856
+ }, [onRefresh]);
103857
+ import_react59.default.useEffect(() => {
103858
+ if (!enabled) {
103859
+ return;
103860
+ }
103861
+ const interval = setInterval(() => {
103862
+ refreshRef.current();
103863
+ }, refreshIntervalMs);
103864
+ return () => {
103865
+ clearInterval(interval);
103866
+ };
103867
+ }, [enabled, refreshIntervalMs]);
103868
+ };
103869
+
103870
+ // src/tui/components/implement-mode-modal.tsx
103871
+ var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
103872
+ var IMPLEMENT_MODES = ["none", "worktree"];
103873
+ var IMPLEMENT_MODE_LABELS = {
103874
+ none: "None",
103875
+ worktree: "Worktree"
103876
+ };
103877
+ var ImplementModeModal = ({ modal }) => {
103878
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103879
+ backgroundColor: "darkGray",
103880
+ flexDirection: "column",
103881
+ paddingX: 2,
103882
+ paddingY: 1,
103883
+ children: [
103884
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103885
+ flexDirection: "column",
103886
+ marginBottom: 1,
103887
+ children: IMPLEMENT_MODES.map((mode, index) => {
103888
+ const selected = index === modal.selection;
103889
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103890
+ children: [
103891
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103892
+ color: selected ? "blue" : "gray",
103893
+ children: selected ? "›" : " "
103894
+ }, undefined, false, undefined, this),
103895
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103896
+ color: "white",
103897
+ children: [
103898
+ " ",
103899
+ IMPLEMENT_MODE_LABELS[mode]
103900
+ ]
103901
+ }, undefined, true, undefined, this)
103902
+ ]
103903
+ }, mode, true, undefined, this);
103904
+ })
103905
+ }, undefined, false, undefined, this),
103906
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103907
+ alignItems: "flex-end",
103908
+ children: [
103909
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103910
+ marginRight: 2,
103911
+ children: [
103912
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103913
+ color: "white",
103914
+ children: "enter"
103915
+ }, undefined, false, undefined, this),
103916
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103917
+ color: "gray",
103918
+ children: " confirm"
103919
+ }, undefined, false, undefined, this)
103920
+ ]
103921
+ }, undefined, true, undefined, this),
103922
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
103923
+ children: [
103924
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103925
+ color: "white",
103926
+ children: "esc"
103927
+ }, undefined, false, undefined, this),
103928
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
103929
+ color: "gray",
103930
+ children: " cancel"
103931
+ }, undefined, false, undefined, this)
103932
+ ]
103933
+ }, undefined, true, undefined, this)
103934
+ ]
103935
+ }, undefined, true, undefined, this)
103936
+ ]
103937
+ }, undefined, true, undefined, this);
103938
+ };
103715
103939
 
103716
103940
  // src/tui/components/change-list.tsx
103717
103941
  var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
@@ -103839,7 +104063,7 @@ var StatusMainView = ({
103839
104063
  marginLeft: 1,
103840
104064
  children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
103841
104065
  color: "gray",
103842
- children: "No active proposals."
104066
+ children: "No pending proposals."
103843
104067
  }, undefined, false, undefined, this)
103844
104068
  }, undefined, false, undefined, this),
103845
104069
  renderShowAllRow(showAllRow, selection === showAllIndex)
@@ -103866,7 +104090,7 @@ var StatusMainView = ({
103866
104090
  marginLeft: 1,
103867
104091
  children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
103868
104092
  color: "gray",
103869
- children: "No active tasks."
104093
+ children: "No pending tasks."
103870
104094
  }, undefined, false, undefined, this)
103871
104095
  }, undefined, false, undefined, this) : null,
103872
104096
  renderShowAllRow(showAllTasksRow, selection === showAllTasksIndex)
@@ -103932,6 +104156,20 @@ var updateStatusModalWithRef = (nextModal, setStatusModal, statusModalRef) => {
103932
104156
  statusModalRef.current = nextModal;
103933
104157
  setStatusModal(nextModal);
103934
104158
  };
104159
+ var updateImplementModeSelection = (current, delta) => {
104160
+ if (!current) {
104161
+ return current;
104162
+ }
104163
+ const nextSelection = clampSelection(current.selection + delta, IMPLEMENT_MODES.length);
104164
+ if (nextSelection === current.selection) {
104165
+ return current;
104166
+ }
104167
+ return { ...current, selection: nextSelection };
104168
+ };
104169
+ var updateImplementModeModalWithRef = (nextModal, setImplementModeModal, implementModeModalRef) => {
104170
+ implementModeModalRef.current = nextModal;
104171
+ setImplementModeModal(nextModal);
104172
+ };
103935
104173
  var applyStatusUpdate = (schubDir, modal) => {
103936
104174
  const option = modal.options[modal.selection];
103937
104175
  if (!option) {
@@ -103951,6 +104189,7 @@ var useStatusViewInput = ({
103951
104189
  repoRoot,
103952
104190
  showAllShortcutRef,
103953
104191
  statusModalRef,
104192
+ implementModeModalRef,
103954
104193
  currentItemsRef,
103955
104194
  selectionRef,
103956
104195
  viewRef,
@@ -103961,6 +104200,7 @@ var useStatusViewInput = ({
103961
104200
  pendingOpencodeActionIdsRef,
103962
104201
  setSelection,
103963
104202
  setStatusModal,
104203
+ setImplementModeModal,
103964
104204
  setRefreshTick,
103965
104205
  updateView,
103966
104206
  onCopyId,
@@ -103978,6 +104218,29 @@ var useStatusViewInput = ({
103978
104218
  const keyName2 = inputState.keyName;
103979
104219
  const keySequence = inputState.keySequence;
103980
104220
  const isCtrlX = key.ctrl && (lowerInput === "x" || keyName2 === "x" || keySequence === "\x18") || input === "\x18";
104221
+ const activeImplementModeModal = implementModeModalRef.current;
104222
+ if (activeImplementModeModal) {
104223
+ if (isEscape || lowerInput === "q") {
104224
+ updateImplementModeModalWithRef(null, setImplementModeModal, implementModeModalRef);
104225
+ return;
104226
+ }
104227
+ if (isUpArrow || isLeftArrow) {
104228
+ const nextModal = updateImplementModeSelection(activeImplementModeModal, -1);
104229
+ updateImplementModeModalWithRef(nextModal, setImplementModeModal, implementModeModalRef);
104230
+ return;
104231
+ }
104232
+ if (isDownArrow || isRightArrow) {
104233
+ const nextModal = updateImplementModeSelection(activeImplementModeModal, 1);
104234
+ updateImplementModeModalWithRef(nextModal, setImplementModeModal, implementModeModalRef);
104235
+ return;
104236
+ }
104237
+ if (isEnter) {
104238
+ const mode = IMPLEMENT_MODES[activeImplementModeModal.selection] ?? "none";
104239
+ onImplement(activeImplementModeModal.taskId, repoRoot, mode);
104240
+ updateImplementModeModalWithRef(null, setImplementModeModal, implementModeModalRef);
104241
+ }
104242
+ return;
104243
+ }
103981
104244
  const activeStatusModal = statusModalRef.current;
103982
104245
  if (activeStatusModal) {
103983
104246
  if (isEscape || lowerInput === "q") {
@@ -104051,19 +104314,28 @@ var useStatusViewInput = ({
104051
104314
  selectionRef.current = nextSelection;
104052
104315
  setSelection(nextSelection);
104053
104316
  }
104054
- if (isEnter && activeItem && !isShowAllRow(activeItem) && !isTaskItem(activeItem)) {
104317
+ if (isEnter && activeItem && !isShowAllRow(activeItem)) {
104318
+ if (isTaskItem(activeItem)) {
104319
+ onOpen?.(repoRoot, activeItem.path);
104320
+ return;
104321
+ }
104055
104322
  onOpenDetail?.(activeItem.id);
104056
104323
  return;
104057
104324
  }
104058
- if (isEnter && activeItem && !isShowAllRow(activeItem) && isTaskItem(activeItem) && activeItem.status === "wip" && onOpenSession && (activeView === "main" || activeView === "show-all-tasks")) {
104059
- const sessions = listOpencodeSessions();
104060
- const match = selectLatestSessionMatch(sessions, activeItem.id);
104061
- if (match) {
104062
- const exported = exportOpencodeSession(match.id);
104063
- const summary = mapOpencodeSessionSummary(exported);
104064
- onOpenSession({ status: "ready", summary });
104065
- } else {
104066
- onOpenSession({ status: "empty" });
104325
+ if (lowerInput === "n" && activeItem && !isShowAllRow(activeItem) && isTaskItem(activeItem) && activeItem.status === "wip" && onOpenSession && (activeView === "main" || activeView === "show-all-tasks")) {
104326
+ try {
104327
+ const sessions = listOpencodeSessions();
104328
+ const match = selectLatestSessionMatch(Array.isArray(sessions) ? sessions : [], activeItem.id);
104329
+ if (match) {
104330
+ const exported = exportOpencodeSession(match.id);
104331
+ const summary = mapOpencodeSessionSummary(exported);
104332
+ onOpenSession({ status: "ready", summary });
104333
+ } else {
104334
+ onOpenSession({ status: "empty" });
104335
+ }
104336
+ } catch (error) {
104337
+ const message = error instanceof Error ? error.message : "Unknown opencode error.";
104338
+ onOpenSession({ status: "error", message });
104067
104339
  }
104068
104340
  return;
104069
104341
  }
@@ -104071,9 +104343,6 @@ var useStatusViewInput = ({
104071
104343
  updateView(isShowAllTasksRow(activeItem) ? "show-all-tasks" : "show-all");
104072
104344
  return;
104073
104345
  }
104074
- if (input === "o" && activeItem && !isShowAllRow(activeItem)) {
104075
- onOpen?.(repoRoot, activeItem.path);
104076
- }
104077
104346
  if (input === "c" && activeItem && !isShowAllRow(activeItem)) {
104078
104347
  onCopyId(activeItem.id);
104079
104348
  }
@@ -104099,13 +104368,13 @@ var useStatusViewInput = ({
104099
104368
  const readyIndex = activeSelection - start;
104100
104369
  const taskId = readyToImplementIdsRef.current[readyIndex];
104101
104370
  if (taskId && !pendingOpencodeActionIdsRef.current.has(taskId)) {
104102
- onImplement(taskId, repoRoot);
104371
+ updateImplementModeModalWithRef({ taskId, selection: 0 }, setImplementModeModal, implementModeModalRef);
104103
104372
  }
104104
104373
  }
104105
104374
  return;
104106
104375
  }
104107
104376
  if (activeView === "show-all-tasks" && activeItem && !isShowAllRow(activeItem) && isTaskItem(activeItem) && readyToImplementIdsRef.current.includes(activeItem.id) && !pendingOpencodeActionIdsRef.current.has(activeItem.id)) {
104108
- onImplement(activeItem.id, repoRoot);
104377
+ updateImplementModeModalWithRef({ taskId: activeItem.id, selection: 0 }, setImplementModeModal, implementModeModalRef);
104109
104378
  }
104110
104379
  }
104111
104380
  }, { isActive });
@@ -104149,13 +104418,32 @@ var StatusShowAllTasksView = ({ selection, sections, pendingIds }) => {
104149
104418
 
104150
104419
  // src/tui/components/status-view.tsx
104151
104420
  var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
104152
- var DEFAULT_REFRESH_INTERVAL_MS2 = 1000;
104153
- var OPEN_SHORTCUT = { keyLabel: "o", label: "preview" };
104421
+ var DEFAULT_REFRESH_INTERVAL_MS = 1000;
104154
104422
  var COPY_SHORTCUT = { keyLabel: "c", label: "copy id" };
104423
+ var SESSION_SHORTCUT = { keyLabel: "n", label: "session" };
104155
104424
  var CREATE_TASKS_SHORTCUT = { keyLabel: "a", label: "create tasks" };
104156
104425
  var REVIEW_SHORTCUT = { keyLabel: "r", label: "review" };
104157
104426
  var IMPLEMENT_SHORTCUT = { keyLabel: "i", label: "implement" };
104158
104427
  var STATUS_SHORTCUT = { keyLabel: "s", label: "status" };
104428
+ var defaultImplementLauncher = (id, repoRoot, mode, prompt) => {
104429
+ const options2 = prompt ? { prompt } : undefined;
104430
+ if (mode !== "worktree") {
104431
+ return launchOpencodeImplement(id, repoRoot, options2);
104432
+ }
104433
+ try {
104434
+ const worktree = createTaskWorktree({ repoRoot, taskId: id });
104435
+ const worktreePath = worktree?.worktreePath;
104436
+ if (!worktreePath) {
104437
+ throw new Error("Unable to resolve git root for worktree.");
104438
+ }
104439
+ return launchOpencodeImplement(id, repoRoot, options2, {
104440
+ env: { ...process.env, SCHUB_CWD: worktreePath },
104441
+ cwd: worktreePath
104442
+ });
104443
+ } catch {
104444
+ return;
104445
+ }
104446
+ };
104159
104447
  var SHOW_ALL_ROW = {
104160
104448
  kind: "show-all",
104161
104449
  id: "show-all-proposals",
@@ -104170,7 +104458,7 @@ var isShowAllTasksRow = (row) => row.id === SHOW_ALL_TASKS_ROW.id;
104170
104458
  var isChangeListItem = (item) => Boolean(item);
104171
104459
  var isTaskListItem2 = (item) => Boolean(item);
104172
104460
  function StatusView({
104173
- refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS2,
104461
+ refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS,
104174
104462
  startDir,
104175
104463
  isActive = true,
104176
104464
  onCopyId,
@@ -104178,13 +104466,13 @@ function StatusView({
104178
104466
  onOpenDetail,
104179
104467
  onOpenSession,
104180
104468
  onCreateTasks = launchOpencodeCreateTasks,
104181
- onImplement = launchOpencodeImplement,
104469
+ onImplement = defaultImplementLauncher,
104182
104470
  onReview = launchOpencodeReview,
104183
104471
  onShortcutsChange,
104184
104472
  onViewChange
104185
104473
  }) {
104186
104474
  const schubDir = findSchubRoot(startDir);
104187
- const [, setRefreshTick] = import_react61.default.useState(0);
104475
+ const [, setRefreshTick] = import_react60.default.useState(0);
104188
104476
  const {
104189
104477
  pendingReview,
104190
104478
  pendingImplementation,
@@ -104196,11 +104484,13 @@ function StatusView({
104196
104484
  reviewed,
104197
104485
  backlog
104198
104486
  } = buildStatusData(schubDir);
104199
- const repoRoot = schubDir ? dirname12(schubDir) : "";
104487
+ const repoRoot = schubDir ? dirname13(schubDir) : "";
104200
104488
  const changeListItems = buildChangeListItems(schubDir);
104201
104489
  const taskListItems = buildTaskListItems(schubDir);
104490
+ const showAllTaskListItems = buildTaskListItems(schubDir, TASK_STATUSES);
104202
104491
  const changeListById = new Map(changeListItems.map((item) => [item.id, item]));
104203
104492
  const taskListById = new Map(taskListItems.map((item) => [item.id, item]));
104493
+ const showAllTaskListById = new Map(showAllTaskListItems.map((item) => [item.id, item]));
104204
104494
  const resolveChangeItems = (items) => items.map((item) => changeListById.get(item.id)).filter(isChangeListItem);
104205
104495
  const resolveTaskItems = (items) => items.map((item) => taskListById.get(item.id)).filter(isTaskListItem2);
104206
104496
  const pendingReviewItems = resolveChangeItems(pendingReview);
@@ -104236,30 +104526,32 @@ function StatusView({
104236
104526
  const showAllTaskGroups = buildShowAllTaskGroups(schubDir);
104237
104527
  const showAllTaskSections = showAllTaskGroups.map((group) => ({
104238
104528
  title: group.label,
104239
- items: group.items.map((item) => taskListById.get(item.id)).filter(isTaskListItem2)
104529
+ items: group.items.map((item) => showAllTaskListById.get(item.id)).filter(isTaskListItem2)
104240
104530
  }));
104241
104531
  const showAllTaskItems = showAllTaskSections.flatMap((section2) => section2.items);
104242
104532
  const changeItems = changeSections.flatMap((section2) => section2.items);
104243
104533
  const taskItems = taskSections.flatMap((section2) => section2.items);
104244
104534
  const mainItems = [...changeItems, SHOW_ALL_ROW, ...taskItems, SHOW_ALL_TASKS_ROW];
104245
104535
  const mainSelectionFloor = changeItems.length === 0 && taskItems.length > 0 ? 1 : 0;
104246
- const [view, setView] = import_react61.default.useState("main");
104247
- const [mainSelection, setMainSelection] = import_react61.default.useState(() => mainSelectionFloor);
104248
- const [showAllSelection, setShowAllSelection] = import_react61.default.useState(0);
104249
- const [showAllTasksSelection, setShowAllTasksSelection] = import_react61.default.useState(0);
104250
- const [statusModal, setStatusModal] = import_react61.default.useState(null);
104251
- const [pendingOpencodeActionIds, setPendingOpencodeActionIds] = import_react61.default.useState(() => new Set);
104252
- const showAllShortcutRef = import_react61.default.useRef(false);
104253
- const statusModalRef = import_react61.default.useRef(null);
104254
- const pendingOpencodeActionIdsRef = import_react61.default.useRef(pendingOpencodeActionIds);
104255
- const updatePendingOpencodeActionIds = import_react61.default.useCallback((updater) => {
104536
+ const [view, setView] = import_react60.default.useState("main");
104537
+ const [mainSelection, setMainSelection] = import_react60.default.useState(() => mainSelectionFloor);
104538
+ const [showAllSelection, setShowAllSelection] = import_react60.default.useState(0);
104539
+ const [showAllTasksSelection, setShowAllTasksSelection] = import_react60.default.useState(0);
104540
+ const [statusModal, setStatusModal] = import_react60.default.useState(null);
104541
+ const [implementModeModal, setImplementModeModal] = import_react60.default.useState(null);
104542
+ const [pendingOpencodeActionIds, setPendingOpencodeActionIds] = import_react60.default.useState(() => new Set);
104543
+ const showAllShortcutRef = import_react60.default.useRef(false);
104544
+ const statusModalRef = import_react60.default.useRef(null);
104545
+ const implementModeModalRef = import_react60.default.useRef(null);
104546
+ const pendingOpencodeActionIdsRef = import_react60.default.useRef(pendingOpencodeActionIds);
104547
+ const updatePendingOpencodeActionIds = import_react60.default.useCallback((updater) => {
104256
104548
  setPendingOpencodeActionIds((current) => {
104257
104549
  const next = updater(current);
104258
104550
  pendingOpencodeActionIdsRef.current = next;
104259
104551
  return next;
104260
104552
  });
104261
104553
  }, []);
104262
- const addPendingOpencodeAction = import_react61.default.useCallback((id) => {
104554
+ const addPendingOpencodeAction = import_react60.default.useCallback((id) => {
104263
104555
  updatePendingOpencodeActionIds((current) => {
104264
104556
  if (current.has(id)) {
104265
104557
  return current;
@@ -104269,7 +104561,7 @@ function StatusView({
104269
104561
  return next;
104270
104562
  });
104271
104563
  }, [updatePendingOpencodeActionIds]);
104272
- const clearPendingOpencodeAction = import_react61.default.useCallback((id) => {
104564
+ const clearPendingOpencodeAction = import_react60.default.useCallback((id) => {
104273
104565
  updatePendingOpencodeActionIds((current) => {
104274
104566
  if (!current.has(id)) {
104275
104567
  return current;
@@ -104279,7 +104571,7 @@ function StatusView({
104279
104571
  return next;
104280
104572
  });
104281
104573
  }, [updatePendingOpencodeActionIds]);
104282
- const trackPendingOpencodeAction = import_react61.default.useCallback((id, launcher) => {
104574
+ const trackPendingOpencodeAction = import_react60.default.useCallback((id, launcher) => {
104283
104575
  if (pendingOpencodeActionIdsRef.current.has(id)) {
104284
104576
  return;
104285
104577
  }
@@ -104305,9 +104597,11 @@ function StatusView({
104305
104597
  const selectedItem = totalItems > 0 ? currentItems[selection] : null;
104306
104598
  const hasOpenableSelection = Boolean(selectedItem && !isShowAllRow(selectedItem));
104307
104599
  const selectedTaskItem = selectedItem && !isShowAllRow(selectedItem) && isTaskItem(selectedItem) ? selectedItem : null;
104600
+ const canOpenSession = Boolean(selectedTaskItem && selectedTaskItem.status === "wip" && (view === "main" || view === "show-all-tasks"));
104308
104601
  const statusModalCandidate = selectedItem && !isShowAllRow(selectedItem) ? buildStatusModalState(selectedItem) : null;
104309
104602
  const canUpdateStatus = Boolean(statusModalCandidate);
104310
- const readyToImplementStart = changeItems.length + 1;
104603
+ const taskStartIndex = changeItems.length + 1;
104604
+ const readyToImplementStart = taskStartIndex + wipItems.length + blockedItems.length;
104311
104605
  const readyToImplementEnd = readyToImplementStart + readyItems.length;
104312
104606
  const readyToImplementIds = readyToImplement.map((task) => task.id);
104313
104607
  const pendingImplementationNoTaskIds = pendingImplementationNoTasks.map((change) => change.id);
@@ -104318,15 +104612,25 @@ function StatusView({
104318
104612
  const canCreateTasks = Boolean(selectedCreateTasksChangeId && !pendingOpencodeActionIds.has(selectedCreateTasksChangeId));
104319
104613
  const canReview = Boolean(selectedReviewChangeId && !pendingOpencodeActionIds.has(selectedReviewChangeId));
104320
104614
  const canImplement = Boolean(selectedReadyTaskId && !pendingOpencodeActionIds.has(selectedReadyTaskId));
104321
- const lastShortcutRef = import_react61.default.useRef(null);
104322
- const readyToImplementIdsRef = import_react61.default.useRef(readyToImplementIds);
104323
- const readyToImplementRangeRef = import_react61.default.useRef({ start: readyToImplementStart, end: readyToImplementEnd });
104324
- const pendingImplementationNoTaskIdsRef = import_react61.default.useRef(pendingImplementationNoTaskIds);
104325
- const pendingReviewIdsRef = import_react61.default.useRef(pendingReviewIds);
104326
- const currentItemsRef = import_react61.default.useRef(currentItems);
104327
- const selectionRef = import_react61.default.useRef(selection);
104328
- const viewRef = import_react61.default.useRef(view);
104329
- import_react61.default.useEffect(() => {
104615
+ const buildImplementPrompt = import_react60.default.useCallback((taskId) => {
104616
+ if (!schubDir) {
104617
+ return;
104618
+ }
104619
+ const taskPath = taskListById.get(taskId)?.path ?? showAllTaskListById.get(taskId)?.path;
104620
+ if (!taskPath) {
104621
+ return;
104622
+ }
104623
+ return prepareImplementContext(schubDir, resolve12(repoRoot, taskPath));
104624
+ }, [repoRoot, schubDir, showAllTaskListById, taskListById]);
104625
+ const lastShortcutRef = import_react60.default.useRef(null);
104626
+ const readyToImplementIdsRef = import_react60.default.useRef(readyToImplementIds);
104627
+ const readyToImplementRangeRef = import_react60.default.useRef({ start: readyToImplementStart, end: readyToImplementEnd });
104628
+ const pendingImplementationNoTaskIdsRef = import_react60.default.useRef(pendingImplementationNoTaskIds);
104629
+ const pendingReviewIdsRef = import_react60.default.useRef(pendingReviewIds);
104630
+ const currentItemsRef = import_react60.default.useRef(currentItems);
104631
+ const selectionRef = import_react60.default.useRef(selection);
104632
+ const viewRef = import_react60.default.useRef(view);
104633
+ import_react60.default.useEffect(() => {
104330
104634
  currentItemsRef.current = currentItems;
104331
104635
  selectionRef.current = selection;
104332
104636
  viewRef.current = view;
@@ -104346,16 +104650,16 @@ function StatusView({
104346
104650
  selection,
104347
104651
  view
104348
104652
  ]);
104349
- import_react61.default.useEffect(() => {
104350
- statusModalRef.current = statusModal;
104351
- }, [statusModal]);
104352
- import_react61.default.useLayoutEffect(() => {
104653
+ import_react60.default.useLayoutEffect(() => {
104353
104654
  if (!onShortcutsChange) {
104354
104655
  return;
104355
104656
  }
104356
104657
  const shortcuts = [];
104357
104658
  if (hasOpenableSelection) {
104358
- shortcuts.push(OPEN_SHORTCUT, COPY_SHORTCUT);
104659
+ shortcuts.push(COPY_SHORTCUT);
104660
+ }
104661
+ if (canOpenSession) {
104662
+ shortcuts.push(SESSION_SHORTCUT);
104359
104663
  }
104360
104664
  if (canCreateTasks) {
104361
104665
  shortcuts.push(CREATE_TASKS_SHORTCUT);
@@ -104375,7 +104679,15 @@ function StatusView({
104375
104679
  }
104376
104680
  lastShortcutRef.current = shortcutKey;
104377
104681
  onShortcutsChange(shortcuts);
104378
- }, [canCreateTasks, canImplement, canReview, canUpdateStatus, hasOpenableSelection, onShortcutsChange]);
104682
+ }, [
104683
+ canCreateTasks,
104684
+ canImplement,
104685
+ canOpenSession,
104686
+ canReview,
104687
+ canUpdateStatus,
104688
+ hasOpenableSelection,
104689
+ onShortcutsChange
104690
+ ]);
104379
104691
  const refresh = () => {
104380
104692
  if (!schubDir) {
104381
104693
  return;
@@ -104384,7 +104696,7 @@ function StatusView({
104384
104696
  setRefreshTick((current) => current + 1);
104385
104697
  };
104386
104698
  useRefreshInterval({ enabled: Boolean(schubDir), refreshIntervalMs, onRefresh: refresh });
104387
- import_react61.default.useEffect(() => {
104699
+ import_react60.default.useEffect(() => {
104388
104700
  if (mainItems.length === 0) {
104389
104701
  setMainSelection(0);
104390
104702
  return;
@@ -104394,40 +104706,42 @@ function StatusView({
104394
104706
  return clamped === 0 ? mainSelectionFloor : clamped;
104395
104707
  });
104396
104708
  }, [mainItems.length, mainSelectionFloor]);
104397
- import_react61.default.useEffect(() => {
104709
+ import_react60.default.useEffect(() => {
104398
104710
  if (showAllItems.length === 0) {
104399
104711
  setShowAllSelection(0);
104400
104712
  return;
104401
104713
  }
104402
104714
  setShowAllSelection((current) => clampSelection(current, showAllItems.length));
104403
104715
  }, [showAllItems.length]);
104404
- import_react61.default.useEffect(() => {
104716
+ import_react60.default.useEffect(() => {
104405
104717
  if (showAllTaskItems.length === 0) {
104406
104718
  setShowAllTasksSelection(0);
104407
104719
  return;
104408
104720
  }
104409
104721
  setShowAllTasksSelection((current) => clampSelection(current, showAllTaskItems.length));
104410
104722
  }, [showAllTaskItems.length]);
104411
- import_react61.default.useEffect(() => {
104723
+ import_react60.default.useEffect(() => {
104412
104724
  if (view) {
104413
104725
  showAllShortcutRef.current = false;
104414
104726
  }
104415
104727
  }, [view]);
104416
- const handleCreateTasks = import_react61.default.useCallback((changeId, root) => {
104728
+ const handleCreateTasks = import_react60.default.useCallback((changeId, root) => {
104417
104729
  trackPendingOpencodeAction(changeId, () => onCreateTasks(changeId, root));
104418
104730
  }, [onCreateTasks, trackPendingOpencodeAction]);
104419
- const handleReview = import_react61.default.useCallback((changeId, root) => {
104731
+ const handleReview = import_react60.default.useCallback((changeId, root) => {
104420
104732
  trackPendingOpencodeAction(changeId, () => onReview(changeId, root));
104421
104733
  }, [onReview, trackPendingOpencodeAction]);
104422
- const handleImplement = import_react61.default.useCallback((taskId, root) => {
104423
- trackPendingOpencodeAction(taskId, () => onImplement(taskId, root));
104424
- }, [onImplement, trackPendingOpencodeAction]);
104734
+ const handleImplement = import_react60.default.useCallback((taskId, root, mode) => {
104735
+ const prompt = buildImplementPrompt(taskId);
104736
+ trackPendingOpencodeAction(taskId, () => onImplement(taskId, root, mode, prompt));
104737
+ }, [buildImplementPrompt, onImplement, trackPendingOpencodeAction]);
104425
104738
  useStatusViewInput({
104426
104739
  isActive,
104427
104740
  schubDir,
104428
104741
  repoRoot,
104429
104742
  showAllShortcutRef,
104430
104743
  statusModalRef,
104744
+ implementModeModalRef,
104431
104745
  currentItemsRef,
104432
104746
  selectionRef,
104433
104747
  viewRef,
@@ -104438,6 +104752,7 @@ function StatusView({
104438
104752
  pendingOpencodeActionIdsRef,
104439
104753
  setSelection,
104440
104754
  setStatusModal,
104755
+ setImplementModeModal,
104441
104756
  setRefreshTick,
104442
104757
  updateView,
104443
104758
  onCopyId,
@@ -104464,14 +104779,11 @@ function StatusView({
104464
104779
  ]
104465
104780
  }, undefined, true, undefined, this);
104466
104781
  }
104467
- if (view === "show-all") {
104468
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(StatusShowAllView, {
104469
- selection,
104470
- sections: showAllSections,
104471
- pendingIds: pendingOpencodeActionIds
104472
- }, undefined, false, undefined, this);
104473
- }
104474
- const content = view === "show-all-tasks" ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(StatusShowAllTasksView, {
104782
+ const content = view === "show-all" ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(StatusShowAllView, {
104783
+ selection,
104784
+ sections: showAllSections,
104785
+ pendingIds: pendingOpencodeActionIds
104786
+ }, undefined, false, undefined, this) : view === "show-all-tasks" ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(StatusShowAllTasksView, {
104475
104787
  selection,
104476
104788
  sections: showAllTaskSections,
104477
104789
  pendingIds: pendingOpencodeActionIds
@@ -104487,6 +104799,9 @@ function StatusView({
104487
104799
  flexDirection: "column",
104488
104800
  children: [
104489
104801
  content,
104802
+ implementModeModal ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(ImplementModeModal, {
104803
+ modal: implementModeModal
104804
+ }, undefined, false, undefined, this) : null,
104490
104805
  statusModal ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(ListItemStatusModal, {
104491
104806
  statusModal
104492
104807
  }, undefined, false, undefined, this) : null
@@ -104495,7 +104810,7 @@ function StatusView({
104495
104810
  }
104496
104811
 
104497
104812
  // src/tui/shared/detail-tasks.ts
104498
- var compareText4 = (left2, right2) => left2.localeCompare(right2, undefined, { sensitivity: "base" });
104813
+ var compareText3 = (left2, right2) => left2.localeCompare(right2, undefined, { sensitivity: "base" });
104499
104814
  var idSortValue3 = (value) => {
104500
104815
  const match = value.match(/\d+/);
104501
104816
  return match ? Number(match[0]) : Number.POSITIVE_INFINITY;
@@ -104505,7 +104820,7 @@ var compareIds3 = (left2, right2) => {
104505
104820
  if (numberDiff !== 0) {
104506
104821
  return numberDiff;
104507
104822
  }
104508
- return compareText4(left2, right2);
104823
+ return compareText3(left2, right2);
104509
104824
  };
104510
104825
  var resolveTaskName2 = (task) => {
104511
104826
  if (task.status === "blocked") {
@@ -104521,38 +104836,93 @@ var sortDetailTasks = (tasks) => [...tasks].sort((left2, right2) => {
104521
104836
  if (idCompare !== 0) {
104522
104837
  return idCompare;
104523
104838
  }
104524
- return compareText4(resolveTaskName2(left2), resolveTaskName2(right2));
104839
+ return compareText3(resolveTaskName2(left2), resolveTaskName2(right2));
104525
104840
  });
104526
104841
  var buildDetailTasks = (tasks) => TASK_STATUSES.flatMap((status) => sortDetailTasks(tasks.filter((task) => task.status === status)));
104527
104842
 
104528
104843
  // src/tui/app.tsx
104529
104844
  var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
104530
- var tabs = [
104531
- { id: "status", label: "Overview" },
104532
- { id: "plan", label: "Plan" }
104533
- ];
104845
+ var TAB_LABEL = "Overview";
104534
104846
  var COPY_BANNER_TEXT = "Copied to clipboard !";
104535
104847
  var COPY_BANNER_TIMEOUT_MS = 1500;
104536
- var OPEN_SHORTCUT2 = { keyLabel: "o", label: "preview" };
104537
104848
  var COPY_SHORTCUT2 = { keyLabel: "c", label: "copy id" };
104849
+ var STATUS_SHORTCUT2 = { keyLabel: "s", label: "status" };
104850
+ var ImplementErrorModal = ({ message }) => {
104851
+ return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104852
+ backgroundColor: "darkGray",
104853
+ flexDirection: "column",
104854
+ paddingX: 2,
104855
+ paddingY: 1,
104856
+ children: [
104857
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104858
+ marginBottom: 1,
104859
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104860
+ color: "red",
104861
+ children: "Implement failed"
104862
+ }, undefined, false, undefined, this)
104863
+ }, undefined, false, undefined, this),
104864
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104865
+ color: "white",
104866
+ children: message
104867
+ }, undefined, false, undefined, this),
104868
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104869
+ alignItems: "flex-end",
104870
+ marginTop: 1,
104871
+ children: [
104872
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104873
+ color: "white",
104874
+ children: "esc"
104875
+ }, undefined, false, undefined, this),
104876
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104877
+ color: "gray",
104878
+ children: " dismiss"
104879
+ }, undefined, false, undefined, this)
104880
+ ]
104881
+ }, undefined, true, undefined, this)
104882
+ ]
104883
+ }, undefined, true, undefined, this);
104884
+ };
104885
+ var updateDetailStatusModalSelection = (current, delta) => {
104886
+ if (!current) {
104887
+ return current;
104888
+ }
104889
+ const nextSelection = clampSelection(current.selection + delta, current.options.length);
104890
+ if (nextSelection === current.selection) {
104891
+ return current;
104892
+ }
104893
+ return { ...current, selection: nextSelection };
104894
+ };
104895
+ var applyDetailStatusUpdate = (schubDir, modal) => {
104896
+ const option = modal.options[modal.selection];
104897
+ if (!option) {
104898
+ return;
104899
+ }
104900
+ if (modal.itemKind !== "task") {
104901
+ return;
104902
+ }
104903
+ if (option.status === "backlog" || option.status === "reviewed" || option.status === "archived") {
104904
+ updateTaskStatuses(schubDir, [modal.itemId], option.status);
104905
+ }
104906
+ };
104538
104907
  function App2({
104539
104908
  copyToClipboard: copyToClipboard2 = copyToClipboard,
104540
104909
  initialSessionView = null,
104541
104910
  onImplement,
104542
104911
  startDir
104543
104912
  }) {
104544
- const [mode, setMode] = import_react62.default.useState("status");
104545
104913
  const { stdout } = use_stdout_default();
104546
- const [dimensions, setDimensions] = import_react62.default.useState(() => ({
104914
+ const [dimensions, setDimensions] = import_react61.default.useState(() => ({
104547
104915
  columns: stdout.columns,
104548
104916
  rows: stdout.rows
104549
104917
  }));
104550
- const [copyBanner, setCopyBanner] = import_react62.default.useState(null);
104551
- const [statusShortcuts, setStatusShortcuts] = import_react62.default.useState([]);
104552
- const [statusView, setStatusView] = import_react62.default.useState("main");
104553
- const [preview, setPreview] = import_react62.default.useState(null);
104554
- const [proposalDetail, setProposalDetail] = import_react62.default.useState(null);
104555
- const [sessionView, setSessionView] = import_react62.default.useState(initialSessionView);
104918
+ const [copyBanner, setCopyBanner] = import_react61.default.useState(null);
104919
+ const [statusShortcuts, setStatusShortcuts] = import_react61.default.useState([]);
104920
+ const [statusView, setStatusView] = import_react61.default.useState("main");
104921
+ const [preview, setPreview] = import_react61.default.useState(null);
104922
+ const [proposalDetail, setProposalDetail] = import_react61.default.useState(null);
104923
+ const [detailStatusModal, setDetailStatusModal] = import_react61.default.useState(null);
104924
+ const [implementError, setImplementError] = import_react61.default.useState(null);
104925
+ const [sessionView, setSessionView] = import_react61.default.useState(initialSessionView);
104556
104926
  const versionLabel = `${package_default.version}`;
104557
104927
  const homeDir = homedir2();
104558
104928
  const resolvedStartDir = startDir ?? process.env.SCHUB_CWD ?? process.cwd();
@@ -104567,15 +104937,27 @@ function App2({
104567
104937
  }
104568
104938
  return targetDir;
104569
104939
  })();
104570
- const repoRoot = schubDir ? dirname13(schubDir) : "";
104940
+ const repoRoot = schubDir ? dirname14(schubDir) : "";
104571
104941
  const isPreviewActive = Boolean(preview);
104572
104942
  const isDetailActive = Boolean(proposalDetail);
104573
- const isSessionActive = mode === "status" && Boolean(sessionView);
104943
+ const isSessionActive = Boolean(sessionView);
104944
+ const isImplementErrorActive = Boolean(implementError);
104574
104945
  const detailSelection = proposalDetail?.selection ?? 0;
104575
104946
  const detailTasks = proposalDetail && schubDir ? buildDetailTasks(listTasksForChange(schubDir, proposalDetail.changeId)) : [];
104576
104947
  const selectedDetailTask = detailTasks[detailSelection] ?? null;
104577
- const detailShortcuts = isDetailActive && detailTasks.length > 0 ? [OPEN_SHORTCUT2, COPY_SHORTCUT2] : [];
104578
- import_react62.default.useEffect(() => {
104948
+ const detailStatusItem = selectedDetailTask ? {
104949
+ id: selectedDetailTask.id,
104950
+ title: selectedDetailTask.title,
104951
+ status: selectedDetailTask.status,
104952
+ sourceStatus: selectedDetailTask.status,
104953
+ path: selectedDetailTask.path,
104954
+ changeId: selectedDetailTask.changeId,
104955
+ blockedReason: selectedDetailTask.blockedReason
104956
+ } : null;
104957
+ const detailStatusModalCandidate = detailStatusItem ? buildStatusModalState(detailStatusItem) : null;
104958
+ const canUpdateDetailStatus = Boolean(detailStatusModalCandidate);
104959
+ const detailShortcuts = isDetailActive && detailTasks.length > 0 ? [COPY_SHORTCUT2, ...canUpdateDetailStatus ? [STATUS_SHORTCUT2] : []] : [];
104960
+ import_react61.default.useEffect(() => {
104579
104961
  if (!copyBanner) {
104580
104962
  return;
104581
104963
  }
@@ -104586,7 +104968,7 @@ function App2({
104586
104968
  clearTimeout(timeout);
104587
104969
  };
104588
104970
  }, [copyBanner]);
104589
- import_react62.default.useEffect(() => {
104971
+ import_react61.default.useEffect(() => {
104590
104972
  const handleResize = () => {
104591
104973
  setDimensions({ columns: stdout.columns, rows: stdout.rows });
104592
104974
  };
@@ -104595,39 +104977,27 @@ function App2({
104595
104977
  stdout.off("resize", handleResize);
104596
104978
  };
104597
104979
  }, [stdout]);
104598
- const showTabs = !(mode === "status" && (statusView !== "main" || isPreviewActive || isDetailActive || isSessionActive));
104980
+ const showTabs = statusView === "main" && !isPreviewActive && !isDetailActive && !isSessionActive;
104599
104981
  const showHeader = showTabs || Boolean(copyBanner);
104600
104982
  const previewHeight = Math.max(10, dimensions.rows || 0);
104601
- use_input_default((_input, key) => {
104602
- if (key.tab) {
104603
- setMode((current) => {
104604
- const currentIndex = tabs.findIndex((tab2) => tab2.id === current);
104605
- const nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % tabs.length;
104606
- return tabs[nextIndex]?.id ?? "status";
104607
- });
104608
- }
104609
- }, { isActive: showTabs });
104610
- import_react62.default.useEffect(() => {
104611
- if (mode !== "status") {
104612
- setStatusShortcuts([]);
104613
- }
104614
- }, [mode]);
104615
104983
  const handleCopyId = (value) => {
104616
104984
  copyToClipboard2(value);
104617
104985
  setCopyBanner(COPY_BANNER_TEXT);
104618
104986
  };
104619
104987
  const handleOpenPreview = (repoRoot2, relativePath) => {
104620
- const targetPath = resolve12(repoRoot2, relativePath);
104621
- const markdown = readFileSync13(targetPath, "utf8");
104622
- setPreview({ fileName: basename6(relativePath), markdown });
104988
+ const targetPath = resolve13(repoRoot2, relativePath);
104989
+ const markdown = readFileSync15(targetPath, "utf8");
104990
+ setPreview({ fileName: basename7(relativePath), markdown });
104623
104991
  };
104624
104992
  const handleClosePreview = () => {
104625
104993
  setPreview(null);
104626
104994
  };
104627
104995
  const handleOpenDetail = (changeId) => {
104996
+ setDetailStatusModal(null);
104628
104997
  setProposalDetail({ changeId, selection: 0 });
104629
104998
  };
104630
104999
  const handleCloseDetail = () => {
105000
+ setDetailStatusModal(null);
104631
105001
  setProposalDetail(null);
104632
105002
  };
104633
105003
  const handleOpenSession = (state) => {
@@ -104636,8 +105006,53 @@ function App2({
104636
105006
  const handleCloseSession = () => {
104637
105007
  setSessionView(null);
104638
105008
  };
105009
+ const handleImplement = import_react61.default.useCallback((taskId, root, selectedMode, prompt) => {
105010
+ if (onImplement) {
105011
+ onImplement(taskId, root, selectedMode, prompt);
105012
+ return;
105013
+ }
105014
+ const options2 = prompt ? { prompt } : undefined;
105015
+ if (selectedMode === "worktree") {
105016
+ try {
105017
+ const worktree = createTaskWorktree({ repoRoot: root, taskId });
105018
+ const worktreePath = worktree?.worktreePath;
105019
+ if (!worktreePath) {
105020
+ throw new Error("Unable to resolve git root for worktree.");
105021
+ }
105022
+ return launchOpencodeImplement(taskId, root, options2, {
105023
+ env: { ...process.env, SCHUB_CWD: worktreePath },
105024
+ cwd: worktreePath
105025
+ });
105026
+ } catch (error) {
105027
+ const message = error instanceof Error ? error.message : String(error);
105028
+ setImplementError({ message });
105029
+ return;
105030
+ }
105031
+ }
105032
+ return launchOpencodeImplement(taskId, root, options2);
105033
+ }, [onImplement]);
104639
105034
  use_input_default((input, key) => {
104640
- const { isEscape, isDownArrow, isUpArrow } = buildInputState(input, key);
105035
+ const inputState = buildInputState(input, key);
105036
+ const { lowerInput, isEscape, isEnter, isDownArrow, isUpArrow, isLeftArrow, isRightArrow } = inputState;
105037
+ if (detailStatusModal) {
105038
+ if (isEscape || lowerInput === "q") {
105039
+ setDetailStatusModal(null);
105040
+ return;
105041
+ }
105042
+ if (isUpArrow || isLeftArrow) {
105043
+ setDetailStatusModal((current) => updateDetailStatusModalSelection(current, -1));
105044
+ return;
105045
+ }
105046
+ if (isDownArrow || isRightArrow) {
105047
+ setDetailStatusModal((current) => updateDetailStatusModalSelection(current, 1));
105048
+ return;
105049
+ }
105050
+ if (isEnter && schubDir) {
105051
+ applyDetailStatusUpdate(schubDir, detailStatusModal);
105052
+ setDetailStatusModal(null);
105053
+ }
105054
+ return;
105055
+ }
104641
105056
  if (isEscape) {
104642
105057
  handleCloseDetail();
104643
105058
  return;
@@ -104674,23 +105089,34 @@ function App2({
104674
105089
  if (!selectedDetailTask) {
104675
105090
  return;
104676
105091
  }
104677
- if (input === "o") {
105092
+ if (lowerInput === "s" && detailStatusModalCandidate) {
105093
+ setDetailStatusModal(detailStatusModalCandidate);
105094
+ return;
105095
+ }
105096
+ if (isEnter) {
104678
105097
  handleOpenPreview(repoRoot, selectedDetailTask.path);
104679
105098
  return;
104680
105099
  }
104681
105100
  if (input === "c") {
104682
105101
  handleCopyId(selectedDetailTask.id);
104683
105102
  }
104684
- }, { isActive: isDetailActive && !isPreviewActive && !isSessionActive });
105103
+ }, { isActive: isDetailActive && !isPreviewActive && !isSessionActive && !isImplementErrorActive });
104685
105104
  use_input_default((input, key) => {
104686
105105
  const { isEscape } = buildInputState(input, key);
104687
105106
  if (isEscape) {
104688
105107
  handleCloseSession();
104689
105108
  }
104690
- }, { isActive: isSessionActive && !isPreviewActive });
104691
- const shortcuts = mode === "status" && !isPreviewActive && !isSessionActive ? isDetailActive ? detailShortcuts : statusShortcuts : [];
104692
- const isStatusListActive = !isPreviewActive && !isDetailActive && !isSessionActive;
104693
- const isDetailVisible = isDetailActive && !isPreviewActive && !isSessionActive;
105109
+ }, { isActive: isSessionActive && !isPreviewActive && !isImplementErrorActive });
105110
+ use_input_default((input, key) => {
105111
+ const { lowerInput, isEscape } = buildInputState(input, key);
105112
+ if (isEscape || lowerInput === "q") {
105113
+ setImplementError(null);
105114
+ }
105115
+ }, { isActive: isImplementErrorActive });
105116
+ const shortcuts = !isPreviewActive && !isSessionActive && !isImplementErrorActive ? isDetailActive ? detailShortcuts : statusShortcuts : [];
105117
+ const isStatusListVisible = !isPreviewActive && !isDetailActive && !isSessionActive;
105118
+ const isStatusListActive = isStatusListVisible && !isImplementErrorActive;
105119
+ const isDetailVisible = isDetailActive && !isPreviewActive && !isSessionActive && !isImplementErrorActive;
104694
105120
  return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104695
105121
  backgroundColor: "black",
104696
105122
  flexDirection: "column",
@@ -104709,26 +105135,23 @@ function App2({
104709
105135
  children: [
104710
105136
  showTabs ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104711
105137
  flexDirection: "row",
104712
- children: tabs.map((tab2) => {
104713
- const isSelected = mode === tab2.id;
104714
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104715
- marginRight: 4,
104716
- borderStyle: "bold",
104717
- borderLeft: isSelected,
104718
- borderTop: false,
104719
- borderRight: false,
104720
- borderBottom: false,
104721
- borderLeftColor: "blueBright",
104722
- flexDirection: "row",
104723
- alignItems: "center",
104724
- paddingLeft: 1,
104725
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104726
- color: isSelected ? "white" : "gray",
104727
- bold: isSelected,
104728
- children: tab2.label
104729
- }, undefined, false, undefined, this)
104730
- }, tab2.id, false, undefined, this);
104731
- })
105138
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
105139
+ marginRight: 4,
105140
+ borderStyle: "bold",
105141
+ borderLeft: true,
105142
+ borderTop: false,
105143
+ borderRight: false,
105144
+ borderBottom: false,
105145
+ borderLeftColor: "blueBright",
105146
+ flexDirection: "row",
105147
+ alignItems: "center",
105148
+ paddingLeft: 1,
105149
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
105150
+ color: "white",
105151
+ bold: true,
105152
+ children: TAB_LABEL
105153
+ }, undefined, false, undefined, this)
105154
+ }, undefined, false, undefined, this)
104732
105155
  }, undefined, false, undefined, this) : null,
104733
105156
  copyBanner ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104734
105157
  backgroundColor: "green",
@@ -104747,42 +105170,49 @@ function App2({
104747
105170
  paddingY: 1,
104748
105171
  flexGrow: 1,
104749
105172
  flexShrink: 1,
104750
- children: mode === "status" ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
104751
- children: [
104752
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104753
- display: isStatusListActive ? "flex" : "none",
104754
- flexDirection: "column",
104755
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(StatusView, {
104756
- startDir: resolvedStartDir,
104757
- isActive: isStatusListActive,
104758
- onCopyId: handleCopyId,
104759
- onImplement,
104760
- onOpen: handleOpenPreview,
104761
- onOpenDetail: handleOpenDetail,
104762
- onOpenSession: handleOpenSession,
104763
- onShortcutsChange: setStatusShortcuts,
104764
- onViewChange: setStatusView
104765
- }, undefined, false, undefined, this)
104766
- }, undefined, false, undefined, this),
104767
- isSessionActive && sessionView ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(SessionView, {
104768
- state: sessionView
104769
- }, undefined, false, undefined, this) : null,
104770
- isDetailVisible && proposalDetail ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ProposalDetailView, {
104771
- changeId: proposalDetail.changeId,
104772
- selection: detailSelection,
104773
- startDir: resolvedStartDir
104774
- }, undefined, false, undefined, this) : null,
104775
- isPreviewActive && preview ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(PreviewPage, {
104776
- fileName: preview.fileName,
104777
- markdown: preview.markdown,
104778
- height: previewHeight,
104779
- onClose: handleClosePreview
104780
- }, undefined, false, undefined, this) : null
104781
- ]
104782
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(PlanView, {
104783
- startDir: resolvedStartDir
104784
- }, undefined, false, undefined, this)
104785
- }, undefined, false, undefined, this),
105173
+ children: [
105174
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
105175
+ display: isStatusListVisible ? "flex" : "none",
105176
+ flexDirection: "column",
105177
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(StatusView, {
105178
+ startDir: resolvedStartDir,
105179
+ isActive: isStatusListActive,
105180
+ onCopyId: handleCopyId,
105181
+ onImplement: handleImplement,
105182
+ onOpen: handleOpenPreview,
105183
+ onOpenDetail: handleOpenDetail,
105184
+ onOpenSession: handleOpenSession,
105185
+ onShortcutsChange: setStatusShortcuts,
105186
+ onViewChange: setStatusView
105187
+ }, undefined, false, undefined, this)
105188
+ }, undefined, false, undefined, this),
105189
+ implementError ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ImplementErrorModal, {
105190
+ message: implementError.message
105191
+ }, undefined, false, undefined, this) : null,
105192
+ isSessionActive && sessionView ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(SessionView, {
105193
+ state: sessionView
105194
+ }, undefined, false, undefined, this) : null,
105195
+ isDetailVisible && proposalDetail ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
105196
+ flexDirection: "column",
105197
+ children: [
105198
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ProposalDetailView, {
105199
+ changeId: proposalDetail.changeId,
105200
+ selection: detailSelection,
105201
+ startDir: resolvedStartDir
105202
+ }, undefined, false, undefined, this),
105203
+ detailStatusModal ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ListItemStatusModal, {
105204
+ statusModal: detailStatusModal
105205
+ }, undefined, false, undefined, this) : null
105206
+ ]
105207
+ }, undefined, true, undefined, this) : null,
105208
+ isPreviewActive && preview ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(PreviewPage, {
105209
+ fileName: preview.fileName,
105210
+ markdown: preview.markdown,
105211
+ height: previewHeight,
105212
+ onClose: handleClosePreview
105213
+ }, undefined, false, undefined, this) : null
105214
+ ]
105215
+ }, undefined, true, undefined, this),
104786
105216
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104787
105217
  flexDirection: "column",
104788
105218
  paddingX: 2,
@@ -104791,44 +105221,31 @@ function App2({
104791
105221
  children: [
104792
105222
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104793
105223
  justifyContent: "space-between",
104794
- children: [
104795
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104796
- flexDirection: "row",
104797
- children: shortcuts.map((shortcut) => /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104798
- marginRight: 2,
104799
- children: [
104800
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104801
- color: "gray",
104802
- children: "["
104803
- }, undefined, false, undefined, this),
104804
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104805
- color: "white",
104806
- children: shortcut.keyLabel
104807
- }, undefined, false, undefined, this),
104808
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104809
- color: "gray",
104810
- children: [
104811
- " ",
104812
- shortcut.label,
104813
- "]"
104814
- ]
104815
- }, undefined, true, undefined, this)
104816
- ]
104817
- }, shortcut.keyLabel, true, undefined, this))
104818
- }, undefined, false, undefined, this),
104819
- showTabs ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
105224
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
105225
+ flexDirection: "row",
105226
+ children: shortcuts.map((shortcut) => /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
105227
+ marginRight: 2,
104820
105228
  children: [
104821
105229
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104822
- children: "tab"
105230
+ color: "gray",
105231
+ children: "["
105232
+ }, undefined, false, undefined, this),
105233
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
105234
+ color: "white",
105235
+ children: shortcut.keyLabel
104823
105236
  }, undefined, false, undefined, this),
104824
105237
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
104825
105238
  color: "gray",
104826
- children: " switch mode"
104827
- }, undefined, false, undefined, this)
105239
+ children: [
105240
+ " ",
105241
+ shortcut.label,
105242
+ "]"
105243
+ ]
105244
+ }, undefined, true, undefined, this)
104828
105245
  ]
104829
- }, undefined, true, undefined, this) : null
104830
- ]
104831
- }, undefined, true, undefined, this),
105246
+ }, shortcut.keyLabel, true, undefined, this))
105247
+ }, undefined, false, undefined, this)
105248
+ }, undefined, false, undefined, this),
104832
105249
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
104833
105250
  justifyContent: "space-between",
104834
105251
  marginTop: 1,
@@ -104873,15 +105290,16 @@ var registerTerminalReset = () => {
104873
105290
  var runTui = () => {
104874
105291
  applyTerminalPrelude();
104875
105292
  registerTerminalReset();
104876
- render_default(import_react63.default.createElement(App2));
105293
+ render_default(import_react62.default.createElement(App2));
104877
105294
  };
104878
105295
 
104879
105296
  // src/features/changes/index.ts
104880
- import { existsSync as existsSync12, mkdirSync as mkdirSync12, readdirSync as readdirSync7, readFileSync as readFileSync14, renameSync as renameSync3, statSync as statSync9, writeFileSync as writeFileSync7 } from "node:fs";
104881
- import { dirname as dirname14, join as join16, relative as relative4 } from "node:path";
105297
+ import { existsSync as existsSync12, mkdirSync as mkdirSync12, readdirSync as readdirSync8, readFileSync as readFileSync16, renameSync as renameSync3, statSync as statSync9, writeFileSync as writeFileSync8 } from "node:fs";
105298
+ import { dirname as dirname15, join as join19, relative as relative4 } from "node:path";
104882
105299
  import { fileURLToPath as fileURLToPath10 } from "node:url";
104883
105300
  var CHANGE_ID_PATTERN2 = /^(?:[Cc]\d+_)?[a-z0-9]+(?:-[a-z0-9]+)*$/;
104884
105301
  var SUMMARY_FALLBACK3 = "No summary provided.";
105302
+ var ARCHIVED_CHANGE_STATUSES2 = new Set(["archived", "rejected"]);
104885
105303
  var isDirectory6 = (path) => {
104886
105304
  try {
104887
105305
  return statSync9(path).isDirectory();
@@ -104896,6 +105314,7 @@ var readFrontmatterString2 = (data, key) => {
104896
105314
  }
104897
105315
  return "";
104898
105316
  };
105317
+ var normalizeStatusValue2 = (status) => status.trim().toLowerCase();
104899
105318
  var parseProposal2 = (content, changeId) => {
104900
105319
  const titleMatch = content.match(/^#\s+Proposal\s+-\s+(.*)$/m);
104901
105320
  const { data } = readFrontmatter(content);
@@ -104933,6 +105352,22 @@ var normalizeChangeId2 = (value) => {
104933
105352
  return trimmed;
104934
105353
  };
104935
105354
  var isValidChangeId2 = (value) => CHANGE_ID_PATTERN2.test(value.trim());
105355
+ var resolveChangePaths2 = (schubDir, changeId) => {
105356
+ const changeDir = join19(schubDir, "changes", changeId);
105357
+ const proposalPath = join19(changeDir, "proposal.md");
105358
+ if (existsSync12(proposalPath)) {
105359
+ return { changeDir, proposalPath, isArchived: false };
105360
+ }
105361
+ const archiveDir = join19(schubDir, "archive", "changes", changeId);
105362
+ const archiveProposalPath = join19(archiveDir, "proposal.md");
105363
+ if (existsSync12(archiveProposalPath)) {
105364
+ return { changeDir: archiveDir, proposalPath: archiveProposalPath, isArchived: true };
105365
+ }
105366
+ throw new Error(`Required change files missing:
105367
+ - ${proposalPath}
105368
+ - ${archiveProposalPath}
105369
+ Create the change proposal before scaffolding docs.`);
105370
+ };
104936
105371
  var readChangeSummary2 = (schubDir, changeId) => {
104937
105372
  const trimmed = changeId.trim();
104938
105373
  if (!trimmed) {
@@ -104942,25 +105377,20 @@ var readChangeSummary2 = (schubDir, changeId) => {
104942
105377
  throw new Error(`Invalid change-id '${changeId}'. Use kebab-case or a C-prefixed id (e.g., C1_add-user-auth).`);
104943
105378
  }
104944
105379
  const normalized = normalizeChangeId2(trimmed);
104945
- const changeDir = join16(schubDir, "changes", normalized);
104946
- const proposalPath = join16(changeDir, "proposal.md");
104947
- if (!existsSync12(proposalPath)) {
104948
- throw new Error(`Required change files missing:
104949
- - ${proposalPath}
104950
- Create the change proposal before scaffolding docs.`);
104951
- }
104952
- const content = readFileSync14(proposalPath, "utf8");
105380
+ const resolved = resolveChangePaths2(schubDir, normalized);
105381
+ const content = readFileSync16(resolved.proposalPath, "utf8");
104953
105382
  const parsed = parseProposal2(content, normalized);
104954
105383
  return {
104955
105384
  changeId: normalized,
104956
105385
  changeTitle: parsed.title,
104957
- changeDir,
104958
- proposalPath
105386
+ changeDir: resolved.changeDir,
105387
+ proposalPath: resolved.proposalPath,
105388
+ isArchived: resolved.isArchived
104959
105389
  };
104960
105390
  };
104961
105391
  var readChangeDetail2 = (schubDir, changeId) => {
104962
105392
  const summary = readChangeSummary2(schubDir, changeId);
104963
- const content = readFileSync14(summary.proposalPath, "utf8");
105393
+ const content = readFileSync16(summary.proposalPath, "utf8");
104964
105394
  const detail = parseProposalDetail2(content, summary.changeId);
104965
105395
  return {
104966
105396
  changeId: detail.changeId,
@@ -104970,13 +105400,14 @@ var readChangeDetail2 = (schubDir, changeId) => {
104970
105400
  summary: detail.summary
104971
105401
  };
104972
105402
  };
105403
+ var isArchivedStatus2 = (value) => ARCHIVED_CHANGE_STATUSES2.has(normalizeStatusValue2(value));
104973
105404
  var updateChangeStatus2 = (schubDir, changeId, status) => {
104974
105405
  const nextStatus = status.trim();
104975
105406
  if (!nextStatus) {
104976
105407
  throw new Error("Provide --status.");
104977
105408
  }
104978
105409
  const summary = readChangeSummary2(schubDir, changeId);
104979
- const content = readFileSync14(summary.proposalPath, "utf8");
105410
+ const content = readFileSync16(summary.proposalPath, "utf8");
104980
105411
  const { data } = readFrontmatter(content);
104981
105412
  const previousStatus = readFrontmatterString2(data, "status");
104982
105413
  if (!previousStatus) {
@@ -104984,7 +105415,39 @@ var updateChangeStatus2 = (schubDir, changeId, status) => {
104984
105415
  Add a 'status' field in frontmatter before updating status.`);
104985
105416
  }
104986
105417
  const updated = updateFrontmatterValue(content, "status", nextStatus);
104987
- writeFileSync7(summary.proposalPath, updated, "utf8");
105418
+ const shouldArchive = isArchivedStatus2(nextStatus);
105419
+ const archiveRoot = join19(schubDir, "archive", "changes");
105420
+ const archivePath = join19(archiveRoot, summary.changeId);
105421
+ const activePath = join19(schubDir, "changes", summary.changeId);
105422
+ if (shouldArchive && !summary.isArchived) {
105423
+ if (existsSync12(archivePath)) {
105424
+ throw new Error(`Archive already exists: ${archivePath}`);
105425
+ }
105426
+ mkdirSync12(archiveRoot, { recursive: true });
105427
+ writeFileSync8(summary.proposalPath, updated, "utf8");
105428
+ renameSync3(summary.changeDir, archivePath);
105429
+ return {
105430
+ changeId: summary.changeId,
105431
+ proposalPath: join19(archivePath, "proposal.md"),
105432
+ previousStatus,
105433
+ status: nextStatus
105434
+ };
105435
+ }
105436
+ if (!shouldArchive && summary.isArchived) {
105437
+ if (existsSync12(activePath)) {
105438
+ throw new Error(`Change already exists: ${activePath}`);
105439
+ }
105440
+ mkdirSync12(join19(schubDir, "changes"), { recursive: true });
105441
+ writeFileSync8(summary.proposalPath, updated, "utf8");
105442
+ renameSync3(summary.changeDir, activePath);
105443
+ return {
105444
+ changeId: summary.changeId,
105445
+ proposalPath: join19(activePath, "proposal.md"),
105446
+ previousStatus,
105447
+ status: nextStatus
105448
+ };
105449
+ }
105450
+ writeFileSync8(summary.proposalPath, updated, "utf8");
104988
105451
  return {
104989
105452
  changeId: summary.changeId,
104990
105453
  proposalPath: summary.proposalPath,
@@ -104994,19 +105457,16 @@ Add a 'status' field in frontmatter before updating status.`);
104994
105457
  };
104995
105458
  var archiveChange2 = (schubDir, changeId) => {
104996
105459
  const summary = readChangeSummary2(schubDir, changeId);
104997
- const archiveRoot = join16(schubDir, "archive", "changes");
104998
- const archivePath = join16(archiveRoot, summary.changeId);
104999
- if (existsSync12(archivePath)) {
105460
+ const archivePath = join19(schubDir, "archive", "changes", summary.changeId);
105461
+ if (summary.isArchived) {
105000
105462
  throw new Error(`Archive already exists: ${archivePath}`);
105001
105463
  }
105002
- mkdirSync12(archiveRoot, { recursive: true });
105003
- const updated = updateChangeStatus2(schubDir, summary.changeId, "Archived");
105004
- renameSync3(summary.changeDir, archivePath);
105464
+ const archived = updateChangeStatus2(schubDir, summary.changeId, "Archived");
105005
105465
  return {
105006
- changeId: updated.changeId,
105007
- previousStatus: updated.previousStatus,
105008
- status: updated.status,
105009
- proposalPath: join16(archivePath, "proposal.md"),
105466
+ changeId: archived.changeId,
105467
+ previousStatus: archived.previousStatus,
105468
+ status: archived.status,
105469
+ proposalPath: archived.proposalPath,
105010
105470
  archivePath
105011
105471
  };
105012
105472
  };
@@ -105018,17 +105478,17 @@ var readChangesFromRoot2 = (changesRoot, repoRoot) => {
105018
105478
  if (!existsSync12(changesRoot) || !isDirectory6(changesRoot)) {
105019
105479
  return [];
105020
105480
  }
105021
- const entries = readdirSync7(changesRoot, { withFileTypes: true });
105481
+ const entries = readdirSync8(changesRoot, { withFileTypes: true });
105022
105482
  const changes = [];
105023
105483
  for (const entry of entries) {
105024
105484
  if (!entry.isDirectory()) {
105025
105485
  continue;
105026
105486
  }
105027
- const proposalPath = join16(changesRoot, entry.name, "proposal.md");
105487
+ const proposalPath = join19(changesRoot, entry.name, "proposal.md");
105028
105488
  if (!existsSync12(proposalPath)) {
105029
105489
  continue;
105030
105490
  }
105031
- const content = readFileSync14(proposalPath, "utf8");
105491
+ const content = readFileSync16(proposalPath, "utf8");
105032
105492
  const parsed = parseProposal2(content, entry.name);
105033
105493
  changes.push({
105034
105494
  id: entry.name,
@@ -105040,8 +105500,8 @@ var readChangesFromRoot2 = (changesRoot, repoRoot) => {
105040
105500
  return changes;
105041
105501
  };
105042
105502
  var listChanges2 = (schubDir) => {
105043
- const repoRoot = dirname14(schubDir);
105044
- const changes = readChangesFromRoot2(join16(schubDir, "changes"), repoRoot);
105503
+ const repoRoot = dirname15(schubDir);
105504
+ const changes = readChangesFromRoot2(join19(schubDir, "changes"), repoRoot);
105045
105505
  return changes.sort((left2, right2) => {
105046
105506
  const numberDiff = changeNumber2(left2.id) - changeNumber2(right2.id);
105047
105507
  if (numberDiff !== 0) {
@@ -105056,6 +105516,7 @@ var STATUS_GROUPS2 = [
105056
105516
  { label: "Accepted", match: "accepted" },
105057
105517
  { label: "Implementing", match: "implement" },
105058
105518
  { label: "Done", match: "done" },
105519
+ { label: "Rejected", match: "reject" },
105059
105520
  { label: "Archived", match: "archiv" }
105060
105521
  ];
105061
105522
  var normalizeStatusLabel2 = (status) => {
@@ -105070,9 +105531,9 @@ var normalizeStatusLabel2 = (status) => {
105070
105531
  return { label: label || "Unknown", order: STATUS_GROUPS2.length };
105071
105532
  };
105072
105533
  var listChangeOverview2 = (schubDir) => {
105073
- const repoRoot = dirname14(schubDir);
105534
+ const repoRoot = dirname15(schubDir);
105074
105535
  const combined = new Map;
105075
- for (const root of [join16(schubDir, "changes"), join16(schubDir, "archive", "changes")]) {
105536
+ for (const root of [join19(schubDir, "changes"), join19(schubDir, "archive", "changes")]) {
105076
105537
  for (const change of readChangesFromRoot2(root, repoRoot)) {
105077
105538
  if (!combined.has(change.id)) {
105078
105539
  combined.set(change.id, change);
@@ -105102,13 +105563,13 @@ var splitPrefixedChangeId2 = (changeId) => {
105102
105563
  return { prefix: null, slug: changeId };
105103
105564
  };
105104
105565
  var nextChangePrefix2 = (schubDir) => {
105105
- const changesRoot = join16(schubDir, "changes");
105106
- const archiveRoot = join16(schubDir, "archive", "changes");
105566
+ const changesRoot = join19(schubDir, "changes");
105567
+ const archiveRoot = join19(schubDir, "archive", "changes");
105107
105568
  const prefixes = [];
105108
105569
  const scan = (root) => {
105109
105570
  if (!existsSync12(root) || !isDirectory6(root))
105110
105571
  return;
105111
- for (const entry of readdirSync7(root, { withFileTypes: true })) {
105572
+ for (const entry of readdirSync8(root, { withFileTypes: true })) {
105112
105573
  if (!entry.isDirectory()) {
105113
105574
  continue;
105114
105575
  }
@@ -105126,24 +105587,24 @@ var nextChangePrefix2 = (schubDir) => {
105126
105587
  var CHANGE_PREFIX2 = "C";
105127
105588
  var BUNDLED_PROPOSAL_TEMPLATE_PATH2 = fileURLToPath10(new URL("../../../templates/create-proposal/proposal-template.md", import.meta.url));
105128
105589
  var readProposalTemplate2 = (schubDir) => {
105129
- const templatePath = resolveTemplatePath(schubDir, join16("create-proposal", "proposal-template.md"), BUNDLED_PROPOSAL_TEMPLATE_PATH2);
105590
+ const templatePath = resolveTemplatePath(schubDir, join19("create-proposal", "proposal-template.md"), BUNDLED_PROPOSAL_TEMPLATE_PATH2);
105130
105591
  try {
105131
- return readFileSync14(templatePath, "utf8");
105592
+ return readFileSync16(templatePath, "utf8");
105132
105593
  } catch {
105133
105594
  throw new Error(`[ERROR] Template not found: ${templatePath}`);
105134
105595
  }
105135
105596
  };
105136
105597
  var isValidSlug2 = (value) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
105137
105598
  var changeExists2 = (schubDir, changeId) => {
105138
- const active = join16(schubDir, "changes", changeId);
105599
+ const active = join19(schubDir, "changes", changeId);
105139
105600
  if (existsSync12(active)) {
105140
105601
  return true;
105141
105602
  }
105142
- const archiveRoot = join16(schubDir, "archive", "changes");
105603
+ const archiveRoot = join19(schubDir, "archive", "changes");
105143
105604
  if (!existsSync12(archiveRoot) || !isDirectory6(archiveRoot)) {
105144
105605
  return false;
105145
105606
  }
105146
- for (const entry of readdirSync7(archiveRoot, { withFileTypes: true })) {
105607
+ for (const entry of readdirSync8(archiveRoot, { withFileTypes: true })) {
105147
105608
  if (entry.isDirectory() && entry.name.includes(changeId)) {
105148
105609
  return true;
105149
105610
  }
@@ -105152,10 +105613,10 @@ var changeExists2 = (schubDir, changeId) => {
105152
105613
  };
105153
105614
  var findChangeByPrefix2 = (schubDir, prefix) => {
105154
105615
  const normalizedPrefix = prefix.toUpperCase();
105155
- for (const root of [join16(schubDir, "changes"), join16(schubDir, "archive", "changes")]) {
105616
+ for (const root of [join19(schubDir, "changes"), join19(schubDir, "archive", "changes")]) {
105156
105617
  if (!existsSync12(root) || !isDirectory6(root))
105157
105618
  continue;
105158
- for (const entry of readdirSync7(root, { withFileTypes: true })) {
105619
+ for (const entry of readdirSync8(root, { withFileTypes: true })) {
105159
105620
  if (entry.isDirectory() && entry.name.toUpperCase().startsWith(`${normalizedPrefix}_`)) {
105160
105621
  return entry.name;
105161
105622
  }
@@ -105204,8 +105665,8 @@ var createChange2 = (schubDir, options2) => {
105204
105665
  throw new Error(`Change prefix '${prefix}' already exists as '${existing}'. Choose a new prefix or omit it to auto-generate.`);
105205
105666
  }
105206
105667
  }
105207
- const changeDir = join16(schubDir, "changes", changeId);
105208
- const proposalPath = join16(changeDir, "proposal.md");
105668
+ const changeDir = join19(schubDir, "changes", changeId);
105669
+ const proposalPath = join19(changeDir, "proposal.md");
105209
105670
  if (changeExists2(schubDir, changeId) && !options2.overwrite) {
105210
105671
  throw new Error(`Change '${changeId}' already exists under ${schubDir}. Choose a unique id or pass --overwrite.`);
105211
105672
  }
@@ -105216,12 +105677,12 @@ var createChange2 = (schubDir, options2) => {
105216
105677
  throw new Error(`Refusing to overwrite existing file: ${proposalPath}`);
105217
105678
  }
105218
105679
  mkdirSync12(changeDir, { recursive: true });
105219
- writeFileSync7(proposalPath, rendered, "utf8");
105680
+ writeFileSync8(proposalPath, rendered, "utf8");
105220
105681
  return proposalPath;
105221
105682
  };
105222
105683
  // src/features/project/index.ts
105223
- import { existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
105224
- import { basename as basename7, dirname as dirname15, join as join17, resolve as resolve13 } from "node:path";
105684
+ import { existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "node:fs";
105685
+ import { basename as basename8, dirname as dirname16, join as join20, resolve as resolve14 } from "node:path";
105225
105686
  import { fileURLToPath as fileURLToPath11 } from "node:url";
105226
105687
  var TEMPLATE_FILES2 = {
105227
105688
  "project-overview.md": "project-overview-template.md",
@@ -105231,7 +105692,7 @@ var TEMPLATE_FILES2 = {
105231
105692
  var TEMPLATES_ROOT2 = fileURLToPath11(new URL("../../../templates/setup-project", import.meta.url));
105232
105693
  var readTemplate2 = (path) => {
105233
105694
  try {
105234
- return readFileSync15(path, "utf8");
105695
+ return readFileSync17(path, "utf8");
105235
105696
  } catch {
105236
105697
  throw new Error(`[ERROR] Template not found: ${path}`);
105237
105698
  }
@@ -105240,18 +105701,18 @@ var writeOutput2 = (path, content, overwrite) => {
105240
105701
  if (existsSync13(path) && !overwrite) {
105241
105702
  throw new Error(`[ERROR] Refusing to overwrite existing file: ${path}`);
105242
105703
  }
105243
- mkdirSync13(dirname15(path), { recursive: true });
105244
- writeFileSync8(path, content, "utf8");
105704
+ mkdirSync13(dirname16(path), { recursive: true });
105705
+ writeFileSync9(path, content, "utf8");
105245
105706
  };
105246
105707
  var deriveProjectName2 = (repoRoot) => {
105247
- const name = basename7(repoRoot).trim();
105708
+ const name = basename8(repoRoot).trim();
105248
105709
  return name || "Project";
105249
105710
  };
105250
105711
  var resolveRepoRoot2 = (startDir, schubRoot, repoRoot) => {
105251
105712
  if (repoRoot) {
105252
- return resolve13(startDir, repoRoot);
105713
+ return resolve14(startDir, repoRoot);
105253
105714
  }
105254
- return resolve13(schubRoot, "..");
105715
+ return resolve14(schubRoot, "..");
105255
105716
  };
105256
105717
  var createProject2 = (startDir, options2) => {
105257
105718
  const schubRoot = resolveSchubRoot(startDir);
@@ -105260,11 +105721,11 @@ var createProject2 = (startDir, options2) => {
105260
105721
  const overwrite = options2.overwrite ?? false;
105261
105722
  const created = [];
105262
105723
  for (const [outputName, templateName] of Object.entries(TEMPLATE_FILES2)) {
105263
- const bundledPath = join17(TEMPLATES_ROOT2, templateName);
105264
- const templatePath = resolveTemplatePath(schubRoot, join17("setup-project", templateName), bundledPath);
105724
+ const bundledPath = join20(TEMPLATES_ROOT2, templateName);
105725
+ const templatePath = resolveTemplatePath(schubRoot, join20("setup-project", templateName), bundledPath);
105265
105726
  const template = readTemplate2(templatePath);
105266
105727
  const rendered = template.split("[Project Name]").join(projectName);
105267
- const outputPath = join17(schubRoot, outputName);
105728
+ const outputPath = join20(schubRoot, outputName);
105268
105729
  writeOutput2(outputPath, rendered, overwrite);
105269
105730
  created.push(outputPath);
105270
105731
  }
@@ -105296,6 +105757,8 @@ var runCommand = async () => {
105296
105757
  runTasksCreate(rawArgs.slice(2), startDir);
105297
105758
  }).command("list [args..]", "List tasks", () => {}, () => {
105298
105759
  runTasksList(resolveSchubDir(), rawArgs.slice(2));
105760
+ }).command("check [args..]", "Update task checklist item", () => {}, () => {
105761
+ runTasksCheck(resolveSchubDir(), rawArgs.slice(2));
105299
105762
  }).command("update [args..]", "Update backlog task status", () => {}, () => {
105300
105763
  runTasksUpdate(resolveSchubDir(), rawArgs.slice(2));
105301
105764
  }).command("implement [args..]", "Assign a task for implementation", () => {}, () => {