@abdullahsahmad/work-kit 0.1.0
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 +147 -0
- package/cli/bin/work-kit.mjs +18 -0
- package/cli/src/commands/complete.ts +123 -0
- package/cli/src/commands/completions.ts +137 -0
- package/cli/src/commands/context.ts +41 -0
- package/cli/src/commands/doctor.ts +79 -0
- package/cli/src/commands/init.test.ts +116 -0
- package/cli/src/commands/init.ts +184 -0
- package/cli/src/commands/loopback.ts +64 -0
- package/cli/src/commands/next.ts +172 -0
- package/cli/src/commands/observe.ts +144 -0
- package/cli/src/commands/setup.ts +159 -0
- package/cli/src/commands/status.ts +50 -0
- package/cli/src/commands/uninstall.ts +89 -0
- package/cli/src/commands/upgrade.ts +12 -0
- package/cli/src/commands/validate.ts +34 -0
- package/cli/src/commands/workflow.ts +125 -0
- package/cli/src/config/agent-map.ts +62 -0
- package/cli/src/config/loopback-routes.ts +45 -0
- package/cli/src/config/phases.ts +119 -0
- package/cli/src/context/extractor.test.ts +77 -0
- package/cli/src/context/extractor.ts +73 -0
- package/cli/src/context/prompt-builder.ts +70 -0
- package/cli/src/engine/loopbacks.test.ts +33 -0
- package/cli/src/engine/loopbacks.ts +32 -0
- package/cli/src/engine/parallel.ts +60 -0
- package/cli/src/engine/phases.ts +23 -0
- package/cli/src/engine/transitions.test.ts +117 -0
- package/cli/src/engine/transitions.ts +97 -0
- package/cli/src/index.ts +248 -0
- package/cli/src/observer/data.ts +237 -0
- package/cli/src/observer/renderer.ts +316 -0
- package/cli/src/observer/watcher.ts +99 -0
- package/cli/src/state/helpers.test.ts +91 -0
- package/cli/src/state/helpers.ts +65 -0
- package/cli/src/state/schema.ts +113 -0
- package/cli/src/state/store.ts +82 -0
- package/cli/src/state/validators.test.ts +105 -0
- package/cli/src/state/validators.ts +81 -0
- package/cli/src/utils/colors.ts +12 -0
- package/package.json +49 -0
- package/skills/auto-kit/SKILL.md +214 -0
- package/skills/build/SKILL.md +88 -0
- package/skills/build/stages/commit.md +43 -0
- package/skills/build/stages/core.md +48 -0
- package/skills/build/stages/integration.md +44 -0
- package/skills/build/stages/migration.md +41 -0
- package/skills/build/stages/red.md +44 -0
- package/skills/build/stages/refactor.md +48 -0
- package/skills/build/stages/setup.md +42 -0
- package/skills/build/stages/ui.md +51 -0
- package/skills/deploy/SKILL.md +62 -0
- package/skills/deploy/stages/merge.md +47 -0
- package/skills/deploy/stages/monitor.md +39 -0
- package/skills/deploy/stages/remediate.md +54 -0
- package/skills/full-kit/SKILL.md +195 -0
- package/skills/plan/SKILL.md +77 -0
- package/skills/plan/stages/architecture.md +53 -0
- package/skills/plan/stages/audit.md +58 -0
- package/skills/plan/stages/blueprint.md +60 -0
- package/skills/plan/stages/clarify.md +61 -0
- package/skills/plan/stages/investigate.md +47 -0
- package/skills/plan/stages/scope.md +46 -0
- package/skills/plan/stages/sketch.md +44 -0
- package/skills/plan/stages/ux-flow.md +49 -0
- package/skills/review/SKILL.md +104 -0
- package/skills/review/stages/compliance.md +48 -0
- package/skills/review/stages/handoff.md +59 -0
- package/skills/review/stages/performance.md +45 -0
- package/skills/review/stages/security.md +49 -0
- package/skills/review/stages/self-review.md +41 -0
- package/skills/test/SKILL.md +83 -0
- package/skills/test/stages/e2e.md +44 -0
- package/skills/test/stages/validate.md +51 -0
- package/skills/test/stages/verify.md +41 -0
- package/skills/wrap-up/SKILL.md +107 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# work-kit
|
|
2
|
+
|
|
3
|
+
Structured development workflow for [Claude Code](https://claude.com/claude-code). Two modes, 6 phases, 27 sub-stages — orchestrated by a TypeScript CLI with reusable skill files.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
**One-step setup** (recommended):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx work-kit setup
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This auto-detects Claude Code projects in your workspace and installs the skill files. If multiple projects are found, it lists them for you to choose.
|
|
14
|
+
|
|
15
|
+
**Global install**:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g work-kit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 1. Set up work-kit in your project
|
|
25
|
+
npx work-kit setup
|
|
26
|
+
|
|
27
|
+
# 2. In Claude Code, start a workflow
|
|
28
|
+
/full-kit add user avatar upload # strict, all phases
|
|
29
|
+
/auto-kit fix login redirect bug # dynamic, only needed stages
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Modes
|
|
33
|
+
|
|
34
|
+
### `/full-kit <description>`
|
|
35
|
+
|
|
36
|
+
Runs every phase and sub-stage in strict order. No shortcuts.
|
|
37
|
+
|
|
38
|
+
Best for: large features, new systems, maximum rigor.
|
|
39
|
+
|
|
40
|
+
### `/auto-kit <description>`
|
|
41
|
+
|
|
42
|
+
Classifies the request (bug-fix, small-change, refactor, feature, large-feature) and builds a dynamic workflow with only the sub-stages needed.
|
|
43
|
+
|
|
44
|
+
Best for: bug fixes, small changes, refactors, well-understood tasks.
|
|
45
|
+
|
|
46
|
+
## CLI commands
|
|
47
|
+
|
|
48
|
+
| Command | Description |
|
|
49
|
+
|---------|-------------|
|
|
50
|
+
| `init <description>` | Initialize a new workflow with a task description |
|
|
51
|
+
| `next` | Advance to the next sub-stage |
|
|
52
|
+
| `complete` | Mark the current sub-stage as complete |
|
|
53
|
+
| `status` | Show current workflow state (phase, sub-stage, progress) |
|
|
54
|
+
| `context` | Generate context summary for the current phase |
|
|
55
|
+
| `validate` | Validate state integrity and phase prerequisites |
|
|
56
|
+
| `loopback` | Route back to a previous stage (max 2 per route) |
|
|
57
|
+
| `workflow` | Display the full workflow plan |
|
|
58
|
+
| `doctor` | Run environment health checks (supports `--json`) |
|
|
59
|
+
| `setup` | Install work-kit skills into a Claude Code project |
|
|
60
|
+
|
|
61
|
+
## Phases
|
|
62
|
+
|
|
63
|
+
| Phase | Sub-stages | Agent |
|
|
64
|
+
|-------|-----------|-------|
|
|
65
|
+
| **Plan** | Clarify, Investigate, Sketch, Scope, UX Flow, Architecture, Blueprint, Audit | Single |
|
|
66
|
+
| **Build** | Setup, Migration, Red, Core, UI, Refactor, Integration, Commit | Single |
|
|
67
|
+
| **Test** | Verify, E2E, Validate | Verify + E2E parallel, then Validate |
|
|
68
|
+
| **Review** | Self-Review, Security, Performance, Compliance, Handoff | 4 parallel reviewers, then Handoff |
|
|
69
|
+
| **Deploy** | Merge, Monitor, Remediate | Single (optional) |
|
|
70
|
+
| **Wrap-up** | Summary + Archive | Single |
|
|
71
|
+
|
|
72
|
+
## Architecture
|
|
73
|
+
|
|
74
|
+
### Context management
|
|
75
|
+
|
|
76
|
+
Each phase runs as a **fresh agent**. The Build agent doesn't carry Plan's investigation notes — no context bloat.
|
|
77
|
+
|
|
78
|
+
Phases communicate through **Final sections** in `.work-kit/state.md`. Each phase writes a `### <Phase>: Final` section that the next phase reads.
|
|
79
|
+
|
|
80
|
+
### State management
|
|
81
|
+
|
|
82
|
+
Dual state files in `.work-kit/`:
|
|
83
|
+
|
|
84
|
+
- **state.json** — state machine (current phase, sub-stage, transitions, loop-back counts)
|
|
85
|
+
- **state.md** — content (working notes, Final sections, accumulated context)
|
|
86
|
+
|
|
87
|
+
All writes are atomic to prevent state corruption.
|
|
88
|
+
|
|
89
|
+
### Parallel agents
|
|
90
|
+
|
|
91
|
+
- **Test phase**: Verify and E2E run in parallel, then Validate runs sequentially
|
|
92
|
+
- **Review phase**: Self-Review, Security, Performance, and Compliance run as 4 parallel reviewers, then Handoff runs sequentially
|
|
93
|
+
|
|
94
|
+
### Loop-back routing
|
|
95
|
+
|
|
96
|
+
Any stage can route back to a previous stage. Each route is enforced with a max count of 2 to prevent infinite loops.
|
|
97
|
+
|
|
98
|
+
### Validation
|
|
99
|
+
|
|
100
|
+
- **full-kit**: phase-level prerequisites (Plan before Build before Test...)
|
|
101
|
+
- **auto-kit**: step-level validation against the `## Workflow` checklist
|
|
102
|
+
- Both modes refuse to skip ahead
|
|
103
|
+
|
|
104
|
+
### Output
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
.claude/work-kit/
|
|
108
|
+
2026-04-03-avatar-upload.md # distilled summary
|
|
109
|
+
archive/
|
|
110
|
+
2026-04-03-avatar-upload.md # full state.md copy
|
|
111
|
+
index.md # log of all completed work
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Repo structure
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
work-kit/
|
|
118
|
+
cli/
|
|
119
|
+
src/
|
|
120
|
+
commands/ # CLI command implementations
|
|
121
|
+
config/ # Configuration and defaults
|
|
122
|
+
context/ # Context generation for phases
|
|
123
|
+
engine/ # Workflow engine and transitions
|
|
124
|
+
state/ # State machine and file management
|
|
125
|
+
index.ts # Entry point
|
|
126
|
+
skills/
|
|
127
|
+
full-kit/SKILL.md # /full-kit orchestrator
|
|
128
|
+
auto-kit/SKILL.md # /auto-kit orchestrator
|
|
129
|
+
plan/SKILL.md # Plan phase runner
|
|
130
|
+
plan/stages/ # 8 stage files
|
|
131
|
+
build/SKILL.md # Build phase runner
|
|
132
|
+
build/stages/ # 8 stage files
|
|
133
|
+
test/SKILL.md # Test phase runner
|
|
134
|
+
test/stages/ # 3 stage files
|
|
135
|
+
review/SKILL.md # Review phase runner
|
|
136
|
+
review/stages/ # 5 stage files
|
|
137
|
+
deploy/SKILL.md # Deploy phase runner
|
|
138
|
+
deploy/stages/ # 3 stage files
|
|
139
|
+
wrap-up/SKILL.md # Final summary + cleanup
|
|
140
|
+
package.json
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Requirements
|
|
144
|
+
|
|
145
|
+
- Node.js >= 18
|
|
146
|
+
- Git
|
|
147
|
+
- [Claude Code](https://claude.com/claude-code)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const entry = resolve(__dirname, "..", "src", "index.ts");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
execFileSync(
|
|
12
|
+
process.execPath,
|
|
13
|
+
["--import", "tsx", entry, ...process.argv.slice(2)],
|
|
14
|
+
{ stdio: "inherit" }
|
|
15
|
+
);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
process.exit(e.status ?? 1);
|
|
18
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { readState, writeState, findWorktreeRoot } from "../state/store.js";
|
|
2
|
+
import { isPhaseComplete } from "../engine/transitions.js";
|
|
3
|
+
import { checkLoopback } from "../engine/loopbacks.js";
|
|
4
|
+
import { PHASE_ORDER } from "../config/phases.js";
|
|
5
|
+
import { parseLocation, resetToLocation } from "../state/helpers.js";
|
|
6
|
+
import type { Action, PhaseName } from "../state/schema.js";
|
|
7
|
+
|
|
8
|
+
export function completeCommand(target: string, outcome?: string, worktreeRoot?: string): Action {
|
|
9
|
+
const root = worktreeRoot || findWorktreeRoot();
|
|
10
|
+
if (!root) {
|
|
11
|
+
return { action: "error", message: "No work-kit state found. Run `work-kit init` first." };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const state = readState(root);
|
|
15
|
+
const { phase, subStage } = parseLocation(target);
|
|
16
|
+
|
|
17
|
+
// Validate phase exists
|
|
18
|
+
if (!state.phases[phase]) {
|
|
19
|
+
return { action: "error", message: `Unknown phase: ${phase}` };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Validate sub-stage exists
|
|
23
|
+
const ssState = state.phases[phase].subStages[subStage];
|
|
24
|
+
if (!ssState) {
|
|
25
|
+
return { action: "error", message: `Unknown sub-stage: ${phase}/${subStage}` };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Validate sub-stage is in a completable state
|
|
29
|
+
if (ssState.status === "completed") {
|
|
30
|
+
return { action: "error", message: `${phase}/${subStage} is already completed.` };
|
|
31
|
+
}
|
|
32
|
+
if (ssState.status === "skipped") {
|
|
33
|
+
return { action: "error", message: `${phase}/${subStage} is skipped and cannot be completed. Add it to the workflow first.` };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Mark sub-stage complete
|
|
37
|
+
ssState.status = "completed";
|
|
38
|
+
ssState.completedAt = new Date().toISOString();
|
|
39
|
+
if (outcome) {
|
|
40
|
+
ssState.outcome = outcome;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check for loop-back triggers
|
|
44
|
+
const loopback = checkLoopback(phase, subStage, outcome);
|
|
45
|
+
if (loopback) {
|
|
46
|
+
// Enforce max 2 loopbacks per route
|
|
47
|
+
const sameRouteCount = state.loopbacks.filter(
|
|
48
|
+
(lb) => lb.from.phase === phase && lb.from.subStage === subStage
|
|
49
|
+
&& lb.to.phase === loopback.to.phase && lb.to.subStage === loopback.to.subStage
|
|
50
|
+
).length;
|
|
51
|
+
|
|
52
|
+
if (sameRouteCount >= 2) {
|
|
53
|
+
// Max reached — proceed without looping back, note the caveat
|
|
54
|
+
writeState(root, state);
|
|
55
|
+
return {
|
|
56
|
+
action: "wait_for_user",
|
|
57
|
+
message: `${phase}/${subStage} triggered loopback (outcome: ${outcome}) but max loopback count (2) reached for this route. Proceeding with noted caveats.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
state.loopbacks.push({
|
|
62
|
+
from: { phase, subStage },
|
|
63
|
+
to: loopback.to,
|
|
64
|
+
reason: loopback.reason,
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
resetToLocation(state, loopback.to);
|
|
69
|
+
state.currentPhase = loopback.to.phase;
|
|
70
|
+
state.currentSubStage = loopback.to.subStage;
|
|
71
|
+
|
|
72
|
+
writeState(root, state);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
action: "loopback",
|
|
76
|
+
from: { phase, subStage },
|
|
77
|
+
to: loopback.to,
|
|
78
|
+
reason: loopback.reason,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check if the phase is now complete
|
|
83
|
+
if (isPhaseComplete(state, phase)) {
|
|
84
|
+
state.phases[phase].status = "completed";
|
|
85
|
+
state.phases[phase].completedAt = new Date().toISOString();
|
|
86
|
+
|
|
87
|
+
// Find next phase
|
|
88
|
+
const phaseIdx = PHASE_ORDER.indexOf(phase);
|
|
89
|
+
const nextPhases = PHASE_ORDER.slice(phaseIdx + 1);
|
|
90
|
+
let nextPhase: PhaseName | null = null;
|
|
91
|
+
|
|
92
|
+
for (const np of nextPhases) {
|
|
93
|
+
if (state.phases[np].status !== "skipped") {
|
|
94
|
+
nextPhase = np;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!nextPhase) {
|
|
100
|
+
state.status = "completed";
|
|
101
|
+
state.currentPhase = null;
|
|
102
|
+
state.currentSubStage = null;
|
|
103
|
+
writeState(root, state);
|
|
104
|
+
return { action: "complete", message: "All phases complete. Work-kit finished." };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
state.currentPhase = null;
|
|
108
|
+
state.currentSubStage = null;
|
|
109
|
+
writeState(root, state);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
action: "wait_for_user",
|
|
113
|
+
message: `${phase} phase complete. Ready to start ${nextPhase}. Proceed?`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
writeState(root, state);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
action: "wait_for_user",
|
|
121
|
+
message: `${phase}/${subStage} complete${outcome ? ` (outcome: ${outcome})` : ""}. Run \`npx work-kit next\` to continue.`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const BASH_COMPLETIONS = `\
|
|
2
|
+
_work_kit_completions() {
|
|
3
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
4
|
+
local commands="init next complete status context validate loopback workflow doctor setup upgrade completions"
|
|
5
|
+
|
|
6
|
+
if [ \$COMP_CWORD -eq 1 ]; then
|
|
7
|
+
COMPREPLY=(\$(compgen -W "\$commands" -- "\$cur"))
|
|
8
|
+
return
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
local cmd="\${COMP_WORDS[1]}"
|
|
12
|
+
case "\$cmd" in
|
|
13
|
+
complete|context|validate)
|
|
14
|
+
local phases="plan/clarify plan/investigate plan/sketch plan/scope plan/ux-flow plan/architecture plan/blueprint plan/audit build/setup build/migration build/red build/core build/ui build/refactor build/integration build/commit test/verify test/e2e test/validate review/self-review review/security review/performance review/compliance review/handoff deploy/merge deploy/monitor deploy/remediate"
|
|
15
|
+
COMPREPLY=(\$(compgen -W "\$phases" -- "\$cur"))
|
|
16
|
+
;;
|
|
17
|
+
init)
|
|
18
|
+
COMPREPLY=(\$(compgen -W "--mode --description --classification --worktree-root" -- "\$cur"))
|
|
19
|
+
;;
|
|
20
|
+
completions)
|
|
21
|
+
COMPREPLY=(\$(compgen -W "bash zsh fish" -- "\$cur"))
|
|
22
|
+
;;
|
|
23
|
+
esac
|
|
24
|
+
}
|
|
25
|
+
complete -F _work_kit_completions work-kit`;
|
|
26
|
+
|
|
27
|
+
const ZSH_COMPLETIONS = `\
|
|
28
|
+
#compdef work-kit
|
|
29
|
+
|
|
30
|
+
_work_kit() {
|
|
31
|
+
local -a commands phases init_opts shells
|
|
32
|
+
|
|
33
|
+
commands=(
|
|
34
|
+
'init:Create worktree and initialize state'
|
|
35
|
+
'next:Get the next action to perform'
|
|
36
|
+
'complete:Mark a phase/sub-stage as complete'
|
|
37
|
+
'status:Show current state summary'
|
|
38
|
+
'context:Extract Final sections needed for a phase agent'
|
|
39
|
+
'validate:Check prerequisites for a phase'
|
|
40
|
+
'loopback:Register a loop-back transition'
|
|
41
|
+
'workflow:Manage auto-kit dynamic workflow'
|
|
42
|
+
'doctor:Check CLI installation and environment health'
|
|
43
|
+
'setup:Install work-kit skills into a project'
|
|
44
|
+
'upgrade:Upgrade work-kit'
|
|
45
|
+
'completions:Output shell completions'
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
phases=(
|
|
49
|
+
'plan/clarify' 'plan/investigate' 'plan/sketch' 'plan/scope'
|
|
50
|
+
'plan/ux-flow' 'plan/architecture' 'plan/blueprint' 'plan/audit'
|
|
51
|
+
'build/setup' 'build/migration' 'build/red' 'build/core'
|
|
52
|
+
'build/ui' 'build/refactor' 'build/integration' 'build/commit'
|
|
53
|
+
'test/verify' 'test/e2e' 'test/validate'
|
|
54
|
+
'review/self-review' 'review/security' 'review/performance'
|
|
55
|
+
'review/compliance' 'review/handoff'
|
|
56
|
+
'deploy/merge' 'deploy/monitor' 'deploy/remediate'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
init_opts=(
|
|
60
|
+
'--mode[Workflow mode]:mode:(full auto)'
|
|
61
|
+
'--description[Description of the work]:text:'
|
|
62
|
+
'--classification[Work classification]:type:(bug-fix small-change refactor feature large-feature)'
|
|
63
|
+
'--worktree-root[Override worktree root directory]:path:_files -/'
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
shells=(bash zsh fish)
|
|
67
|
+
|
|
68
|
+
if (( CURRENT == 2 )); then
|
|
69
|
+
_describe -t commands 'work-kit command' commands
|
|
70
|
+
return
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
case \$words[2] in
|
|
74
|
+
complete|context|validate)
|
|
75
|
+
_describe -t phases 'phase' phases
|
|
76
|
+
;;
|
|
77
|
+
init)
|
|
78
|
+
_arguments \$init_opts
|
|
79
|
+
;;
|
|
80
|
+
completions)
|
|
81
|
+
_describe -t shells 'shell' shells
|
|
82
|
+
;;
|
|
83
|
+
esac
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
compdef _work_kit work-kit`;
|
|
87
|
+
|
|
88
|
+
const FISH_COMPLETIONS = `\
|
|
89
|
+
# Disable file completions by default
|
|
90
|
+
complete -c work-kit -f
|
|
91
|
+
|
|
92
|
+
# Top-level commands
|
|
93
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'init' -d 'Create worktree and initialize state'
|
|
94
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'next' -d 'Get the next action to perform'
|
|
95
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'complete' -d 'Mark a phase/sub-stage as complete'
|
|
96
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'status' -d 'Show current state summary'
|
|
97
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'context' -d 'Extract Final sections needed for a phase agent'
|
|
98
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'validate' -d 'Check prerequisites for a phase'
|
|
99
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'loopback' -d 'Register a loop-back transition'
|
|
100
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'workflow' -d 'Manage auto-kit dynamic workflow'
|
|
101
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'doctor' -d 'Check CLI installation and environment health'
|
|
102
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'setup' -d 'Install work-kit skills into a project'
|
|
103
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'upgrade' -d 'Upgrade work-kit'
|
|
104
|
+
complete -c work-kit -n '__fish_use_subcommand' -a 'completions' -d 'Output shell completions'
|
|
105
|
+
|
|
106
|
+
# Phase completions for complete, context, validate
|
|
107
|
+
set -l phase_cmds 'complete context validate'
|
|
108
|
+
set -l phases 'plan/clarify' 'plan/investigate' 'plan/sketch' 'plan/scope' 'plan/ux-flow' 'plan/architecture' 'plan/blueprint' 'plan/audit' 'build/setup' 'build/migration' 'build/red' 'build/core' 'build/ui' 'build/refactor' 'build/integration' 'build/commit' 'test/verify' 'test/e2e' 'test/validate' 'review/self-review' 'review/security' 'review/performance' 'review/compliance' 'review/handoff' 'deploy/merge' 'deploy/monitor' 'deploy/remediate'
|
|
109
|
+
for phase in \$phases
|
|
110
|
+
complete -c work-kit -n "__fish_seen_subcommand_from \$phase_cmds" -a "\$phase"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# init options
|
|
114
|
+
complete -c work-kit -n '__fish_seen_subcommand_from init' -l mode -d 'Workflow mode' -ra 'full auto'
|
|
115
|
+
complete -c work-kit -n '__fish_seen_subcommand_from init' -l description -d 'Description of the work'
|
|
116
|
+
complete -c work-kit -n '__fish_seen_subcommand_from init' -l classification -d 'Work classification' -ra 'bug-fix small-change refactor feature large-feature'
|
|
117
|
+
complete -c work-kit -n '__fish_seen_subcommand_from init' -l worktree-root -d 'Override worktree root directory'
|
|
118
|
+
|
|
119
|
+
# completions subcommand
|
|
120
|
+
complete -c work-kit -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish'`;
|
|
121
|
+
|
|
122
|
+
export function completionsCommand(shell: string): void {
|
|
123
|
+
switch (shell) {
|
|
124
|
+
case "bash":
|
|
125
|
+
console.log(BASH_COMPLETIONS);
|
|
126
|
+
break;
|
|
127
|
+
case "zsh":
|
|
128
|
+
console.log(ZSH_COMPLETIONS);
|
|
129
|
+
break;
|
|
130
|
+
case "fish":
|
|
131
|
+
console.log(FISH_COMPLETIONS);
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readState, readStateMd, findWorktreeRoot } from "../state/store.js";
|
|
2
|
+
import { getContextFor } from "../config/agent-map.js";
|
|
3
|
+
import { extractSection, extractTopSection } from "../context/extractor.js";
|
|
4
|
+
import type { PhaseName } from "../state/schema.js";
|
|
5
|
+
|
|
6
|
+
interface ContextResult {
|
|
7
|
+
phase: PhaseName;
|
|
8
|
+
sections: Record<string, string | null>;
|
|
9
|
+
needsGitDiff: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function contextCommand(phase: PhaseName, worktreeRoot?: string): ContextResult {
|
|
13
|
+
const root = worktreeRoot || findWorktreeRoot();
|
|
14
|
+
if (!root) {
|
|
15
|
+
throw new Error("No work-kit state found. Run `work-kit init` first.");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const state = readState(root);
|
|
19
|
+
const stateMd = readStateMd(root);
|
|
20
|
+
|
|
21
|
+
if (!stateMd) {
|
|
22
|
+
throw new Error("No state.md found.");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ctx = getContextFor(phase);
|
|
26
|
+
const sections: Record<string, string | null> = {};
|
|
27
|
+
|
|
28
|
+
for (const sectionName of ctx.sections) {
|
|
29
|
+
if (sectionName.startsWith("### ")) {
|
|
30
|
+
sections[sectionName] = extractSection(stateMd, sectionName);
|
|
31
|
+
} else {
|
|
32
|
+
sections[sectionName] = extractTopSection(stateMd, sectionName);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
phase,
|
|
38
|
+
sections,
|
|
39
|
+
needsGitDiff: !!ctx.needsGitDiff,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { findWorktreeRoot, readState } from "../state/store.js";
|
|
5
|
+
|
|
6
|
+
interface Check {
|
|
7
|
+
name: string;
|
|
8
|
+
status: "pass" | "fail" | "warn";
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function doctorCommand(worktreeRoot?: string): { ok: boolean; checks: Check[] } {
|
|
13
|
+
const checks: Check[] = [];
|
|
14
|
+
|
|
15
|
+
// 1. Node.js version
|
|
16
|
+
const nodeVersion = process.version;
|
|
17
|
+
const major = parseInt(nodeVersion.slice(1), 10);
|
|
18
|
+
if (major >= 18) {
|
|
19
|
+
checks.push({ name: "node", status: "pass", message: `Node.js ${nodeVersion}` });
|
|
20
|
+
} else {
|
|
21
|
+
checks.push({ name: "node", status: "fail", message: `Node.js ${nodeVersion} — requires >= 18` });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 2. Skills installed
|
|
25
|
+
const cwd = worktreeRoot || process.cwd();
|
|
26
|
+
const skillsDir = path.join(cwd, ".claude", "skills");
|
|
27
|
+
const requiredSkills = ["full-kit", "auto-kit"];
|
|
28
|
+
for (const skill of requiredSkills) {
|
|
29
|
+
const skillPath = path.join(skillsDir, skill, "SKILL.md");
|
|
30
|
+
if (fs.existsSync(skillPath)) {
|
|
31
|
+
checks.push({ name: `skill:${skill}`, status: "pass", message: `${skill}/SKILL.md found` });
|
|
32
|
+
} else {
|
|
33
|
+
checks.push({ name: `skill:${skill}`, status: "fail", message: `${skill}/SKILL.md not found at ${skillPath}` });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 3. Phase skill files
|
|
38
|
+
const phases = ["plan", "build", "test", "review", "deploy", "wrap-up"];
|
|
39
|
+
let phasesMissing = 0;
|
|
40
|
+
for (const phase of phases) {
|
|
41
|
+
const phasePath = path.join(skillsDir, phase, "SKILL.md");
|
|
42
|
+
if (!fs.existsSync(phasePath)) {
|
|
43
|
+
phasesMissing++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (phasesMissing === 0) {
|
|
47
|
+
checks.push({ name: "skill:phases", status: "pass", message: `All ${phases.length} phase skills found` });
|
|
48
|
+
} else {
|
|
49
|
+
checks.push({ name: "skill:phases", status: "fail", message: `${phasesMissing} phase skill(s) missing from ${skillsDir}` });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 4. Git available
|
|
53
|
+
try {
|
|
54
|
+
const gitVersion = execFileSync("git", ["--version"], { encoding: "utf-8" }).trim();
|
|
55
|
+
checks.push({ name: "git", status: "pass", message: gitVersion });
|
|
56
|
+
} catch {
|
|
57
|
+
checks.push({ name: "git", status: "fail", message: "git not found in PATH" });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 5. State file health (if in a worktree)
|
|
61
|
+
const root = worktreeRoot ? worktreeRoot : findWorktreeRoot();
|
|
62
|
+
if (root) {
|
|
63
|
+
try {
|
|
64
|
+
const state = readState(root);
|
|
65
|
+
if (state.version === 1 && state.slug && state.status) {
|
|
66
|
+
checks.push({ name: "state", status: "pass", message: `Active work-kit: "${state.slug}" (${state.status})` });
|
|
67
|
+
} else {
|
|
68
|
+
checks.push({ name: "state", status: "warn", message: "state.json exists but has unexpected structure" });
|
|
69
|
+
}
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
checks.push({ name: "state", status: "warn", message: `state.json error: ${e.message}` });
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
checks.push({ name: "state", status: "pass", message: "No active worktree (OK — run `work-kit init` to start)" });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ok = checks.every((c) => c.status !== "fail");
|
|
78
|
+
return { ok, checks };
|
|
79
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, afterEach } from "node:test";
|
|
2
|
+
import * as assert from "node:assert/strict";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { initCommand } from "./init.js";
|
|
8
|
+
|
|
9
|
+
function makeTmpDir(): string {
|
|
10
|
+
const dir = path.join(os.tmpdir(), `work-kit-test-${randomUUID()}`);
|
|
11
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
12
|
+
return dir;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let tmpDirs: string[] = [];
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
for (const dir of tmpDirs) {
|
|
19
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
tmpDirs = [];
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("initCommand", () => {
|
|
25
|
+
it("creates state.json and state.md", () => {
|
|
26
|
+
const tmp = makeTmpDir();
|
|
27
|
+
tmpDirs.push(tmp);
|
|
28
|
+
|
|
29
|
+
initCommand({
|
|
30
|
+
mode: "full",
|
|
31
|
+
description: "Add user login",
|
|
32
|
+
worktreeRoot: tmp,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
assert.ok(fs.existsSync(path.join(tmp, ".work-kit", "state.json")));
|
|
36
|
+
assert.ok(fs.existsSync(path.join(tmp, ".work-kit", "state.md")));
|
|
37
|
+
|
|
38
|
+
const state = JSON.parse(
|
|
39
|
+
fs.readFileSync(path.join(tmp, ".work-kit", "state.json"), "utf-8")
|
|
40
|
+
);
|
|
41
|
+
assert.equal(state.slug, "add-user-login");
|
|
42
|
+
assert.equal(state.status, "in-progress");
|
|
43
|
+
assert.equal(state.currentPhase, "plan");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns spawn_agent action", () => {
|
|
47
|
+
const tmp = makeTmpDir();
|
|
48
|
+
tmpDirs.push(tmp);
|
|
49
|
+
|
|
50
|
+
const result = initCommand({
|
|
51
|
+
mode: "full",
|
|
52
|
+
description: "Add user login",
|
|
53
|
+
worktreeRoot: tmp,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
assert.equal(result.action, "spawn_agent");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("blocks double init", () => {
|
|
60
|
+
const tmp = makeTmpDir();
|
|
61
|
+
tmpDirs.push(tmp);
|
|
62
|
+
|
|
63
|
+
initCommand({
|
|
64
|
+
mode: "full",
|
|
65
|
+
description: "First init",
|
|
66
|
+
worktreeRoot: tmp,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const result = initCommand({
|
|
70
|
+
mode: "full",
|
|
71
|
+
description: "Second init",
|
|
72
|
+
worktreeRoot: tmp,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
assert.equal(result.action, "error");
|
|
76
|
+
if (result.action === "error") {
|
|
77
|
+
assert.ok(result.message.includes("already exists"));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("auto mode requires classification", () => {
|
|
82
|
+
const tmp = makeTmpDir();
|
|
83
|
+
tmpDirs.push(tmp);
|
|
84
|
+
|
|
85
|
+
const result = initCommand({
|
|
86
|
+
mode: "auto",
|
|
87
|
+
description: "Some task",
|
|
88
|
+
worktreeRoot: tmp,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
assert.equal(result.action, "error");
|
|
92
|
+
if (result.action === "error") {
|
|
93
|
+
assert.ok(result.message.includes("classification"));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("auto mode with classification succeeds", () => {
|
|
98
|
+
const tmp = makeTmpDir();
|
|
99
|
+
tmpDirs.push(tmp);
|
|
100
|
+
|
|
101
|
+
const result = initCommand({
|
|
102
|
+
mode: "auto",
|
|
103
|
+
description: "Fix login bug",
|
|
104
|
+
classification: "bug-fix",
|
|
105
|
+
worktreeRoot: tmp,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
assert.equal(result.action, "spawn_agent");
|
|
109
|
+
|
|
110
|
+
const state = JSON.parse(
|
|
111
|
+
fs.readFileSync(path.join(tmp, ".work-kit", "state.json"), "utf-8")
|
|
112
|
+
);
|
|
113
|
+
assert.equal(state.mode, "auto-kit");
|
|
114
|
+
assert.equal(state.classification, "bug-fix");
|
|
115
|
+
});
|
|
116
|
+
});
|