@mindfoldhq/trellis 0.1.1 → 0.1.3

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 (132) hide show
  1. package/dist/{templates/agents/bodies → .claude/agents}/check.md +7 -0
  2. package/dist/{templates/agents/bodies → .claude/agents}/debug.md +7 -0
  3. package/dist/{templates/agents/bodies → .claude/agents}/dispatch.md +11 -8
  4. package/dist/{templates/agents/bodies → .claude/agents}/implement.md +7 -0
  5. package/dist/.claude/agents/plan.md +396 -0
  6. package/dist/{templates/agents/bodies → .claude/agents}/research.md +7 -0
  7. package/dist/{templates/commands/common/check-cross-layer.txt → .claude/commands/check-cross-layer.md} +29 -29
  8. package/dist/{templates → .claude}/hooks/inject-subagent-context.py +63 -0
  9. package/dist/.cursor/commands/before-backend-dev.md +13 -0
  10. package/dist/.cursor/commands/before-frontend-dev.md +13 -0
  11. package/dist/.cursor/commands/break-loop.md +107 -0
  12. package/dist/.cursor/commands/check-backend.md +13 -0
  13. package/dist/.cursor/commands/check-cross-layer.md +153 -0
  14. package/dist/.cursor/commands/check-frontend.md +13 -0
  15. package/dist/.cursor/commands/create-command.md +154 -0
  16. package/dist/.cursor/commands/finish-work.md +129 -0
  17. package/dist/.cursor/commands/integrate-skill.md +219 -0
  18. package/dist/.cursor/commands/onboard-developer.md +355 -0
  19. package/dist/.cursor/commands/record-agent-flow.md +62 -0
  20. package/dist/.trellis/scripts/common/phase.sh +150 -0
  21. package/dist/{templates/scripts/feature.sh.txt → .trellis/scripts/feature.sh} +8 -3
  22. package/dist/{templates/scripts/multi-agent/cleanup.sh.txt → .trellis/scripts/multi-agent/cleanup.sh} +107 -18
  23. package/dist/.trellis/scripts/multi-agent/create-pr.sh +241 -0
  24. package/dist/.trellis/scripts/multi-agent/plan.sh +232 -0
  25. package/dist/{templates/scripts/multi-agent/start.sh.txt → .trellis/scripts/multi-agent/start.sh} +21 -0
  26. package/dist/{templates/scripts/multi-agent/status.sh.txt → .trellis/scripts/multi-agent/status.sh} +282 -10
  27. package/dist/.trellis/structure/backend/database-guidelines.md +51 -0
  28. package/dist/.trellis/structure/backend/directory-structure.md +209 -0
  29. package/dist/.trellis/structure/backend/error-handling.md +278 -0
  30. package/dist/.trellis/structure/backend/index.md +38 -0
  31. package/dist/.trellis/structure/backend/logging-guidelines.md +266 -0
  32. package/dist/.trellis/structure/backend/quality-guidelines.md +313 -0
  33. package/dist/.trellis/structure/frontend/component-guidelines.md +59 -0
  34. package/dist/.trellis/structure/frontend/directory-structure.md +54 -0
  35. package/dist/.trellis/structure/frontend/hook-guidelines.md +51 -0
  36. package/dist/.trellis/structure/frontend/index.md +39 -0
  37. package/dist/.trellis/structure/frontend/quality-guidelines.md +51 -0
  38. package/dist/.trellis/structure/frontend/state-management.md +51 -0
  39. package/dist/.trellis/structure/frontend/type-safety.md +51 -0
  40. package/dist/.trellis/structure/guides/code-reuse-thinking-guide.md +92 -0
  41. package/dist/.trellis/structure/guides/cross-layer-thinking-guide.md +94 -0
  42. package/dist/.trellis/structure/guides/index.md +79 -0
  43. package/dist/{templates/scripts/worktree.yaml.txt → .trellis/worktree.yaml} +1 -1
  44. package/dist/commands/init.d.ts.map +1 -1
  45. package/dist/commands/init.js +5 -16
  46. package/dist/commands/init.js.map +1 -1
  47. package/dist/configurators/claude.d.ts +17 -17
  48. package/dist/configurators/claude.d.ts.map +1 -1
  49. package/dist/configurators/claude.js +29 -59
  50. package/dist/configurators/claude.js.map +1 -1
  51. package/dist/configurators/cursor.d.ts +3 -3
  52. package/dist/configurators/cursor.d.ts.map +1 -1
  53. package/dist/configurators/cursor.js +11 -15
  54. package/dist/configurators/cursor.js.map +1 -1
  55. package/dist/configurators/opencode.d.ts +11 -10
  56. package/dist/configurators/opencode.d.ts.map +1 -1
  57. package/dist/configurators/opencode.js +14 -33
  58. package/dist/configurators/opencode.js.map +1 -1
  59. package/dist/configurators/workflow.d.ts +7 -0
  60. package/dist/configurators/workflow.d.ts.map +1 -1
  61. package/dist/configurators/workflow.js +30 -99
  62. package/dist/configurators/workflow.js.map +1 -1
  63. package/dist/templates/extract.d.ts +73 -8
  64. package/dist/templates/extract.d.ts.map +1 -1
  65. package/dist/templates/extract.js +149 -10
  66. package/dist/templates/extract.js.map +1 -1
  67. package/dist/templates/markdown/gitignore.txt +6 -1
  68. package/dist/templates/markdown/index.d.ts +5 -1
  69. package/dist/templates/markdown/index.d.ts.map +1 -1
  70. package/dist/templates/markdown/index.js +54 -26
  71. package/dist/templates/markdown/index.js.map +1 -1
  72. package/dist/templates/markdown/structure/backend/directory-structure.md.txt +6 -6
  73. package/dist/templates/markdown/structure/backend/error-handling.md.txt +8 -8
  74. package/dist/templates/markdown/structure/backend/index.md.txt +5 -5
  75. package/dist/templates/markdown/structure/backend/logging-guidelines.md.txt +8 -8
  76. package/dist/templates/markdown/structure/frontend/index.md.txt +6 -6
  77. package/dist/templates/markdown/structure/guides/cross-layer-thinking-guide.md.txt +5 -5
  78. package/dist/templates/markdown/structure/guides/index.md.txt +7 -7
  79. package/dist/templates/markdown/worktree.yaml.txt +58 -0
  80. package/package.json +1 -1
  81. package/dist/configurators/templates.d.ts +0 -40
  82. package/dist/configurators/templates.d.ts.map +0 -1
  83. package/dist/configurators/templates.js +0 -67
  84. package/dist/configurators/templates.js.map +0 -1
  85. package/dist/templates/agents/index.d.ts +0 -42
  86. package/dist/templates/agents/index.d.ts.map +0 -1
  87. package/dist/templates/agents/index.js +0 -148
  88. package/dist/templates/agents/index.js.map +0 -1
  89. package/dist/templates/agents/metadata.d.ts +0 -48
  90. package/dist/templates/agents/metadata.d.ts.map +0 -1
  91. package/dist/templates/agents/metadata.js +0 -101
  92. package/dist/templates/agents/metadata.js.map +0 -1
  93. package/dist/templates/commands/index.d.ts +0 -48
  94. package/dist/templates/commands/index.d.ts.map +0 -1
  95. package/dist/templates/commands/index.js +0 -167
  96. package/dist/templates/commands/index.js.map +0 -1
  97. package/dist/templates/commands/opencode/start.md.txt +0 -127
  98. package/dist/templates/hooks/index.d.ts +0 -33
  99. package/dist/templates/hooks/index.d.ts.map +0 -1
  100. package/dist/templates/hooks/index.js +0 -53
  101. package/dist/templates/hooks/index.js.map +0 -1
  102. package/dist/templates/scripts/index.d.ts +0 -36
  103. package/dist/templates/scripts/index.d.ts.map +0 -1
  104. package/dist/templates/scripts/index.js +0 -41
  105. package/dist/templates/scripts/index.js.map +0 -1
  106. /package/dist/{templates/commands/common/before-backend-dev.txt → .claude/commands/before-backend-dev.md} +0 -0
  107. /package/dist/{templates/commands/common/before-frontend-dev.txt → .claude/commands/before-frontend-dev.md} +0 -0
  108. /package/dist/{templates/commands/common/break-loop.txt → .claude/commands/break-loop.md} +0 -0
  109. /package/dist/{templates/commands/common/check-backend.txt → .claude/commands/check-backend.md} +0 -0
  110. /package/dist/{templates/commands/common/check-frontend.txt → .claude/commands/check-frontend.md} +0 -0
  111. /package/dist/{templates/commands/common/create-command.txt → .claude/commands/create-command.md} +0 -0
  112. /package/dist/{templates/commands/common/finish-work.txt → .claude/commands/finish-work.md} +0 -0
  113. /package/dist/{templates/commands/common/integrate-skill.txt → .claude/commands/integrate-skill.md} +0 -0
  114. /package/dist/{templates/commands/common/onboard-developer.txt → .claude/commands/onboard-developer.md} +0 -0
  115. /package/dist/{templates/commands/claude/parallel.md.txt → .claude/commands/parallel.md} +0 -0
  116. /package/dist/{templates/commands/common/record-agent-flow.txt → .claude/commands/record-agent-flow.md} +0 -0
  117. /package/dist/{templates/commands/claude/start.md.txt → .claude/commands/start.md} +0 -0
  118. /package/dist/{templates/hooks → .claude}/settings.json +0 -0
  119. /package/dist/{templates/commands/cursor/start.md.txt → .cursor/commands/start.md} +0 -0
  120. /package/dist/{templates/markdown/agent-traces-index.md.txt → .trellis/agent-traces/index.md} +0 -0
  121. /package/dist/{templates/scripts/add-session.sh.txt → .trellis/scripts/add-session.sh} +0 -0
  122. /package/dist/{templates/scripts/common/developer.sh.txt → .trellis/scripts/common/developer.sh} +0 -0
  123. /package/dist/{templates/scripts/common/git-context.sh.txt → .trellis/scripts/common/git-context.sh} +0 -0
  124. /package/dist/{templates/scripts/common/paths.sh.txt → .trellis/scripts/common/paths.sh} +0 -0
  125. /package/dist/{templates/scripts/common/worktree.sh.txt → .trellis/scripts/common/worktree.sh} +0 -0
  126. /package/dist/{templates/scripts/create-bootstrap.sh.txt → .trellis/scripts/create-bootstrap.sh} +0 -0
  127. /package/dist/{templates/scripts/get-context.sh.txt → .trellis/scripts/get-context.sh} +0 -0
  128. /package/dist/{templates/scripts/get-developer.sh.txt → .trellis/scripts/get-developer.sh} +0 -0
  129. /package/dist/{templates/scripts/init-developer.sh.txt → .trellis/scripts/init-developer.sh} +0 -0
  130. /package/dist/{templates/markdown/workflow.md.txt → .trellis/workflow.md} +0 -0
  131. /package/dist/templates/markdown/{agents.md.txt → agents.md} +0 -0
  132. /package/dist/templates/markdown/{init-agent.md.txt → init-agent.md} +0 -0
