forge-workflow 0.0.4 → 0.0.6

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 (248) hide show
  1. package/.claude/commands/dev.md +345 -340
  2. package/.claude/commands/plan.md +566 -521
  3. package/.claude/commands/premerge.md +186 -176
  4. package/.claude/commands/research.md +42 -42
  5. package/.claude/commands/review.md +448 -442
  6. package/.claude/commands/rollback.md +721 -721
  7. package/.claude/commands/ship.md +212 -164
  8. package/.claude/commands/sonarcloud.md +152 -152
  9. package/.claude/commands/status.md +90 -48
  10. package/.claude/commands/validate.md +288 -282
  11. package/.claude/commands/verify.md +269 -221
  12. package/.claude/rules/greptile-review-process.md +285 -285
  13. package/.claude/rules/workflow.md +121 -105
  14. package/.claude/scripts/greptile-resolve.sh +558 -526
  15. package/.claude/scripts/load-env.sh +32 -32
  16. package/.cline/workflows/dev.md +342 -337
  17. package/.cline/workflows/plan.md +563 -518
  18. package/.cline/workflows/premerge.md +183 -173
  19. package/.cline/workflows/research.md +39 -39
  20. package/.cline/workflows/review.md +445 -439
  21. package/.cline/workflows/rollback.md +718 -718
  22. package/.cline/workflows/ship.md +209 -161
  23. package/.cline/workflows/sonarcloud.md +146 -146
  24. package/.cline/workflows/status.md +87 -45
  25. package/.cline/workflows/validate.md +285 -279
  26. package/.cline/workflows/verify.md +266 -218
  27. package/.codex/config.toml +11 -11
  28. package/.codex/skills/dev/SKILL.md +345 -340
  29. package/.codex/skills/plan/SKILL.md +566 -521
  30. package/.codex/skills/premerge/SKILL.md +186 -176
  31. package/.codex/skills/research/SKILL.md +42 -42
  32. package/.codex/skills/review/SKILL.md +448 -442
  33. package/.codex/skills/rollback/SKILL.md +721 -721
  34. package/.codex/skills/ship/SKILL.md +212 -164
  35. package/.codex/skills/sonarcloud/SKILL.md +149 -149
  36. package/.codex/skills/status/SKILL.md +90 -48
  37. package/.codex/skills/validate/SKILL.md +288 -282
  38. package/.codex/skills/verify/SKILL.md +269 -221
  39. package/.cursor/commands/dev.md +342 -337
  40. package/.cursor/commands/plan.md +563 -518
  41. package/.cursor/commands/premerge.md +183 -173
  42. package/.cursor/commands/research.md +39 -39
  43. package/.cursor/commands/review.md +445 -439
  44. package/.cursor/commands/rollback.md +718 -718
  45. package/.cursor/commands/ship.md +209 -161
  46. package/.cursor/commands/sonarcloud.md +146 -146
  47. package/.cursor/commands/status.md +87 -45
  48. package/.cursor/commands/validate.md +285 -279
  49. package/.cursor/commands/verify.md +266 -218
  50. package/.cursor/rules/permissions-guidance.mdc +37 -37
  51. package/.forge/hooks/check-tdd.js +240 -240
  52. package/.github/PLUGIN_TEMPLATE.json +32 -32
  53. package/.github/prompts/dev.prompt.md +347 -342
  54. package/.github/prompts/plan.prompt.md +568 -523
  55. package/.github/prompts/premerge.prompt.md +188 -178
  56. package/.github/prompts/research.prompt.md +44 -44
  57. package/.github/prompts/review.prompt.md +450 -444
  58. package/.github/prompts/rollback.prompt.md +723 -723
  59. package/.github/prompts/ship.prompt.md +214 -166
  60. package/.github/prompts/sonarcloud.prompt.md +151 -151
  61. package/.github/prompts/status.prompt.md +92 -50
  62. package/.github/prompts/validate.prompt.md +290 -284
  63. package/.github/prompts/verify.prompt.md +271 -223
  64. package/.github/workflows/beads-to-github.yml +56 -0
  65. package/.github/workflows/github-to-beads.yml +97 -0
  66. package/.kilocode/workflows/dev.md +346 -341
  67. package/.kilocode/workflows/plan.md +567 -522
  68. package/.kilocode/workflows/premerge.md +187 -177
  69. package/.kilocode/workflows/research.md +43 -43
  70. package/.kilocode/workflows/review.md +449 -443
  71. package/.kilocode/workflows/rollback.md +722 -722
  72. package/.kilocode/workflows/ship.md +213 -165
  73. package/.kilocode/workflows/sonarcloud.md +150 -150
  74. package/.kilocode/workflows/status.md +91 -49
  75. package/.kilocode/workflows/validate.md +289 -283
  76. package/.kilocode/workflows/verify.md +270 -222
  77. package/.mcp.json.example +12 -12
  78. package/.opencode/commands/dev.md +345 -340
  79. package/.opencode/commands/plan.md +566 -521
  80. package/.opencode/commands/premerge.md +186 -176
  81. package/.opencode/commands/research.md +42 -42
  82. package/.opencode/commands/review.md +448 -442
  83. package/.opencode/commands/rollback.md +721 -721
  84. package/.opencode/commands/ship.md +212 -164
  85. package/.opencode/commands/sonarcloud.md +149 -149
  86. package/.opencode/commands/status.md +90 -48
  87. package/.opencode/commands/validate.md +288 -282
  88. package/.opencode/commands/verify.md +269 -221
  89. package/.roo/commands/dev.md +346 -341
  90. package/.roo/commands/plan.md +567 -522
  91. package/.roo/commands/premerge.md +187 -177
  92. package/.roo/commands/research.md +43 -43
  93. package/.roo/commands/review.md +449 -443
  94. package/.roo/commands/rollback.md +722 -722
  95. package/.roo/commands/ship.md +213 -165
  96. package/.roo/commands/sonarcloud.md +150 -150
  97. package/.roo/commands/status.md +91 -49
  98. package/.roo/commands/validate.md +289 -283
  99. package/.roo/commands/verify.md +270 -222
  100. package/AGENTS.md +272 -175
  101. package/CLAUDE.md +110 -100
  102. package/README.md +429 -416
  103. package/bin/forge-cmd.js +317 -313
  104. package/bin/forge-preflight.js +322 -309
  105. package/bin/forge.js +4765 -4303
  106. package/docs/AGENT_INSTALL_PROMPT.md +342 -342
  107. package/docs/BEADS_GITHUB_SYNC.md +251 -251
  108. package/docs/ENHANCED_ONBOARDING.md +612 -602
  109. package/docs/EXAMPLES.md +482 -482
  110. package/docs/GREPTILE_SETUP.md +400 -400
  111. package/docs/MANUAL_REVIEW_GUIDE.md +106 -106
  112. package/docs/ROADMAP.md +359 -359
  113. package/docs/SETUP.md +663 -631
  114. package/docs/TOOLCHAIN.md +653 -630
  115. package/docs/VALIDATION.md +363 -363
  116. package/install.sh +40 -1056
  117. package/lefthook.yml +50 -39
  118. package/lib/agents/README.md +198 -198
  119. package/lib/agents/claude.plugin.json +28 -28
  120. package/lib/agents/cline.plugin.json +22 -22
  121. package/lib/agents/codex.plugin.json +19 -19
  122. package/lib/agents/copilot.plugin.json +24 -24
  123. package/lib/agents/cursor.plugin.json +25 -25
  124. package/lib/agents/kilocode.plugin.json +22 -22
  125. package/lib/agents/opencode.plugin.json +20 -20
  126. package/lib/agents/roo.plugin.json +23 -23
  127. package/lib/agents-config.js +2112 -2112
  128. package/lib/beads-health-check.js +143 -0
  129. package/lib/beads-setup.js +341 -0
  130. package/lib/beads-sync-scaffold.js +260 -0
  131. package/lib/commands/_registry.js +134 -0
  132. package/lib/commands/clean.js +181 -0
  133. package/lib/commands/dev.js +571 -513
  134. package/lib/commands/plan.js +692 -692
  135. package/lib/commands/push.js +196 -0
  136. package/lib/commands/recommend.js +119 -119
  137. package/lib/commands/ship.js +377 -377
  138. package/lib/commands/status.js +378 -378
  139. package/lib/commands/sync.js +55 -0
  140. package/lib/commands/team.js +37 -0
  141. package/lib/commands/test.js +207 -0
  142. package/lib/commands/validate.js +602 -602
  143. package/lib/commands/worktree.js +310 -0
  144. package/lib/context-merge.js +359 -359
  145. package/lib/dep-guard/analyzer.js +294 -294
  146. package/lib/dep-guard/behavior-detector.js +98 -98
  147. package/lib/dep-guard/contract-detector.js +162 -162
  148. package/lib/dep-guard/import-detector.js +498 -498
  149. package/lib/dep-guard/path-utils.js +13 -13
  150. package/lib/dep-guard/rubric.js +120 -120
  151. package/lib/dep-guard/task-parser.js +318 -318
  152. package/lib/detect-agent.js +191 -191
  153. package/lib/detect-worktree.js +47 -47
  154. package/lib/docs-command.js +51 -0
  155. package/lib/docs-copy.js +50 -0
  156. package/lib/file-hash.js +26 -26
  157. package/lib/freshness-token.js +148 -0
  158. package/lib/greptile-match.js +80 -0
  159. package/lib/husky-migration.js +450 -0
  160. package/lib/lefthook-check.js +65 -0
  161. package/lib/pat-setup.js +207 -0
  162. package/lib/plugin-catalog.js +350 -350
  163. package/lib/plugin-manager.js +166 -166
  164. package/lib/plugin-recommender.js +141 -141
  165. package/lib/project-discovery.js +491 -491
  166. package/lib/reset.js +309 -0
  167. package/lib/setup-action-log.js +139 -139
  168. package/lib/setup-summary-renderer.js +106 -106
  169. package/lib/setup-utils.js +96 -0
  170. package/lib/setup.js +192 -192
  171. package/lib/smart-merge.js +64 -0
  172. package/lib/symlink-utils.js +81 -0
  173. package/lib/task-ownership.js +117 -0
  174. package/lib/workflow-profiles.js +197 -197
  175. package/package.json +131 -128
  176. package/scripts/beads-context.sh +426 -0
  177. package/scripts/beads-context.test.js +567 -0
  178. package/scripts/behavioral-judge.sh +378 -0
  179. package/scripts/benchmark.js +85 -0
  180. package/scripts/branch-protection.js +183 -0
  181. package/scripts/check-agents.js +172 -0
  182. package/scripts/check-forge-token.js +98 -0
  183. package/scripts/commitlint.js +42 -0
  184. package/scripts/conflict-detect.sh +323 -0
  185. package/scripts/dep-guard-analyze.js +71 -0
  186. package/scripts/dep-guard.sh +789 -0
  187. package/scripts/eval_win.py +249 -0
  188. package/scripts/file-index.sh +493 -0
  189. package/scripts/forge-team/index.sh +86 -0
  190. package/scripts/forge-team/lib/agent-prompt.sh +52 -0
  191. package/scripts/forge-team/lib/claim.sh +256 -0
  192. package/scripts/forge-team/lib/dashboard.sh +341 -0
  193. package/scripts/forge-team/lib/epic.sh +332 -0
  194. package/scripts/forge-team/lib/hooks.sh +253 -0
  195. package/scripts/forge-team/lib/identity.sh +235 -0
  196. package/scripts/forge-team/lib/sync-github.sh +317 -0
  197. package/scripts/forge-team/lib/verify.sh +284 -0
  198. package/scripts/forge-team/lib/workload.sh +296 -0
  199. package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
  200. package/scripts/forge-team/tests/claim.test.sh +179 -0
  201. package/scripts/forge-team/tests/dashboard.test.sh +170 -0
  202. package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
  203. package/scripts/forge-team/tests/epic.test.sh +176 -0
  204. package/scripts/forge-team/tests/hooks.test.sh +239 -0
  205. package/scripts/forge-team/tests/identity.test.sh +176 -0
  206. package/scripts/forge-team/tests/integration.test.sh +371 -0
  207. package/scripts/forge-team/tests/sync-github.test.sh +209 -0
  208. package/scripts/forge-team/tests/verify.test.sh +314 -0
  209. package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
  210. package/scripts/forge-team/tests/workload.test.sh +209 -0
  211. package/scripts/github-beads-sync/comment.mjs +64 -0
  212. package/scripts/github-beads-sync/config.mjs +148 -0
  213. package/scripts/github-beads-sync/github-api.mjs +131 -0
  214. package/scripts/github-beads-sync/index.mjs +332 -0
  215. package/scripts/github-beads-sync/label-mapper.mjs +54 -0
  216. package/scripts/github-beads-sync/mapping.mjs +78 -0
  217. package/scripts/github-beads-sync/reverse-sync-cli.mjs +31 -0
  218. package/scripts/github-beads-sync/reverse-sync.mjs +138 -0
  219. package/scripts/github-beads-sync/run-bd.mjs +159 -0
  220. package/scripts/github-beads-sync/sanitize.mjs +121 -0
  221. package/scripts/github-beads-sync.config.json +26 -0
  222. package/scripts/improve-command.js +375 -0
  223. package/scripts/lib/eval-runner.js +268 -0
  224. package/scripts/lib/eval-schema.js +135 -0
  225. package/scripts/lib/eval-storage.js +78 -0
  226. package/scripts/lib/grading.js +203 -0
  227. package/scripts/lib/jsonl-lock.sh +48 -0
  228. package/scripts/lib/sanitize.sh +116 -0
  229. package/scripts/lib/transcript-parser.js +63 -0
  230. package/scripts/lint.js +47 -0
  231. package/scripts/migrate-to-bun-test.js +412 -0
  232. package/scripts/pr-coordinator.sh +706 -0
  233. package/scripts/run-command-eval.js +236 -0
  234. package/scripts/smart-status.sh +809 -0
  235. package/scripts/sync-commands.js +571 -0
  236. package/scripts/sync-utils.sh +455 -0
  237. package/scripts/test-dashboard.js +123 -0
  238. package/scripts/test.js +46 -0
  239. package/scripts/validate.sh +94 -0
  240. package/skills/parallel-deep-research/SKILL.md +108 -108
  241. package/skills/parallel-deep-research/evals/README.md +27 -27
  242. package/skills/parallel-deep-research/evals/evals.json +62 -62
  243. package/skills/sonarcloud-analysis/SKILL.md +171 -171
  244. package/skills/sonarcloud-analysis/evals/README.md +27 -27
  245. package/skills/sonarcloud-analysis/evals/evals.json +50 -50
  246. package/skills/sonarcloud-analysis/references/api-reference.md +466 -466
  247. package/.cursor/hooks/state/continual-learning-index.json +0 -19
  248. package/.cursor/hooks/state/continual-learning.json +0 -8
