zhuge-workflow 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 (80) hide show
  1. package/dist/index.js +801 -0
  2. package/dist/index.js.map +1 -0
  3. package/package.json +61 -0
  4. package/templates/claude/CLAUDE-ccg.md +258 -0
  5. package/templates/claude/CLAUDE.md +106 -0
  6. package/templates/claude/commands/ccg/_context.md +152 -0
  7. package/templates/claude/commands/ccg/spec-impl.md +161 -0
  8. package/templates/claude/commands/ccg/spec-plan-trellis.md +239 -0
  9. package/templates/claude/commands/ccg/spec-plan.md +225 -0
  10. package/templates/claude/commands/ccg/spec-research.md +113 -0
  11. package/templates/claude/commands/ccg/spec-review.md +127 -0
  12. package/templates/claude/commands/developer/brainstorm.md +5 -0
  13. package/templates/claude/commands/developer/design-checklist.md +81 -0
  14. package/templates/claude/commands/developer/design-doc.md +188 -0
  15. package/templates/claude/commands/developer/requirement-doc.md +150 -0
  16. package/templates/claude/commands/developer/requirement-interrogate.md +71 -0
  17. package/templates/claude/commands/developer/status.md +55 -0
  18. package/templates/claude/rules/bash-style.md +46 -0
  19. package/templates/claude/rules/claude-code-defensive.md +99 -0
  20. package/templates/claude/rules/doc-sync.md +49 -0
  21. package/templates/claude/rules/ops-safety.md +32 -0
  22. package/templates/claude/skills/bash-style/SKILL.md +244 -0
  23. package/templates/claude/skills/brainstorming/SKILL.md +48 -0
  24. package/templates/claude/skills/dotnet-dev/SKILL.md +250 -0
  25. package/templates/claude/skills/dotnet-dev/references/dotnet-style.md +991 -0
  26. package/templates/claude/skills/mcp-builder/LICENSE.txt +202 -0
  27. package/templates/claude/skills/mcp-builder/SKILL.md +328 -0
  28. package/templates/claude/skills/mcp-builder/reference/evaluation.md +602 -0
  29. package/templates/claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
  30. package/templates/claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
  31. package/templates/claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
  32. package/templates/claude/skills/mcp-builder/scripts/connections.py +151 -0
  33. package/templates/claude/skills/mcp-builder/scripts/evaluation.py +373 -0
  34. package/templates/claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  35. package/templates/claude/skills/mcp-builder/scripts/requirements.txt +2 -0
  36. package/templates/claude/skills/ops-safety/SKILL.md +130 -0
  37. package/templates/claude/skills/python-dev/SKILL.md +281 -0
  38. package/templates/claude/skills/skill-creator/LICENSE.txt +202 -0
  39. package/templates/claude/skills/skill-creator/SKILL.md +209 -0
  40. package/templates/claude/skills/skill-creator/scripts/init_skill.py +303 -0
  41. package/templates/claude/skills/skill-creator/scripts/package_skill.py +110 -0
  42. package/templates/claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  43. package/templates/claude/skills/sqlserver-executor/SKILL.md +201 -0
  44. package/templates/claude/skills/sqlserver-executor/assets/config-example.json +26 -0
  45. package/templates/claude/skills/sqlserver-executor/config.json +12 -0
  46. package/templates/claude/skills/sqlserver-executor/scripts/sql_executor.py +404 -0
  47. package/templates/claude/skills/ui-ux-pro-max/SKILL.md +228 -0
  48. package/templates/claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  49. package/templates/claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  50. package/templates/claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  51. package/templates/claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  52. package/templates/claude/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  53. package/templates/claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  54. package/templates/claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  55. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  56. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  57. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  58. package/templates/claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  59. package/templates/claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  60. package/templates/claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  61. package/templates/claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  62. package/templates/claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  63. package/templates/claude/skills/ui-ux-pro-max/data/styles.csv +59 -0
  64. package/templates/claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  65. package/templates/claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  66. package/templates/claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  67. package/templates/claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
  68. package/templates/claude/skills/ui-ux-pro-max/scripts/core.py +238 -0
  69. package/templates/claude/skills/ui-ux-pro-max/scripts/search.py +61 -0
  70. package/templates/claude/skills/webapp-testing/LICENSE.txt +202 -0
  71. package/templates/claude/skills/webapp-testing/SKILL.md +96 -0
  72. package/templates/claude/skills/webapp-testing/examples/console_logging.py +35 -0
  73. package/templates/claude/skills/webapp-testing/examples/element_discovery.py +40 -0
  74. package/templates/claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
  75. package/templates/claude/skills/webapp-testing/scripts/with_server.py +106 -0
  76. package/templates/init/claude-agents/ccg-impl.md +199 -0
  77. package/templates/init/claude-agents/ccg-review.md +146 -0
  78. package/templates/init/claude-agents/dispatch.md +253 -0
  79. package/templates/init/claude-hooks/inject-subagent-context.py +964 -0
  80. package/templates/init/trellis-scripts/task.sh +1326 -0