@@ -0,0 +1,150 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # Phase Management Utilities
4
+ # =============================================================================
5
+ # Centralized phase tracking for multi-agent pipeline
6
+ #
7
+ # Usage:
8
+ # source common/phase.sh
9
+ #
10
+ # get_current_phase "$feature_json" # Returns current phase number
11
+ # get_total_phases "$feature_json" # Returns total phase count
12
+ # get_phase_action "$feature_json" "$phase" # Returns action name for phase
13
+ # get_phase_info "$feature_json" # Returns "N/M (action)" format
14
+ # set_phase "$feature_json" "$phase" # Sets current_phase
15
+ # advance_phase "$feature_json" # Advances to next phase
16
+ # get_phase_for_action "$feature_json" "$action" # Returns phase number for action
17
+ # =============================================================================
18
+
19
+ # Get current phase number
20
+ get_current_phase() {
21
+ local feature_json="$1"
22
+ if [ ! -f "$feature_json" ]; then
23
+ echo "0"
24
+ return
25
+ fi
26
+ jq -r '.current_phase // 0' "$feature_json"
27
+ }
28
+
29
+ # Get total number of phases
30
+ get_total_phases() {
31
+ local feature_json="$1"
32
+ if [ ! -f "$feature_json" ]; then
33
+ echo "0"
34
+ return
35
+ fi
36
+ jq -r '.next_action | length // 0' "$feature_json"
37
+ }
38
+
39
+ # Get action name for a specific phase
40
+ get_phase_action() {
41
+ local feature_json="$1"
42
+ local phase="$2"
43
+ if [ ! -f "$feature_json" ]; then
44
+ echo "unknown"
45
+ return
46
+ fi
47
+ jq -r --argjson phase "$phase" '.next_action[] | select(.phase == $phase) | .action // "unknown"' "$feature_json"
48
+ }
49
+
50
+ # Get formatted phase info: "N/M (action)"
51
+ get_phase_info() {
52
+ local feature_json="$1"
53
+ if [ ! -f "$feature_json" ]; then
54
+ echo "N/A"
55
+ return
56
+ fi
57
+
58
+ local current_phase=$(get_current_phase "$feature_json")
59
+ local total_phases=$(get_total_phases "$feature_json")
60
+ local action_name=$(get_phase_action "$feature_json" "$current_phase")
61
+
62
+ if [ "$current_phase" = "0" ] || [ "$current_phase" = "null" ]; then
63
+ echo "0/${total_phases} (pending)"
64
+ else
65
+ echo "${current_phase}/${total_phases} (${action_name})"
66
+ fi
67
+ }
68
+
69
+ # Set current phase to a specific value
70
+ set_phase() {
71
+ local feature_json="$1"
72
+ local phase="$2"
73
+
74
+ if [ ! -f "$feature_json" ]; then
75
+ echo "Error: feature.json not found: $feature_json" >&2
76
+ return 1
77
+ fi
78
+
79
+ jq --argjson phase "$phase" '.current_phase = $phase' "$feature_json" > "${feature_json}.tmp"
80
+ mv "${feature_json}.tmp" "$feature_json"
81
+ }
82
+
83
+ # Advance to next phase
84
+ advance_phase() {
85
+ local feature_json="$1"
86
+
87
+ if [ ! -f "$feature_json" ]; then
88
+ echo "Error: feature.json not found: $feature_json" >&2
89
+ return 1
90
+ fi
91
+
92
+ local current=$(get_current_phase "$feature_json")
93
+ local total=$(get_total_phases "$feature_json")
94
+ local next=$((current + 1))
95
+
96
+ if [ "$next" -gt "$total" ]; then
97
+ echo "Warning: Already at final phase" >&2
98
+ return 0
99
+ fi
100
+
101
+ set_phase "$feature_json" "$next"
102
+ }
103
+
104
+ # Get phase number for a specific action name
105
+ get_phase_for_action() {
106
+ local feature_json="$1"
107
+ local action="$2"
108
+
109
+ if [ ! -f "$feature_json" ]; then
110
+ echo "0"
111
+ return
112
+ fi
113
+
114
+ jq -r --arg action "$action" '.next_action[] | select(.action == $action) | .phase // 0' "$feature_json"
115
+ }
116
+
117
+ # Map subagent type to action name
118
+ # Used by hooks to determine which action a subagent corresponds to
119
+ map_subagent_to_action() {
120
+ local subagent_type="$1"
121
+
122
+ case "$subagent_type" in
123
+ implement) echo "implement" ;;
124
+ check) echo "check" ;;
125
+ debug) echo "debug" ;;
126
+ research) echo "research" ;;
127
+ # finish uses check agent but is a different action
128
+ *) echo "$subagent_type" ;;
129
+ esac
130
+ }
131
+
132
+ # Check if a phase is completed (current_phase > phase)
133
+ is_phase_completed() {
134
+ local feature_json="$1"
135
+ local phase="$2"
136
+
137
+ local current=$(get_current_phase "$feature_json")
138
+ [ "$current" -gt "$phase" ]
139
+ }
140
+
141
+ # Check if we're at a specific action
142
+ is_current_action() {
143
+ local feature_json="$1"
144
+ local action="$2"
145
+
146
+ local current=$(get_current_phase "$feature_json")
147
+ local action_phase=$(get_phase_for_action "$feature_json" "$action")
148
+
149
+ [ "$current" = "$action_phase" ]
150
+ }
@@ -880,10 +880,14 @@ cmd_create_pr() {
880
880
  echo -e "[DRY-RUN] Would update feature.json:"
881
881
  echo -e " status: review"
882
882
  echo -e " pr_url: ${pr_url}"
883
+ echo -e " current_phase: (set to create-pr phase)"
883
884
  else
884
- jq --arg url "$pr_url" '.status = "review" | .pr_url = $url' "$feature_json" > "${feature_json}.tmp"
885
+ # Find the phase number for create-pr action
886
+ local create_pr_phase=$(jq -r '.next_action[] | select(.action == "create-pr") | .phase // 4' "$feature_json")
887
+ jq --arg url "$pr_url" --argjson phase "$create_pr_phase" \
888
+ '.status = "review" | .pr_url = $url | .current_phase = $phase' "$feature_json" > "${feature_json}.tmp"
885
889
  mv "${feature_json}.tmp" "$feature_json"
886
- echo -e "${GREEN}Feature status updated to 'review'${NC}"
890
+ echo -e "${GREEN}Feature status updated to 'review', phase ${create_pr_phase}${NC}"
887
891
  fi
888
892
 
889
893
  # In dry-run, reset the staging area
@@ -970,8 +974,9 @@ case "${1:-}" in
970
974
  cmd_set_scope "$2" "$3"
971
975
  ;;
972
976
  create-pr)
