@abdullahsahmad/work-kit 0.1.4 → 0.1.5

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.
@@ -15,9 +15,12 @@ export interface WorkItemView {
15
15
  status: string;
16
16
  currentPhase: string | null;
17
17
  currentSubStage: string | null;
18
+ currentPhaseStartedAt?: string;
19
+ currentSubStageIndex?: number;
20
+ currentPhaseTotal?: number;
18
21
  startedAt: string;
19
22
  progress: { completed: number; total: number; percent: number };
20
- phases: { name: string; status: string }[];
23
+ phases: { name: string; status: string; startedAt?: string; completedAt?: string }[];
21
24
  loopbacks: { count: number; lastReason?: string; lastFrom?: string; lastTo?: string };
22
25
  }
23
26
 
@@ -81,12 +84,17 @@ export function collectWorkItem(worktreeRoot: string): WorkItemView | null {
81
84
  // Compute progress
82
85
  let completed = 0;
83
86
  let total = 0;
84
- const phaseViews: { name: string; status: string }[] = [];
87
+ const phaseViews: { name: string; status: string; startedAt?: string; completedAt?: string }[] = [];
85
88
 
86
89
  const phaseList: PhaseName[] = state.mode === "auto-kit" && state.workflow
87
90
  ? getAutoKitPhases(state)
88
91
  : [...PHASE_NAMES];
89
92
 
93
+ // Track current phase substage position
94
+ let currentPhaseStartedAt: string | undefined;
95
+ let currentSubStageIndex: number | undefined;
96
+ let currentPhaseTotal: number | undefined;
97
+
90
98
  for (const phaseName of phaseList) {
91
99
  const phase = state.phases[phaseName];
92
100
  if (!phase) {
@@ -97,7 +105,12 @@ export function collectWorkItem(worktreeRoot: string): WorkItemView | null {
97
105
  continue;
98
106
  }
99
107
 
100
- phaseViews.push({ name: phaseName, status: phase.status });
108
+ phaseViews.push({
109
+ name: phaseName,
110
+ status: phase.status,
111
+ startedAt: phase.startedAt,
112
+ completedAt: phase.completedAt,
113
+ });
101
114
 
102
115
  const subStageKeys = Object.keys(phase.subStages);
103
116
  if (subStageKeys.length === 0) {
@@ -107,15 +120,25 @@ export function collectWorkItem(worktreeRoot: string): WorkItemView | null {
107
120
  total += defaults.length;
108
121
  if (phase.status === "completed") completed += defaults.length;
109
122
  } else {
110
- for (const key of subStageKeys) {
123
+ let phaseIdx = 0;
124
+ const activeKeys = subStageKeys.filter(k => phase.subStages[k].status !== "skipped");
125
+ for (const key of activeKeys) {
111
126
  const sub = phase.subStages[key];
112
- // Skip excluded substages — they shouldn't affect progress
113
- if (sub.status === "skipped") continue;
114
127
  total++;
128
+ phaseIdx++;
115
129
  if (sub.status === "completed") {
116
130
  completed++;
117
131
  }
118
132
  }
133
+ // Track position within current phase
134
+ if (phaseName === state.currentPhase) {
135
+ currentPhaseStartedAt = phase.startedAt;
136
+ currentPhaseTotal = activeKeys.length;
137
+ const idx = state.currentSubStage
138
+ ? activeKeys.indexOf(state.currentSubStage)
139
+ : -1;
140
+ currentSubStageIndex = idx >= 0 ? idx + 1 : undefined;
141
+ }
119
142
  }
120
143
  }
121
144
 
@@ -142,6 +165,9 @@ export function collectWorkItem(worktreeRoot: string): WorkItemView | null {
142
165
  status: state.status,
143
166
  currentPhase: state.currentPhase,
144
167
  currentSubStage: state.currentSubStage,
168
+ currentPhaseStartedAt,
169
+ currentSubStageIndex,
170
+ currentPhaseTotal,
145
171
  startedAt: state.started,
146
172
  progress: { completed, total, percent },
147
173
  phases: phaseViews,
@@ -173,49 +199,19 @@ export function collectCompletedItems(mainRepoRoot: string): CompletedItemView[]
173
199
  }
174
200
 
175
201
  const items: CompletedItemView[] = [];
176
- // Parse markdown table or list entries
177
202
  // Format: | Date | Slug | PR | Status | Phases |
178
- // or list format: - slug (#PR) - date - phases
179
203
  const lines = content.split("\n");
180
204
  for (const line of lines) {
181
- // Try 5-column table: | Date | Slug | PR | Status | Phases |
182
- const table5Match = line.match(/^\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|/);
183
- if (table5Match) {
184
- const col1 = table5Match[1].trim();
185
- if (col1 === "Date" || col1 === "---" || col1.startsWith("-")) continue; // skip header
186
- items.push({
187
- slug: table5Match[2].trim(),
188
- pr: table5Match[3].trim() !== "n/a" ? table5Match[3].trim() : undefined,
189
- completedAt: col1,
190
- phases: table5Match[5].trim(),
191
- });
192
- continue;
193
- }
194
-
195
- // Try 4-column table: | slug | PR | date | phases |
196
- const table4Match = line.match(/^\|\s*(.+?)\s*\|\s*(#?\d+)?\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|/);
197
- if (table4Match) {
198
- const slug = table4Match[1].trim();
199
- if (slug === "Slug" || slug === "---" || slug.startsWith("-")) continue;
200
- items.push({
201
- slug,
202
- pr: table4Match[2]?.trim() || undefined,
203
- completedAt: table4Match[3].trim(),
204
- phases: table4Match[4].trim(),
205
- });
206
- continue;
207
- }
208
-
209
- // Try list format: - **slug** (#38) completed 2d ago — plan→review
210
- const listMatch = line.match(/^[-*]\s+\*?\*?(.+?)\*?\*?\s+\(?(#\d+)?\)?\s*[-—]?\s*(.+?)?\s*[-—]\s*(.+)$/);
211
- if (listMatch) {
212
- items.push({
213
- slug: listMatch[1].trim(),
214
- pr: listMatch[2]?.trim() || undefined,
215
- completedAt: listMatch[3]?.trim() || "",
216
- phases: listMatch[4]?.trim() || "",
217
- });
218
- }
205
+ const match = line.match(/^\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|/);
206
+ if (!match) continue;
207
+ const col1 = match[1].trim();
208
+ if (col1 === "Date" || col1.startsWith("-")) continue;
209
+ items.push({
210
+ slug: match[2].trim(),
211
+ pr: match[3].trim() !== "n/a" ? match[3].trim() : undefined,
212
+ completedAt: col1,
213
+ phases: match[5].trim(),
214
+ });
219
215
  }
220
216
 
221
217
  return items;
@@ -113,9 +113,15 @@ function statusDot(status: string): string {
113
113
 
114
114
  // ── Render Work Item ────────────────────────────────────────────────
115
115
 
116
+ function formatMode(mode: string, classification?: string): string {
117
+ const label = mode === "full-kit" ? "Full Kit" : "Auto Kit";
118
+ return classification ? `${label} · ${classification}` : label;
119
+ }
120
+
116
121
  function renderWorkItem(item: WorkItemView, innerWidth: number): string[] {
117
122
  const lines: string[] = [];
118
123
 
124
+ // Line 1: slug + branch (right-aligned)
119
125
  const slugText = `${statusDot(item.status)} ${bold(item.slug)}`;
120
126
  const branchText = dim(item.branch);
121
127
  const slugPlainLen = stripAnsi(slugText).length;
@@ -123,18 +129,31 @@ function renderWorkItem(item: WorkItemView, innerWidth: number): string[] {
123
129
  const gap1 = Math.max(2, innerWidth - slugPlainLen - branchPlainLen);
124
130
  lines.push(slugText + " ".repeat(gap1) + branchText);
125
131
 
126
- const modeText = item.mode + (item.classification ? ` (${item.classification})` : "");
132
+ // Line 2: mode + timing (right-aligned)
133
+ const modeText = formatMode(item.mode, item.classification);
127
134
  const pausedBadge = item.status === "paused" ? " " + bgYellow(" PAUSED ") : "";
128
- const startedText = dim(`Started: ${formatTimeAgo(item.startedAt)}`);
135
+ const elapsed = formatTimeAgo(item.startedAt);
136
+ let timingRight = `Elapsed: ${elapsed}`;
137
+ if (item.currentPhaseStartedAt) {
138
+ timingRight += ` Phase: ${formatTimeAgo(item.currentPhaseStartedAt)}`;
139
+ }
140
+ const timingText = dim(timingRight);
129
141
  const modeStr = ` ${modeText}${pausedBadge}`;
130
142
  const modePlainLen = stripAnsi(modeStr).length;
131
- const startedPlainLen = stripAnsi(startedText).length;
132
- const gap2 = Math.max(2, innerWidth - modePlainLen - startedPlainLen);
133
- lines.push(modeStr + " ".repeat(gap2) + startedText);
134
-
135
- const phaseLabel = item.currentPhase
136
- ? (item.currentSubStage ? `${item.currentPhase}/${item.currentSubStage}` : item.currentPhase)
137
- : "—";
143
+ const timingPlainLen = stripAnsi(timingText).length;
144
+ const gap2 = Math.max(2, innerWidth - modePlainLen - timingPlainLen);
145
+ lines.push(modeStr + " ".repeat(gap2) + timingText);
146
+
147
+ // Line 3: progress bar with phase label + substage position
148
+ let phaseLabel = "—";
149
+ if (item.currentPhase) {
150
+ phaseLabel = item.currentSubStage
151
+ ? `${item.currentPhase}/${item.currentSubStage}`
152
+ : item.currentPhase;
153
+ if (item.currentSubStageIndex != null && item.currentPhaseTotal != null) {
154
+ phaseLabel += ` (${item.currentSubStageIndex}/${item.currentPhaseTotal})`;
155
+ }
156
+ }
138
157
  const barMaxWidth = Math.max(20, Math.min(40, innerWidth - 30));
139
158
  lines.push(" " + renderProgressBar(
140
159
  item.progress.completed,
@@ -144,9 +163,11 @@ function renderWorkItem(item: WorkItemView, innerWidth: number): string[] {
144
163
  barMaxWidth
145
164
  ));
146
165
 
166
+ // Line 4: phase indicators
147
167
  const phaseStrs = item.phases.map(p => `${p.name} ${phaseIndicator(p.status)}`);
148
168
  lines.push(" " + phaseStrs.join(" "));
149
169
 
170
+ // Line 5 (optional): loopbacks
150
171
  if (item.loopbacks.count > 0) {
151
172
  const lb = item.loopbacks;
152
173
  let loopStr = ` ${cyan("⟳")} ${lb.count} loopback${lb.count > 1 ? "s" : ""}`;
@@ -164,14 +185,29 @@ function renderWorkItem(item: WorkItemView, innerWidth: number): string[] {
164
185
 
165
186
  // ── Render Completed Item ───────────────────────────────────────────
166
187
 
167
- function renderCompletedItem(item: CompletedItemView, innerWidth: number): string {
188
+ interface CompletedColumnWidths {
189
+ slug: number;
190
+ pr: number;
191
+ date: number;
192
+ }
193
+
194
+ function computeCompletedWidths(items: CompletedItemView[]): CompletedColumnWidths {
195
+ let slug = 4, pr = 2, date = 4; // minimums
196
+ for (const item of items) {
197
+ slug = Math.max(slug, item.slug.length);
198
+ pr = Math.max(pr, (item.pr || "—").length);
199
+ date = Math.max(date, (item.completedAt || "").length);
200
+ }
201
+ return { slug, pr, date };
202
+ }
203
+
204
+ function renderCompletedItem(item: CompletedItemView, cols: CompletedColumnWidths): string {
168
205
  const check = green("✓");
169
- const slug = item.slug;
170
- const pr = item.pr ? ` ${dim(item.pr)}` : "";
171
- const date = item.completedAt ? ` ${dim(item.completedAt)}` : "";
172
- const phases = item.phases ? ` ${dim(item.phases)}` : "";
173
- const content = `${check} ${slug}${pr}${date}${phases}`;
174
- return content;
206
+ const slug = padRight(item.slug, cols.slug);
207
+ const pr = padRight(dim(item.pr || ""), cols.pr);
208
+ const date = padRight(dim(item.completedAt || ""), cols.date);
209
+ const phases = item.phases ? dim(item.phases) : "";
210
+ return `${check} ${slug} ${pr} ${date} ${phases}`;
175
211
  }
176
212
 
177
213
  // ── Main Render Function ────────────────────────────────────────────
@@ -243,8 +279,9 @@ export function renderDashboard(
243
279
 
244
280
  const maxCompleted = 5;
245
281
  const displayed = data.completedItems.slice(0, maxCompleted);
282
+ const cols = computeCompletedWidths(displayed);
246
283
  for (const item of displayed) {
247
- const content = renderCompletedItem(item, innerWidth);
284
+ const content = renderCompletedItem(item, cols);
248
285
  allLines.push(boxLine(" " + content, innerWidth));
249
286
  }
250
287
  if (data.completedItems.length > maxCompleted) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abdullahsahmad/work-kit",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Structured development workflow for Claude Code. Two modes, 6 phases, 27 sub-stages.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -79,7 +79,7 @@ Based on the classification, select sub-stages. Use this table as a starting poi
79
79
  | **Review: Performance**| skip | skip | YES | skip | YES |
80
80
  | **Review: Compliance** | skip | skip | skip | YES | YES |
81
81
  | **Review: Handoff** | YES | YES | YES | YES | YES |
82
- | **Deploy** | optional| optional | optional | optional| optional |
82
+ | **Deploy: Merge** | YES | YES | YES | YES | YES |
83
83
  | **Wrap-up** | YES | YES | YES | YES | YES |
84
84
 
85
85
  The table is a guide, not a rigid rule. Adjust based on the actual request:
@@ -87,6 +87,8 @@ The table is a guide, not a rigid rule. Adjust based on the actual request:
87
87
  - A refactor that changes DB queries → add Migration, Performance review
88
88
  - A small-change that affects public API → add Investigate, Blueprint
89
89
 
90
+ **Deploy and Wrap-up are MANDATORY and cannot be removed from any workflow.** Deploy handles syncing with the default branch, creating a PR, and merging it — fully autonomous, no user confirmation needed. Wrap-up archives the work history so past work is discoverable in future sessions. Always spawn real agents for both — never just mark them complete.
91
+
90
92
  ### Step 3: Initialize
91
93
 
92
94
  1. Create a git worktree and initialize state with the CLI:
@@ -54,4 +54,6 @@ description: "Deploy sub-stage: Get the PR merged safely."
54
54
  - NEVER merge with failing CI
55
55
  - If CI fails, diagnose the issue — don't just retry
56
56
  - If rebase conflicts are non-trivial, explain them to the user before resolving
57
- - Merge is automatic — do NOT ask the user for permission (review phase already approved it)
57
+ - Merge is fully autonomous — do NOT ask the user for permission at any step (review phase already approved it)
58
+ - Push, create PR, and merge without stopping for confirmation
59
+ - The entire sync → push → PR → merge flow should complete in one agent pass
@@ -28,7 +28,7 @@ Do not proceed until `doctor` reports all checks passed.
28
28
  2. **Build** (8 steps) — Setup → Migration → Red → Core → UI → Refactor → Integration → Commit
29
29
  3. **Test** (3 steps) — Verify → E2E → Validate
30
30
  4. **Review** (5 steps) — Self-Review → Security → Performance → Compliance → Handoff
31
- 5. **Deploy** (3 steps, optional) — Merge → Monitor → Remediate
31
+ 5. **Deploy** (3 steps) — Merge → Monitor → Remediate
32
32
  6. **Wrap-up** — Synthesize work-kit summary, clean up worktree
33
33
 
34
34
  ## Starting New Work (`/full-kit <description>`)
@@ -186,6 +186,8 @@ When all phases are done (or deploy is skipped):
186
186
 
187
187
  Run **Wrap-up** — read `.claude/skills/wrap-up.md` and follow its instructions. It handles writing the work-kit summary, committing it, and cleaning up the worktree.
188
188
 
189
+ **Deploy and Wrap-up are MANDATORY.** Deploy handles syncing, PR creation, and merging — fully autonomous, no user confirmation needed. Wrap-up archives the work history so past work is discoverable in future sessions. Always spawn real agents for both — never just mark them complete or skip them.
190
+
189
191
  ## Important
190
192
 
191
193
  - **Always work inside the worktree directory** — `cd worktrees/<slug>` before running any commands
@@ -73,10 +73,9 @@ After writing the summary:
73
73
  git add .claude/work-kit/
74
74
  git commit -m "work-kit: <slug>"
75
75
  ```
76
- 3. Ask the user: "Remove the worktree `worktrees/<slug>` and delete branch `feature/<slug>`?"
77
- 4. If yes:
76
+ 3. Remove the worktree and delete the feature branch (merge already happened, cleanup is safe):
78
77
  ```bash
79
- git worktree remove worktrees/<slug>
80
- git branch -d feature/<slug>
78
+ git worktree remove worktrees/<slug> --force
79
+ git branch -d feature/<slug> 2>/dev/null || true
81
80
  ```
82
- 5. Report: summary written, worktree removed, branch deleted, done.
81
+ 4. Report: summary written, worktree removed, branch deleted, done.