@@ -0,0 +1,1326 @@
1
+ #!/bin/bash
2
+ # Task Management Script for Multi-Agent Pipeline
3
+ #
4
+ # Usage:
5
+ # ./.trellis/scripts/task.sh create "<title>" [--slug <name>] [--assignee <dev>] [--priority P0|P1|P2|P3]
6
+ # ./.trellis/scripts/task.sh init-context <dir> <type> # Initialize jsonl files
7
+ # ./.trellis/scripts/task.sh add-context <dir> <file> <path> [reason] # Add jsonl entry
8
+ # ./.trellis/scripts/task.sh validate <dir> # Validate jsonl files
9
+ # ./.trellis/scripts/task.sh list-context <dir> # List jsonl entries
10
+ # ./.trellis/scripts/task.sh start <dir> # Set as current task
11
+ # ./.trellis/scripts/task.sh finish # Clear current task
12
+ # ./.trellis/scripts/task.sh set-branch <dir> <branch> # Set git branch
13
+ # ./.trellis/scripts/task.sh set-scope <dir> <scope> # Set scope for PR title
14
+ # ./.trellis/scripts/task.sh create-pr [dir] [--dry-run] # Create PR from task
15
+ # ./.trellis/scripts/task.sh archive <task-name> # Archive completed task
16
+ # ./.trellis/scripts/task.sh list # List active tasks
17
+ # ./.trellis/scripts/task.sh list-archive [month] # List archived tasks
18
+ #
19
+ # Task Directory Structure:
20
+ # tasks/
21
+ # ├── 01-21-my-task/
22
+ # │ ├── task.json # Metadata
23
+ # │ ├── prd.md # Requirements
24
+ # │ ├── info.md # Technical design (optional)
25
+ # │ ├── implement.jsonl # Implement agent context
26
+ # │ ├── check.jsonl # Check agent context
27
+ # │ └── debug.jsonl # Debug agent context
28
+ # └── archive/
29
+ # └── 2026-01/
30
+ # └── 01-21-old-task/
31
+
32
+ set -e
33
+
34
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35
+ source "$SCRIPT_DIR/common/paths.sh"
36
+ source "$SCRIPT_DIR/common/developer.sh"
37
+ source "$SCRIPT_DIR/common/task-queue.sh"
38
+ source "$SCRIPT_DIR/common/task-utils.sh"
39
+
40
+ # Colors
41
+ RED='\033[0;31m'
42
+ GREEN='\033[0;32m'
43
+ YELLOW='\033[1;33m'
44
+ BLUE='\033[0;34m'
45
+ CYAN='\033[0;36m'
46
+ NC='\033[0m'
47
+
48
+ REPO_ROOT=$(get_repo_root)
49
+
50
+ # =============================================================================
51
+ # Helper Functions
52
+ # =============================================================================
53
+
54
+ # Convert title to slug (only works with ASCII)
55
+ _slugify() {
56
+ local result=$(echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
57
+ echo "$result"
58
+ }
59
+
60
+ # =============================================================================
61
+ # jsonl Default Content Generators
62
+ # =============================================================================
63
+
64
+ get_implement_base() {
65
+ cat << EOF
66
+ {"file": "$DIR_WORKFLOW/workflow.md", "reason": "Project workflow and conventions"}
67
+ {"file": "$DIR_WORKFLOW/$DIR_SPEC/shared/index.md", "reason": "Shared coding standards"}
68
+ EOF
69
+ }
70
+
71
+ get_implement_backend() {
72
+ cat << EOF
73
+ {"file": "$DIR_WORKFLOW/$DIR_SPEC/backend/index.md", "reason": "Backend development guide"}
74
+ {"file": "$DIR_WORKFLOW/$DIR_SPEC/backend/api-module.md", "reason": "API module conventions"}
75
+ {"file": "$DIR_WORKFLOW/$DIR_SPEC/backend/quality.md", "reason": "Code quality requirements"}
76
+ EOF
77
+ }
78
+
79
+ get_implement_frontend() {
80
+ cat << EOF
81
+ {"file": "$DIR_WORKFLOW/$DIR_SPEC/frontend/index.md", "reason": "Frontend development guide"}
82
+ {"file": "$DIR_WORKFLOW/$DIR_SPEC/frontend/components.md", "reason": "Component conventions"}
83
+ EOF
84
+ }
85
+
86
+ get_check_context() {
87
+ local dev_type="$1"
88
+
89
+ cat << EOF
90
+ {"file": ".claude/commands/trellis/finish-work.md", "reason": "Finish work checklist"}
91
+ {"file": "$DIR_WORKFLOW/$DIR_SPEC/shared/index.md", "reason": "Shared coding standards"}
92
+ EOF
93
+
94
+ if [[ "$dev_type" == "backend" ]] || [[ "$dev_type" == "fullstack" ]]; then
95
+ echo '{"file": ".claude/commands/trellis/check-backend.md", "reason": "Backend check spec"}'
96
+ fi
97
+ if [[ "$dev_type" == "frontend" ]] || [[ "$dev_type" == "fullstack" ]]; then
98
+ echo '{"file": ".claude/commands/trellis/check-frontend.md", "reason": "Frontend check spec"}'
99
+ fi
100
+ }
101
+
102
+ get_debug_context() {
103
+ local dev_type="$1"
104
+
105
+ echo "{\"file\": \"$DIR_WORKFLOW/$DIR_SPEC/shared/index.md\", \"reason\": \"Shared coding standards\"}"
106
+
107
+ if [[ "$dev_type" == "backend" ]] || [[ "$dev_type" == "fullstack" ]]; then
108
+ echo '{"file": ".claude/commands/trellis/check-backend.md", "reason": "Backend check spec"}'
109
+ fi
110
+ if [[ "$dev_type" == "frontend" ]] || [[ "$dev_type" == "fullstack" ]]; then
111
+ echo '{"file": ".claude/commands/trellis/check-frontend.md", "reason": "Frontend check spec"}'
112
+ fi
113
+ }
114
+
115
+ # =============================================================================
116
+ # Task Operations
117
+ # =============================================================================
118
+
119
+ ensure_tasks_dir() {
120
+ local tasks_dir=$(get_tasks_dir)
121
+ local archive_dir="$tasks_dir/archive"
122
+
123
+ if [[ ! -d "$tasks_dir" ]]; then
124
+ mkdir -p "$tasks_dir"
125
+ echo -e "${GREEN}Created tasks directory: $tasks_dir${NC}" >&2
126
+ fi
127
+
128
+ if [[ ! -d "$archive_dir" ]]; then
129
+ mkdir -p "$archive_dir"
130
+ fi
131
+ }
132
+
133
+ # =============================================================================
134
+ # Command: create
135
+ # =============================================================================
136
+
137
+ cmd_create() {
138
+ local title=""
139
+ local assignee=""
140
+ local priority="P2"
141
+ local slug=""
142
+ local description=""
143
+
144
+ # Parse arguments
145
+ while [[ $# -gt 0 ]]; do
146
+ case "$1" in
147
+ --assignee|-a)
148
+ assignee="$2"
149
+ shift 2
150
+ ;;
151
+ --priority|-p)
152
+ priority="$2"
153
+ shift 2
154
+ ;;
155
+ --slug|-s)
156
+ slug="$2"
157
+ shift 2
158
+ ;;
159
+ --description|-d)
160
+ description="$2"
161
+ shift 2
162
+ ;;
163
+ -*)
164
+ echo -e "${RED}Error: Unknown option $1${NC}" >&2
165
+ exit 1
166
+ ;;
167
+ *)
168
+ if [[ -z "$title" ]]; then
169
+ title="$1"
170
+ fi
171
+ shift
172
+ ;;
173
+ esac
174
+ done
175
+
176
+ # Validate required fields
177
+ if [[ -z "$title" ]]; then
178
+ echo -e "${RED}Error: title is required${NC}" >&2
179
+ echo "Usage: $0 create <title> [--assignee <dev>] [--priority P0|P1|P2|P3] [--slug <slug>]" >&2
180
+ exit 1
181
+ fi
182
+
183
+ # Default assignee to current developer
184
+ if [[ -z "$assignee" ]]; then
185
+ assignee=$(get_developer "$REPO_ROOT")
186
+ if [[ -z "$assignee" ]]; then
187
+ echo -e "${RED}Error: No developer set. Run init-developer.sh first or use --assignee${NC}" >&2
188
+ exit 1
189
+ fi
190
+ fi
191
+
192
+ ensure_tasks_dir
193
+
194
+ # Get current developer as creator
195
+ local creator=$(get_developer "$REPO_ROOT")
196
+ if [[ -z "$creator" ]]; then
197
+ creator="$assignee"
198
+ fi
199
+
200
+ # Generate slug if not provided
201
+ if [[ -z "$slug" ]]; then
202
+ slug=$(_slugify "$title")
203
+ fi
204
+
205
+ # Validate slug
206
+ if [[ -z "$slug" ]]; then
207
+ echo -e "${RED}Error: could not generate slug from title${NC}" >&2
208
+ exit 1
209
+ fi
210
+
211
+ # Create task directory with MM-DD-slug format
212
+ local tasks_dir=$(get_tasks_dir)
213
+ local date_prefix=$(generate_task_date_prefix)
214
+ local dir_name="${date_prefix}-${slug}"
215
+ local task_dir="$tasks_dir/$dir_name"
216
+ local task_json="$task_dir/$FILE_TASK_JSON"
217
+
218
+ if [[ -d "$task_dir" ]]; then
219
+ echo -e "${YELLOW}Warning: Task directory already exists: $dir_name${NC}" >&2
220
+ else
221
+ mkdir -p "$task_dir"
222
+ fi
223
+
224
+ local today=$(date +%Y-%m-%d)
225
+
226
+ cat > "$task_json" << EOF
227
+ {
228
+ "id": "$slug",
229
+ "name": "$slug",
230
+ "title": "$title",
231
+ "description": "$description",
232
+ "status": "planning",
233
+ "dev_type": null,
234
+ "scope": null,
235
+ "priority": "$priority",
236
+ "creator": "$creator",
237
+ "assignee": "$assignee",
238
+ "createdAt": "$today",
239
+ "completedAt": null,
240
+ "branch": null,
241
+ "base_branch": null,
242
+ "worktree_path": null,
243
+ "current_phase": 0,
244
+ "next_action": [
245
+ {"phase": 1, "action": "implement"},
246
+ {"phase": 2, "action": "check"},
247
+ {"phase": 3, "action": "finish"},
248
+ {"phase": 4, "action": "create-pr"}
249
+ ],
250
+ "commit": null,
251
+ "pr_url": null,
252
+ "subtasks": [],
253
+ "relatedFiles": [],
254
+ "notes": ""
255
+ }
256
+ EOF
257
+
258
+ echo -e "${GREEN}Created task: $dir_name${NC}" >&2
259
+ echo -e "" >&2
260
+ echo -e "${BLUE}Next steps:${NC}" >&2
261
+ echo -e " 1. Create prd.md with requirements" >&2
262
+ echo -e " 2. Run: $0 init-context <dir> <dev_type>" >&2
263
+ echo -e " 3. Run: $0 start <dir>" >&2
264
+ echo "" >&2
265
+
266
+ # Output relative path for script chaining
267
+ echo "$DIR_WORKFLOW/$DIR_TASKS/$dir_name"
268
+ }
269
+
270
+ # =============================================================================
271
+ # Command: init-context
272
+ # =============================================================================
273
+
274
+ cmd_init_context() {
275
+ local target_dir="$1"
276
+ local dev_type="$2"
277
+
278
+ if [[ -z "$target_dir" ]] || [[ -z "$dev_type" ]]; then
279
+ echo -e "${RED}Error: Missing arguments${NC}"
280
+ echo "Usage: $0 init-context <task-dir> <dev_type>"
281
+ echo " dev_type: backend | frontend | fullstack | test | docs"
282
+ exit 1
283
+ fi
284
+
285
+ # Support relative paths
286
+ if [[ ! "$target_dir" = /* ]]; then
287
+ target_dir="$REPO_ROOT/$target_dir"
288
+ fi
289
+
290
+ if [[ ! -d "$target_dir" ]]; then
291
+ echo -e "${RED}Error: Directory not found: $target_dir${NC}"
292
+ exit 1
293
+ fi
294
+
295
+ echo -e "${BLUE}=== Initializing Agent Context Files ===${NC}"
296
+ echo -e "Target dir: $target_dir"
297
+ echo -e "Dev type: $dev_type"
298
+ echo ""
299
+
300
+ # implement.jsonl
301
+ echo -e "${CYAN}Creating implement.jsonl...${NC}"
302
+ local implement_file="$target_dir/implement.jsonl"
303
+ {
304
+ get_implement_base
305
+ case "$dev_type" in
306
+ backend|test) get_implement_backend ;;
307
+ frontend) get_implement_frontend ;;
308
+ fullstack)
309
+ get_implement_backend
310
+ get_implement_frontend
311
+ ;;
312
+ esac
313
+ } > "$implement_file"
314
+ echo -e " ${GREEN}✓${NC} $(wc -l < "$implement_file" | tr -d ' ') entries"
315
+
316
+ # check.jsonl
317
+ echo -e "${CYAN}Creating check.jsonl...${NC}"
318
+ local check_file="$target_dir/check.jsonl"
319
+ get_check_context "$dev_type" > "$check_file"
320
+ echo -e " ${GREEN}✓${NC} $(wc -l < "$check_file" | tr -d ' ') entries"
321
+
322
+ # debug.jsonl
323
+ echo -e "${CYAN}Creating debug.jsonl...${NC}"
324
+ local debug_file="$target_dir/debug.jsonl"
325
+ get_debug_context "$dev_type" > "$debug_file"
326
+ echo -e " ${GREEN}✓${NC} $(wc -l < "$debug_file" | tr -d ' ') entries"
327
+
328
+ echo ""
329
+ echo -e "${GREEN}✓ All context files created${NC}"
330
+ echo -e ""
331
+ echo -e "${BLUE}Next steps:${NC}"
332
+ echo -e " 1. Add task-specific specs: $0 add-context <dir> <jsonl> <path>"
333
+ echo -e " 2. Set as current: $0 start <dir>"
334
+ }
335
+
336
+ # =============================================================================
337
+ # Command: add-context
338
+ # =============================================================================
339
+
340
+ cmd_add_context() {
341
+ local target_dir="$1"
342
+ local jsonl_name="$2"
343
+ local path="$3"
344
+ local reason="${4:-Added manually}"
345
+
346
+ if [[ -z "$target_dir" ]] || [[ -z "$jsonl_name" ]] || [[ -z "$path" ]]; then
347
+ echo -e "${RED}Error: Missing arguments${NC}"
348
+ echo "Usage: $0 add-context <task-dir> <jsonl-file> <path> [reason]"
349
+ echo " jsonl-file: implement | check | debug (or full filename)"
350
+ exit 1
351
+ fi
352
+
353
+ # Support relative paths
354
+ if [[ ! "$target_dir" = /* ]]; then
355
+ target_dir="$REPO_ROOT/$target_dir"
356
+ fi
357
+
358
+ # Support shorthand
359
+ if [[ "$jsonl_name" != *.jsonl ]]; then
360
+ jsonl_name="${jsonl_name}.jsonl"
361
+ fi
362
+
363
+ local jsonl_file="$target_dir/$jsonl_name"
364
+ local full_path="$REPO_ROOT/$path"
365
+ local entry_type="file"
366
+
367
+ if [[ -d "$full_path" ]]; then
368
+ entry_type="directory"
369
+ [[ "$path" != */ ]] && path="$path/"
370
+ elif [[ ! -f "$full_path" ]]; then
371
+ echo -e "${RED}Error: Path not found: $path${NC}"
372
+ exit 1
373
+ fi
374
+
375
+ # Check if already exists
376
+ if [[ -f "$jsonl_file" ]] && grep -q "\"$path\"" "$jsonl_file" 2>/dev/null; then
377
+ echo -e "${YELLOW}Warning: Entry already exists for $path${NC}"
378
+ exit 0
379
+ fi
380
+
381
+ # Add entry
382
+ if [[ "$entry_type" == "directory" ]]; then
383
+ echo "{\"file\": \"$path\", \"type\": \"directory\", \"reason\": \"$reason\"}" >> "$jsonl_file"
384
+ else
385
+ echo "{\"file\": \"$path\", \"reason\": \"$reason\"}" >> "$jsonl_file"
386
+ fi
387
+
388
+ echo -e "${GREEN}Added $entry_type: $path${NC}"
389
+ }
390
+
391
+ # =============================================================================
392
+ # Command: validate
393
+ # =============================================================================
394
+
395
+ validate_jsonl() {
396
+ local jsonl_file="$1"
397
+ local file_name=$(basename "$jsonl_file")
398
+ local errors=0
399
+ local line_num=0
400
+
401
+ if [[ ! -f "$jsonl_file" ]]; then
402
+ echo -e " ${YELLOW}$file_name: not found (skipped)${NC}"
403
+ return 0
404
+ fi
405
+
406
+ while IFS= read -r line || [[ -n "$line" ]]; do
407
+ line_num=$((line_num + 1))
408
+ [[ -z "$line" ]] && continue
409
+
410
+ if ! echo "$line" | jq -e . > /dev/null 2>&1; then
411
+ echo -e " ${RED}$file_name:$line_num: Invalid JSON${NC}"
412
+ errors=$((errors + 1))
413
+ continue
414
+ fi
415
+
416
+ local file_path=$(echo "$line" | jq -r '.file // empty')
417
+ local entry_type=$(echo "$line" | jq -r '.type // "file"')
418
+
419
+ if [[ -z "$file_path" ]]; then
420
+ echo -e " ${RED}$file_name:$line_num: Missing 'file' field${NC}"
421
+ errors=$((errors + 1))
422
+ continue
423
+ fi
424
+
425
+ local full_path="$REPO_ROOT/$file_path"
426
+ if [[ "$entry_type" == "directory" ]]; then
427
+ if [[ ! -d "$full_path" ]]; then
428
+ echo -e " ${RED}$file_name:$line_num: Directory not found: $file_path${NC}"
429
+ errors=$((errors + 1))
430
+ fi
431
+ else
432
+ if [[ ! -f "$full_path" ]]; then
433
+ echo -e " ${RED}$file_name:$line_num: File not found: $file_path${NC}"
434
+ errors=$((errors + 1))
435
+ fi
436
+ fi
437
+ done < "$jsonl_file"
438
+
439
+ if [[ $errors -eq 0 ]]; then
440
+ echo -e " ${GREEN}$file_name: ✓ ($line_num entries)${NC}"
441
+ else
442
+ echo -e " ${RED}$file_name: ✗ ($errors errors)${NC}"
443
+ fi
444
+
445
+ return $errors
446
+ }
447
+
448
+ cmd_validate() {
449
+ local target_dir="$1"
450
+
451
+ if [[ -z "$target_dir" ]]; then
452
+ echo -e "${RED}Error: task directory required${NC}"
453
+ exit 1
454
+ fi
455
+
456
+ if [[ ! "$target_dir" = /* ]]; then
457
+ target_dir="$REPO_ROOT/$target_dir"
458
+ fi
459
+
460
+ echo -e "${BLUE}=== Validating Context Files ===${NC}"
461
+ echo -e "Target dir: $target_dir"
462
+ echo ""
463
+
464
+ local total_errors=0
465
+ for jsonl_file in "$target_dir"/{implement,check,debug}.jsonl; do
466
+ validate_jsonl "$jsonl_file"
467
+ total_errors=$((total_errors + $?))
468
+ done
469
+
470
+ echo ""
471
+ if [[ $total_errors -eq 0 ]]; then
472
+ echo -e "${GREEN}✓ All validations passed${NC}"
473
+ else
474
+ echo -e "${RED}✗ Validation failed ($total_errors errors)${NC}"
475
+ exit 1
476
+ fi
477
+ }
478
+
479
+ # =============================================================================
480
+ # Command: list-context
481
+ # =============================================================================
482
+
483
+ cmd_list_context() {
484
+ local target_dir="$1"
485
+
486
+ if [[ -z "$target_dir" ]]; then
487
+ echo -e "${RED}Error: task directory required${NC}"
488
+ exit 1
489
+ fi
490
+
491
+ if [[ ! "$target_dir" = /* ]]; then
492
+ target_dir="$REPO_ROOT/$target_dir"
493
+ fi
494
+
495
+ echo -e "${BLUE}=== Context Files ===${NC}"
496
+ echo ""
497
+
498
+ for jsonl_file in "$target_dir"/{implement,check,debug}.jsonl; do
499
+ local file_name=$(basename "$jsonl_file")
500
+ [[ ! -f "$jsonl_file" ]] && continue
501
+
502
+ echo -e "${CYAN}[$file_name]${NC}"
503
+
504
+ local count=0
505
+ while IFS= read -r line || [[ -n "$line" ]]; do
506
+ [[ -z "$line" ]] && continue
507
+
508
+ local file_path=$(echo "$line" | jq -r '.file // "?"')
509
+ local entry_type=$(echo "$line" | jq -r '.type // "file"')
510
+ local reason=$(echo "$line" | jq -r '.reason // "-"')
511
+ count=$((count + 1))
512
+
513
+ if [[ "$entry_type" == "directory" ]]; then
514
+ echo -e " ${GREEN}$count.${NC} [DIR] $file_path"
515
+ else
516
+ echo -e " ${GREEN}$count.${NC} $file_path"
517
+ fi
518
+ echo -e " ${YELLOW}→${NC} $reason"
519
+ done < "$jsonl_file"
520
+
521
+ echo ""
522
+ done
523
+ }
524
+
525
+ # =============================================================================
526
+ # Command: start / finish
527
+ # =============================================================================
528
+
529
+ cmd_start() {
530
+ local task_dir="$1"
531
+
532
+ if [[ -z "$task_dir" ]]; then
533
+ echo -e "${RED}Error: task directory required${NC}"
534
+ exit 1
535
+ fi
536
+
537
+ # Convert to relative path
538
+ if [[ "$task_dir" = /* ]]; then
539
+ task_dir="${task_dir#$REPO_ROOT/}"
540
+ fi
541
+
542
+ # Verify directory exists
543
+ if [[ ! -d "$REPO_ROOT/$task_dir" ]]; then
544
+ echo -e "${RED}Error: Task directory not found: $task_dir${NC}"
545
+ exit 1
546
+ fi
547
+
548
+ set_current_task "$task_dir"
549
+ echo -e "${GREEN}✓ Current task set to: $task_dir${NC}"
550
+ echo ""
551
+ echo -e "${BLUE}The hook will now inject context from this task's jsonl files.${NC}"
552
+ }
553
+
554
+ cmd_finish() {
555
+ local current=$(get_current_task)
556
+
557
+ if [[ -z "$current" ]]; then
558
+ echo -e "${YELLOW}No current task set${NC}"
559
+ exit 0
560
+ fi
561
+
562
+ clear_current_task
563
+ echo -e "${GREEN}✓ Cleared current task (was: $current)${NC}"
564
+ }
565
+
566
+ # =============================================================================
567
+ # Command: archive
568
+ # =============================================================================
569
+
570
+ cmd_archive() {
571
+ local task_name="$1"
572
+
573
+ if [[ -z "$task_name" ]]; then
574
+ echo -e "${RED}Error: Task name is required${NC}" >&2
575
+ echo "Usage: $0 archive <task-name>" >&2
576
+ exit 1
577
+ fi
578
+
579
+ local tasks_dir=$(get_tasks_dir)
580
+
581
+ # Find task directory using common function
582
+ local task_dir=$(find_task_by_name "$task_name" "$tasks_dir")
583
+
584
+ if [[ -z "$task_dir" ]] || [[ ! -d "$task_dir" ]]; then
585
+ echo -e "${RED}Error: Task not found: $task_name${NC}" >&2
586
+ echo "Active tasks:" >&2
587
+ cmd_list >&2
588
+ exit 1
589
+ fi
590
+
591
+ local dir_name=$(basename "$task_dir")
592
+ local task_json="$task_dir/$FILE_TASK_JSON"
593
+
594
+ # Update status before archiving
595
+ local today=$(date +%Y-%m-%d)
596
+ if [[ -f "$task_json" ]] && command -v jq &> /dev/null; then
597
+ local temp_file=$(mktemp)
598
+ jq --arg date "$today" '.status = "completed" | .completedAt = $date' "$task_json" > "$temp_file"
599
+ mv "$temp_file" "$task_json"
600
+ fi
601
+
602
+ # Clear if current task
603
+ local current=$(get_current_task)
604
+ if [[ "$current" == *"$dir_name"* ]]; then
605
+ clear_current_task
606
+ fi
607
+
608
+ # Use common archive function
609
+ local result=$(archive_task_complete "$task_dir" "$REPO_ROOT")
610
+ local archive_dest=""
611
+
612
+ echo "$result" | while IFS= read -r line; do
613
+ case "$line" in
614
+ archived_to:*)
615
+ archive_dest="${line#archived_to:}"
616
+ local year_month=$(basename "$(dirname "$archive_dest")")
617
+ echo -e "${GREEN}Archived: $dir_name -> archive/$year_month/${NC}" >&2
618
+ ;;
619
+ esac
620
+ done
621
+
622
+ # Return the archive path
623
+ local year_month=$(date +%Y-%m)
624
+ echo "$DIR_WORKFLOW/$DIR_TASKS/$DIR_ARCHIVE/$year_month/$dir_name"
625
+ }
626
+
627
+ # =============================================================================
628
+ # Command: list
629
+ # =============================================================================
630
+
631
+ cmd_list() {
632
+ local filter_mine=false
633
+ local filter_status=""
634
+
635
+ # Parse arguments
636
+ while [[ $# -gt 0 ]]; do
637
+ case "$1" in
638
+ --mine|-m)
639
+ filter_mine=true
640
+ shift
641
+ ;;
642
+ --status|-s)
643
+ filter_status="$2"
644
+ shift 2
645
+ ;;
646
+ *)
647
+ shift
648
+ ;;
649
+ esac
650
+ done
651
+
652
+ local tasks_dir=$(get_tasks_dir)
653
+ local current_task=$(get_current_task)
654
+ local developer=$(get_developer "$REPO_ROOT")
655
+
656
+ if [[ "$filter_mine" == "true" ]]; then
657
+ if [[ -z "$developer" ]]; then
658
+ echo -e "${RED}Error: No developer set. Run init-developer.sh first${NC}" >&2
659
+ exit 1
660
+ fi
661
+ echo -e "${BLUE}My tasks (assignee: $developer):${NC}"
662
+ else
663
+ echo -e "${BLUE}All active tasks:${NC}"
664
+ fi
665
+ echo ""
666
+
667
+ local count=0
668
+
669
+ for d in "$tasks_dir"/*/; do
670
+ if [[ -d "$d" ]] && [[ "$(basename "$d")" != "archive" ]]; then
671
+ local dir_name=$(basename "$d")
672
+ local task_json="$d/$FILE_TASK_JSON"
673
+ local status="unknown"
674
+ local assignee="-"
675
+ local relative_path="$DIR_WORKFLOW/$DIR_TASKS/$dir_name"
676
+
677
+ if [[ -f "$task_json" ]] && command -v jq &> /dev/null; then
678
+ status=$(jq -r '.status // "unknown"' "$task_json")
679
+ assignee=$(jq -r '.assignee // "-"' "$task_json")
680
+ fi
681
+
682
+ # Apply --mine filter
683
+ if [[ "$filter_mine" == "true" ]] && [[ "$assignee" != "$developer" ]]; then
684
+ continue
685
+ fi
686
+
687
+ # Apply --status filter
688
+ if [[ -n "$filter_status" ]] && [[ "$status" != "$filter_status" ]]; then
689
+ continue
690
+ fi
691
+
692
+ local marker=""
693
+ if [[ "$relative_path" == "$current_task" ]]; then
694
+ marker=" ${GREEN}<- current${NC}"
695
+ fi
696
+
697
+ if [[ "$filter_mine" == "true" ]]; then
698
+ echo -e " - $dir_name/ ($status)$marker"
699
+ else
700
+ echo -e " - $dir_name/ ($status) [${CYAN}$assignee${NC}]$marker"
701
+ fi
702
+ ((count++))
703
+ fi
704
+ done
705
+
706
+ if [[ $count -eq 0 ]]; then
707
+ if [[ "$filter_mine" == "true" ]]; then
708
+ echo " (no tasks assigned to you)"
709
+ else
710
+ echo " (no active tasks)"
711
+ fi
712
+ fi
713
+
714
+ echo ""
715
+ echo "Total: $count task(s)"
716
+ }
717
+
718
+ # =============================================================================
719
+ # Command: list-archive
720
+ # =============================================================================
721
+
722
+ cmd_list_archive() {
723
+ local month="$1"
724
+
725
+ local tasks_dir=$(get_tasks_dir)
726
+ local archive_dir="$tasks_dir/archive"
727
+
728
+ echo -e "${BLUE}Archived tasks:${NC}"
729
+ echo ""
730
+
731
+ if [[ -n "$month" ]]; then
732
+ local month_dir="$archive_dir/$month"
733
+ if [[ -d "$month_dir" ]]; then
734
+ echo "[$month]"
735
+ for d in "$month_dir"/*/; do
736
+ if [[ -d "$d" ]]; then
737
+ echo " - $(basename "$d")/"
738
+ fi
739
+ done
740
+ else
741
+ echo " No archives for $month"
742
+ fi
743
+ else
744
+ for month_dir in "$archive_dir"/*/; do
745
+ if [[ -d "$month_dir" ]]; then
746
+ local month_name=$(basename "$month_dir")
747
+ local count=$(find "$month_dir" -maxdepth 1 -type d ! -name "$(basename "$month_dir")" | wc -l | tr -d ' ')
748
+ echo "[$month_name] - $count task(s)"
749
+ fi
750
+ done
751
+ fi
752
+ }
753
+
754
+ # =============================================================================
755
+ # Command: set-branch
756
+ # =============================================================================
757
+
758
+ cmd_set_branch() {
759
+ local target_dir="$1"
760
+ local branch="$2"
761
+
762
+ if [[ -z "$target_dir" ]] || [[ -z "$branch" ]]; then
763
+ echo -e "${RED}Error: Missing arguments${NC}"
764
+ echo "Usage: $0 set-branch <task-dir> <branch-name>"
765
+ echo "Example: $0 set-branch <dir> task/my-task"
766
+ exit 1
767
+ fi
768
+
769
+ # Support relative paths
770
+ if [[ ! "$target_dir" = /* ]]; then
771
+ target_dir="$REPO_ROOT/$target_dir"
772
+ fi
773
+
774
+ local task_json="$target_dir/$FILE_TASK_JSON"
775
+ if [[ ! -f "$task_json" ]]; then
776
+ echo -e "${RED}Error: task.json not found at $target_dir${NC}"
777
+ exit 1
778
+ fi
779
+
780
+ # Update branch field
781
+ jq --arg branch "$branch" '.branch = $branch' "$task_json" > "${task_json}.tmp"
782
+ mv "${task_json}.tmp" "$task_json"
783
+
784
+ echo -e "${GREEN}✓ Branch set to: $branch${NC}"
785
+ echo ""
786
+ echo -e "${BLUE}Now you can start the multi-agent pipeline:${NC}"
787
+ echo " ./.trellis/scripts/multi-agent/start.sh $1"
788
+ }
789
+
790
+ # =============================================================================
791
+ # Command: set-scope
792
+ # =============================================================================
793
+
794
+ cmd_set_scope() {
795
+ local target_dir="$1"
796
+ local scope="$2"
797
+
798
+ if [[ -z "$target_dir" ]] || [[ -z "$scope" ]]; then
799
+ echo -e "${RED}Error: Missing arguments${NC}"
800
+ echo "Usage: $0 set-scope <task-dir> <scope>"
801
+ echo "Example: $0 set-scope <dir> api"
802
+ exit 1
803
+ fi
804
+
805
+ # Support relative paths
806
+ if [[ ! "$target_dir" = /* ]]; then
807
+ target_dir="$REPO_ROOT/$target_dir"
808
+ fi
809
+
810
+ local task_json="$target_dir/$FILE_TASK_JSON"
811
+ if [[ ! -f "$task_json" ]]; then
812
+ echo -e "${RED}Error: task.json not found at $target_dir${NC}"
813
+ exit 1
814
+ fi
815
+
816
+ # Update scope field
817
+ jq --arg scope "$scope" '.scope = $scope' "$task_json" > "${task_json}.tmp"
818
+ mv "${task_json}.tmp" "$task_json"
819
+
820
+ echo -e "${GREEN}✓ Scope set to: $scope${NC}"
821
+ }
822
+
823
+ # =============================================================================
824
+ # Command: create-pr
825
+ # =============================================================================
826
+
827
+ cmd_create_pr() {
828
+ local target_dir=""
829
+ local dry_run=false
830
+
831
+ # Parse arguments
832
+ while [[ $# -gt 0 ]]; do
833
+ case "$1" in
834
+ --dry-run)
835
+ dry_run=true
836
+ shift
837
+ ;;
838
+ *)
839
+ if [[ -z "$target_dir" ]]; then
840
+ target_dir="$1"
841
+ fi
842
+ shift
843
+ ;;
844
+ esac
845
+ done
846
+
847
+ # Get task directory
848
+ if [[ -z "$target_dir" ]]; then
849
+ target_dir=$(get_current_task)
850
+ if [[ -z "$target_dir" ]]; then
851
+ echo -e "${RED}Error: No task directory specified and no current task set${NC}"
852
+ echo "Usage: $0 create-pr [task-dir] [--dry-run]"
853
+ exit 1
854
+ fi
855
+ fi
856
+
857
+ # Support relative paths
858
+ if [[ ! "$target_dir" = /* ]]; then
859
+ target_dir="$REPO_ROOT/$target_dir"
860
+ fi
861
+
862
+ local task_json="$target_dir/$FILE_TASK_JSON"
863
+ if [[ ! -f "$task_json" ]]; then
864
+ echo -e "${RED}Error: task.json not found at $target_dir${NC}"
865
+ exit 1
866
+ fi
867
+
868
+ echo -e "${BLUE}=== Create PR ===${NC}"
869
+ if [[ "$dry_run" == "true" ]]; then
870
+ echo -e "${YELLOW}[DRY-RUN MODE] No actual changes will be made${NC}"
871
+ fi
872
+ echo ""
873
+
874
+ # Read task config
875
+ local task_name=$(jq -r '.name' "$task_json")
876
+ local base_branch=$(jq -r '.base_branch // "main"' "$task_json")
877
+ local scope=$(jq -r '.scope // "core"' "$task_json")
878
+ local dev_type=$(jq -r '.dev_type // "feature"' "$task_json")
879
+
880
+ # Map dev_type to commit prefix
881
+ local commit_prefix
882
+ case "$dev_type" in
883
+ feature|frontend|backend|fullstack) commit_prefix="feat" ;;
884
+ bugfix|fix) commit_prefix="fix" ;;
885
+ refactor) commit_prefix="refactor" ;;
886
+ docs) commit_prefix="docs" ;;
887
+ test) commit_prefix="test" ;;
888
+ *) commit_prefix="feat" ;;
889
+ esac
890
+
891
+ echo -e "Task: ${task_name}"
892
+ echo -e "Base branch: ${base_branch}"
893
+ echo -e "Scope: ${scope}"
894
+ echo -e "Commit prefix: ${commit_prefix}"
895
+ echo ""
896
+
897
+ # Get current branch
898
+ local current_branch=$(git branch --show-current)
899
+ echo -e "Current branch: ${current_branch}"
900
+
901
+ # Check for changes
902
+ echo -e "${YELLOW}Checking for changes...${NC}"
903
+
904
+ # Stage changes (even in dry-run to detect what would be committed)
905
+ git add -A
906
+ # Exclude workspace and temp files
907
+ git reset "$DIR_WORKFLOW/$DIR_WORKSPACE/" 2>/dev/null || true
908
+ git reset .agent-log .agent-runner.sh 2>/dev/null || true
909
+
910
+ # Check if there are staged changes
911
+ if git diff --cached --quiet 2>/dev/null; then
912
+ echo -e "${YELLOW}No staged changes to commit${NC}"
913
+
914
+ # Check for unpushed commits
915
+ local unpushed=$(git log "origin/${current_branch}..HEAD" --oneline 2>/dev/null | wc -l | tr -d ' ' || echo "0")
916
+ if [[ "$unpushed" -eq 0 ]] 2>/dev/null; then
917
+ # In dry-run, also reset the staging
918
+ if [[ "$dry_run" == "true" ]]; then
919
+ git reset HEAD >/dev/null 2>&1 || true
920
+ fi
921
+ echo -e "${RED}No changes to create PR${NC}"
922
+ exit 1
923
+ fi
924
+ echo -e "Found ${unpushed} unpushed commit(s)"
925
+ else
926
+ # Commit changes
927
+ echo -e "${YELLOW}Committing changes...${NC}"
928
+ local commit_msg="${commit_prefix}(${scope}): ${task_name}"
929
+
930
+ if [[ "$dry_run" == "true" ]]; then
931
+ echo -e "[DRY-RUN] Would commit with message: ${commit_msg}"
932
+ echo -e "[DRY-RUN] Staged files:"
933
+ git diff --cached --name-only | sed 's/^/ - /'
934
+ else
935
+ git commit -m "$commit_msg"
936
+ echo -e "${GREEN}Committed: ${commit_msg}${NC}"
937
+ fi
938
+ fi
939
+
940
+ # Push to remote
941
+ echo -e "${YELLOW}Pushing to remote...${NC}"
942
+ if [[ "$dry_run" == "true" ]]; then
943
+ echo -e "[DRY-RUN] Would push to: origin/${current_branch}"
944
+ else
945
+ git push -u origin "$current_branch"
946
+ echo -e "${GREEN}Pushed to origin/${current_branch}${NC}"
947
+ fi
948
+
949
+ # Create PR
950
+ echo -e "${YELLOW}Creating PR...${NC}"
951
+ local pr_title="${commit_prefix}(${scope}): ${task_name}"
952
+ local pr_url=""
953
+
954
+ if [[ "$dry_run" == "true" ]]; then
955
+ echo -e "[DRY-RUN] Would create PR:"
956
+ echo -e " Title: ${pr_title}"
957
+ echo -e " Base: ${base_branch}"
958
+ echo -e " Head: ${current_branch}"
959
+ if [[ -f "$target_dir/prd.md" ]]; then
960
+ echo -e " Body: (from prd.md)"
961
+ fi
962
+ pr_url="https://github.com/example/repo/pull/DRY-RUN"
963
+ else
964
+ # Check if PR already exists
965
+ local existing_pr=$(gh pr list --head "$current_branch" --base "$base_branch" --json url --jq '.[0].url' 2>/dev/null || echo "")
966
+
967
+ if [[ -n "$existing_pr" ]]; then
968
+ echo -e "${YELLOW}PR already exists: ${existing_pr}${NC}"
969
+ pr_url="$existing_pr"
970
+ else
971
+ # Read PRD as PR body
972
+ local pr_body=""
973
+ if [[ -f "$target_dir/prd.md" ]]; then
974
+ pr_body=$(cat "$target_dir/prd.md")
975
+ fi
976
+
977
+ # Create PR
978
+ pr_url=$(gh pr create \
979
+ --draft \
980
+ --base "$base_branch" \
981
+ --title "$pr_title" \
982
+ --body "$pr_body" \
983
+ 2>&1)
984
+
985
+ echo -e "${GREEN}PR created: ${pr_url}${NC}"
986
+ fi
987
+ fi
988
+
989
+ # Update task.json
990
+ echo -e "${YELLOW}Updating task status...${NC}"
991
+ if [[ "$dry_run" == "true" ]]; then
992
+ echo -e "[DRY-RUN] Would update task.json:"
993
+ echo -e " status: review"
994
+ echo -e " pr_url: ${pr_url}"
995
+ echo -e " current_phase: (set to create-pr phase)"
996
+ else
997
+ # Find the phase number for create-pr action
998
+ local create_pr_phase=$(jq -r '.next_action[] | select(.action == "create-pr") | .phase // 4' "$task_json")
999
+ jq --arg url "$pr_url" --argjson phase "$create_pr_phase" \
1000
+ '.status = "review" | .pr_url = $url | .current_phase = $phase' "$task_json" > "${task_json}.tmp"
1001
+ mv "${task_json}.tmp" "$task_json"
1002
+ echo -e "${GREEN}Task status updated to 'review', phase ${create_pr_phase}${NC}"
1003
+ fi
1004
+
1005
+ # In dry-run, reset the staging area
1006
+ if [[ "$dry_run" == "true" ]]; then
1007
+ git reset HEAD >/dev/null 2>&1 || true
1008
+ fi
1009
+
1010
+ echo ""
1011
+ echo -e "${GREEN}=== PR Created Successfully ===${NC}"
1012
+ echo -e "PR URL: ${pr_url}"
1013
+ echo -e "Target: ${base_branch}"
1014
+ echo -e "Source: ${current_branch}"
1015
+ }
1016
+
1017
+ # =============================================================================
1018
+ # Command: create-from-phase
1019
+ # =============================================================================
1020
+
1021
+ cmd_create_from_phase() {
1022
+ local change_dir=""
1023
+ local phase_num=""
1024
+ local dev_type="fullstack"
1025
+ local priority="P2"
1026
+
1027
+ # Parse arguments
1028
+ while [[ $# -gt 0 ]]; do
1029
+ case "$1" in
1030
+ --change|-c)
1031
+ change_dir="$2"
1032
+ shift 2
1033
+ ;;
1034
+ --phase|-p)
1035
+ phase_num="$2"
1036
+ shift 2
1037
+ ;;
1038
+ --dev-type|-t)
1039
+ dev_type="$2"
1040
+ shift 2
1041
+ ;;
1042
+ --priority)
1043
+ priority="$2"
1044
+ shift 2
1045
+ ;;
1046
+ *)
1047
+ echo -e "${RED}Error: Unknown option $1${NC}" >&2
1048
+ exit 1
1049
+ ;;
1050
+ esac
1051
+ done
1052
+
1053
+ # Validate required fields
1054
+ if [[ -z "$change_dir" ]] || [[ -z "$phase_num" ]]; then
1055
+ echo -e "${RED}Error: --change and --phase are required${NC}" >&2
1056
+ echo "Usage: $0 create-from-phase --change <openspec-change-dir> --phase <N> [--dev-type <type>]" >&2
1057
+ echo "Example: $0 create-from-phase --change openspec/changes/my-feature --phase 1 --dev-type backend" >&2
1058
+ exit 1
1059
+ fi
1060
+
1061
+ # Validate change directory exists
1062
+ if [[ ! -d "$REPO_ROOT/$change_dir" ]]; then
1063
+ echo -e "${RED}Error: Change directory not found: $change_dir${NC}" >&2
1064
+ exit 1
1065
+ fi
1066
+
1067
+ # Validate tasks.md exists
1068
+ local tasks_md="$REPO_ROOT/$change_dir/tasks.md"
1069
+ if [[ ! -f "$tasks_md" ]]; then
1070
+ echo -e "${RED}Error: tasks.md not found in $change_dir${NC}" >&2
1071
+ exit 1
1072
+ fi
1073
+
1074
+ # Extract phase title from tasks.md
1075
+ # Format: ## Phase N: Title
1076
+ local phase_title=$(grep -E "^## Phase ${phase_num}:" "$tasks_md" | sed "s/^## Phase ${phase_num}: //")
1077
+ if [[ -z "$phase_title" ]]; then
1078
+ echo -e "${RED}Error: Phase $phase_num not found in tasks.md${NC}" >&2
1079
+ exit 1
1080
+ fi
1081
+
1082
+ # Generate slug from change name and phase
1083
+ local change_name=$(basename "$change_dir")
1084
+ local slug="${change_name}-phase-${phase_num}"
1085
+
1086
+ # Get current developer
1087
+ local developer=$(get_developer "$REPO_ROOT")
1088
+ if [[ -z "$developer" ]]; then
1089
+ echo -e "${RED}Error: No developer set. Run init-developer.sh first${NC}" >&2
1090
+ exit 1
1091
+ fi
1092
+
1093
+ ensure_tasks_dir
1094
+
1095
+ # Create task directory
1096
+ local tasks_dir=$(get_tasks_dir)
1097
+ local date_prefix=$(generate_task_date_prefix)
1098
+ local dir_name="${date_prefix}-${slug}"
1099
+ local task_dir="$tasks_dir/$dir_name"
1100
+
1101
+ if [[ -d "$task_dir" ]]; then
1102
+ echo -e "${YELLOW}Warning: Task directory already exists: $dir_name${NC}" >&2
1103
+ else
1104
+ mkdir -p "$task_dir"
1105
+ fi
1106
+
1107
+ local today=$(date +%Y-%m-%d)
1108
+
1109
+ # Create task.json with ccg-impl as first action
1110
+ cat > "$task_dir/$FILE_TASK_JSON" << EOF
1111
+ {
1112
+ "id": "$slug",
1113
+ "name": "$slug",
1114
+ "title": "Phase $phase_num: $phase_title",
1115
+ "description": "Execute Phase $phase_num of OpenSpec change: $change_name",
1116
+ "status": "planning",
1117
+ "dev_type": "$dev_type",
1118
+ "scope": "$change_name",
1119
+ "priority": "$priority",
1120
+ "creator": "$developer",
1121
+ "assignee": "$developer",
1122
+ "createdAt": "$today",
1123
+ "completedAt": null,
1124
+ "branch": "feature/${change_name}",
1125
+ "base_branch": "main",
1126
+ "worktree_path": null,
1127
+ "current_phase": 0,
1128
+ "next_action": [
1129
+ {"phase": 1, "action": "ccg-impl"},
1130
+ {"phase": 2, "action": "ccg-review"},
1131
+ {"phase": 3, "action": "finish"},
1132
+ {"phase": 4, "action": "create-pr"}
1133
+ ],
1134
+ "openspec_change": "$change_dir",
1135
+ "phase_number": $phase_num,
1136
+ "commit": null,
1137
+ "pr_url": null,
1138
+ "subtasks": [],
1139
+ "relatedFiles": [],
1140
+ "notes": "Auto-generated from OpenSpec change"
1141
+ }
1142
+ EOF
1143
+
1144
+ # Create prd.md
1145
+ cat > "$task_dir/prd.md" << EOF
1146
+ # Task: 执行 OpenSpec Change Phase
1147
+
1148
+ ## Change
1149
+ $change_dir
1150
+
1151
+ ## Phase
1152
+ $phase_num
1153
+
1154
+ ## Phase 标题
1155
+ $phase_title
1156
+
1157
+ ## 说明
1158
+ 执行上述 change 的 Phase $phase_num 任务。
1159
+
1160
+ 按照 ccg-impl 工作流:
1161
+ 1. 读取 $change_dir/tasks.md 中 Phase $phase_num 的任务列表
1162
+ 2. 读取 $change_dir/specs.md 和 design.md 理解规范和设计
1163
+ 3. 多模型协作实现(后端用 Codex,前端用 Gemini)
1164
+ 4. 外部模型只返回 diff patch,Claude 重写为生产代码
1165
+ 5. 完成后更新 tasks.md 标记 [x]
1166
+ EOF
1167
+
1168
+ # Initialize context files based on dev_type
1169
+ echo -e "${CYAN}Initializing context files...${NC}" >&2
1170
+
1171
+ # implement.jsonl - add openspec artifacts
1172
+ local implement_file="$task_dir/implement.jsonl"
1173
+ {
1174
+ get_implement_base
1175
+ case "$dev_type" in
1176
+ backend|test) get_implement_backend ;;
1177
+ frontend) get_implement_frontend ;;
1178
+ fullstack)
1179
+ get_implement_backend
1180
+ get_implement_frontend
1181
+ ;;
1182
+ esac
1183
+ # Add openspec artifacts
1184
+ echo "{\"file\": \"$change_dir/specs.md\", \"reason\": \"OpenSpec requirements and constraints\"}"
1185
+ echo "{\"file\": \"$change_dir/design.md\", \"reason\": \"OpenSpec technical design\"}"
1186
+ echo "{\"file\": \"$change_dir/tasks.md\", \"reason\": \"OpenSpec task list\"}"
1187
+ } > "$implement_file"
1188
+
1189
+ # check.jsonl
1190
+ local check_file="$task_dir/check.jsonl"
1191
+ {
1192
+ get_check_context "$dev_type"
1193
+ echo "{\"file\": \"$change_dir/specs.md\", \"reason\": \"Verify against OpenSpec requirements\"}"
1194
+ } > "$check_file"
1195
+
1196
+ # debug.jsonl
1197
+ local debug_file="$task_dir/debug.jsonl"
1198
+ {
1199
+ get_debug_context "$dev_type"
1200
+ echo "{\"file\": \"$change_dir/specs.md\", \"reason\": \"Debug against OpenSpec requirements\"}"
1201
+ } > "$debug_file"
1202
+
1203
+ echo -e "${GREEN}Created task from OpenSpec phase:${NC}" >&2
1204
+ echo -e " Task: $dir_name" >&2
1205
+ echo -e " Change: $change_dir" >&2
1206
+ echo -e " Phase: $phase_num - $phase_title" >&2
1207
+ echo -e " Dev Type: $dev_type" >&2
1208
+ echo "" >&2
1209
+ echo -e "${BLUE}Next steps:${NC}" >&2
1210
+ echo -e " 1. Review: cat $task_dir/prd.md" >&2
1211
+ echo -e " 2. Start: $0 start $DIR_WORKFLOW/$DIR_TASKS/$dir_name" >&2
1212
+ echo -e " 3. Or use dispatch to run ccg-impl agent" >&2
1213
+ echo "" >&2
1214
+
1215
+ # Output relative path for script chaining
1216
+ echo "$DIR_WORKFLOW/$DIR_TASKS/$dir_name"
1217
+ }
1218
+
1219
+ # =============================================================================
1220
+ # Help
1221
+ # =============================================================================
1222
+
1223
+ show_usage() {
1224
+ cat << EOF
1225
+ Task Management Script for Multi-Agent Pipeline
1226
+
1227
+ Usage:
1228
+ $0 create <title> Create new task directory
1229
+ $0 init-context <dir> <dev_type> Initialize jsonl files
1230
+ $0 add-context <dir> <jsonl> <path> [reason] Add entry to jsonl
1231
+ $0 validate <dir> Validate jsonl files
1232
+ $0 list-context <dir> List jsonl entries
1233
+ $0 start <dir> Set as current task
1234
+ $0 finish Clear current task
1235
+ $0 set-branch <dir> <branch> Set git branch for multi-agent
1236
+ $0 set-scope <dir> <scope> Set scope for PR title
1237
+ $0 create-pr [dir] [--dry-run] Create PR from task
1238
+ $0 archive <task-name> Archive completed task
1239
+ $0 list [--mine] [--status <status>] List tasks
1240
+ $0 list-archive [YYYY-MM] List archived tasks
1241
+ $0 create-from-phase --change <dir> --phase <N> [--dev-type <type>]
1242
+ Create task from OpenSpec change phase
1243
+
1244
+ Arguments:
1245
+ dev_type: backend | frontend | fullstack | test | docs
1246
+
1247
+ List options:
1248
+ --mine, -m Show only tasks assigned to current developer
1249
+ --status, -s <s> Filter by status (planning, in_progress, review, completed)
1250
+
1251
+ Examples:
1252
+ $0 create "Add login feature" --slug add-login
1253
+ $0 init-context .trellis/tasks/01-21-add-login backend
1254
+ $0 add-context <dir> implement .trellis/spec/backend/auth.md "Auth guidelines"
1255
+ $0 set-branch <dir> task/add-login
1256
+ $0 start .trellis/tasks/01-21-add-login
1257
+ $0 create-pr # Uses current task
1258
+ $0 create-pr <dir> --dry-run # Preview without changes
1259
+ $0 finish
1260
+ $0 archive add-login
1261
+ $0 list # List all active tasks
1262
+ $0 list --mine # List my tasks only
1263
+ $0 list --mine --status in_progress # List my in-progress tasks
1264
+ EOF
1265
+ }
1266
+
1267
+ # =============================================================================
1268
+ # Main Entry
1269
+ # =============================================================================
1270
+
1271
+ case "${1:-}" in
1272
+ create)
1273
+ shift
1274
+ cmd_create "$@"
1275
+ ;;
1276
+ init-context)
1277
+ cmd_init_context "$2" "$3"
1278
+ ;;
1279
+ add-context)
1280
+ cmd_add_context "$2" "$3" "$4" "$5"
1281
+ ;;
1282
+ validate)
1283
+ cmd_validate "$2"
1284
+ ;;
1285
+ list-context)
1286
+ cmd_list_context "$2"
1287
+ ;;
1288
+ start)
1289
+ cmd_start "$2"
1290
+ ;;
1291
+ finish)
1292
+ cmd_finish
1293
+ ;;
1294
+ set-branch)
1295
+ cmd_set_branch "$2" "$3"
1296
+ ;;
1297
+ set-scope)
1298
+ cmd_set_scope "$2" "$3"
1299
+ ;;
1300
+ create-pr)
1301
+ # Delegate to multi-agent/create-pr.sh
1302
+ shift
1303
+ "$SCRIPT_DIR/multi-agent/create-pr.sh" "$@"
1304
+ ;;
1305
+ archive)
1306
+ cmd_archive "$2"
1307
+ ;;
1308
+ list)
1309
+ shift
1310
+ cmd_list "$@"
1311
+ ;;
1312
+ list-archive)
1313
+ cmd_list_archive "$2"
1314
+ ;;
1315
+ create-from-phase)
1316
+ shift
1317
+ cmd_create_from_phase "$@"
1318
+ ;;
1319
+ -h|--help|help)
1320
+ show_usage
1321
+ ;;
1322
+ *)
1323
+ show_usage
1324
+ exit 1
1325
+ ;;
1326
+ esac