977
+ # Delegate to multi-agent/create-pr.sh
973
978
  shift
974
- cmd_create_pr "$@"
979
+ "$SCRIPT_DIR/multi-agent/create-pr.sh" "$@"
975
980
  ;;
976
981
  archive)
977
982
  cmd_archive "$2"
@@ -155,6 +155,76 @@ remove_from_registry() {
155
155
  log_info "Removed from registry"
156
156
  }
157
157
 
158
+ # =============================================================================
159
+ # Cleanup from Registry Only (no worktree)
160
+ # =============================================================================
161
+ cleanup_registry_only() {
162
+ local search="$1"
163
+
164
+ AGENTS_DIR=$(get_agents_dir)
165
+ REGISTRY_FILE="${AGENTS_DIR}/registry.json"
166
+
167
+ if [ ! -f "$REGISTRY_FILE" ]; then
168
+ log_error "No registry found"
169
+ exit 1
170
+ fi
171
+
172
+ # Find agent by id or feature_dir containing search term
173
+ local agent_info=$(jq -r --arg search "$search" '.agents[] | select(.id == $search or (.feature_dir | contains($search)))' "$REGISTRY_FILE" 2>/dev/null | head -1)
174
+
175
+ if [ -z "$agent_info" ] || [ "$agent_info" = "null" ]; then
176
+ log_error "No agent found in registry matching: $search"
177
+ exit 1
178
+ fi
179
+
180
+ local agent_id=$(echo "$agent_info" | jq -r '.id')
181
+ local feature_dir=$(echo "$agent_info" | jq -r '.feature_dir')
182
+ local worktree_path=$(echo "$agent_info" | jq -r '.worktree_path')
183
+
184
+ echo ""
185
+ echo -e "${BLUE}=== Cleanup Agent (no worktree) ===${NC}"
186
+ echo " Agent ID: $agent_id"
187
+ echo " Feature Dir: $feature_dir"
188
+ echo ""
189
+
190
+ # Confirmation
191
+ if [ "$SKIP_CONFIRM" != "true" ]; then
192
+ if [ -t 0 ]; then
193
+ read -p "Archive feature and remove from registry? [y/N] " -n 1 -r
194
+ echo
195
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
196
+ log_info "Aborted"
197
+ exit 0
198
+ fi
199
+ else
200
+ log_error "Non-interactive mode detected. Use -y to skip confirmation."
201
+ exit 1
202
+ fi
203
+ fi
204
+
205
+ # 1. Archive feature directory if exists
206
+ FEATURE_DIR_ABS="${PROJECT_ROOT}/${feature_dir}"
207
+ if [ -d "$FEATURE_DIR_ABS" ]; then
208
+ local features_dir=$(get_features_dir)
209
+ local archive_dir="$features_dir/archive"
210
+ local year_month=$(date +%Y-%m)
211
+ local month_dir="$archive_dir/$year_month"
212
+
213
+ mkdir -p "$month_dir"
214
+
215
+ local feature_name=$(basename "$feature_dir")
216
+ mv "$FEATURE_DIR_ABS" "$month_dir/"
217
+ log_success "Archived feature: $feature_name -> archive/$year_month/"
218
+ fi
219
+
220
+ # 2. Remove from registry
221
+ local updated=$(jq --arg id "$agent_id" '.agents = [.agents[] | select(.id != $id)]' "$REGISTRY_FILE")
222
+ echo "$updated" | jq '.' > "$REGISTRY_FILE"
223
+ log_success "Removed from registry: $agent_id"
224
+
225
+ log_success "Cleanup complete"
226
+ }
227
+
158
228
  # =============================================================================
