@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.
Files changed (76) hide show
  1. package/README.md +147 -0
  2. package/cli/bin/work-kit.mjs +18 -0
  3. package/cli/src/commands/complete.ts +123 -0
  4. package/cli/src/commands/completions.ts +137 -0
  5. package/cli/src/commands/context.ts +41 -0
  6. package/cli/src/commands/doctor.ts +79 -0
  7. package/cli/src/commands/init.test.ts +116 -0
  8. package/cli/src/commands/init.ts +184 -0
  9. package/cli/src/commands/loopback.ts +64 -0
  10. package/cli/src/commands/next.ts +172 -0
  11. package/cli/src/commands/observe.ts +144 -0
  12. package/cli/src/commands/setup.ts +159 -0
  13. package/cli/src/commands/status.ts +50 -0
  14. package/cli/src/commands/uninstall.ts +89 -0
  15. package/cli/src/commands/upgrade.ts +12 -0
  16. package/cli/src/commands/validate.ts +34 -0
  17. package/cli/src/commands/workflow.ts +125 -0
  18. package/cli/src/config/agent-map.ts +62 -0
  19. package/cli/src/config/loopback-routes.ts +45 -0
  20. package/cli/src/config/phases.ts +119 -0
  21. package/cli/src/context/extractor.test.ts +77 -0
  22. package/cli/src/context/extractor.ts +73 -0
  23. package/cli/src/context/prompt-builder.ts +70 -0
  24. package/cli/src/engine/loopbacks.test.ts +33 -0
  25. package/cli/src/engine/loopbacks.ts +32 -0
  26. package/cli/src/engine/parallel.ts +60 -0
  27. package/cli/src/engine/phases.ts +23 -0
  28. package/cli/src/engine/transitions.test.ts +117 -0
  29. package/cli/src/engine/transitions.ts +97 -0
  30. package/cli/src/index.ts +248 -0
  31. package/cli/src/observer/data.ts +237 -0
  32. package/cli/src/observer/renderer.ts +316 -0
  33. package/cli/src/observer/watcher.ts +99 -0
  34. package/cli/src/state/helpers.test.ts +91 -0
  35. package/cli/src/state/helpers.ts +65 -0
  36. package/cli/src/state/schema.ts +113 -0
  37. package/cli/src/state/store.ts +82 -0
  38. package/cli/src/state/validators.test.ts +105 -0
  39. package/cli/src/state/validators.ts +81 -0
  40. package/cli/src/utils/colors.ts +12 -0
  41. package/package.json +49 -0
  42. package/skills/auto-kit/SKILL.md +214 -0
  43. package/skills/build/SKILL.md +88 -0
  44. package/skills/build/stages/commit.md +43 -0
  45. package/skills/build/stages/core.md +48 -0
  46. package/skills/build/stages/integration.md +44 -0
  47. package/skills/build/stages/migration.md +41 -0
  48. package/skills/build/stages/red.md +44 -0
  49. package/skills/build/stages/refactor.md +48 -0
  50. package/skills/build/stages/setup.md +42 -0
  51. package/skills/build/stages/ui.md +51 -0
  52. package/skills/deploy/SKILL.md +62 -0
  53. package/skills/deploy/stages/merge.md +47 -0
  54. package/skills/deploy/stages/monitor.md +39 -0
  55. package/skills/deploy/stages/remediate.md +54 -0
  56. package/skills/full-kit/SKILL.md +195 -0
  57. package/skills/plan/SKILL.md +77 -0
  58. package/skills/plan/stages/architecture.md +53 -0
  59. package/skills/plan/stages/audit.md +58 -0
  60. package/skills/plan/stages/blueprint.md +60 -0
  61. package/skills/plan/stages/clarify.md +61 -0
  62. package/skills/plan/stages/investigate.md +47 -0
  63. package/skills/plan/stages/scope.md +46 -0
  64. package/skills/plan/stages/sketch.md +44 -0
  65. package/skills/plan/stages/ux-flow.md +49 -0
  66. package/skills/review/SKILL.md +104 -0
  67. package/skills/review/stages/compliance.md +48 -0
  68. package/skills/review/stages/handoff.md +59 -0
  69. package/skills/review/stages/performance.md +45 -0
  70. package/skills/review/stages/security.md +49 -0
  71. package/skills/review/stages/self-review.md +41 -0
  72. package/skills/test/SKILL.md +83 -0
  73. package/skills/test/stages/e2e.md +44 -0
  74. package/skills/test/stages/validate.md +51 -0
  75. package/skills/test/stages/verify.md +41 -0
  76. 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
+ });