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,314 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+
6
+ # ── Setup temp dir for test isolation ────────────────────────────────────
7
+ TEST_TMP="$(mktemp -d)"
8
+ trap 'rm -rf "$TEST_TMP"' EXIT
9
+
10
+ # Override TEAM_MAP_ROOT so tests don't touch real .beads/
11
+ export TEAM_MAP_ROOT="$TEST_TMP"
12
+
13
+ # ── Create mock directories ────────────────────────────────────────────
14
+ mock_dir="$(mktemp -d)"
15
+
16
+ # ── Mock gh (authenticated, returns user "testuser") ───────────────────
17
+ cat > "$mock_dir/gh" << 'MOCK'
18
+ #!/usr/bin/env bash
19
+ case "$1" in
20
+ auth)
21
+ if [[ "${2:-}" == "status" ]]; then
22
+ echo "github.com"
23
+ echo " ✓ Logged in to github.com account harshanandak"
24
+ exit 0
25
+ fi
26
+ ;;
27
+ api)
28
+ if [[ "${2:-}" == "user" ]]; then
29
+ echo "testuser"
30
+ exit 0
31
+ fi
32
+ ;;
33
+ issue)
34
+ if [[ "${2:-}" == "list" ]]; then
35
+ # Return JSON array of open issues
36
+ if [[ -n "${GH_MOCK_ISSUES:-}" ]]; then
37
+ echo "$GH_MOCK_ISSUES"
38
+ else
39
+ echo "[]"
40
+ fi
41
+ exit 0
42
+ fi
43
+ ;;
44
+ esac
45
+ echo "unknown gh call: $*" >&2
46
+ exit 1
47
+ MOCK
48
+ chmod +x "$mock_dir/gh"
49
+
50
+ # ── Mock gh (not authenticated) ────────────────────────────────────────
51
+ cat > "$mock_dir/gh-noauth" << 'MOCK'
52
+ #!/usr/bin/env bash
53
+ case "$1" in
54
+ auth)
55
+ if [[ "${2:-}" == "status" ]]; then
56
+ echo "You are not logged into any GitHub hosts." >&2
57
+ exit 1
58
+ fi
59
+ ;;
60
+ api)
61
+ echo "not logged in" >&2
62
+ exit 1
63
+ ;;
64
+ esac
65
+ exit 1
66
+ MOCK
67
+ chmod +x "$mock_dir/gh-noauth"
68
+
69
+ # ── Mock bd ────────────────────────────────────────────────────────────
70
+ cat > "$mock_dir/bd" << 'MOCK'
71
+ #!/usr/bin/env bash
72
+ case "$1" in
73
+ list)
74
+ if [[ -n "${BD_MOCK_OUTPUT:-}" ]]; then
75
+ echo "$BD_MOCK_OUTPUT"
76
+ fi
77
+ exit 0
78
+ ;;
79
+ show)
80
+ case "${2:-}" in
81
+ forge-aaa)
82
+ echo "◐ forge-aaa · Feature A [● P2 · IN_PROGRESS]"
83
+ echo "Owner: devone"
84
+ if [[ -z "${BD_MOCK_NO_GITHUB_AAA:-}" ]]; then
85
+ echo "State: github_issue:10"
86
+ fi
87
+ ;;
88
+ forge-bbb)
89
+ echo "○ forge-bbb · Feature B [● P2 · OPEN]"
90
+ echo "Owner: devtwo"
91
+ if [[ -z "${BD_MOCK_NO_GITHUB_BBB:-}" ]]; then
92
+ echo "State: github_issue:20"
93
+ fi
94
+ ;;
95
+ forge-ccc)
96
+ echo "◐ forge-ccc · Feature C [● P1 · IN_PROGRESS]"
97
+ echo "Owner: devone"
98
+ # forge-ccc never has github_issue (for orphan tests)
99
+ ;;
100
+ esac
101
+ exit 0
102
+ ;;
103
+ esac
104
+ exit 0
105
+ MOCK
106
+ chmod +x "$mock_dir/bd"
107
+
108
+ export GH_CMD="$mock_dir/gh"
109
+ export BD_CMD="$mock_dir/bd"
110
+
111
+ # Source the library under test
112
+ source "$SCRIPT_DIR/lib/agent-prompt.sh"
113
+ source "$SCRIPT_DIR/lib/identity.sh"
114
+ source "$SCRIPT_DIR/lib/verify.sh"
115
+
116
+ PASS=0
117
+ FAIL=0
118
+
119
+ assert_eq() {
120
+ local label="$1" expected="$2" actual="$3"
121
+ if [[ "$actual" == "$expected" ]]; then
122
+ PASS=$((PASS + 1)); echo " PASS: $label"
123
+ else
124
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected '$expected', got '$actual')"
125
+ fi
126
+ }
127
+
128
+ assert_contains() {
129
+ local label="$1" expected="$2" actual="$3"
130
+ if [[ "$actual" == *"$expected"* ]]; then
131
+ PASS=$((PASS + 1)); echo " PASS: $label"
132
+ else
133
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected to contain '$expected', got '$actual')"
134
+ fi
135
+ }
136
+
137
+ assert_not_contains() {
138
+ local label="$1" unexpected="$2" actual="$3"
139
+ if [[ "$actual" != *"$unexpected"* ]]; then
140
+ PASS=$((PASS + 1)); echo " PASS: $label"
141
+ else
142
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected NOT to contain '$unexpected')"
143
+ fi
144
+ }
145
+
146
+ assert_exit() {
147
+ local label="$1" expected="$2" actual="$3"
148
+ if [[ "$actual" -eq "$expected" ]]; then
149
+ PASS=$((PASS + 1)); echo " PASS: $label"
150
+ else
151
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected exit $expected, got $actual)"
152
+ fi
153
+ }
154
+
155
+ # ── Test 1: All clean → exit 0, all checks pass ─────────────────────────
156
+ echo "── Test 1: All clean → exit 0 ──"
157
+
158
+ # Setup: identity exists in team map
159
+ unset _GITHUB_USER_CACHE
160
+ export GH_CMD="$mock_dir/gh"
161
+ export BD_CMD="$mock_dir/bd"
162
+ team_map_add "testuser" "Test User"
163
+
164
+ # No beads issues, no github issues
165
+ export BD_MOCK_OUTPUT=""
166
+ export GH_MOCK_ISSUES="[]"
167
+
168
+ # Create empty mapping file
169
+ mkdir -p "$TEST_TMP/.github"
170
+ echo '{}' > "$TEST_TMP/.github/beads-mapping.json"
171
+
172
+ rc=0
173
+ output="$(cmd_verify 2>&1)" || rc=$?
174
+ assert_exit "all clean exits 0" 0 "$rc"
175
+ assert_contains "shows github cli check" "GitHub CLI" "$output"
176
+ assert_contains "shows identity check" "Identity" "$output"
177
+
178
+ # ── Test 2: gh not authenticated → exit 1, error message ────────────────
179
+ echo ""
180
+ echo "── Test 2: gh not authenticated → exit 1 ──"
181
+
182
+ unset _GITHUB_USER_CACHE
183
+ export GH_CMD="$mock_dir/gh-noauth"
184
+
185
+ rc=0
186
+ output="$(cmd_verify 2>&1)" || rc=$?
187
+ assert_exit "no auth exits 1" 1 "$rc"
188
+ assert_contains "error about auth" "not authenticated" "$output"
189
+ assert_contains "error has agent prefix" "FORGE_AGENT_7f3a:ERROR" "$output"
190
+
191
+ # ── Test 3: Orphan Beads issue (no github_issue state) → detected ───────
192
+ echo ""
193
+ echo "── Test 3: Orphan Beads issue detected ──"
194
+
195
+ unset _GITHUB_USER_CACHE
196
+ export GH_CMD="$mock_dir/gh"
197
+ export BD_MOCK_OUTPUT="◐ forge-ccc · Feature C"
198
+ export GH_MOCK_ISSUES="[]"
199
+
200
+ # forge-ccc has no github_issue state (see mock bd above)
201
+ rc=0
202
+ output="$(cmd_verify 2>&1)" || rc=$?
203
+ assert_exit "orphan beads exits 1" 1 "$rc"
204
+ assert_contains "orphan beads detected" "forge-ccc" "$output"
205
+ assert_contains "prompt for sync" "forge team sync-issue" "$output"
206
+
207
+ # ── Test 4: Orphan GitHub issue (not in mapping) → detected ─────────────
208
+ echo ""
209
+ echo "── Test 4: Orphan GitHub issue detected ──"
210
+
211
+ unset _GITHUB_USER_CACHE
212
+ export GH_CMD="$mock_dir/gh"
213
+ export BD_MOCK_OUTPUT=""
214
+ export GH_MOCK_ISSUES='[{"number":42,"title":"Some feature"},{"number":45,"title":"Another feature"}]'
215
+
216
+ # Mapping file has neither #42 nor #45
217
+ echo '{}' > "$TEST_TMP/.github/beads-mapping.json"
218
+
219
+ rc=0
220
+ output="$(cmd_verify 2>&1)" || rc=$?
221
+ assert_exit "orphan github exits 1" 1 "$rc"
222
+ assert_contains "orphan github #42 detected" "#42" "$output"
223
+ assert_contains "orphan github #45 detected" "#45" "$output"
224
+ assert_contains "prompt for import" "forge team import" "$output"
225
+
226
+ # ── Test 5: Assignee mismatch → info message ────────────────────────────
227
+ echo ""
228
+ echo "── Test 5: Assignee mismatch → info message ──"
229
+
230
+ unset _GITHUB_USER_CACHE
231
+ export GH_CMD="$mock_dir/gh"
232
+
233
+ # Setup: forge-aaa has github_issue:10, forge-bbb has github_issue:20
234
+ export BD_MOCK_OUTPUT="◐ forge-aaa · Feature A
235
+ ○ forge-bbb · Feature B"
236
+
237
+ # Create mapping with both issues
238
+ cat > "$TEST_TMP/.github/beads-mapping.json" << 'EOF'
239
+ {
240
+ "10": {"beads_id": "forge-aaa", "assignee": "devone"},
241
+ "20": {"beads_id": "forge-bbb", "assignee": "devtwo"},
242
+ "42": {"beads_id": "forge-ddd", "assignee": "devone"}
243
+ }
244
+ EOF
245
+
246
+ # Override gh to return mismatched assignees from github
247
+ cat > "$mock_dir/gh-mismatch" << 'MOCK'
248
+ #!/usr/bin/env bash
249
+ case "$1" in
250
+ auth)
251
+ if [[ "${2:-}" == "status" ]]; then
252
+ echo " ✓ Logged in to github.com account harshanandak"
253
+ exit 0
254
+ fi
255
+ ;;
256
+ api)
257
+ if [[ "${2:-}" == "user" ]]; then
258
+ echo "testuser"
259
+ exit 0
260
+ fi
261
+ ;;
262
+ issue)
263
+ if [[ "${2:-}" == "list" ]]; then
264
+ echo '[{"number":10,"title":"Feature A"},{"number":20,"title":"Feature B"},{"number":42,"title":"Feature D"}]'
265
+ exit 0
266
+ fi
267
+ # view --json assignees
268
+ if [[ "${2:-}" == "view" ]]; then
269
+ case "${3:-}" in
270
+ 10)
271
+ echo '{"assignees":[{"login":"devone"}]}'
272
+ ;;
273
+ 20)
274
+ echo '{"assignees":[{"login":"devthree"}]}'
275
+ ;;
276
+ 42)
277
+ echo '{"assignees":[{"login":"devone"}]}'
278
+ ;;
279
+ esac
280
+ exit 0
281
+ fi
282
+ ;;
283
+ esac
284
+ echo "unknown gh call: $*" >&2
285
+ exit 1
286
+ MOCK
287
+ chmod +x "$mock_dir/gh-mismatch"
288
+ export GH_CMD="$mock_dir/gh-mismatch"
289
+
290
+ rc=0
291
+ output="$(cmd_verify 2>&1)" || rc=$?
292
+ assert_exit "assignee mismatch exits 1" 1 "$rc"
293
+ assert_contains "mismatch info message" "mismatch" "$output"
294
+ assert_contains "mismatch shows forge-bbb" "forge-bbb" "$output"
295
+ assert_contains "mismatch shows devtwo vs devthree" "devtwo" "$output"
296
+
297
+ # ── Test 6: No issues at all (no beads, no github) → exit 0 ─────────────
298
+ echo ""
299
+ echo "── Test 6: No issues at all → exit 0 ──"
300
+
301
+ unset _GITHUB_USER_CACHE
302
+ export GH_CMD="$mock_dir/gh"
303
+ export BD_MOCK_OUTPUT=""
304
+ export GH_MOCK_ISSUES="[]"
305
+ echo '{}' > "$TEST_TMP/.github/beads-mapping.json"
306
+
307
+ rc=0
308
+ output="$(cmd_verify 2>&1)" || rc=$?
309
+ assert_exit "no issues exits 0" 0 "$rc"
310
+
311
+ # ── Results ──────────────────────────────────────────────────────────────
312
+ echo ""
313
+ echo "Results: $PASS passed, $FAIL failed"
314
+ [[ $FAIL -eq 0 ]] && exit 0 || exit 1
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
5
+ PASS=0
6
+ FAIL=0
7
+
8
+ assert_contains_file() {
9
+ local label="$1" pattern="$2" file="$3"
10
+ if grep -q "$pattern" "$file" 2>/dev/null; then
11
+ PASS=$((PASS + 1)); echo " PASS: $label"
12
+ else
13
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (pattern '$pattern' not in $file)"
14
+ fi
15
+ }
16
+
17
+ echo "── /status integration ──"
18
+ STATUS="$SCRIPT_DIR/.claude/commands/status.md"
19
+ assert_contains_file "status calls workload --me" "forge-team.*workload.*--me" "$STATUS"
20
+ assert_contains_file "status calls dashboard" "forge-team.*dashboard" "$STATUS"
21
+
22
+ echo ""
23
+ echo "── /plan integration ──"
24
+ PLAN="$SCRIPT_DIR/.claude/commands/plan.md"
25
+ assert_contains_file "plan calls verify" "forge-team.*verify" "$PLAN"
26
+
27
+ echo ""
28
+ echo "── /ship integration ──"
29
+ SHIP="$SCRIPT_DIR/.claude/commands/ship.md"
30
+ assert_contains_file "ship calls sync" "forge-team.*sync" "$SHIP"
31
+ assert_contains_file "ship calls verify" "forge-team.*verify" "$SHIP"
32
+
33
+ echo ""
34
+ echo "── Sync check ──"
35
+ if node "$SCRIPT_DIR/scripts/sync-commands.js" --check 2>&1 | grep -qi 'error\|drift\|mismatch'; then
36
+ FAIL=$((FAIL + 1)); echo " FAIL: sync drift detected"
37
+ else
38
+ PASS=$((PASS + 1)); echo " PASS: all agent dirs in sync"
39
+ fi
40
+
41
+ echo ""
42
+ echo "Results: $PASS passed, $FAIL failed"
43
+ [[ $FAIL -eq 0 ]] && exit 0 || exit 1
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+
6
+ # ── Setup temp dir for test isolation ────────────────────────────────────
7
+ TEST_TMP="$(mktemp -d)"
8
+ trap 'rm -rf "$TEST_TMP"' EXIT
9
+
10
+ # Override TEAM_MAP_ROOT so tests don't touch real .beads/
11
+ export TEAM_MAP_ROOT="$TEST_TMP"
12
+
13
+ # Fix "now" so stale detection is deterministic
14
+ # forge-bbb updated 2026-03-25T10:00:00Z → 50h ago at this fixed time
15
+ export WORKLOAD_NOW="2026-03-27T12:00:00Z"
16
+
17
+ # ── Create mock gh ───────────────────────────────────────────────────────
18
+ mock_dir="$(mktemp -d)"
19
+
20
+ cat > "$mock_dir/gh" << 'MOCK'
21
+ #!/usr/bin/env bash
22
+ case "$1 $2" in
23
+ "api user")
24
+ echo "devone"
25
+ ;;
26
+ esac
27
+ MOCK
28
+ chmod +x "$mock_dir/gh"
29
+
30
+ # ── Create mock bd ───────────────────────────────────────────────────────
31
+ cat > "$mock_dir/bd" << 'MOCK'
32
+ #!/usr/bin/env bash
33
+ case "$1 $2" in
34
+ "list --status=open,in_progress")
35
+ echo "◐ forge-aaa · Feature A"
36
+ echo "○ forge-bbb · Feature B"
37
+ echo "◐ forge-ccc · Feature C"
38
+ ;;
39
+ "show forge-aaa")
40
+ echo "◐ forge-aaa · Feature A [● P2 · IN_PROGRESS]"
41
+ echo "Owner: devone"
42
+ echo "Updated: 2026-03-27T10:00:00Z"
43
+ ;;
44
+ "show forge-bbb")
45
+ echo "○ forge-bbb · Feature B [● P2 · OPEN]"
46
+ echo "Owner: devtwo"
47
+ echo "Updated: 2026-03-25T10:00:00Z"
48
+ ;;
49
+ "show forge-ccc")
50
+ echo "◐ forge-ccc · Feature C [● P1 · IN_PROGRESS]"
51
+ echo "Owner: devone"
52
+ echo "Updated: 2026-03-27T09:00:00Z"
53
+ echo ""
54
+ echo "DEPENDS ON"
55
+ echo " → forge-aaa: Feature A"
56
+ ;;
57
+ esac
58
+ MOCK
59
+ chmod +x "$mock_dir/bd"
60
+
61
+ # ── Create mock bd with no issues ────────────────────────────────────────
62
+ cat > "$mock_dir/bd-empty" << 'MOCK'
63
+ #!/usr/bin/env bash
64
+ case "$1 $2" in
65
+ "list --status=open,in_progress")
66
+ echo ""
67
+ ;;
68
+ esac
69
+ MOCK
70
+ chmod +x "$mock_dir/bd-empty"
71
+
72
+ export GH_CMD="$mock_dir/gh"
73
+ export BD_CMD="$mock_dir/bd"
74
+
75
+ # Source the library under test
76
+ source "$SCRIPT_DIR/lib/workload.sh"
77
+
78
+ PASS=0
79
+ FAIL=0
80
+
81
+ assert_eq() {
82
+ local label="$1" expected="$2" actual="$3"
83
+ if [[ "$actual" == "$expected" ]]; then
84
+ PASS=$((PASS + 1)); echo " PASS: $label"
85
+ else
86
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected '$expected', got '$actual')"
87
+ fi
88
+ }
89
+
90
+ assert_contains() {
91
+ local label="$1" expected="$2" actual="$3"
92
+ if [[ "$actual" == *"$expected"* ]]; then
93
+ PASS=$((PASS + 1)); echo " PASS: $label"
94
+ else
95
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected to contain '$expected', got '$actual')"
96
+ fi
97
+ }
98
+
99
+ assert_not_contains() {
100
+ local label="$1" unexpected="$2" actual="$3"
101
+ if [[ "$actual" != *"$unexpected"* ]]; then
102
+ PASS=$((PASS + 1)); echo " PASS: $label"
103
+ else
104
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected NOT to contain '$unexpected', got '$actual')"
105
+ fi
106
+ }
107
+
108
+ assert_exit() {
109
+ local label="$1" expected="$2" actual="$3"
110
+ if [[ "$actual" -eq "$expected" ]]; then
111
+ PASS=$((PASS + 1)); echo " PASS: $label"
112
+ else
113
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected exit $expected, got $actual)"
114
+ fi
115
+ }
116
+
117
+ # ── Test 1: All developers shown with grouped issues ─────────────────────
118
+ echo "── Test 1: All developers shown with grouped issues ──"
119
+ unset _GITHUB_USER_CACHE
120
+ rc=0
121
+ output="$(cmd_workload 2>/dev/null)" || rc=$?
122
+ assert_exit "exits 0" 0 "$rc"
123
+ assert_contains "shows devone header" "Developer: devone" "$output"
124
+ assert_contains "shows devtwo header" "Developer: devtwo" "$output"
125
+ assert_contains "shows forge-aaa" "forge-aaa" "$output"
126
+ assert_contains "shows forge-bbb" "forge-bbb" "$output"
127
+ assert_contains "shows forge-ccc" "forge-ccc" "$output"
128
+
129
+ # ── Test 2: --developer=devone filters correctly ─────────────────────────
130
+ echo ""
131
+ echo "── Test 2: --developer=devone filters correctly ──"
132
+ unset _GITHUB_USER_CACHE
133
+ rc=0
134
+ output="$(cmd_workload --developer=devone 2>/dev/null)" || rc=$?
135
+ assert_exit "exits 0" 0 "$rc"
136
+ assert_contains "shows devone" "Developer: devone" "$output"
137
+ assert_not_contains "excludes devtwo" "Developer: devtwo" "$output"
138
+ assert_contains "shows forge-aaa" "forge-aaa" "$output"
139
+ assert_contains "shows forge-ccc" "forge-ccc" "$output"
140
+ assert_not_contains "excludes forge-bbb" "forge-bbb" "$output"
141
+
142
+ # ── Test 3: --me uses get_github_user() to filter ────────────────────────
143
+ echo ""
144
+ echo "── Test 3: --me uses get_github_user() to filter ──"
145
+ unset _GITHUB_USER_CACHE
146
+ rc=0
147
+ output="$(cmd_workload --me 2>/dev/null)" || rc=$?
148
+ assert_exit "exits 0" 0 "$rc"
149
+ assert_contains "shows devone (current user)" "Developer: devone" "$output"
150
+ assert_not_contains "excludes devtwo" "Developer: devtwo" "$output"
151
+
152
+ # ── Test 4: No issues → "No active work" ─────────────────────────────────
153
+ echo ""
154
+ echo "── Test 4: No issues → No active work ──"
155
+ unset _GITHUB_USER_CACHE
156
+ export BD_CMD="$mock_dir/bd-empty"
157
+ rc=0
158
+ output="$(cmd_workload 2>/dev/null)" || rc=$?
159
+ assert_exit "exits 0" 0 "$rc"
160
+ assert_contains "shows no active work message" "No active work" "$output"
161
+ export BD_CMD="$mock_dir/bd"
162
+
163
+ # ── Test 5: Stale assignment flagged (>48h) ──────────────────────────────
164
+ echo ""
165
+ echo "── Test 5: Stale assignment flagged (>48h) ──"
166
+ unset _GITHUB_USER_CACHE
167
+ rc=0
168
+ output="$(cmd_workload 2>/dev/null)" || rc=$?
169
+ assert_exit "exits 0" 0 "$rc"
170
+ # forge-bbb was updated 2026-03-25T10:00:00Z, which is >48h ago from "now"
171
+ assert_contains "stale flag on forge-bbb" "stale:" "$output"
172
+
173
+ # ── Test 6: --format=json returns valid JSON ─────────────────────────────
174
+ echo ""
175
+ echo "── Test 6: --format=json returns valid JSON ──"
176
+ unset _GITHUB_USER_CACHE
177
+ rc=0
178
+ output="$(cmd_workload --format=json 2>/dev/null)" || rc=$?
179
+ assert_exit "exits 0" 0 "$rc"
180
+ # Validate it's parseable JSON
181
+ if echo "$output" | jq . >/dev/null 2>&1; then
182
+ PASS=$((PASS + 1)); echo " PASS: output is valid JSON"
183
+ else
184
+ FAIL=$((FAIL + 1)); echo " FAIL: output is not valid JSON: $output"
185
+ fi
186
+ # Check JSON has developer keys
187
+ if echo "$output" | jq -e '.devone' >/dev/null 2>&1; then
188
+ PASS=$((PASS + 1)); echo " PASS: JSON contains devone key"
189
+ else
190
+ FAIL=$((FAIL + 1)); echo " FAIL: JSON missing devone key"
191
+ fi
192
+ if echo "$output" | jq -e '.devtwo' >/dev/null 2>&1; then
193
+ PASS=$((PASS + 1)); echo " PASS: JSON contains devtwo key"
194
+ else
195
+ FAIL=$((FAIL + 1)); echo " FAIL: JSON missing devtwo key"
196
+ fi
197
+
198
+ # ── Test 7: Blocked issue flagged ─────────────────────────────────────────
199
+ echo ""
200
+ echo "── Test 7: Blocked issue flagged ──"
201
+ unset _GITHUB_USER_CACHE
202
+ rc=0
203
+ output="$(cmd_workload 2>/dev/null)" || rc=$?
204
+ assert_contains "blocked flag on forge-ccc" "blocked by forge-aaa" "$output"
205
+
206
+ # ── Results ──────────────────────────────────────────────────────────────
207
+ echo ""
208
+ echo "Results: $PASS passed, $FAIL failed"
209
+ [[ $FAIL -eq 0 ]] && exit 0 || exit 1
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Bot comment parser/builder for GitHub-Beads sync.
3
+ *
4
+ * Produces and parses Vercel-inspired sync comments that use an
5
+ * edit-don't-create pattern with a stable HTML comment tag.
6
+ *
7
+ * @module comment
8
+ */
9
+
10
+ /** HTML comment prefix used to identify sync comments. */
11
+ export const SYNC_TAG_PREFIX = '<!-- beads-sync:';
12
+
13
+ /**
14
+ * Build a markdown sync comment for a GitHub issue.
15
+ *
16
+ * @param {string} beadsId - The Beads issue ID (e.g. "forge-abc").
17
+ * @param {number} issueNumber - The GitHub issue number.
18
+ * @param {object} [metadata] - Optional metadata fields.
19
+ * @param {string} [metadata.type] - Issue type (feature, bug, etc.).
20
+ * @param {string} [metadata.priority] - Priority label (P0-P4).
21
+ * @param {string} [metadata.externalRef] - External reference string.
22
+ * @returns {string} Markdown comment body.
23
+ */
24
+ export function buildComment(beadsId, issueNumber, metadata = {}) {
25
+ const timestamp = new Date().toISOString();
26
+
27
+ const detailLines = [];
28
+ if (metadata.type) detailLines.push(`- Type: ${metadata.type}`);
29
+ if (metadata.priority != null) detailLines.push(`- Priority: ${metadata.priority}`);
30
+ if (metadata.externalRef) detailLines.push(`- External ref: ${metadata.externalRef}`);
31
+ detailLines.push(`- Synced: ${timestamp}`);
32
+
33
+ return [
34
+ `${SYNC_TAG_PREFIX}${issueNumber} -->`,
35
+ `**Beads:** \`${beadsId}\``,
36
+ '<details>',
37
+ '<summary>Sync details</summary>',
38
+ '',
39
+ ...detailLines,
40
+ '</details>',
41
+ ].join('\n');
42
+ }
43
+
44
+ /**
45
+ * Parse a comment body and extract sync metadata.
46
+ *
47
+ * @param {string|null|undefined} commentBody - Raw comment markdown.
48
+ * @returns {{ beadsId: string, issueNumber: number } | null}
49
+ * Extracted IDs, or `null` if the comment is not a sync comment.
50
+ */
51
+ export function parseComment(commentBody) {
52
+ if (!commentBody) return null;
53
+
54
+ const tagMatch = commentBody.match(/<!--\s*beads-sync:(\d+)\s*-->/);
55
+ if (!tagMatch) return null;
56
+
57
+ const beadsMatch = commentBody.match(/\*\*Beads:\*\*\s*`([a-zA-Z]+-[a-zA-Z0-9]+)`/);
58
+ if (!beadsMatch) return null;
59
+
60
+ return {
61
+ beadsId: beadsMatch[1],
62
+ issueNumber: Number(tagMatch[1]),
63
+ };
64
+ }