autoworkflow 3.6.1 → 3.8.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/.claude/hooks/pre-edit.sh +192 -119
- package/README.md +67 -14
- package/bin/cli.js +343 -73
- package/package.json +1 -1
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# AutoWorkflow Pre-Edit Hook
|
|
2
|
+
# AutoWorkflow Pre-Edit Hook (v3.7.0 - Fail-Closed Design)
|
|
3
3
|
# Runs on: PreToolUse for Write/Edit tools
|
|
4
|
-
# Purpose:
|
|
4
|
+
# Purpose: BLOCK edits by default unless workflow state is properly set
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# DESIGN PRINCIPLE: Fail-closed
|
|
7
|
+
# - If state is not initialized → BLOCK (auto-init as feature)
|
|
8
|
+
# - If plan not approved → BLOCK
|
|
9
|
+
# - If suggestions not shown (feature tasks) → BLOCK
|
|
10
|
+
# - If multiple edits per turn → BLOCK
|
|
11
|
+
# - Only allow edit if ALL checks pass
|
|
8
12
|
|
|
9
13
|
# Colors
|
|
10
14
|
RED='\033[0;31m'
|
|
@@ -24,14 +28,43 @@ TASK_TYPE_FILE="$STATE_DIR/task-type"
|
|
|
24
28
|
PLAN_APPROVED_FILE="$STATE_DIR/plan-approved"
|
|
25
29
|
SUGGESTIONS_SHOWN_FILE="$STATE_DIR/suggestions-shown"
|
|
26
30
|
CURRENT_TURN_EDITS_FILE="$STATE_DIR/current-turn-edits"
|
|
27
|
-
|
|
31
|
+
WORKFLOW_INITIALIZED_FILE="$STATE_DIR/workflow-initialized"
|
|
32
|
+
|
|
33
|
+
# Auto-initialize workflow state with safe defaults
|
|
34
|
+
auto_init_state() {
|
|
35
|
+
mkdir -p "$STATE_DIR"
|
|
36
|
+
|
|
37
|
+
# Set safe defaults - assume feature task (strictest)
|
|
38
|
+
echo "feature" > "$TASK_TYPE_FILE"
|
|
39
|
+
echo "IMPLEMENT" > "$PHASE_FILE"
|
|
40
|
+
echo "false" > "$PLAN_APPROVED_FILE"
|
|
41
|
+
echo "false" > "$SUGGESTIONS_SHOWN_FILE"
|
|
42
|
+
echo "0" > "$CURRENT_TURN_EDITS_FILE"
|
|
43
|
+
echo "true" > "$WORKFLOW_INITIALIZED_FILE"
|
|
44
|
+
|
|
45
|
+
echo ""
|
|
46
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
47
|
+
echo -e "${CYAN}${BOLD}📋 AUTOWORKFLOW: STATE INITIALIZED${NC}"
|
|
48
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Workflow state auto-initialized with safe defaults:"
|
|
51
|
+
echo " Task type: feature (strictest)"
|
|
52
|
+
echo " Plan approved: false"
|
|
53
|
+
echo " Suggestions shown: false"
|
|
54
|
+
echo ""
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Check if workflow is initialized
|
|
58
|
+
is_workflow_initialized() {
|
|
59
|
+
[ -d "$STATE_DIR" ] && [ -f "$WORKFLOW_INITIALIZED_FILE" ]
|
|
60
|
+
}
|
|
28
61
|
|
|
29
62
|
# Get current phase
|
|
30
63
|
get_phase() {
|
|
31
64
|
if [ -f "$PHASE_FILE" ]; then
|
|
32
65
|
cat "$PHASE_FILE"
|
|
33
66
|
else
|
|
34
|
-
echo "
|
|
67
|
+
echo "UNKNOWN"
|
|
35
68
|
fi
|
|
36
69
|
}
|
|
37
70
|
|
|
@@ -40,7 +73,7 @@ get_task_type() {
|
|
|
40
73
|
if [ -f "$TASK_TYPE_FILE" ]; then
|
|
41
74
|
cat "$TASK_TYPE_FILE"
|
|
42
75
|
else
|
|
43
|
-
echo "
|
|
76
|
+
echo "feature" # Default to strictest
|
|
44
77
|
fi
|
|
45
78
|
}
|
|
46
79
|
|
|
@@ -53,7 +86,7 @@ is_plan_approved() {
|
|
|
53
86
|
return 1
|
|
54
87
|
}
|
|
55
88
|
|
|
56
|
-
# Check if suggestions were shown
|
|
89
|
+
# Check if suggestions were shown
|
|
57
90
|
suggestions_shown() {
|
|
58
91
|
if [ -f "$SUGGESTIONS_SHOWN_FILE" ]; then
|
|
59
92
|
local status=$(cat "$SUGGESTIONS_SHOWN_FILE")
|
|
@@ -79,142 +112,182 @@ increment_turn_edits() {
|
|
|
79
112
|
echo "$next"
|
|
80
113
|
}
|
|
81
114
|
|
|
82
|
-
# Check if
|
|
83
|
-
|
|
84
|
-
if [ -f "$SELECTED_ITEMS_FILE" ]; then
|
|
85
|
-
local content=$(cat "$SELECTED_ITEMS_FILE")
|
|
86
|
-
[ -n "$content" ] && return 0
|
|
87
|
-
fi
|
|
88
|
-
return 1
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
# Check if task type requires approval
|
|
92
|
-
requires_approval() {
|
|
115
|
+
# Check if task type requires strict workflow
|
|
116
|
+
requires_strict_workflow() {
|
|
93
117
|
local task_type="$1"
|
|
94
118
|
case "$task_type" in
|
|
95
|
-
#
|
|
119
|
+
# Strict workflow required
|
|
96
120
|
feature|fix|refactor|perf|security|test)
|
|
97
121
|
return 0
|
|
98
122
|
;;
|
|
99
|
-
#
|
|
100
|
-
docs|style|config
|
|
123
|
+
# Relaxed workflow
|
|
124
|
+
docs|style|config)
|
|
101
125
|
return 1
|
|
102
126
|
;;
|
|
103
|
-
# Unknown
|
|
127
|
+
# Unknown = strict
|
|
104
128
|
*)
|
|
105
129
|
return 0
|
|
106
130
|
;;
|
|
107
131
|
esac
|
|
108
132
|
}
|
|
109
133
|
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
local
|
|
113
|
-
local task_type=$(get_task_type)
|
|
134
|
+
# GATE 1: Plan Approval Check
|
|
135
|
+
check_plan_approval() {
|
|
136
|
+
local task_type="$1"
|
|
114
137
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
exit 0
|
|
138
|
+
if ! requires_strict_workflow "$task_type"; then
|
|
139
|
+
return 0 # Pass for relaxed task types
|
|
118
140
|
fi
|
|
119
141
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
if ! is_plan_approved; then
|
|
143
|
+
echo ""
|
|
144
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
145
|
+
echo -e "${RED}${BOLD}⛔ GATE 1: PLAN APPROVAL REQUIRED${NC}"
|
|
146
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
147
|
+
echo ""
|
|
148
|
+
echo -e "${CYAN}Task Type:${NC} $task_type"
|
|
149
|
+
echo -e "${CYAN}Plan Approved:${NC} NO"
|
|
150
|
+
echo ""
|
|
151
|
+
echo "You MUST get user approval before making any edits."
|
|
152
|
+
echo ""
|
|
153
|
+
echo -e "${BOLD}Required steps:${NC}"
|
|
154
|
+
echo " 1. Analyze the codebase"
|
|
155
|
+
echo " 2. Present your plan to the user"
|
|
156
|
+
echo " 3. Wait for explicit approval (yes/proceed/approved)"
|
|
157
|
+
echo " 4. THEN implement"
|
|
158
|
+
echo ""
|
|
159
|
+
echo -e "${DIM}To mark approved: echo 'true' > $PLAN_APPROVED_FILE${NC}"
|
|
160
|
+
echo ""
|
|
161
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
162
|
+
echo ""
|
|
163
|
+
return 1
|
|
123
164
|
fi
|
|
165
|
+
return 0
|
|
166
|
+
}
|
|
124
167
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
168
|
+
# GATE 2: Suggestions Check (for feature tasks)
|
|
169
|
+
check_suggestions() {
|
|
170
|
+
local task_type="$1"
|
|
171
|
+
|
|
172
|
+
# Only enforce for feature tasks
|
|
173
|
+
if [ "$task_type" != "feature" ]; then
|
|
174
|
+
return 0
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
if ! suggestions_shown; then
|
|
178
|
+
echo ""
|
|
179
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
180
|
+
echo -e "${RED}${BOLD}⛔ GATE 2: SUGGESTIONS REQUIRED${NC}"
|
|
181
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
182
|
+
echo ""
|
|
183
|
+
echo -e "${CYAN}Task Type:${NC} feature"
|
|
184
|
+
echo ""
|
|
185
|
+
echo "For feature tasks, you MUST show 3-tier suggestions FIRST:"
|
|
186
|
+
echo ""
|
|
187
|
+
echo " 🔴 Required - Must implement"
|
|
188
|
+
echo " 🟡 Recommended - Should implement"
|
|
189
|
+
echo " 🟢 Optional - Nice to have"
|
|
190
|
+
echo ""
|
|
191
|
+
echo "Show suggestions, let user select, THEN implement ONE at a time."
|
|
192
|
+
echo ""
|
|
193
|
+
echo -e "${DIM}To mark shown: echo 'true' > $SUGGESTIONS_SHOWN_FILE${NC}"
|
|
194
|
+
echo ""
|
|
195
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
196
|
+
echo ""
|
|
197
|
+
return 1
|
|
198
|
+
fi
|
|
199
|
+
return 0
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# GATE 3: One Edit Per Turn Check (applies to ALL implementation)
|
|
203
|
+
check_one_edit_per_turn() {
|
|
204
|
+
local task_type="$1"
|
|
132
205
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if ! is_plan_approved; then
|
|
136
|
-
echo ""
|
|
137
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
138
|
-
echo -e "${RED}${BOLD}⛔ AUTOWORKFLOW: PLAN APPROVAL REQUIRED${NC}"
|
|
139
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
140
|
-
echo ""
|
|
141
|
-
echo -e "${CYAN}Current Phase:${NC} $phase"
|
|
142
|
-
echo -e "${CYAN}Task Type:${NC} $task_type"
|
|
143
|
-
echo -e "${CYAN}Plan Approved:${NC} NO"
|
|
144
|
-
echo ""
|
|
145
|
-
echo "Cannot edit files before plan approval."
|
|
146
|
-
echo ""
|
|
147
|
-
echo -e "${BOLD}Required workflow:${NC}"
|
|
148
|
-
echo " 1. Complete ANALYZE phase (read relevant files)"
|
|
149
|
-
echo " 2. Present PLAN with suggestions"
|
|
150
|
-
echo " 3. Wait for user approval"
|
|
151
|
-
echo " 4. THEN implement"
|
|
152
|
-
echo ""
|
|
153
|
-
echo -e "${DIM}To approve, user must say: yes, proceed, approved, go ahead${NC}"
|
|
154
|
-
echo ""
|
|
155
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
156
|
-
echo ""
|
|
157
|
-
|
|
158
|
-
# Exit with error to BLOCK the edit
|
|
159
|
-
exit 1
|
|
160
|
-
fi
|
|
206
|
+
if ! requires_strict_workflow "$task_type"; then
|
|
207
|
+
return 0 # Pass for relaxed task types
|
|
161
208
|
fi
|
|
162
209
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
exit 1
|
|
186
|
-
fi
|
|
210
|
+
local edit_count=$(get_turn_edit_count)
|
|
211
|
+
|
|
212
|
+
if [ "$edit_count" -ge 1 ]; then
|
|
213
|
+
echo ""
|
|
214
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
215
|
+
echo -e "${YELLOW}${BOLD}⛔ GATE 3: ONE EDIT PER TURN${NC}"
|
|
216
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
217
|
+
echo ""
|
|
218
|
+
echo -e "${CYAN}Edits this turn:${NC} $edit_count"
|
|
219
|
+
echo ""
|
|
220
|
+
echo "You can only make ONE edit per user turn."
|
|
221
|
+
echo ""
|
|
222
|
+
echo "This ensures:"
|
|
223
|
+
echo " 1. Easier to track changes"
|
|
224
|
+
echo " 2. Errors caught early"
|
|
225
|
+
echo " 3. User can review incrementally"
|
|
226
|
+
echo ""
|
|
227
|
+
echo "Wait for user's next message before making another edit."
|
|
228
|
+
echo ""
|
|
229
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
230
|
+
echo ""
|
|
231
|
+
return 1
|
|
187
232
|
fi
|
|
188
233
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
234
|
+
return 0
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# Main execution
|
|
238
|
+
main() {
|
|
239
|
+
# STEP 1: Check if workflow is initialized
|
|
240
|
+
if ! is_workflow_initialized; then
|
|
241
|
+
# Auto-initialize with safe defaults
|
|
242
|
+
auto_init_state
|
|
243
|
+
|
|
244
|
+
# After init, BLOCK this edit attempt
|
|
245
|
+
echo ""
|
|
246
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
247
|
+
echo -e "${RED}${BOLD}⛔ AUTOWORKFLOW: EDIT BLOCKED${NC}"
|
|
248
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
249
|
+
echo ""
|
|
250
|
+
echo "Workflow just initialized. Cannot edit yet."
|
|
251
|
+
echo ""
|
|
252
|
+
echo "You must first:"
|
|
253
|
+
echo " 1. Show your analysis/plan to the user"
|
|
254
|
+
echo " 2. Get user approval"
|
|
255
|
+
echo " 3. For features: Show 3-tier suggestions"
|
|
256
|
+
echo " 4. THEN implement one fix at a time"
|
|
257
|
+
echo ""
|
|
258
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
259
|
+
echo ""
|
|
260
|
+
exit 1
|
|
215
261
|
fi
|
|
216
262
|
|
|
217
|
-
#
|
|
263
|
+
# Get current state
|
|
264
|
+
local task_type=$(get_task_type)
|
|
265
|
+
local phase=$(get_phase)
|
|
266
|
+
|
|
267
|
+
# STEP 2: Run all gates
|
|
268
|
+
|
|
269
|
+
# GATE 1: Plan Approval
|
|
270
|
+
if ! check_plan_approval "$task_type"; then
|
|
271
|
+
exit 1
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
# GATE 2: Suggestions (feature tasks only)
|
|
275
|
+
if ! check_suggestions "$task_type"; then
|
|
276
|
+
exit 1
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
# GATE 3: One Edit Per Turn
|
|
280
|
+
if ! check_one_edit_per_turn "$task_type"; then
|
|
281
|
+
exit 1
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
# ALL GATES PASSED - Allow edit and track it
|
|
285
|
+
increment_turn_edits > /dev/null
|
|
286
|
+
|
|
287
|
+
echo ""
|
|
288
|
+
echo -e "${GREEN}✓${NC} Edit allowed (turn edit #$(get_turn_edit_count))"
|
|
289
|
+
echo ""
|
|
290
|
+
|
|
218
291
|
exit 0
|
|
219
292
|
}
|
|
220
293
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Automated workflow enforcement for Claude Code via hooks and system prompts.
|
|
4
4
|
|
|
5
|
-
**v3.
|
|
5
|
+
**v3.8.0** - Smart CLI updates: version tracking + user file preservation.
|
|
6
6
|
|
|
7
7
|
When you use Claude Code with AutoWorkflow, hooks automatically enforce workflow phases, block unauthorized edits, and guide Claude through a structured process for all coding tasks.
|
|
8
8
|
|
|
@@ -14,10 +14,61 @@ When you use Claude Code with AutoWorkflow, hooks automatically enforce workflow
|
|
|
14
14
|
npx autoworkflow init
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
### CLI Commands
|
|
18
|
+
|
|
19
|
+
| Command | Description |
|
|
20
|
+
|---------|-------------|
|
|
21
|
+
| `npx autoworkflow init` | Fresh install (required + recommended files) |
|
|
22
|
+
| `npx autoworkflow init --all` | Include .vscode and config templates |
|
|
23
|
+
| `npx autoworkflow init --minimal` | Required files only |
|
|
24
|
+
| `npx autoworkflow init --force` | Full reinstall (backup + overwrite all) |
|
|
25
|
+
| `npx autoworkflow update` | Smart update - core files only, preserve user files |
|
|
26
|
+
| `npx autoworkflow status` | Show installed version and components |
|
|
27
|
+
|
|
28
|
+
### Smart Updates (v3.8.0)
|
|
29
|
+
|
|
30
|
+
The CLI now tracks installed versions and preserves user customizations:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
npx autoworkflow status # Check version and what would change
|
|
34
|
+
npx autoworkflow update # Update core files, preserve BLUEPRINT.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| File Category | On `init` | On `update` |
|
|
38
|
+
|---------------|-----------|-------------|
|
|
39
|
+
| **Core** (hooks, system, CLAUDE.md) | Installed | Updated |
|
|
40
|
+
| **User** (BLUEPRINT.md, AI_RULES.md) | Created if missing | **Preserved** |
|
|
41
|
+
| **Commands/Skills** | Installed | Updated |
|
|
42
|
+
| **Optional** (.vscode, configs) | Only with `--all` | Skipped |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## What's New in v3.7.0
|
|
47
|
+
|
|
48
|
+
### Fail-Closed Enforcement Design
|
|
49
|
+
Previous versions "failed open" - if state wasn't set, edits were allowed. v3.7.0 "fails closed":
|
|
50
|
+
|
|
51
|
+
| Scenario | Before (v3.6) | After (v3.7) |
|
|
52
|
+
|----------|---------------|--------------|
|
|
53
|
+
| No workflow state | ✅ Allowed | ⛔ BLOCKED |
|
|
54
|
+
| Plan not approved | ⛔ Blocked (in PLAN phase only) | ⛔ BLOCKED (always) |
|
|
55
|
+
| No suggestions shown | ⛔ Blocked (in IMPLEMENT only) | ⛔ BLOCKED (always for features) |
|
|
56
|
+
| Multiple edits/turn | ⛔ Blocked (in FIX only) | ⛔ BLOCKED (always) |
|
|
57
|
+
|
|
58
|
+
### Auto-Initialization with Safe Defaults
|
|
59
|
+
When Claude attempts the first edit without workflow state:
|
|
60
|
+
1. Auto-creates state with `task-type: feature` (strictest)
|
|
61
|
+
2. Sets `plan-approved: false`
|
|
62
|
+
3. Sets `suggestions-shown: false`
|
|
63
|
+
4. **BLOCKS the edit** immediately
|
|
64
|
+
5. Claude must show plan/suggestions first, get approval, then try again
|
|
65
|
+
|
|
66
|
+
### Three Gates (All Must Pass)
|
|
67
|
+
```
|
|
68
|
+
GATE 1: Plan Approval → Must have user approval
|
|
69
|
+
GATE 2: Suggestions → Must show 3-tier suggestions (features)
|
|
70
|
+
GATE 3: One Edit/Turn → Max 1 edit per user message
|
|
71
|
+
```
|
|
21
72
|
|
|
22
73
|
---
|
|
23
74
|
|
|
@@ -149,16 +200,18 @@ ANALYZE → PLAN → CONFIRM → IMPLEMENT → VERIFY → AUDIT → COMMIT → U
|
|
|
149
200
|
|
|
150
201
|
---
|
|
151
202
|
|
|
152
|
-
## Blocking Gates
|
|
203
|
+
## Blocking Gates (Fail-Closed)
|
|
204
|
+
|
|
205
|
+
| Gate | Blocks If | Applies To |
|
|
206
|
+
|------|-----------|------------|
|
|
207
|
+
| **State Init** | No workflow state exists | ALL tasks |
|
|
208
|
+
| **Plan Approval** | User hasn't approved | feature, fix, refactor, perf, security, test |
|
|
209
|
+
| **Suggestions** | 3-tier suggestions not shown | feature tasks |
|
|
210
|
+
| **One Edit/Turn** | Already edited this turn | feature, fix, refactor, perf, security, test |
|
|
211
|
+
| **Verify** | Language-specific errors | ALL (post-edit) |
|
|
212
|
+
| **Pre-Commit** | TODO/FIXME, console.log | ALL commits |
|
|
153
213
|
|
|
154
|
-
|
|
155
|
-
|------|-----------|-------------|
|
|
156
|
-
| Plan Approval | User hasn't approved the plan | `pre-edit.sh` (exit 1) |
|
|
157
|
-
| Suggestions | Feature task without 3-tier suggestions shown | `pre-edit.sh` (exit 1) |
|
|
158
|
-
| One-Fix-At-A-Time | Multiple edits in FIX phase per turn | `pre-edit.sh` (exit 1) |
|
|
159
|
-
| Verify | TypeScript/ESLint/language-specific errors | `post-edit.sh` loop |
|
|
160
|
-
| Audit | Orphan features or circular dependencies | Required before commit |
|
|
161
|
-
| Pre-Commit | TODO/FIXME, console.log, bad format | `pre-commit-check.sh` (exit 1) |
|
|
214
|
+
**Design:** All gates are checked on EVERY edit. Unknown state = strictest defaults.
|
|
162
215
|
|
|
163
216
|
---
|
|
164
217
|
|
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync, cpSync, mkdirSync, chmodSync, renameSync, unlinkSync } from 'fs';
|
|
3
|
+
import { existsSync, cpSync, mkdirSync, chmodSync, renameSync, unlinkSync, readFileSync, writeFileSync } from 'fs';
|
|
4
4
|
import { dirname, join } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
|
|
@@ -11,6 +11,10 @@ const packageRoot = join(__dirname, '..');
|
|
|
11
11
|
const args = process.argv.slice(2);
|
|
12
12
|
const command = args[0];
|
|
13
13
|
|
|
14
|
+
// Get package version
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
16
|
+
const PACKAGE_VERSION = packageJson.version;
|
|
17
|
+
|
|
14
18
|
const colors = {
|
|
15
19
|
green: (text) => `\x1b[32m${text}\x1b[0m`,
|
|
16
20
|
yellow: (text) => `\x1b[33m${text}\x1b[0m`,
|
|
@@ -20,50 +24,147 @@ const colors = {
|
|
|
20
24
|
dim: (text) => `\x1b[2m${text}\x1b[0m`,
|
|
21
25
|
};
|
|
22
26
|
|
|
27
|
+
// File categories for smart updates
|
|
28
|
+
const FILE_CATEGORIES = {
|
|
29
|
+
// Core files - always updated (system functionality)
|
|
30
|
+
core: [
|
|
31
|
+
{ src: '.claude/hooks', dest: '.claude/hooks', name: '.claude/hooks/' },
|
|
32
|
+
{ src: '.claude/settings.json', dest: '.claude/settings.json', name: '.claude/settings.json' },
|
|
33
|
+
{ src: 'system', dest: 'system', name: 'system/' },
|
|
34
|
+
{ src: 'CLAUDE.md', dest: 'CLAUDE.md', name: 'CLAUDE.md' },
|
|
35
|
+
],
|
|
36
|
+
// User-customizable files - preserved on update, created on init
|
|
37
|
+
user: [
|
|
38
|
+
{ src: 'instructions/AI_RULES.md', dest: 'instructions/AI_RULES.md', name: 'instructions/AI_RULES.md' },
|
|
39
|
+
// BLUEPRINT.md is special - never overwrite if user has content
|
|
40
|
+
],
|
|
41
|
+
// Commands and skills - updated (but could have user additions)
|
|
42
|
+
commands: [
|
|
43
|
+
{ src: '.claude/commands', dest: '.claude/commands', name: '.claude/commands/' },
|
|
44
|
+
{ src: '.claude/skills', dest: '.claude/skills', name: '.claude/skills/' },
|
|
45
|
+
],
|
|
46
|
+
// Scripts and git hooks
|
|
47
|
+
scripts: [
|
|
48
|
+
{ src: 'scripts', dest: 'scripts', name: 'scripts/' },
|
|
49
|
+
{ src: 'hooks', dest: 'hooks', name: 'hooks/' },
|
|
50
|
+
],
|
|
51
|
+
// Optional config templates - skip if exists
|
|
52
|
+
optional: [
|
|
53
|
+
{ src: '.vscode', dest: '.vscode', name: '.vscode/' },
|
|
54
|
+
{ src: '.prettierrc', dest: '.prettierrc', name: '.prettierrc' },
|
|
55
|
+
{ src: 'eslint.config.example.js', dest: 'eslint.config.js', name: 'eslint.config.js' },
|
|
56
|
+
{ src: 'tsconfig.example.json', dest: 'tsconfig.json', name: 'tsconfig.json' },
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
|
|
23
60
|
function printHelp() {
|
|
24
61
|
console.log(`
|
|
25
|
-
${colors.bold('AutoWorkflow CLI')}
|
|
62
|
+
${colors.bold('AutoWorkflow CLI')} ${colors.dim(`v${PACKAGE_VERSION}`)}
|
|
26
63
|
|
|
27
64
|
Usage: npx autoworkflow <command> [options]
|
|
28
65
|
|
|
29
66
|
Commands:
|
|
30
|
-
init
|
|
31
|
-
init --all
|
|
32
|
-
init --minimal
|
|
33
|
-
|
|
67
|
+
${colors.cyan('init')} Set up AutoWorkflow in current directory
|
|
68
|
+
${colors.cyan('init --all')} Include optional files (.vscode, configs)
|
|
69
|
+
${colors.cyan('init --minimal')} Required files only
|
|
70
|
+
${colors.cyan('init --force')} Full reinstall (backup + overwrite all)
|
|
71
|
+
|
|
72
|
+
${colors.cyan('update')} Smart update - core files only, preserve user files
|
|
73
|
+
${colors.cyan('status')} Show installed version and what would change
|
|
74
|
+
|
|
75
|
+
${colors.cyan('help')} Show this help message
|
|
34
76
|
|
|
35
77
|
Options:
|
|
36
|
-
--no-backup
|
|
78
|
+
--no-backup Overwrite existing files without backup
|
|
79
|
+
--force Force overwrite all files (with backup)
|
|
37
80
|
|
|
38
81
|
Examples:
|
|
39
|
-
npx autoworkflow init
|
|
40
|
-
npx autoworkflow
|
|
82
|
+
npx autoworkflow init # Fresh install
|
|
83
|
+
npx autoworkflow update # Update core files only
|
|
84
|
+
npx autoworkflow status # Check what would change
|
|
85
|
+
npx autoworkflow init --force # Full reinstall
|
|
41
86
|
`);
|
|
42
87
|
}
|
|
43
88
|
|
|
89
|
+
// Version tracking
|
|
90
|
+
function getInstalledVersion(cwd) {
|
|
91
|
+
const versionFile = join(cwd, '.claude', '.autoworkflow', 'version');
|
|
92
|
+
if (existsSync(versionFile)) {
|
|
93
|
+
try {
|
|
94
|
+
return readFileSync(versionFile, 'utf8').trim();
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function setInstalledVersion(cwd, version) {
|
|
103
|
+
const stateDir = join(cwd, '.claude', '.autoworkflow');
|
|
104
|
+
mkdirSync(stateDir, { recursive: true });
|
|
105
|
+
writeFileSync(join(stateDir, 'version'), version);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check if BLUEPRINT.md has user content (not just template)
|
|
109
|
+
function hasUserBlueprint(cwd) {
|
|
110
|
+
const blueprintPath = join(cwd, 'instructions', 'BLUEPRINT.md');
|
|
111
|
+
if (!existsSync(blueprintPath)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const content = readFileSync(blueprintPath, 'utf8');
|
|
117
|
+
// Check if it's just a template or has real content
|
|
118
|
+
const templateMarkers = [
|
|
119
|
+
'<!-- TEMPLATE',
|
|
120
|
+
'<!-- AUTO-GENERATED TEMPLATE',
|
|
121
|
+
'## Features\n\n(To be documented)',
|
|
122
|
+
'## Features\n\n_None documented yet_',
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// If it has any template markers, it's not user content
|
|
126
|
+
for (const marker of templateMarkers) {
|
|
127
|
+
if (content.includes(marker)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If it has substantial content (more than 200 chars after stripping headers), it's user content
|
|
133
|
+
const strippedContent = content.replace(/^#.*$/gm, '').replace(/\s+/g, ' ').trim();
|
|
134
|
+
return strippedContent.length > 200;
|
|
135
|
+
} catch (e) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
44
140
|
function backupFile(dest, name) {
|
|
45
141
|
if (existsSync(dest)) {
|
|
46
142
|
const backupPath = `${dest}.backup`;
|
|
47
|
-
// If backup already exists, add timestamp
|
|
48
143
|
const finalBackupPath = existsSync(backupPath)
|
|
49
144
|
? `${dest}.backup.${Date.now()}`
|
|
50
145
|
: backupPath;
|
|
51
146
|
try {
|
|
52
147
|
renameSync(dest, finalBackupPath);
|
|
53
|
-
console.log(` ${colors.yellow('↪')} ${name} ${colors.dim(`→ backed up
|
|
148
|
+
console.log(` ${colors.yellow('↪')} ${name} ${colors.dim(`→ backed up`)}`);
|
|
54
149
|
return true;
|
|
55
150
|
} catch (err) {
|
|
56
151
|
console.log(` ${colors.red('✗')} Failed to backup ${name}: ${err.message}`);
|
|
57
152
|
return false;
|
|
58
153
|
}
|
|
59
154
|
}
|
|
60
|
-
return true;
|
|
155
|
+
return true;
|
|
61
156
|
}
|
|
62
157
|
|
|
63
158
|
function copyFile(src, dest, name, options = {}) {
|
|
64
|
-
const noBackup =
|
|
159
|
+
const { noBackup = false, skipIfExists = false } = options;
|
|
65
160
|
|
|
66
161
|
if (existsSync(src)) {
|
|
162
|
+
// Skip if exists and skipIfExists is true
|
|
163
|
+
if (skipIfExists && existsSync(dest)) {
|
|
164
|
+
console.log(` ${colors.dim('○')} ${name} ${colors.dim('(exists, skipped)')}`);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
67
168
|
// Backup existing file/folder if it exists
|
|
68
169
|
if (!noBackup && existsSync(dest)) {
|
|
69
170
|
const backed = backupFile(dest, name);
|
|
@@ -84,49 +185,19 @@ function copyFile(src, dest, name, options = {}) {
|
|
|
84
185
|
}
|
|
85
186
|
}
|
|
86
187
|
|
|
87
|
-
function
|
|
88
|
-
const cwd = process.cwd();
|
|
89
|
-
const all = options.all || args.includes('--all');
|
|
90
|
-
const minimal = options.minimal || args.includes('--minimal');
|
|
91
|
-
const noBackup = args.includes('--no-backup');
|
|
92
|
-
|
|
93
|
-
console.log(`\n${colors.bold('AutoWorkflow Setup')}\n`);
|
|
94
|
-
console.log(`Installing to: ${colors.cyan(cwd)}`);
|
|
95
|
-
if (!noBackup) {
|
|
96
|
-
console.log(`${colors.dim('Existing files will be backed up with .backup suffix')}\n`);
|
|
97
|
-
} else {
|
|
98
|
-
console.log(`${colors.yellow('⚠')} ${colors.dim('--no-backup: Existing files will be overwritten')}\n`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Required files
|
|
102
|
-
console.log(colors.bold('Required files:'));
|
|
103
|
-
copyFile(join(packageRoot, 'CLAUDE.md'), join(cwd, 'CLAUDE.md'), 'CLAUDE.md');
|
|
104
|
-
copyFile(join(packageRoot, 'system'), join(cwd, 'system'), 'system/');
|
|
105
|
-
copyFile(join(packageRoot, '.claude'), join(cwd, '.claude'), '.claude/');
|
|
106
|
-
|
|
107
|
-
// Copy instructions folder but remove BLUEPRINT.md (hook will auto-generate it)
|
|
108
|
-
copyFile(join(packageRoot, 'instructions'), join(cwd, 'instructions'), 'instructions/');
|
|
109
|
-
const blueprintPath = join(cwd, 'instructions', 'BLUEPRINT.md');
|
|
110
|
-
if (existsSync(blueprintPath)) {
|
|
111
|
-
try {
|
|
112
|
-
unlinkSync(blueprintPath);
|
|
113
|
-
console.log(` ${colors.cyan('ℹ')} BLUEPRINT.md removed (will be auto-generated on first run)`);
|
|
114
|
-
} catch (e) {
|
|
115
|
-
// Ignore
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Make Claude hooks executable
|
|
188
|
+
function makeHooksExecutable(cwd) {
|
|
120
189
|
if (process.platform !== 'win32' && existsSync(join(cwd, '.claude', 'hooks'))) {
|
|
121
190
|
try {
|
|
122
191
|
const hookFiles = [
|
|
123
192
|
'session-check.sh',
|
|
124
193
|
'post-edit.sh',
|
|
194
|
+
'pre-edit.sh',
|
|
125
195
|
'pre-commit-check.sh',
|
|
126
196
|
'pre-tool-router.sh',
|
|
127
197
|
'phase-transition.sh',
|
|
128
198
|
'audit-runner.sh',
|
|
129
|
-
'blueprint-generator.sh'
|
|
199
|
+
'blueprint-generator.sh',
|
|
200
|
+
'post-commit.sh',
|
|
130
201
|
];
|
|
131
202
|
hookFiles.forEach(hook => {
|
|
132
203
|
const hookPath = join(cwd, '.claude', 'hooks', hook);
|
|
@@ -134,36 +205,15 @@ function init(options = {}) {
|
|
|
134
205
|
chmodSync(hookPath, 0o755);
|
|
135
206
|
}
|
|
136
207
|
});
|
|
137
|
-
console.log(` ${colors.green('✓')}
|
|
208
|
+
console.log(` ${colors.green('✓')} Hooks made executable`);
|
|
138
209
|
} catch (e) {
|
|
139
210
|
// Ignore chmod errors
|
|
140
211
|
}
|
|
141
212
|
}
|
|
213
|
+
}
|
|
142
214
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
console.log(`\n${colors.bold('Recommended files:')}`);
|
|
146
|
-
copyFile(join(packageRoot, 'scripts'), join(cwd, 'scripts'), 'scripts/');
|
|
147
|
-
copyFile(join(packageRoot, 'hooks'), join(cwd, 'hooks'), 'hooks/');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (all) {
|
|
151
|
-
// Optional files
|
|
152
|
-
console.log(`\n${colors.bold('Optional files:')}`);
|
|
153
|
-
copyFile(join(packageRoot, '.vscode'), join(cwd, '.vscode'), '.vscode/');
|
|
154
|
-
copyFile(join(packageRoot, '.prettierrc'), join(cwd, '.prettierrc'), '.prettierrc');
|
|
155
|
-
|
|
156
|
-
// Copy example configs with proper names
|
|
157
|
-
if (existsSync(join(packageRoot, 'eslint.config.example.js'))) {
|
|
158
|
-
copyFile(join(packageRoot, 'eslint.config.example.js'), join(cwd, 'eslint.config.js'), 'eslint.config.js');
|
|
159
|
-
}
|
|
160
|
-
if (existsSync(join(packageRoot, 'tsconfig.example.json'))) {
|
|
161
|
-
copyFile(join(packageRoot, 'tsconfig.example.json'), join(cwd, 'tsconfig.json'), 'tsconfig.json');
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Setup git hooks if hooks were copied
|
|
166
|
-
if (!minimal && existsSync(join(cwd, 'hooks')) && existsSync(join(cwd, '.git'))) {
|
|
215
|
+
function setupGitHooks(cwd) {
|
|
216
|
+
if (existsSync(join(cwd, 'hooks')) && existsSync(join(cwd, '.git'))) {
|
|
167
217
|
console.log(`\n${colors.bold('Git hooks:')}`);
|
|
168
218
|
const hooksDir = join(cwd, '.git', 'hooks');
|
|
169
219
|
mkdirSync(hooksDir, { recursive: true });
|
|
@@ -171,7 +221,6 @@ function init(options = {}) {
|
|
|
171
221
|
const hooksCopied = copyFile(join(cwd, 'hooks', 'pre-commit'), join(hooksDir, 'pre-commit'), 'pre-commit');
|
|
172
222
|
copyFile(join(cwd, 'hooks', 'commit-msg'), join(hooksDir, 'commit-msg'), 'commit-msg');
|
|
173
223
|
|
|
174
|
-
// Make hooks executable (Unix only)
|
|
175
224
|
if (hooksCopied && process.platform !== 'win32') {
|
|
176
225
|
try {
|
|
177
226
|
chmodSync(join(hooksDir, 'pre-commit'), 0o755);
|
|
@@ -181,6 +230,217 @@ function init(options = {}) {
|
|
|
181
230
|
}
|
|
182
231
|
}
|
|
183
232
|
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// STATUS command
|
|
236
|
+
function status() {
|
|
237
|
+
const cwd = process.cwd();
|
|
238
|
+
const installedVersion = getInstalledVersion(cwd);
|
|
239
|
+
|
|
240
|
+
console.log(`\n${colors.bold('AutoWorkflow Status')}\n`);
|
|
241
|
+
console.log(`Package version: ${colors.cyan(PACKAGE_VERSION)}`);
|
|
242
|
+
console.log(`Installed version: ${installedVersion ? colors.cyan(installedVersion) : colors.dim('not installed')}`);
|
|
243
|
+
|
|
244
|
+
if (installedVersion && installedVersion === PACKAGE_VERSION) {
|
|
245
|
+
console.log(`\n${colors.green('✓')} You're on the latest version.\n`);
|
|
246
|
+
} else if (installedVersion) {
|
|
247
|
+
console.log(`\n${colors.yellow('⚠')} Update available: ${installedVersion} → ${PACKAGE_VERSION}`);
|
|
248
|
+
console.log(` Run: ${colors.cyan('npx autoworkflow update')}\n`);
|
|
249
|
+
} else {
|
|
250
|
+
console.log(`\n${colors.yellow('⚠')} AutoWorkflow not initialized in this directory.`);
|
|
251
|
+
console.log(` Run: ${colors.cyan('npx autoworkflow init')}\n`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check what exists
|
|
256
|
+
console.log(`${colors.bold('Installed components:')}`);
|
|
257
|
+
const components = [
|
|
258
|
+
{ path: 'CLAUDE.md', name: 'CLAUDE.md' },
|
|
259
|
+
{ path: '.claude/hooks', name: 'Hooks' },
|
|
260
|
+
{ path: '.claude/settings.json', name: 'Settings' },
|
|
261
|
+
{ path: '.claude/commands', name: 'Commands' },
|
|
262
|
+
{ path: '.claude/skills', name: 'Skills' },
|
|
263
|
+
{ path: 'system', name: 'System docs' },
|
|
264
|
+
{ path: 'instructions/AI_RULES.md', name: 'AI Rules' },
|
|
265
|
+
{ path: 'instructions/BLUEPRINT.md', name: 'Blueprint' },
|
|
266
|
+
{ path: 'scripts', name: 'Scripts' },
|
|
267
|
+
{ path: 'hooks', name: 'Git hooks' },
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
components.forEach(({ path, name }) => {
|
|
271
|
+
const exists = existsSync(join(cwd, path));
|
|
272
|
+
const status = exists ? colors.green('✓') : colors.dim('○');
|
|
273
|
+
console.log(` ${status} ${name}`);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Check for user customizations
|
|
277
|
+
const hasBlueprint = hasUserBlueprint(cwd);
|
|
278
|
+
if (hasBlueprint) {
|
|
279
|
+
console.log(`\n${colors.cyan('ℹ')} User BLUEPRINT.md detected - will be preserved on update.`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log('');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// UPDATE command - smart update, preserve user files
|
|
286
|
+
function update() {
|
|
287
|
+
const cwd = process.cwd();
|
|
288
|
+
const installedVersion = getInstalledVersion(cwd);
|
|
289
|
+
const noBackup = args.includes('--no-backup');
|
|
290
|
+
|
|
291
|
+
console.log(`\n${colors.bold('AutoWorkflow Update')}\n`);
|
|
292
|
+
|
|
293
|
+
if (!installedVersion) {
|
|
294
|
+
console.log(`${colors.yellow('⚠')} AutoWorkflow not initialized. Run ${colors.cyan('npx autoworkflow init')} instead.\n`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log(`Updating: ${colors.dim(installedVersion)} → ${colors.cyan(PACKAGE_VERSION)}`);
|
|
299
|
+
console.log(`${colors.dim('User files (BLUEPRINT.md, AI_RULES.md) will be preserved')}\n`);
|
|
300
|
+
|
|
301
|
+
// Update core files
|
|
302
|
+
console.log(colors.bold('Updating core files:'));
|
|
303
|
+
FILE_CATEGORIES.core.forEach(({ src, dest, name }) => {
|
|
304
|
+
copyFile(join(packageRoot, src), join(cwd, dest), name, { noBackup });
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Update commands and skills
|
|
308
|
+
console.log(`\n${colors.bold('Updating commands/skills:')}`);
|
|
309
|
+
FILE_CATEGORIES.commands.forEach(({ src, dest, name }) => {
|
|
310
|
+
copyFile(join(packageRoot, src), join(cwd, dest), name, { noBackup });
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Make hooks executable
|
|
314
|
+
makeHooksExecutable(cwd);
|
|
315
|
+
|
|
316
|
+
// Update version
|
|
317
|
+
setInstalledVersion(cwd, PACKAGE_VERSION);
|
|
318
|
+
|
|
319
|
+
console.log(`\n${colors.green('✓')} ${colors.bold('Updated to v' + PACKAGE_VERSION)}`);
|
|
320
|
+
console.log(`\n${colors.dim('User files preserved. Run')} ${colors.cyan('npx autoworkflow init --force')} ${colors.dim('for full reinstall.')}\n`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// INIT command
|
|
324
|
+
function init(options = {}) {
|
|
325
|
+
const cwd = process.cwd();
|
|
326
|
+
const all = options.all || args.includes('--all');
|
|
327
|
+
const minimal = options.minimal || args.includes('--minimal');
|
|
328
|
+
const force = options.force || args.includes('--force');
|
|
329
|
+
const noBackup = args.includes('--no-backup');
|
|
330
|
+
|
|
331
|
+
const installedVersion = getInstalledVersion(cwd);
|
|
332
|
+
|
|
333
|
+
// IMPORTANT: Check for user BLUEPRINT BEFORE any copying happens
|
|
334
|
+
const userHasBlueprint = hasUserBlueprint(cwd);
|
|
335
|
+
const blueprintPath = join(cwd, 'instructions', 'BLUEPRINT.md');
|
|
336
|
+
let savedBlueprintContent = null;
|
|
337
|
+
|
|
338
|
+
// Save user's BLUEPRINT.md content before any operations
|
|
339
|
+
if (userHasBlueprint) {
|
|
340
|
+
try {
|
|
341
|
+
savedBlueprintContent = readFileSync(blueprintPath, 'utf8');
|
|
342
|
+
} catch (e) {
|
|
343
|
+
// Ignore read errors
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.log(`\n${colors.bold('AutoWorkflow Setup')} ${colors.dim(`v${PACKAGE_VERSION}`)}\n`);
|
|
348
|
+
console.log(`Installing to: ${colors.cyan(cwd)}`);
|
|
349
|
+
|
|
350
|
+
if (installedVersion && !force) {
|
|
351
|
+
console.log(`\n${colors.yellow('⚠')} AutoWorkflow v${installedVersion} already installed.`);
|
|
352
|
+
console.log(` Use ${colors.cyan('npx autoworkflow update')} to update core files`);
|
|
353
|
+
console.log(` Use ${colors.cyan('npx autoworkflow init --force')} to reinstall everything\n`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (!noBackup) {
|
|
358
|
+
console.log(`${colors.dim('Existing files will be backed up with .backup suffix')}\n`);
|
|
359
|
+
} else {
|
|
360
|
+
console.log(`${colors.yellow('⚠')} ${colors.dim('--no-backup: Existing files will be overwritten')}\n`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Required files
|
|
364
|
+
console.log(colors.bold('Core files:'));
|
|
365
|
+
copyFile(join(packageRoot, 'CLAUDE.md'), join(cwd, 'CLAUDE.md'), 'CLAUDE.md', { noBackup });
|
|
366
|
+
copyFile(join(packageRoot, 'system'), join(cwd, 'system'), 'system/', { noBackup });
|
|
367
|
+
copyFile(join(packageRoot, '.claude'), join(cwd, '.claude'), '.claude/', { noBackup });
|
|
368
|
+
|
|
369
|
+
// Handle instructions folder - preserve user BLUEPRINT.md
|
|
370
|
+
console.log(`\n${colors.bold('Instructions:')}`);
|
|
371
|
+
const instructionsDir = join(cwd, 'instructions');
|
|
372
|
+
mkdirSync(instructionsDir, { recursive: true });
|
|
373
|
+
|
|
374
|
+
// Copy AI_RULES.md (skip if exists and not forcing)
|
|
375
|
+
const aiRulesPath = join(cwd, 'instructions', 'AI_RULES.md');
|
|
376
|
+
if (!existsSync(aiRulesPath) || force) {
|
|
377
|
+
copyFile(
|
|
378
|
+
join(packageRoot, 'instructions', 'AI_RULES.md'),
|
|
379
|
+
aiRulesPath,
|
|
380
|
+
'instructions/AI_RULES.md',
|
|
381
|
+
{ noBackup }
|
|
382
|
+
);
|
|
383
|
+
} else {
|
|
384
|
+
console.log(` ${colors.dim('○')} instructions/AI_RULES.md ${colors.dim('(exists, preserved)')}`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// BLUEPRINT.md - Restore user content if we saved it
|
|
388
|
+
if (savedBlueprintContent) {
|
|
389
|
+
// Restore the user's BLUEPRINT.md content
|
|
390
|
+
try {
|
|
391
|
+
writeFileSync(blueprintPath, savedBlueprintContent);
|
|
392
|
+
console.log(` ${colors.cyan('★')} instructions/BLUEPRINT.md ${colors.dim('(user content preserved)')}`);
|
|
393
|
+
} catch (e) {
|
|
394
|
+
console.log(` ${colors.red('✗')} Failed to restore BLUEPRINT.md: ${e.message}`);
|
|
395
|
+
}
|
|
396
|
+
} else if (existsSync(blueprintPath)) {
|
|
397
|
+
// Check if current file is a template (package just copied it)
|
|
398
|
+
const currentContent = readFileSync(blueprintPath, 'utf8');
|
|
399
|
+
const isTemplate = currentContent.includes('<!-- TEMPLATE') ||
|
|
400
|
+
currentContent.includes('(To be documented)') ||
|
|
401
|
+
currentContent.includes('_None documented yet_') ||
|
|
402
|
+
currentContent.replace(/^#.*$/gm, '').replace(/\s+/g, ' ').trim().length < 200;
|
|
403
|
+
|
|
404
|
+
if (isTemplate) {
|
|
405
|
+
// Remove template so hook can regenerate project-specific one
|
|
406
|
+
try {
|
|
407
|
+
unlinkSync(blueprintPath);
|
|
408
|
+
console.log(` ${colors.cyan('ℹ')} BLUEPRINT.md template removed (will be auto-generated)`);
|
|
409
|
+
} catch (e) {
|
|
410
|
+
// Ignore
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
console.log(` ${colors.cyan('★')} instructions/BLUEPRINT.md ${colors.dim('(content preserved)')}`);
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
console.log(` ${colors.cyan('ℹ')} BLUEPRINT.md will be auto-generated on first run`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Make Claude hooks executable
|
|
420
|
+
makeHooksExecutable(cwd);
|
|
421
|
+
|
|
422
|
+
if (!minimal) {
|
|
423
|
+
// Recommended files
|
|
424
|
+
console.log(`\n${colors.bold('Scripts:')}`);
|
|
425
|
+
copyFile(join(packageRoot, 'scripts'), join(cwd, 'scripts'), 'scripts/', { noBackup });
|
|
426
|
+
copyFile(join(packageRoot, 'hooks'), join(cwd, 'hooks'), 'hooks/', { noBackup });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (all) {
|
|
430
|
+
// Optional files - skip if exists
|
|
431
|
+
console.log(`\n${colors.bold('Optional files:')}`);
|
|
432
|
+
FILE_CATEGORIES.optional.forEach(({ src, dest, name }) => {
|
|
433
|
+
copyFile(join(packageRoot, src), join(cwd, dest), name, { noBackup, skipIfExists: !force });
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Setup git hooks
|
|
438
|
+
if (!minimal) {
|
|
439
|
+
setupGitHooks(cwd);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Save installed version
|
|
443
|
+
setInstalledVersion(cwd, PACKAGE_VERSION);
|
|
184
444
|
|
|
185
445
|
console.log(`\n${colors.green('✓')} ${colors.bold('AutoWorkflow initialized!')}\n`);
|
|
186
446
|
|
|
@@ -198,12 +458,22 @@ switch (command) {
|
|
|
198
458
|
case 'init':
|
|
199
459
|
init();
|
|
200
460
|
break;
|
|
461
|
+
case 'update':
|
|
462
|
+
update();
|
|
463
|
+
break;
|
|
464
|
+
case 'status':
|
|
465
|
+
status();
|
|
466
|
+
break;
|
|
201
467
|
case 'help':
|
|
202
468
|
case '--help':
|
|
203
469
|
case '-h':
|
|
204
470
|
case undefined:
|
|
205
471
|
printHelp();
|
|
206
472
|
break;
|
|
473
|
+
case '--version':
|
|
474
|
+
case '-v':
|
|
475
|
+
console.log(PACKAGE_VERSION);
|
|
476
|
+
break;
|
|
207
477
|
default:
|
|
208
478
|
console.log(`${colors.red('Unknown command:')} ${command}`);
|
|
209
479
|
printHelp();
|