159
229
  # Cleanup Single Worktree
160
230
  # =============================================================================
@@ -169,8 +239,11 @@ cleanup_worktree() {
169
239
  local worktree_path=$(echo "$worktree_info" | grep "^worktree " | cut -d' ' -f2-)
170
240
 
171
241
  if [ -z "$worktree_path" ]; then
172
- log_error "No worktree found for branch: $branch"
173
- exit 1
242
+ # No worktree found, try to cleanup from registry only
243
+ log_warn "No worktree found for: $branch"
244
+ log_info "Trying to cleanup from registry..."
245
+ cleanup_registry_only "$branch"
246
+ return
174
247
  fi
175
248
 
176
249
  echo ""
@@ -181,11 +254,17 @@ cleanup_worktree() {
181
254
 
182
255
  # Confirmation
183
256
  if [ "$SKIP_CONFIRM" != "true" ]; then
184
- read -p "Remove this worktree? [y/N] " -n 1 -r
185
- echo
186
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
187
- log_info "Aborted"
188
- exit 0
257
+ # Check if running interactively
258
+ if [ -t 0 ]; then
259
+ read -p "Remove this worktree? [y/N] " -n 1 -r
260
+ echo
261
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
262
+ log_info "Aborted"
263
+ exit 0
264
+ fi
265
+ else
266
+ log_error "Non-interactive mode detected. Use -y to skip confirmation."
267
+ exit 1
189
268
  fi
190
269
  fi
191
270
 
@@ -243,11 +322,16 @@ cmd_merged() {
243
322
  echo ""
244
323
 
245
324
  if [ "$SKIP_CONFIRM" != "true" ]; then
246
- read -p "Remove these merged worktrees? [y/N] " -n 1 -r
247
- echo
248
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
249
- log_info "Aborted"
250
- exit 0
325
+ if [ -t 0 ]; then
326
+ read -p "Remove these merged worktrees? [y/N] " -n 1 -r
327
+ echo
328
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
329
+ log_info "Aborted"
330
+ exit 0
331
+ fi
332
+ else
333
+ log_error "Non-interactive mode detected. Use -y to skip confirmation."
334
+ exit 1
251
335
  fi
252
336
  fi
253
337
 
@@ -279,12 +363,17 @@ cmd_all() {
279
363
  echo ""
280
364
 
281
365
  if [ "$SKIP_CONFIRM" != "true" ]; then
282
- echo -e "${RED}WARNING: This will remove ALL worktrees!${NC}"
283
- read -p "Are you sure? [y/N] " -n 1 -r
284
- echo
285
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
286
- log_info "Aborted"
287
- exit 0
366
+ if [ -t 0 ]; then
367
+ echo -e "${RED}WARNING: This will remove ALL worktrees!${NC}"
368
+ read -p "Are you sure? [y/N] " -n 1 -r
369
+ echo
370
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
371
+ log_info "Aborted"
372
+ exit 0
373
+ fi
374
+ else
375
+ log_error "Non-interactive mode detected. Use -y to skip confirmation."
376
+ exit 1
288
377
  fi
289
378
  fi
290
379
 
@@ -0,0 +1,241 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # Multi-Agent Pipeline: Create PR
4
+ # =============================================================================
5
+ # Usage:
6
+ # ./create-pr.sh [feature-dir] [--dry-run]
7
+ #
8
+ # This script:
9
+ # 1. Stages and commits all changes (excluding agent-traces)
10
+ # 2. Pushes to origin
11
+ # 3. Creates a Draft PR using `gh pr create`
12
+ # 4. Updates feature.json with status="review", pr_url, and current_phase
13
+ #
14
+ # Note: This is the only action that performs git commit, as it's the final
15
+ # step after all implementation and checks are complete.
16
+ # =============================================================================
17
+
18
+ set -e
19
+
20
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ source "$SCRIPT_DIR/../common/paths.sh"
22
+ source "$SCRIPT_DIR/../common/phase.sh"
23
+
24
+ # Colors
25
+ RED='\033[0;31m'
26
+ GREEN='\033[0;32m'
27
+ YELLOW='\033[1;33m'
28
+ BLUE='\033[0;34m'
29
+ NC='\033[0m'
30
+
31
+ REPO_ROOT=$(get_repo_root)
32
+
33
+ # =============================================================================
34
+ # Parse Arguments
35
+ # =============================================================================
36
+ TARGET_DIR=""
37
+ DRY_RUN=false
38
+
39
+ while [[ $# -gt 0 ]]; do
40
+ case "$1" in
41
+ --dry-run)
42
+ DRY_RUN=true
43
+ shift
44
+ ;;
45
+ -h|--help)
46
+ echo "Usage: $0 [feature-dir] [--dry-run]"
47
+ echo ""
48
+ echo "Options:"
49
+ echo " --dry-run Show what would be done without making changes"
50
+ echo " -h, --help Show this help message"
51
+ exit 0
52
+ ;;
53
+ *)
54
+ if [[ -z "$TARGET_DIR" ]]; then
55
+ TARGET_DIR="$1"
56
+ fi
57
+ shift
58
+ ;;
59
+ esac
60
+ done
61
+
62
+ # =============================================================================
63
+ # Get Feature Directory
64
+ # =============================================================================
65
+ if [[ -z "$TARGET_DIR" ]]; then
66
+ # Try to get from .current-feature
67
+ CURRENT_FEATURE_FILE="$REPO_ROOT/.trellis/.current-feature"
68
+ if [[ -f "$CURRENT_FEATURE_FILE" ]]; then
69
+ TARGET_DIR=$(cat "$CURRENT_FEATURE_FILE")
70
+ fi
71
+
72
+ if [[ -z "$TARGET_DIR" ]]; then
73
+ echo -e "${RED}Error: No feature directory specified and no current feature set${NC}"
74
+ echo "Usage: $0 [feature-dir] [--dry-run]"
75
+ exit 1
76
+ fi
77
+ fi
78
+
79
+ # Support relative paths
80
+ if [[ ! "$TARGET_DIR" = /* ]]; then
81
+ TARGET_DIR="$REPO_ROOT/$TARGET_DIR"
82
+ fi
83
+
84
+ FEATURE_JSON="$TARGET_DIR/feature.json"
85
+ if [[ ! -f "$FEATURE_JSON" ]]; then
86
+ echo -e "${RED}Error: feature.json not found at $TARGET_DIR${NC}"
87
+ exit 1
88
+ fi
89
+
90
+ # =============================================================================
91
+ # Main
92
+ # =============================================================================
93
+ echo -e "${BLUE}=== Create PR ===${NC}"
94
+ if [[ "$DRY_RUN" == "true" ]]; then
95
+ echo -e "${YELLOW}[DRY-RUN MODE] No actual changes will be made${NC}"
96
+ fi
97
+ echo ""
98
+
99
+ # Read feature config
100
+ FEATURE_NAME=$(jq -r '.name' "$FEATURE_JSON")
101
+ BASE_BRANCH=$(jq -r '.base_branch // "main"' "$FEATURE_JSON")
102
+ SCOPE=$(jq -r '.scope // "core"' "$FEATURE_JSON")
103
+ DEV_TYPE=$(jq -r '.dev_type // "feature"' "$FEATURE_JSON")
104
+
105
+ # Map dev_type to commit prefix
106
+ case "$DEV_TYPE" in
107
+ feature|frontend|backend|fullstack) COMMIT_PREFIX="feat" ;;
108
+ bugfix|fix) COMMIT_PREFIX="fix" ;;
109
+ refactor) COMMIT_PREFIX="refactor" ;;
110
+ docs) COMMIT_PREFIX="docs" ;;
111
+ test) COMMIT_PREFIX="test" ;;
112
+ *) COMMIT_PREFIX="feat" ;;
113
+ esac
114
+
115
+ echo -e "Feature: ${FEATURE_NAME}"
116
+ echo -e "Base branch: ${BASE_BRANCH}"
117
+ echo -e "Scope: ${SCOPE}"
118
+ echo -e "Commit prefix: ${COMMIT_PREFIX}"
119
+ echo ""
120
+
121
+ # Get current branch
122
+ CURRENT_BRANCH=$(git branch --show-current)
123
+ echo -e "Current branch: ${CURRENT_BRANCH}"
124
+
125
+ # Check for changes
126
+ echo -e "${YELLOW}Checking for changes...${NC}"
127
+
128
+ # Stage changes (even in dry-run to detect what would be committed)
129
+ git add -A
130
+
131
+ # Exclude agent traces and temp files
132
+ git reset ".trellis/agent-traces/" 2>/dev/null || true
133
+ git reset .agent-log .agent-prompt .agent-runner.sh 2>/dev/null || true
134
+
135
+ # Check if there are staged changes
136
+ if git diff --cached --quiet 2>/dev/null; then
137
+ echo -e "${YELLOW}No staged changes to commit${NC}"
138
+
139
+ # Check for unpushed commits
140
+ UNPUSHED=$(git log "origin/${CURRENT_BRANCH}..HEAD" --oneline 2>/dev/null | wc -l | tr -d ' ' || echo "0")
141
+ if [[ "$UNPUSHED" -eq 0 ]] 2>/dev/null; then
142
+ # In dry-run, also reset the staging
143
+ if [[ "$DRY_RUN" == "true" ]]; then
144
+ git reset HEAD >/dev/null 2>&1 || true
145
+ fi
146
+ echo -e "${RED}No changes to create PR${NC}"
147
+ exit 1
148
+ fi
149
+ echo -e "Found ${UNPUSHED} unpushed commit(s)"
150
+ else
151
+ # Commit changes
152
+ echo -e "${YELLOW}Committing changes...${NC}"
153
+ COMMIT_MSG="${COMMIT_PREFIX}(${SCOPE}): ${FEATURE_NAME}"
154
+
155
+ if [[ "$DRY_RUN" == "true" ]]; then
156
+ echo -e "[DRY-RUN] Would commit with message: ${COMMIT_MSG}"
157
+ echo -e "[DRY-RUN] Staged files:"
158
+ git diff --cached --name-only | sed 's/^/ - /'
159
+ else
160
+ git commit -m "$COMMIT_MSG"
161
+ echo -e "${GREEN}Committed: ${COMMIT_MSG}${NC}"
162
+ fi
163
+ fi
164
+
165
+ # Push to remote
166
+ echo -e "${YELLOW}Pushing to remote...${NC}"
167
+ if [[ "$DRY_RUN" == "true" ]]; then
168
+ echo -e "[DRY-RUN] Would push to: origin/${CURRENT_BRANCH}"
169
+ else
170
+ git push -u origin "$CURRENT_BRANCH"
171
+ echo -e "${GREEN}Pushed to origin/${CURRENT_BRANCH}${NC}"
172
+ fi
173
+
174
+ # Create PR
175
+ echo -e "${YELLOW}Creating PR...${NC}"
176
+ PR_TITLE="${COMMIT_PREFIX}(${SCOPE}): ${FEATURE_NAME}"
177
+ PR_URL=""
178
+
179
+ if [[ "$DRY_RUN" == "true" ]]; then
180
+ echo -e "[DRY-RUN] Would create PR:"
181
+ echo -e " Title: ${PR_TITLE}"
182
+ echo -e " Base: ${BASE_BRANCH}"
183
+ echo -e " Head: ${CURRENT_BRANCH}"
184
+ if [[ -f "$TARGET_DIR/prd.md" ]]; then
185
+ echo -e " Body: (from prd.md)"
186
+ fi
187
+ PR_URL="https://github.com/example/repo/pull/DRY-RUN"
188
+ else
189
+ # Check if PR already exists
190
+ EXISTING_PR=$(gh pr list --head "$CURRENT_BRANCH" --base "$BASE_BRANCH" --json url --jq '.[0].url' 2>/dev/null || echo "")
191
+
192
+ if [[ -n "$EXISTING_PR" ]]; then
193
+ echo -e "${YELLOW}PR already exists: ${EXISTING_PR}${NC}"
194
+ PR_URL="$EXISTING_PR"
195
+ else
196
+ # Read PRD as PR body
197
+ PR_BODY=""
198
+ if [[ -f "$TARGET_DIR/prd.md" ]]; then
199
+ PR_BODY=$(cat "$TARGET_DIR/prd.md")
200
+ fi
201
+
202
+ # Create PR
203
+ PR_URL=$(gh pr create \
204
+ --draft \
205
+ --base "$BASE_BRANCH" \
206
+ --title "$PR_TITLE" \
207
+ --body "$PR_BODY" \
208
+ 2>&1)
209
+
210
+ echo -e "${GREEN}PR created: ${PR_URL}${NC}"
211
+ fi
212
+ fi
213
+
214
+ # Update feature.json
215
+ echo -e "${YELLOW}Updating feature status...${NC}"
216
+ if [[ "$DRY_RUN" == "true" ]]; then
217
+ echo -e "[DRY-RUN] Would update feature.json:"
218
+ echo -e " status: review"
219
+ echo -e " pr_url: ${PR_URL}"
220
+ echo -e " current_phase: (set to create-pr phase)"
221
+ else
222
+ # Get the phase number for create-pr action using common/phase.sh
223
+ CREATE_PR_PHASE=$(get_phase_for_action "$FEATURE_JSON" "create-pr")
224
+ if [[ -z "$CREATE_PR_PHASE" ]] || [[ "$CREATE_PR_PHASE" == "0" ]]; then
225
+ CREATE_PR_PHASE=4 # Default fallback
226
+ fi
227
+
228
+ jq --arg url "$PR_URL" --argjson phase "$CREATE_PR_PHASE" \
229
+ '.status = "review" | .pr_url = $url | .current_phase = $phase' "$FEATURE_JSON" > "${FEATURE_JSON}.tmp"
230
+ mv "${FEATURE_JSON}.tmp" "$FEATURE_JSON"
231
+ echo -e "${GREEN}Feature status updated to 'review', phase ${CREATE_PR_PHASE}${NC}"
232
+ fi
233
+
234
+ # In dry-run, reset the staging area
235
+ if [[ "$DRY_RUN" == "true" ]]; then
236
+ git reset HEAD >/dev/null 2>&1 || true
237
+ fi
238
+
239
+ echo ""
240
+ echo -e "${GREEN}=== PR Created Successfully ===${NC}"
241
+ echo -e "PR URL: ${PR_URL}"