work-kit-cli 0.2.4 → 0.2.6
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/README.md +1 -1
- package/cli/src/commands/cancel.ts +103 -0
- package/cli/src/commands/complete.ts +11 -3
- package/cli/src/commands/init.ts +16 -0
- package/cli/src/commands/next.ts +1 -1
- package/cli/src/commands/observe.ts +12 -1
- package/cli/src/commands/status.ts +1 -1
- package/cli/src/engine/transitions.ts +1 -1
- package/cli/src/index.ts +18 -0
- package/cli/src/observer/data.ts +17 -8
- package/cli/src/observer/renderer.ts +15 -7
- package/cli/src/state/helpers.ts +1 -1
- package/cli/src/state/schema.ts +1 -1
- package/package.json +1 -1
- package/skills/auto-kit/SKILL.md +2 -2
- package/skills/cancel-kit/SKILL.md +34 -0
- package/skills/full-kit/SKILL.md +2 -2
- package/skills/wk-deploy/stages/merge.md +7 -3
- package/skills/wk-wrap-up/SKILL.md +4 -4
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ Any stage can route back to a previous stage. Each route is enforced with a max
|
|
|
104
104
|
### Output
|
|
105
105
|
|
|
106
106
|
```
|
|
107
|
-
.
|
|
107
|
+
.work-kit-tracker/
|
|
108
108
|
2026-04-03-avatar-upload.md # distilled summary
|
|
109
109
|
archive/
|
|
110
110
|
2026-04-03-avatar-upload.md # full state.md copy
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { readState, findWorktreeRoot, stateDir } from "../state/store.js";
|
|
5
|
+
|
|
6
|
+
export interface CancelResult {
|
|
7
|
+
action: "cancelled" | "error";
|
|
8
|
+
slug?: string;
|
|
9
|
+
branch?: string;
|
|
10
|
+
worktreeRemoved: boolean;
|
|
11
|
+
branchDeleted: boolean;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveMainRepoRoot(worktreeRoot: string): string {
|
|
16
|
+
try {
|
|
17
|
+
const output = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
18
|
+
cwd: worktreeRoot,
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
timeout: 5000,
|
|
21
|
+
});
|
|
22
|
+
const firstLine = output.split("\n").find(l => l.startsWith("worktree "));
|
|
23
|
+
if (firstLine) return firstLine.slice("worktree ".length).trim();
|
|
24
|
+
} catch {
|
|
25
|
+
// fallback
|
|
26
|
+
}
|
|
27
|
+
return worktreeRoot;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function cancelCommand(worktreeRoot?: string): CancelResult {
|
|
31
|
+
const root = worktreeRoot || findWorktreeRoot();
|
|
32
|
+
if (!root) {
|
|
33
|
+
return {
|
|
34
|
+
action: "error",
|
|
35
|
+
worktreeRemoved: false,
|
|
36
|
+
branchDeleted: false,
|
|
37
|
+
message: "No work-kit state found. Nothing to cancel.",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const state = readState(root);
|
|
42
|
+
|
|
43
|
+
if (state.status === "completed") {
|
|
44
|
+
return {
|
|
45
|
+
action: "error",
|
|
46
|
+
slug: state.slug,
|
|
47
|
+
worktreeRemoved: false,
|
|
48
|
+
branchDeleted: false,
|
|
49
|
+
message: `${state.slug} is already completed. Nothing to cancel.`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const slug = state.slug;
|
|
54
|
+
const branch = state.branch;
|
|
55
|
+
const mainRoot = resolveMainRepoRoot(root);
|
|
56
|
+
const isWorktree = path.resolve(root) !== path.resolve(mainRoot);
|
|
57
|
+
|
|
58
|
+
let worktreeRemoved = false;
|
|
59
|
+
let branchDeleted = false;
|
|
60
|
+
|
|
61
|
+
// Remove .work-kit/ state directory
|
|
62
|
+
const stDir = stateDir(root);
|
|
63
|
+
if (fs.existsSync(stDir)) {
|
|
64
|
+
fs.rmSync(stDir, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Remove the worktree (if we're in one)
|
|
68
|
+
if (isWorktree) {
|
|
69
|
+
try {
|
|
70
|
+
execFileSync("git", ["worktree", "remove", root, "--force"], {
|
|
71
|
+
cwd: mainRoot,
|
|
72
|
+
encoding: "utf-8",
|
|
73
|
+
timeout: 10000,
|
|
74
|
+
});
|
|
75
|
+
worktreeRemoved = true;
|
|
76
|
+
} catch {
|
|
77
|
+
// Worktree removal failed — may need manual cleanup
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Delete the feature branch
|
|
82
|
+
if (branch) {
|
|
83
|
+
try {
|
|
84
|
+
execFileSync("git", ["branch", "-D", branch], {
|
|
85
|
+
cwd: mainRoot,
|
|
86
|
+
encoding: "utf-8",
|
|
87
|
+
timeout: 5000,
|
|
88
|
+
});
|
|
89
|
+
branchDeleted = true;
|
|
90
|
+
} catch {
|
|
91
|
+
// Branch may not exist or may be checked out elsewhere
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
action: "cancelled",
|
|
97
|
+
slug,
|
|
98
|
+
branch,
|
|
99
|
+
worktreeRemoved,
|
|
100
|
+
branchDeleted,
|
|
101
|
+
message: `Cancelled ${slug}.${worktreeRemoved ? " Worktree removed." : ""}${branchDeleted ? " Branch deleted." : ""}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -108,8 +108,16 @@ export function completeCommand(target: string, outcome?: string, worktreeRoot?:
|
|
|
108
108
|
return { action: "complete", message: "All phases complete. Work-kit finished." };
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
// Mark the first pending sub-stage of the next phase as "waiting"
|
|
112
|
+
// so the observer can distinguish "running" from "waiting for human"
|
|
113
|
+
const nextSubs = Object.entries(state.phases[nextPhase].subStages);
|
|
114
|
+
const firstPending = nextSubs.find(([_, ss]) => ss.status === "pending");
|
|
115
|
+
if (firstPending) {
|
|
116
|
+
firstPending[1].status = "waiting";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
state.currentPhase = nextPhase;
|
|
120
|
+
state.currentSubStage = firstPending ? firstPending[0] : null;
|
|
113
121
|
writeState(root, state);
|
|
114
122
|
|
|
115
123
|
return {
|
|
@@ -156,7 +164,7 @@ function archiveCompleted(worktreeRoot: string, state: WorkKitState): void {
|
|
|
156
164
|
const mainRoot = resolveMainRepoRoot(worktreeRoot);
|
|
157
165
|
const date = new Date().toISOString().split("T")[0];
|
|
158
166
|
const slug = state.slug;
|
|
159
|
-
const wkDir = path.join(mainRoot, ".
|
|
167
|
+
const wkDir = path.join(mainRoot, ".work-kit-tracker");
|
|
160
168
|
const archiveDir = path.join(wkDir, "archive");
|
|
161
169
|
|
|
162
170
|
// Ensure directories exist
|
package/cli/src/commands/init.ts
CHANGED
|
@@ -91,6 +91,19 @@ ${description}
|
|
|
91
91
|
return md;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function ensureGitignored(worktreeRoot: string): void {
|
|
95
|
+
const gitignorePath = path.join(worktreeRoot, ".gitignore");
|
|
96
|
+
const entry = ".work-kit/";
|
|
97
|
+
|
|
98
|
+
if (fs.existsSync(gitignorePath)) {
|
|
99
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
100
|
+
if (content.split("\n").some((line) => line.trim() === entry)) return;
|
|
101
|
+
fs.appendFileSync(gitignorePath, `\n${entry}\n`);
|
|
102
|
+
} else {
|
|
103
|
+
fs.writeFileSync(gitignorePath, `${entry}\n`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
export function initCommand(options: {
|
|
95
108
|
mode: "full" | "auto";
|
|
96
109
|
description: string;
|
|
@@ -171,6 +184,9 @@ export function initCommand(options: {
|
|
|
171
184
|
},
|
|
172
185
|
};
|
|
173
186
|
|
|
187
|
+
// Ensure .work-kit/ is gitignored (temp working state, not for commits)
|
|
188
|
+
ensureGitignored(worktreeRoot);
|
|
189
|
+
|
|
174
190
|
// Write state files
|
|
175
191
|
writeState(worktreeRoot, state);
|
|
176
192
|
writeStateMd(worktreeRoot, generateStateMd(slug, branch, modeLabel, description, classification, workflow));
|
package/cli/src/commands/next.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function nextCommand(worktreeRoot?: string): Action {
|
|
|
48
48
|
state.phases[phase].startedAt = new Date().toISOString();
|
|
49
49
|
|
|
50
50
|
const subStages = Object.entries(state.phases[phase].subStages);
|
|
51
|
-
const firstActive = subStages.find(([_, ss]) => ss.status === "pending");
|
|
51
|
+
const firstActive = subStages.find(([_, ss]) => ss.status === "pending" || ss.status === "waiting");
|
|
52
52
|
|
|
53
53
|
if (!firstActive) {
|
|
54
54
|
return { action: "error", message: `No pending sub-stages in ${phase}` };
|
|
@@ -30,12 +30,17 @@ export async function observeCommand(opts: { mainRepo?: string }): Promise<void>
|
|
|
30
30
|
: findMainRepoRoot(process.cwd());
|
|
31
31
|
|
|
32
32
|
let scrollOffset = 0;
|
|
33
|
+
let tick = 0;
|
|
34
|
+
let tickInterval: ReturnType<typeof setInterval> | null = null;
|
|
33
35
|
let cleanedUp = false;
|
|
34
36
|
|
|
35
37
|
function cleanup(): void {
|
|
36
38
|
if (cleanedUp) return;
|
|
37
39
|
cleanedUp = true;
|
|
38
40
|
|
|
41
|
+
// Stop tick interval
|
|
42
|
+
if (tickInterval) { clearInterval(tickInterval); tickInterval = null; }
|
|
43
|
+
|
|
39
44
|
// Restore terminal
|
|
40
45
|
exitAlternateScreen();
|
|
41
46
|
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
@@ -54,7 +59,7 @@ export async function observeCommand(opts: { mainRepo?: string }): Promise<void>
|
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
const data = collectDashboardData(mainRepoRoot, watcher.getWorktrees());
|
|
57
|
-
const frame = moveCursorHome() + renderDashboard(data, width, height, scrollOffset);
|
|
62
|
+
const frame = moveCursorHome() + renderDashboard(data, width, height, scrollOffset, tick);
|
|
58
63
|
process.stdout.write(frame);
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -80,6 +85,12 @@ export async function observeCommand(opts: { mainRepo?: string }): Promise<void>
|
|
|
80
85
|
// Initial render
|
|
81
86
|
render();
|
|
82
87
|
|
|
88
|
+
// Tick interval for flashing active stage indicator
|
|
89
|
+
tickInterval = setInterval(() => {
|
|
90
|
+
tick++;
|
|
91
|
+
render();
|
|
92
|
+
}, 500);
|
|
93
|
+
|
|
83
94
|
// Handle terminal resize
|
|
84
95
|
process.stdout.on("resize", () => {
|
|
85
96
|
render();
|
|
@@ -30,7 +30,7 @@ export function statusCommand(worktreeRoot?: string): StatusOutput {
|
|
|
30
30
|
if (ss.status === "skipped") continue;
|
|
31
31
|
total++;
|
|
32
32
|
if (ss.status === "completed") completed++;
|
|
33
|
-
else if (ss.status === "in-progress") active++;
|
|
33
|
+
else if (ss.status === "in-progress" || ss.status === "waiting") active++;
|
|
34
34
|
}
|
|
35
35
|
phases[phase] = { status: ps.status, completed, total, active };
|
|
36
36
|
}
|
|
@@ -17,7 +17,7 @@ export function nextSubStageInPhase(state: WorkKitState, phase: PhaseName): stri
|
|
|
17
17
|
|
|
18
18
|
for (const ss of subStages) {
|
|
19
19
|
const ssState = phaseState.subStages[ss];
|
|
20
|
-
if (ssState && (ssState.status === "pending" || ssState.status === "in-progress")) {
|
|
20
|
+
if (ssState && (ssState.status === "pending" || ssState.status === "in-progress" || ssState.status === "waiting")) {
|
|
21
21
|
return ss;
|
|
22
22
|
}
|
|
23
23
|
}
|
package/cli/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { completionsCommand } from "./commands/completions.js";
|
|
|
16
16
|
import { observeCommand } from "./commands/observe.js";
|
|
17
17
|
import { uninstallCommand } from "./commands/uninstall.js";
|
|
18
18
|
import { bootstrapCommand } from "./commands/bootstrap.js";
|
|
19
|
+
import { cancelCommand } from "./commands/cancel.js";
|
|
19
20
|
import { bold, green, yellow, red } from "./utils/colors.js";
|
|
20
21
|
import type { Classification, PhaseName } from "./state/schema.js";
|
|
21
22
|
|
|
@@ -269,4 +270,21 @@ program
|
|
|
269
270
|
}
|
|
270
271
|
});
|
|
271
272
|
|
|
273
|
+
// ── cancel ──────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
program
|
|
276
|
+
.command("cancel")
|
|
277
|
+
.description("Cancel the active work-kit, remove worktree and branch")
|
|
278
|
+
.option("--worktree-root <path>", "Override worktree root")
|
|
279
|
+
.action((opts) => {
|
|
280
|
+
try {
|
|
281
|
+
const result = cancelCommand(opts.worktreeRoot);
|
|
282
|
+
console.log(JSON.stringify(result, null, 2));
|
|
283
|
+
process.exit(result.action === "error" ? 1 : 0);
|
|
284
|
+
} catch (e: any) {
|
|
285
|
+
console.error(JSON.stringify({ action: "error", message: e.message }));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
272
290
|
program.parse();
|
package/cli/src/observer/data.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface WorkItemView {
|
|
|
16
16
|
currentPhase: string | null;
|
|
17
17
|
currentSubStage: string | null;
|
|
18
18
|
currentPhaseStartedAt?: string;
|
|
19
|
+
currentSubStageStatus?: string;
|
|
19
20
|
currentSubStageIndex?: number;
|
|
20
21
|
currentPhaseTotal?: number;
|
|
21
22
|
startedAt: string;
|
|
@@ -92,6 +93,7 @@ export function collectWorkItem(worktreeRoot: string): WorkItemView | null {
|
|
|
92
93
|
|
|
93
94
|
// Track current phase substage position
|
|
94
95
|
let currentPhaseStartedAt: string | undefined;
|
|
96
|
+
let currentSubStageStatus: string | undefined;
|
|
95
97
|
let currentSubStageIndex: number | undefined;
|
|
96
98
|
let currentPhaseTotal: number | undefined;
|
|
97
99
|
|
|
@@ -105,9 +107,11 @@ export function collectWorkItem(worktreeRoot: string): WorkItemView | null {
|
|
|
105
107
|
continue;
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
// If any sub-stage is "waiting", show the phase as waiting in the view
|
|
111
|
+
const hasWaiting = Object.values(phase.subStages).some(ss => ss.status === "waiting");
|
|
108
112
|
phaseViews.push({
|
|
109
113
|
name: phaseName,
|
|
110
|
-
status: phase.status,
|
|
114
|
+
status: hasWaiting ? "waiting" : phase.status,
|
|
111
115
|
startedAt: phase.startedAt,
|
|
112
116
|
completedAt: phase.completedAt,
|
|
113
117
|
});
|
|
@@ -134,10 +138,12 @@ export function collectWorkItem(worktreeRoot: string): WorkItemView | null {
|
|
|
134
138
|
if (phaseName === state.currentPhase) {
|
|
135
139
|
currentPhaseStartedAt = phase.startedAt;
|
|
136
140
|
currentPhaseTotal = activeKeys.length;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
:
|
|
140
|
-
|
|
141
|
+
if (state.currentSubStage) {
|
|
142
|
+
const idx = activeKeys.indexOf(state.currentSubStage);
|
|
143
|
+
currentSubStageIndex = idx >= 0 ? idx + 1 : undefined;
|
|
144
|
+
const ss = phase.subStages[state.currentSubStage];
|
|
145
|
+
currentSubStageStatus = ss?.status;
|
|
146
|
+
}
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
149
|
}
|
|
@@ -166,6 +172,7 @@ export function collectWorkItem(worktreeRoot: string): WorkItemView | null {
|
|
|
166
172
|
currentPhase: state.currentPhase,
|
|
167
173
|
currentSubStage: state.currentSubStage,
|
|
168
174
|
currentPhaseStartedAt,
|
|
175
|
+
currentSubStageStatus,
|
|
169
176
|
currentSubStageIndex,
|
|
170
177
|
currentPhaseTotal,
|
|
171
178
|
startedAt: state.started,
|
|
@@ -188,7 +195,7 @@ function getAutoKitPhases(state: WorkKitState): PhaseName[] {
|
|
|
188
195
|
// ── Collect Completed Items ─────────────────────────────────────────
|
|
189
196
|
|
|
190
197
|
export function collectCompletedItems(mainRepoRoot: string): CompletedItemView[] {
|
|
191
|
-
const indexPath = path.join(mainRepoRoot, ".
|
|
198
|
+
const indexPath = path.join(mainRepoRoot, ".work-kit-tracker", "index.md");
|
|
192
199
|
if (!fs.existsSync(indexPath)) return [];
|
|
193
200
|
|
|
194
201
|
let content: string;
|
|
@@ -252,11 +259,13 @@ export function collectDashboardData(mainRepoRoot: string, cachedWorktrees?: str
|
|
|
252
259
|
});
|
|
253
260
|
|
|
254
261
|
// Merge completed items from worktrees and index file, dedup by slug
|
|
262
|
+
// Also exclude any slug that is currently active (re-initialized features)
|
|
263
|
+
const activeSlugs = new Set(activeItems.map(i => i.slug));
|
|
255
264
|
const indexItems = collectCompletedItems(mainRepoRoot);
|
|
256
265
|
const seen = new Set(completedFromWorktrees.map(i => i.slug));
|
|
257
266
|
const completedItems = [
|
|
258
|
-
...completedFromWorktrees,
|
|
259
|
-
...indexItems.filter(i => !seen.has(i.slug)),
|
|
267
|
+
...completedFromWorktrees.filter(i => !activeSlugs.has(i.slug)),
|
|
268
|
+
...indexItems.filter(i => !seen.has(i.slug) && !activeSlugs.has(i.slug)),
|
|
260
269
|
];
|
|
261
270
|
|
|
262
271
|
return {
|
|
@@ -90,10 +90,11 @@ function renderProgressBar(
|
|
|
90
90
|
|
|
91
91
|
// ── Phase Status Indicators ─────────────────────────────────────────
|
|
92
92
|
|
|
93
|
-
function phaseIndicator(status: string): string {
|
|
93
|
+
function phaseIndicator(status: string, tick: number = 0): string {
|
|
94
94
|
switch (status) {
|
|
95
95
|
case "completed": return green("✓");
|
|
96
|
-
case "in-progress": return cyan("▶");
|
|
96
|
+
case "in-progress": return tick % 2 === 0 ? cyan("▶") : dim("▶");
|
|
97
|
+
case "waiting": return tick % 2 === 0 ? yellow("◉") : dim("◉");
|
|
97
98
|
case "pending": return dim("·");
|
|
98
99
|
case "skipped": return dim("⊘");
|
|
99
100
|
case "failed": return red("✗");
|
|
@@ -118,7 +119,7 @@ function formatMode(mode: string, classification?: string): string {
|
|
|
118
119
|
return classification ? `${label} · ${classification}` : label;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
function renderWorkItem(item: WorkItemView, innerWidth: number): string[] {
|
|
122
|
+
function renderWorkItem(item: WorkItemView, innerWidth: number, tick: number = 0): string[] {
|
|
122
123
|
const lines: string[] = [];
|
|
123
124
|
|
|
124
125
|
// Line 1: slug + branch (right-aligned)
|
|
@@ -156,16 +157,22 @@ function renderWorkItem(item: WorkItemView, innerWidth: number): string[] {
|
|
|
156
157
|
));
|
|
157
158
|
|
|
158
159
|
// Line 4: phase indicators with sub-stage shown under current phase
|
|
159
|
-
const phaseStrs = item.phases.map(p => `${p.name} ${phaseIndicator(p.status)}`);
|
|
160
|
+
const phaseStrs = item.phases.map(p => `${p.name} ${phaseIndicator(p.status, tick)}`);
|
|
160
161
|
lines.push(" " + phaseStrs.join(" "));
|
|
161
162
|
|
|
162
163
|
// Line 5 (optional): current sub-stage detail under the phase line
|
|
163
164
|
if (item.currentSubStage && item.currentPhase) {
|
|
165
|
+
const isWaiting = item.currentSubStageStatus === "waiting";
|
|
164
166
|
let subLabel = `↳ ${item.currentSubStage}`;
|
|
165
167
|
if (item.currentSubStageIndex != null && item.currentPhaseTotal != null) {
|
|
166
168
|
subLabel += ` (${item.currentSubStageIndex}/${item.currentPhaseTotal})`;
|
|
167
169
|
}
|
|
168
|
-
|
|
170
|
+
if (isWaiting) {
|
|
171
|
+
const badge = tick % 2 === 0 ? bgYellow(" WAITING ") : dim(" WAITING ");
|
|
172
|
+
lines.push(" " + yellow(subLabel) + " " + badge);
|
|
173
|
+
} else {
|
|
174
|
+
lines.push(" " + (tick % 2 === 0 ? cyan(subLabel) : dim(subLabel)));
|
|
175
|
+
}
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
// Line 5 (optional): loopbacks
|
|
@@ -217,7 +224,8 @@ export function renderDashboard(
|
|
|
217
224
|
data: DashboardData,
|
|
218
225
|
width: number,
|
|
219
226
|
height: number,
|
|
220
|
-
scrollOffset: number = 0
|
|
227
|
+
scrollOffset: number = 0,
|
|
228
|
+
tick: number = 0
|
|
221
229
|
): string {
|
|
222
230
|
const maxWidth = Math.min(width, 120);
|
|
223
231
|
const innerWidth = maxWidth - 4; // account for "║ " and " ║"
|
|
@@ -261,7 +269,7 @@ export function renderDashboard(
|
|
|
261
269
|
|
|
262
270
|
for (let i = 0; i < data.activeItems.length; i++) {
|
|
263
271
|
const item = data.activeItems[i];
|
|
264
|
-
const itemLines = renderWorkItem(item, innerWidth);
|
|
272
|
+
const itemLines = renderWorkItem(item, innerWidth, tick);
|
|
265
273
|
for (const line of itemLines) {
|
|
266
274
|
allLines.push(boxLine(line, innerWidth));
|
|
267
275
|
}
|
package/cli/src/state/helpers.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function resetToLocation(state: WorkKitState, location: Location): void {
|
|
|
38
38
|
let reset = false;
|
|
39
39
|
for (const [ss, ssState] of Object.entries(targetPhaseState.subStages)) {
|
|
40
40
|
if (ss === location.subStage) reset = true;
|
|
41
|
-
if (reset && ssState.status === "completed") {
|
|
41
|
+
if (reset && (ssState.status === "completed" || ssState.status === "waiting")) {
|
|
42
42
|
ssState.status = "pending";
|
|
43
43
|
delete ssState.completedAt;
|
|
44
44
|
delete ssState.outcome;
|
package/cli/src/state/schema.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type Classification = "bug-fix" | "small-change" | "refactor" | "feature"
|
|
|
35
35
|
// ── Phase & Sub-stage State ──────────────────────────────────────────
|
|
36
36
|
|
|
37
37
|
export type PhaseStatus = "pending" | "in-progress" | "completed" | "skipped";
|
|
38
|
-
export type SubStageStatus = "pending" | "in-progress" | "completed" | "skipped";
|
|
38
|
+
export type SubStageStatus = "pending" | "in-progress" | "completed" | "skipped" | "waiting";
|
|
39
39
|
|
|
40
40
|
export interface SubStageState {
|
|
41
41
|
status: SubStageStatus;
|
package/package.json
CHANGED
package/skills/auto-kit/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: auto-kit
|
|
3
3
|
description: "Smart pipeline that analyzes the request and builds a dynamic workflow. Usage: /auto-kit <description> to start, /auto-kit to continue."
|
|
4
4
|
user-invocable: true
|
|
5
|
-
argument-hint: "[description]"
|
|
5
|
+
argument-hint: "[--gated] [description]"
|
|
6
6
|
allowed-tools: Agent, Bash, Read, Write, Edit, Glob, Grep
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -97,7 +97,7 @@ The table is a guide, not a rigid rule. Adjust based on the actual request:
|
|
|
97
97
|
cd worktrees/<slug>
|
|
98
98
|
npx work-kit-cli init --mode auto --description "<description>" --classification <classification>
|
|
99
99
|
```
|
|
100
|
-
If the user
|
|
100
|
+
If the user passed `--gated` (e.g., `/auto-kit --gated fix login bug`), add `--gated` to the init command. Strip `--gated` from the description text.
|
|
101
101
|
2. Show the workflow to the user: `npx work-kit-cli workflow`
|
|
102
102
|
3. User can adjust: `npx work-kit-cli workflow --add review/security` or `npx work-kit-cli workflow --remove test/e2e`
|
|
103
103
|
4. **Wait for approval** — user can add/remove steps before proceeding
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cancel-kit
|
|
3
|
+
description: "Cancel the active work-kit session. Removes state, worktree, and feature branch."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
allowed-tools: Bash, Read
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Cancel Kit
|
|
9
|
+
|
|
10
|
+
Cancels the active work-kit session and cleans up all artifacts.
|
|
11
|
+
|
|
12
|
+
## What it does
|
|
13
|
+
|
|
14
|
+
1. Finds the active work-kit session (via `bootstrap`)
|
|
15
|
+
2. Confirms with the user before proceeding
|
|
16
|
+
3. Runs `npx work-kit-cli cancel` to:
|
|
17
|
+
- Remove `.work-kit/` state directory
|
|
18
|
+
- Remove the git worktree
|
|
19
|
+
- Delete the feature branch
|
|
20
|
+
4. Reports what was cleaned up
|
|
21
|
+
|
|
22
|
+
## Instructions
|
|
23
|
+
|
|
24
|
+
1. Run `npx work-kit-cli bootstrap` to detect the active session
|
|
25
|
+
2. If no active session: tell the user there's nothing to cancel
|
|
26
|
+
3. If active: show the user what will be cancelled:
|
|
27
|
+
- Slug and branch name
|
|
28
|
+
- Current phase and sub-stage
|
|
29
|
+
- Any uncommitted work in the worktree will be lost
|
|
30
|
+
4. **Ask the user to confirm** — do not proceed without explicit confirmation
|
|
31
|
+
5. `cd` into the worktree directory
|
|
32
|
+
6. Run `npx work-kit-cli cancel`
|
|
33
|
+
7. `cd` back to the main repo root
|
|
34
|
+
8. Report the result to the user
|
package/skills/full-kit/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: full-kit
|
|
3
3
|
description: "Full pipeline for feature development. Runs all phases and sub-stages in order. Usage: /full-kit <description> to start, /full-kit to continue."
|
|
4
4
|
user-invocable: true
|
|
5
|
-
argument-hint: "[description]"
|
|
5
|
+
argument-hint: "[--gated] [description]"
|
|
6
6
|
allowed-tools: Agent, Bash, Read, Write, Edit, Glob, Grep
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -39,7 +39,7 @@ Do not proceed until `doctor` reports all checks passed.
|
|
|
39
39
|
cd worktrees/<slug>
|
|
40
40
|
npx work-kit-cli init --mode full --description "<description>"
|
|
41
41
|
```
|
|
42
|
-
If the user
|
|
42
|
+
If the user passed `--gated` (e.g., `/full-kit --gated add user avatar`), add `--gated` to the init command. Strip `--gated` from the description text.
|
|
43
43
|
2. Parse the JSON response and follow the action
|
|
44
44
|
3. Continue with the execution loop below
|
|
45
45
|
|
|
@@ -13,14 +13,18 @@ description: "Deploy sub-stage: Get the PR merged safely."
|
|
|
13
13
|
```bash
|
|
14
14
|
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main"
|
|
15
15
|
```
|
|
16
|
-
2.
|
|
16
|
+
2. Remove `.work-kit/` from git tracking before merging (state is transient — the archive in `.work-kit-tracker/` is the permanent record):
|
|
17
|
+
```bash
|
|
18
|
+
git rm -r --cached .work-kit/ 2>/dev/null && git commit -m "work-kit: remove transient state before merge" || true
|
|
19
|
+
```
|
|
20
|
+
3. Sync the feature branch with the default branch:
|
|
17
21
|
```bash
|
|
18
22
|
git fetch origin
|
|
19
23
|
git rebase origin/<default-branch>
|
|
20
24
|
```
|
|
21
25
|
If rebase conflicts occur, resolve them. If they're non-trivial, report to the user and abort.
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
4. If a PR exists, check CI status — all checks must pass. If CI fails, diagnose and fix (loop back to Build if needed).
|
|
27
|
+
5. Merge automatically using the project's preferred method (check CONTRIBUTING.md or README; default to squash):
|
|
24
28
|
- If a PR exists: merge via `gh pr merge --squash --delete-branch`
|
|
25
29
|
- If no PR: merge locally:
|
|
26
30
|
```bash
|
|
@@ -12,16 +12,16 @@ allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
|
|
12
12
|
|
|
13
13
|
## Instructions
|
|
14
14
|
|
|
15
|
-
> **Note:** Archiving state.md and appending to `.
|
|
15
|
+
> **Note:** Archiving state.md and appending to `.work-kit-tracker/index.md` are handled automatically by the CLI when you run `work-kit complete` on the last sub-stage. You do NOT need to do these manually.
|
|
16
16
|
|
|
17
17
|
1. **Read the full `.work-kit/state.md`** — every phase output from Plan through the last completed phase
|
|
18
18
|
2. **Synthesize the work-kit log entry** — not a copy-paste of state, but a distilled record that a future developer (or agent) would find useful
|
|
19
|
-
3. **Write the summary file** to `.
|
|
19
|
+
3. **Write the summary file** to `.work-kit-tracker/<date>-<slug>.md` on the **main branch** (not the worktree)
|
|
20
20
|
4. **Ask the user** if they want the worktree and branch removed
|
|
21
21
|
|
|
22
22
|
## Work-Kit Log Entry Format
|
|
23
23
|
|
|
24
|
-
Write to `.
|
|
24
|
+
Write to `.work-kit-tracker/<YYYY-MM-DD>-<slug>.md`:
|
|
25
25
|
|
|
26
26
|
```markdown
|
|
27
27
|
---
|
|
@@ -88,7 +88,7 @@ After writing the summary:
|
|
|
88
88
|
1. Switch to main branch: `cd` back to the main repo root
|
|
89
89
|
2. Stage and commit all work-kit log files:
|
|
90
90
|
```bash
|
|
91
|
-
git add .
|
|
91
|
+
git add .work-kit-tracker/
|
|
92
92
|
git commit -m "work-kit: <slug>"
|
|
93
93
|
```
|
|
94
94
|
3. Remove the worktree and delete the feature branch (merge already happened, cleanup is safe):
|