@abdullahsahmad/work-kit 0.1.3 → 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.
package/cli/src/observer/data.ts
CHANGED
|
@@ -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({
|
|
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
|
-
|
|
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,35 +199,19 @@ export function collectCompletedItems(mainRepoRoot: string): CompletedItemView[]
|
|
|
173
199
|
}
|
|
174
200
|
|
|
175
201
|
const items: CompletedItemView[] = [];
|
|
176
|
-
//
|
|
177
|
-
// Expected format: | slug | PR | date | phases |
|
|
178
|
-
// or list format: - slug (#PR) - date - phases
|
|
202
|
+
// Format: | Date | Slug | PR | Status | Phases |
|
|
179
203
|
const lines = content.split("\n");
|
|
180
204
|
for (const line of lines) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
});
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Try list format: - **slug** (#38) completed 2d ago — plan→review
|
|
196
|
-
const listMatch = line.match(/^[-*]\s+\*?\*?(.+?)\*?\*?\s+\(?(#\d+)?\)?\s*[-—]?\s*(.+?)?\s*[-—]\s*(.+)$/);
|
|
197
|
-
if (listMatch) {
|
|
198
|
-
items.push({
|
|
199
|
-
slug: listMatch[1].trim(),
|
|
200
|
-
pr: listMatch[2]?.trim() || undefined,
|
|
201
|
-
completedAt: listMatch[3]?.trim() || "",
|
|
202
|
-
phases: listMatch[4]?.trim() || "",
|
|
203
|
-
});
|
|
204
|
-
}
|
|
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
|
+
});
|
|
205
215
|
}
|
|
206
216
|
|
|
207
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
|
-
|
|
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
|
|
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
|
|
132
|
-
const gap2 = Math.max(2, innerWidth - modePlainLen -
|
|
133
|
-
lines.push(modeStr + " ".repeat(gap2) +
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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 =
|
|
171
|
-
const date =
|
|
172
|
-
const phases = item.phases ?
|
|
173
|
-
|
|
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,
|
|
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
package/skills/auto-kit/SKILL.md
CHANGED
|
@@ -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**
|
|
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
|
|
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
|
package/skills/full-kit/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
package/skills/wrap-up/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
81
|
+
4. Report: summary written, worktree removed, branch deleted, done.
|