@@ -0,0 +1,426 @@
1
+ #!/usr/bin/env bash
2
+ # beads-context.sh — Helper script to manage Beads issue context
3
+ # for the Forge 7-stage workflow.
4
+ #
5
+ # Subcommands:
6
+ # set-design <issue-id> <task-count> <task-file-path>
7
+ # set-acceptance <issue-id> "<criteria-text>"
8
+ # update-progress <issue-id> <task-num> <total> "<title>" <commit-sha> <test-count> <gate-count>
9
+ # parse-progress <issue-id>
10
+ # stage-transition <issue-id> <completed-stage> <next-stage> [--summary "..."] [--decisions "..."] [--artifacts "..."] [--next "..."]
11
+ # validate <issue-id>
12
+ #
13
+ # Cross-platform: works on Windows (Git Bash), macOS, and Linux.
14
+ # OWASP A03: All variables properly quoted to prevent shell injection.
15
+
16
+ set -euo pipefail
17
+
18
+ # Source shared sanitize library
19
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
+ source "$SCRIPT_DIR/lib/sanitize.sh"
21
+
22
+ # ── Helpers ──────────────────────────────────────────────────────────────
23
+
24
+ usage() {
25
+ cat >&2 <<'EOF'
26
+ Usage: beads-context.sh <subcommand> [args...]
27
+
28
+ Subcommands:
29
+ set-design <issue-id> <task-count> <task-file-path>
30
+ set-acceptance <issue-id> "<criteria-text>"
31
+ update-progress <issue-id> <task-num> <total> "<title>" <commit-sha> <test-count> <gate-count>
32
+ parse-progress <issue-id>
33
+ stage-transition <issue-id> <completed-stage> <next-stage> [--summary "..."] [--decisions "..."] [--artifacts "..."] [--next "..."]
34
+ validate <issue-id>
35
+ EOF
36
+ exit 1
37
+ }
38
+
39
+ die() {
40
+ echo "Error: $1" >&2
41
+ exit 1
42
+ }
43
+
44
+ # Run bd update and check for errors in both exit code and output.
45
+ # bd update exits 0 even for non-existent issues, so we check stdout too.
46
+ bd_update() {
47
+ local output
48
+ output="$(bd update "$@" 2>&1)"
49
+ local rc=$?
50
+
51
+ if [[ $rc -ne 0 ]]; then
52
+ echo "$output" >&2
53
+ return 1
54
+ fi
55
+
56
+ # bd prints "Error resolving/updating ..." to stdout for non-existent issues
57
+ # Use specific patterns to avoid false positives from data containing "error"
58
+ if printf '%s' "$output" | grep -Eqi '^Error|Error resolving|Error updating'; then
59
+ echo "$output" >&2
60
+ return 1
61
+ fi
62
+
63
+ echo "$output"
64
+ return 0
65
+ }
66
+
67
+ # Run bd comments add and check for errors similarly.
68
+ bd_comment() {
69
+ local output
70
+ output="$(bd comments add "$@" 2>&1)"
71
+ local rc=$?
72
+
73
+ if [[ $rc -ne 0 ]]; then
74
+ echo "$output" >&2
75
+ return 1
76
+ fi
77
+
78
+ # Use specific error patterns to avoid false positives from data containing "error"
79
+ if printf '%s' "$output" | grep -Eqi '^Error|Error resolving|Error adding'; then
80
+ echo "$output" >&2
81
+ return 1
82
+ fi
83
+
84
+ echo "$output"
85
+ return 0
86
+ }
87
+
88
+ # ── Subcommands ──────────────────────────────────────────────────────────
89
+
90
+ cmd_set_design() {
91
+ if [[ $# -lt 3 ]]; then
92
+ echo "Usage: beads-context.sh set-design <issue-id> <task-count> <task-file-path>" >&2
93
+ exit 1
94
+ fi
95
+
96
+ local issue_id="$1"
97
+ local task_count
98
+ task_count="$(sanitize "$2")"
99
+ local task_file
100
+ task_file="$(sanitize "$3")"
101
+
102
+ local design_text="${task_count} tasks | ${task_file}"
103
+
104
+ if ! bd_update "$issue_id" --design "$design_text" > /dev/null; then
105
+ die "Failed to set design on issue ${issue_id}"
106
+ fi
107
+
108
+ echo "Design set on ${issue_id}: ${design_text}"
109
+ }
110
+
111
+ cmd_set_acceptance() {
112
+ if [[ $# -lt 2 ]]; then
113
+ echo "Usage: beads-context.sh set-acceptance <issue-id> \"<criteria-text>\"" >&2
114
+ exit 1
115
+ fi
116
+
117
+ local issue_id="$1"
118
+ local criteria
119
+ criteria="$(sanitize "$2")"
120
+
121
+ if ! bd_update "$issue_id" --acceptance "$criteria" > /dev/null; then
122
+ die "Failed to set acceptance on issue ${issue_id}"
123
+ fi
124
+
125
+ echo "Acceptance criteria set on ${issue_id}"
126
+ }
127
+
128
+ cmd_update_progress() {
129
+ if [[ $# -lt 7 ]]; then
130
+ echo "Usage: beads-context.sh update-progress <issue-id> <task-num> <total> \"<title>\" <commit-sha> <test-count> <gate-count>" >&2
131
+ exit 1
132
+ fi
133
+
134
+ local issue_id="$1"
135
+ local task_num
136
+ task_num="$(sanitize "$2")"
137
+ local total
138
+ total="$(sanitize "$3")"
139
+ local title
140
+ title="$(sanitize "$4")"
141
+ local commit_sha
142
+ commit_sha="$(sanitize "$5")"
143
+ local test_count
144
+ test_count="$(sanitize "$6")"
145
+ local gate_count
146
+ gate_count="$(sanitize "$7")"
147
+
148
+ # Strip backslashes from progress fields only — these are read back via
149
+ # printf '%b' in parse-progress, where \n/\t would expand into real escapes.
150
+ # Other commands (set-design, set-acceptance, stage-transition) keep backslashes
151
+ # intact to preserve Windows-style paths like docs\plans\tasks.md.
152
+ task_num="${task_num//\\/}"
153
+ total="${total//\\/}"
154
+ title="${title//\\/}"
155
+ commit_sha="${commit_sha//\\/}"
156
+ test_count="${test_count//\\/}"
157
+ gate_count="${gate_count//\\/}"
158
+
159
+ local note="Task ${task_num}/${total} done: ${title} | ${test_count} tests | ${commit_sha} | ${gate_count} gates"
160
+
161
+ if ! bd_update "$issue_id" --append-notes "$note" > /dev/null; then
162
+ die "Failed to update progress on issue ${issue_id}"
163
+ fi
164
+
165
+ echo "Progress updated on ${issue_id}: Task ${task_num}/${total}"
166
+ }
167
+
168
+ cmd_parse_progress() {
169
+ if [[ $# -lt 1 ]]; then
170
+ echo "Usage: beads-context.sh parse-progress <issue-id>" >&2
171
+ exit 1
172
+ fi
173
+
174
+ local issue_id="$1"
175
+
176
+ # Get the issue JSON — detect non-existent issues
177
+ local json
178
+ json="$(bd show "$issue_id" --json 2>&1)" || die "Failed to show issue ${issue_id}"
179
+
180
+ # bd show may exit 0 but print an error for non-existent issues
181
+ # Match specific bd error patterns, not the word "error" in data fields
182
+ if printf '%s' "$json" | grep -Eqi '^Error (fetching|resolving)'; then
183
+ die "Issue not found: ${issue_id}"
184
+ fi
185
+
186
+ # bd show returns "[]" or empty for non-existent issues
187
+ if [[ -z "$json" || "$json" == "[]" || "$json" == "null" ]]; then
188
+ die "Issue not found: ${issue_id}"
189
+ fi
190
+
191
+ # Extract notes field from JSON — prefer jq if available, fall back to grep/sed
192
+ local notes
193
+ if command -v jq &>/dev/null; then
194
+ # Handle both array ([{...}]) and object ({...}) responses from bd show
195
+ notes="$(printf '%s' "$json" | jq -r 'if type == "array" then .[0].notes else .notes end // empty' 2>/dev/null || true)"
196
+ else
197
+ notes="$(printf '%s' "$json" | grep -o '"notes": *"[^"]*"' | head -1 | sed 's/^"notes": *"//;s/"$//' || true)"
198
+ fi
199
+
200
+ if [[ -z "$notes" ]]; then
201
+ echo "No progress data"
202
+ return 0
203
+ fi
204
+
205
+ # Split on literal \n (two chars: backslash + n) and count task lines
206
+ # Use printf to expand the \n into actual newlines for grep (POSIX-compliant)
207
+ local expanded
208
+ expanded="$(printf '%b' "$notes")"
209
+
210
+ local task_lines
211
+ task_lines="$(echo "$expanded" | grep -c 'Task [0-9]*/[0-9]* done:' || true)"
212
+
213
+ if [[ "$task_lines" -eq 0 ]]; then
214
+ echo "No progress data"
215
+ return 0
216
+ fi
217
+
218
+ # Get the last task line
219
+ local last_line
220
+ last_line="$(echo "$expanded" | grep 'Task [0-9]*/[0-9]* done:' | tail -1 || true)"
221
+
222
+ # Extract total (N/M) from last line
223
+ local total
224
+ total="$(echo "$last_line" | grep -o '[0-9]*/[0-9]*' | head -1 | cut -d/ -f2)"
225
+
226
+ # Extract last task title (between "done: " and " |")
227
+ local last_title
228
+ last_title="$(echo "$last_line" | sed 's/.*done: //;s/ |.*//')"
229
+
230
+ # Extract commit sha — 4th pipe-separated field
231
+ # Format: "Task N/M done: <title> | <test_count> tests | <sha> | <gate_count> gates"
232
+ local last_sha
233
+ # Use [|] character class for portable literal pipe matching across gawk and BSD awk
234
+ last_sha="$(echo "$last_line" | awk -F ' [|] ' '{print $3}' | sed 's/^ *//;s/ *$//')"
235
+
236
+ echo "${task_lines}/${total} tasks done | Last: ${last_title} (${last_sha})"
237
+ }
238
+
239
+ cmd_stage_transition() {
240
+ if [[ $# -lt 3 ]]; then
241
+ echo "Usage: beads-context.sh stage-transition <issue-id> <completed-stage> <next-stage> [--summary \"...\"] [--decisions \"...\"] [--artifacts \"...\"] [--next \"...\"]" >&2
242
+ exit 1
243
+ fi
244
+
245
+ local issue_id="$1"
246
+ local completed
247
+ completed="$(sanitize "$2")"
248
+ local next
249
+ next="$(sanitize "$3")"
250
+ shift 3
251
+
252
+ # Parse optional flags
253
+ local flag_summary="" flag_decisions="" flag_artifacts="" flag_next=""
254
+ while [[ $# -gt 0 ]]; do
255
+ case "$1" in
256
+ --summary)
257
+ shift
258
+ flag_summary="$(sanitize "${1:-}")"
259
+ ;;
260
+ --decisions)
261
+ shift
262
+ flag_decisions="$(sanitize "${1:-}")"
263
+ ;;
264
+ --artifacts)
265
+ shift
266
+ flag_artifacts="$(sanitize "${1:-}")"
267
+ ;;
268
+ --next)
269
+ shift
270
+ flag_next="$(sanitize "${1:-}")"
271
+ ;;
272
+ *)
273
+ # Ignore unknown flags for forward compatibility
274
+ ;;
275
+ esac
276
+ shift
277
+ done
278
+
279
+ # Build comment: header line always present
280
+ local comment="Stage: ${completed} complete → ready for ${next}"
281
+
282
+ # Append structured fields only if provided
283
+ if [[ -n "$flag_summary" ]]; then
284
+ comment="${comment}
285
+ Summary: ${flag_summary}"
286
+ fi
287
+ if [[ -n "$flag_decisions" ]]; then
288
+ comment="${comment}
289
+ Decisions: ${flag_decisions}"
290
+ fi
291
+ if [[ -n "$flag_artifacts" ]]; then
292
+ comment="${comment}
293
+ Artifacts: ${flag_artifacts}"
294
+ fi
295
+ if [[ -n "$flag_next" ]]; then
296
+ comment="${comment}
297
+ Next: ${flag_next}"
298
+ fi
299
+
300
+ if ! bd_comment "$issue_id" "$comment" > /dev/null; then
301
+ die "Failed to record stage transition on issue ${issue_id}"
302
+ fi
303
+
304
+ echo "Stage transition recorded on ${issue_id}: ${completed} → ${next}"
305
+ }
306
+
307
+ cmd_validate() {
308
+ if [[ $# -lt 1 ]]; then
309
+ echo "Usage: beads-context.sh validate <issue-id>" >&2
310
+ exit 1
311
+ fi
312
+
313
+ local issue_id="$1"
314
+ local warnings=0
315
+
316
+ # Get issue JSON
317
+ local json
318
+ json="$(bd show "$issue_id" --json 2>&1)" || {
319
+ echo "Error: Failed to retrieve issue ${issue_id}" >&2
320
+ exit 1
321
+ }
322
+
323
+ # Check for bd error patterns
324
+ if printf '%s' "$json" | grep -Eqi '^Error|Error resolving|Error fetching'; then
325
+ echo "Error: Issue not found: ${issue_id}" >&2
326
+ exit 1
327
+ fi
328
+
329
+ if [[ -z "$json" || "$json" == "[]" || "$json" == "null" ]]; then
330
+ echo "Error: Issue not found: ${issue_id}" >&2
331
+ exit 1
332
+ fi
333
+
334
+ # Extract fields — prefer jq, fall back to grep/sed
335
+ local description="" design=""
336
+ if command -v jq &>/dev/null; then
337
+ description="$(printf '%s' "$json" | jq -r 'if type == "array" then .[0].description else .description end // empty' 2>/dev/null || true)"
338
+ design="$(printf '%s' "$json" | jq -r 'if type == "array" then .[0].design else .design end // empty' 2>/dev/null || true)"
339
+ else
340
+ description="$(printf '%s' "$json" | grep -o '"description": *"[^"]*"' | head -1 | sed 's/^"description": *"//;s/"$//' || true)"
341
+ design="$(printf '%s' "$json" | grep -o '"design": *"[^"]*"' | head -1 | sed 's/^"design": *"//;s/"$//' || true)"
342
+ fi
343
+
344
+ # Check 1: Issue has description
345
+ if [[ -z "$description" ]]; then
346
+ echo "Warning: Issue ${issue_id} has no description"
347
+ warnings=$((warnings + 1))
348
+ fi
349
+
350
+ # Get comments to check for stage transitions
351
+ local comments
352
+ comments="$(bd comments list "$issue_id" 2>/dev/null || true)"
353
+
354
+ # Check 2: At least one stage transition exists
355
+ local has_transition=false
356
+ if echo "$comments" | grep -q 'Stage:.*complete'; then
357
+ has_transition=true
358
+ fi
359
+
360
+ if [[ "$has_transition" == "false" ]]; then
361
+ echo "Warning: No stage transition found on issue ${issue_id}"
362
+ warnings=$((warnings + 1))
363
+ fi
364
+
365
+ # Check 3: Most recent transition has summary
366
+ # Isolate the last transition block to avoid false-positives from earlier summaries
367
+ if [[ "$has_transition" == "true" ]]; then
368
+ local has_summary=false
369
+ local last_block
370
+ last_block="$(echo "$comments" | awk '/Stage:.*complete/{found=1; block=""} found{block=block"\n"$0} END{print block}')"
371
+ if echo "$last_block" | grep -q 'Summary:'; then
372
+ has_summary=true
373
+ fi
374
+ if [[ "$has_summary" == "false" ]]; then
375
+ echo "Warning: Most recent stage transition has no summary on issue ${issue_id}"
376
+ warnings=$((warnings + 1))
377
+ fi
378
+ fi
379
+
380
+ # Check 4: Design metadata is set if past plan stage
381
+ # "Past plan" means there's a transition FROM plan or any later stage
382
+ local past_plan=false
383
+ if echo "$comments" | grep -q 'Stage: plan complete'; then
384
+ past_plan=true
385
+ fi
386
+ if echo "$comments" | grep -Eq 'Stage: (dev|validate|ship|review|premerge) complete'; then
387
+ past_plan=true
388
+ fi
389
+
390
+ if [[ "$past_plan" == "true" && -z "$design" ]]; then
391
+ echo "Warning: Design metadata not set on issue ${issue_id} (past plan stage)"
392
+ warnings=$((warnings + 1))
393
+ fi
394
+
395
+ # Summary
396
+ if [[ $warnings -eq 0 ]]; then
397
+ echo "All context fields present on issue ${issue_id}"
398
+ else
399
+ echo "${warnings} warning(s) found on issue ${issue_id}"
400
+ fi
401
+
402
+ # Always exit 0 — advisory only
403
+ exit 0
404
+ }
405
+
406
+ # ── Main dispatcher ──────────────────────────────────────────────────────
407
+
408
+ if [[ $# -lt 1 ]]; then
409
+ usage
410
+ fi
411
+
412
+ subcommand="$1"
413
+ shift
414
+
415
+ case "$subcommand" in
416
+ set-design) cmd_set_design "$@" ;;
417
+ set-acceptance) cmd_set_acceptance "$@" ;;
418
+ update-progress) cmd_update_progress "$@" ;;
419
+ parse-progress) cmd_parse_progress "$@" ;;
420
+ stage-transition) cmd_stage_transition "$@" ;;
421
+ validate) cmd_validate "$@" ;;
422
+ *)
423
+ echo "Error: Unknown subcommand '${subcommand}'" >&2
424
+ usage
425
+ ;;
426
+ esac