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,179 @@
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 gh ───────────────────────────────────────────────────────
14
+ mock_dir="$(mktemp -d)"
15
+
16
+ cat > "$mock_dir/gh" << 'MOCK'
17
+ #!/usr/bin/env bash
18
+ case "$1 $2" in
19
+ "issue view")
20
+ # Determine issue number from args
21
+ issue_num=""
22
+ for arg in "$@"; do
23
+ if [[ "$arg" =~ ^[0-9]+$ ]]; then
24
+ issue_num="$arg"
25
+ break
26
+ fi
27
+ done
28
+ if echo "$*" | grep -q "assignees"; then
29
+ case "$issue_num" in
30
+ 42) echo "otherdev" ;;
31
+ 43) echo "" ;;
32
+ 44) echo "testuser" ;;
33
+ *) echo "" ;;
34
+ esac
35
+ fi
36
+ ;;
37
+ "issue edit")
38
+ echo "edited"
39
+ ;;
40
+ "api user")
41
+ echo "testuser"
42
+ ;;
43
+ esac
44
+ MOCK
45
+ chmod +x "$mock_dir/gh"
46
+
47
+ # Mock gh that fails (not authenticated)
48
+ cat > "$mock_dir/gh-fail" << 'MOCK'
49
+ #!/usr/bin/env bash
50
+ echo "not logged in" >&2
51
+ exit 1
52
+ MOCK
53
+ chmod +x "$mock_dir/gh-fail"
54
+
55
+ # ── Create mock bd ───────────────────────────────────────────────────────
56
+ cat > "$mock_dir/bd" << 'MOCK'
57
+ #!/usr/bin/env bash
58
+ case "$1" in
59
+ show)
60
+ case "$2" in
61
+ forge-assigned) echo "forge-assigned Assigned Issue [github_issue:42]" ;;
62
+ forge-unassigned) echo "forge-unassigned Unassigned Issue [github_issue:43]" ;;
63
+ forge-mine) echo "forge-mine My Issue [github_issue:44]" ;;
64
+ forge-nogithub) echo "forge-nogithub No GitHub Issue" ;;
65
+ *) echo "unknown issue" ;;
66
+ esac ;;
67
+ update) echo "Updated" ;;
68
+ comments) echo "Comment added" ;;
69
+ esac
70
+ MOCK
71
+ chmod +x "$mock_dir/bd"
72
+
73
+ export GH_CMD="$mock_dir/gh"
74
+ export BD_CMD="$mock_dir/bd"
75
+
76
+ # Source the library under test
77
+ source "$SCRIPT_DIR/lib/claim.sh"
78
+
79
+ PASS=0
80
+ FAIL=0
81
+
82
+ assert_eq() {
83
+ local label="$1" expected="$2" actual="$3"
84
+ if [[ "$actual" == "$expected" ]]; then
85
+ PASS=$((PASS + 1)); echo " PASS: $label"
86
+ else
87
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected '$expected', got '$actual')"
88
+ fi
89
+ }
90
+
91
+ assert_contains() {
92
+ local label="$1" expected="$2" actual="$3"
93
+ if [[ "$actual" == *"$expected"* ]]; then
94
+ PASS=$((PASS + 1)); echo " PASS: $label"
95
+ else
96
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected to contain '$expected', got '$actual')"
97
+ fi
98
+ }
99
+
100
+ assert_exit() {
101
+ local label="$1" expected="$2" actual="$3"
102
+ if [[ "$actual" -eq "$expected" ]]; then
103
+ PASS=$((PASS + 1)); echo " PASS: $label"
104
+ else
105
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected exit $expected, got $actual)"
106
+ fi
107
+ }
108
+
109
+ # ── Test 1: Pre-claim on unassigned issue → returns 0 ───────────────────
110
+ echo "── Test 1: Pre-claim on unassigned issue ──"
111
+ unset _GITHUB_USER_CACHE
112
+ mkdir -p "$TEST_TMP/.beads"
113
+ rc=0
114
+ pre_claim_check "forge-unassigned" 2>/dev/null || rc=$?
115
+ assert_exit "unassigned issue returns 0" 0 "$rc"
116
+
117
+ # ── Test 2: Pre-claim on issue assigned to someone else → returns 1 ─────
118
+ echo ""
119
+ echo "── Test 2: Pre-claim on issue assigned to someone else ──"
120
+ unset _GITHUB_USER_CACHE
121
+ rc=0
122
+ output="$(pre_claim_check "forge-assigned" 2>&1)" || rc=$?
123
+ assert_exit "assigned to other returns 1" 1 "$rc"
124
+ assert_contains "output mentions claimed" "claimed by" "$output"
125
+ assert_contains "output mentions PROMPT prefix" "FORGE_AGENT_7f3a:PROMPT:" "$output"
126
+
127
+ # ── Test 3: Pre-claim on issue assigned to current user → returns 0 ─────
128
+ echo ""
129
+ echo "── Test 3: Pre-claim on issue assigned to current user ──"
130
+ unset _GITHUB_USER_CACHE
131
+ rc=0
132
+ pre_claim_check "forge-mine" 2>/dev/null || rc=$?
133
+ assert_exit "assigned to self returns 0" 0 "$rc"
134
+
135
+ # ── Test 4: --force overrides pre-claim check, logs override ────────────
136
+ echo ""
137
+ echo "── Test 4: --force overrides pre-claim check ──"
138
+ unset _GITHUB_USER_CACHE
139
+ mkdir -p "$TEST_TMP/.beads"
140
+ rc=0
141
+ output="$(forge_team_claim "forge-assigned" "--force" 2>&1)" || rc=$?
142
+ assert_exit "force claim succeeds" 0 "$rc"
143
+ assert_contains "confirmation output" "claimed" "$output"
144
+
145
+ # ── Test 5: Claim with lock succeeds → bd update --claim AND gh issue edit both called ──
146
+ echo ""
147
+ echo "── Test 5: Claim with lock succeeds ──"
148
+ unset _GITHUB_USER_CACHE
149
+ mkdir -p "$TEST_TMP/.beads"
150
+ # Use the unassigned issue so pre-claim passes
151
+ rc=0
152
+ output="$(forge_team_claim "forge-unassigned" 2>&1)" || rc=$?
153
+ assert_exit "claim succeeds" 0 "$rc"
154
+ assert_contains "confirmation output" "claimed" "$output"
155
+
156
+ # ── Test 6: Missing GitHub issue number → error ─────────────────────────
157
+ echo ""
158
+ echo "── Test 6: Missing GitHub issue number ──"
159
+ unset _GITHUB_USER_CACHE
160
+ rc=0
161
+ output="$(pre_claim_check "forge-nogithub" 2>&1)" || rc=$?
162
+ assert_exit "missing issue number returns 1" 1 "$rc"
163
+ assert_contains "error about missing issue" "github_issue" "$output"
164
+
165
+ # ── Test 7: gh not authenticated → error ─────────────────────────────────
166
+ echo ""
167
+ echo "── Test 7: gh not authenticated ──"
168
+ unset _GITHUB_USER_CACHE
169
+ export GH_CMD="$mock_dir/gh-fail"
170
+ rc=0
171
+ output="$(pre_claim_check "forge-unassigned" 2>&1)" || rc=$?
172
+ assert_exit "unauthenticated returns 1" 1 "$rc"
173
+ # Restore working mock
174
+ export GH_CMD="$mock_dir/gh"
175
+
176
+ # ── Results ──────────────────────────────────────────────────────────────
177
+ echo ""
178
+ echo "Results: $PASS passed, $FAIL failed"
179
+ [[ $FAIL -eq 0 ]] && exit 0 || exit 1
@@ -0,0 +1,170 @@
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
+ # ── Create mock bd ───────────────────────────────────────────────────────
11
+ mock_dir="$(mktemp -d)"
12
+
13
+ cat > "$mock_dir/bd" << 'MOCK'
14
+ #!/usr/bin/env bash
15
+ case "$1" in
16
+ list)
17
+ if echo "$*" | grep -q -- "--status=open,in_progress"; then
18
+ if [[ -n "${BD_MOCK_EMPTY:-}" ]]; then
19
+ # No output for empty test
20
+ :
21
+ elif [[ -n "${BD_MOCK_SINGLE:-}" ]]; then
22
+ echo "◐ forge-aaa · Feature A"
23
+ else
24
+ echo "◐ forge-aaa · Feature A"
25
+ echo "○ forge-bbb · Feature B"
26
+ echo "◐ forge-ccc · Feature C"
27
+ fi
28
+ fi
29
+ ;;
30
+ show)
31
+ case "$2" in
32
+ forge-aaa)
33
+ echo "◐ forge-aaa · Feature A [● P2 · IN_PROGRESS]"
34
+ echo "Owner: devone"
35
+ echo "Updated: 2026-03-27T10:00:00Z"
36
+ ;;
37
+ forge-bbb)
38
+ echo "○ forge-bbb · Feature B [● P2 · OPEN]"
39
+ echo "Owner: devtwo"
40
+ echo "Updated: 2026-03-24T10:00:00Z"
41
+ ;;
42
+ forge-ccc)
43
+ echo "◐ forge-ccc · Feature C [● P1 · IN_PROGRESS]"
44
+ echo "Owner: devone"
45
+ echo "Updated: 2026-03-27T09:00:00Z"
46
+ echo ""
47
+ echo "DEPENDS ON"
48
+ echo " → ◐ forge-aaa: Feature A"
49
+ ;;
50
+ esac
51
+ ;;
52
+ esac
53
+ MOCK
54
+ chmod +x "$mock_dir/bd"
55
+
56
+ export BD_CMD="$mock_dir/bd"
57
+
58
+ # Source the library under test
59
+ source "$SCRIPT_DIR/lib/dashboard.sh"
60
+
61
+ PASS=0
62
+ FAIL=0
63
+
64
+ assert_eq() {
65
+ local label="$1" expected="$2" actual="$3"
66
+ if [[ "$actual" == "$expected" ]]; then
67
+ PASS=$((PASS + 1)); echo " PASS: $label"
68
+ else
69
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected '$expected', got '$actual')"
70
+ fi
71
+ }
72
+
73
+ assert_contains() {
74
+ local label="$1" expected="$2" actual="$3"
75
+ if [[ "$actual" == *"$expected"* ]]; then
76
+ PASS=$((PASS + 1)); echo " PASS: $label"
77
+ else
78
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected to contain '$expected', got '$actual')"
79
+ fi
80
+ }
81
+
82
+ assert_not_contains() {
83
+ local label="$1" unexpected="$2" actual="$3"
84
+ if [[ "$actual" != *"$unexpected"* ]]; then
85
+ PASS=$((PASS + 1)); echo " PASS: $label"
86
+ else
87
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected NOT to contain '$unexpected')"
88
+ fi
89
+ }
90
+
91
+ assert_exit() {
92
+ local label="$1" expected="$2" actual="$3"
93
+ if [[ "$actual" -eq "$expected" ]]; then
94
+ PASS=$((PASS + 1)); echo " PASS: $label"
95
+ else
96
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected exit $expected, got $actual)"
97
+ fi
98
+ }
99
+
100
+ # ── Test 1: Full dashboard with multiple developers ──────────────────────
101
+ echo "── Test 1: Full dashboard with multiple developers ──"
102
+
103
+ # Override _now to a fixed time: 2026-03-27T12:00:00Z = 1774836000
104
+ export FORGE_NOW=1774836000
105
+
106
+ rc=0
107
+ output="$(cmd_dashboard 2>&1)" || rc=$?
108
+ assert_exit "dashboard exits 0" 0 "$rc"
109
+ assert_contains "shows Team Dashboard header" "Team Dashboard" "$output"
110
+ assert_contains "shows devone stats" "devone" "$output"
111
+ assert_contains "shows devtwo stats" "devtwo" "$output"
112
+ assert_contains "shows total summary" "Total:" "$output"
113
+
114
+ # ── Test 2: Stale issue flagged (forge-bbb >48h old) ────────────────────
115
+ echo ""
116
+ echo "── Test 2: Stale issue flagged ──"
117
+
118
+ # forge-bbb updated 2026-03-24T10:00:00Z, now is 2026-03-27T12:00:00Z => ~74h
119
+ assert_contains "stale section shown" "Stale assignments" "$output"
120
+ assert_contains "forge-bbb flagged as stale" "forge-bbb" "$output"
121
+ assert_contains "stale issue shows devtwo" "devtwo" "$output"
122
+
123
+ # ── Test 3: Blocked issue detected (forge-ccc depends on forge-aaa) ─────
124
+ echo ""
125
+ echo "── Test 3: Blocked issue detected ──"
126
+
127
+ assert_contains "blocked section shown" "Blocked issues" "$output"
128
+ assert_contains "forge-ccc flagged as blocked" "forge-ccc" "$output"
129
+ assert_contains "blocked shows dependency" "forge-aaa" "$output"
130
+
131
+ # ── Test 4: No issues → "No active issues" ──────────────────────────────
132
+ echo ""
133
+ echo "── Test 4: No issues → clear message ──"
134
+
135
+ export BD_MOCK_EMPTY=1
136
+ rc=0
137
+ output_empty="$(cmd_dashboard 2>&1)" || rc=$?
138
+ assert_exit "empty dashboard exits 0" 0 "$rc"
139
+ assert_contains "shows no active issues" "No active issues" "$output_empty"
140
+ unset BD_MOCK_EMPTY
141
+
142
+ # ── Test 5: Single developer → works correctly ──────────────────────────
143
+ echo ""
144
+ echo "── Test 5: Single developer ──"
145
+
146
+ export BD_MOCK_SINGLE=1
147
+ rc=0
148
+ output_single="$(cmd_dashboard 2>&1)" || rc=$?
149
+ assert_exit "single dev dashboard exits 0" 0 "$rc"
150
+ assert_contains "shows devone" "devone" "$output_single"
151
+ assert_not_contains "no devtwo in single mode" "devtwo" "$output_single"
152
+ unset BD_MOCK_SINGLE
153
+
154
+ # ── Test 6: --format=json → valid JSON ──────────────────────────────────
155
+ echo ""
156
+ echo "── Test 6: --format=json → valid JSON ──"
157
+
158
+ rc=0
159
+ output_json="$(cmd_dashboard --format=json 2>&1)" || rc=$?
160
+ assert_exit "json format exits 0" 0 "$rc"
161
+ # Check it starts with { and contains expected keys
162
+ assert_contains "json has developers key" "developers" "$output_json"
163
+ assert_contains "json has total key" "total" "$output_json"
164
+ assert_contains "json has stale key" "stale" "$output_json"
165
+ assert_contains "json has blocked key" "blocked" "$output_json"
166
+
167
+ # ── Results ──────────────────────────────────────────────────────────────
168
+ echo ""
169
+ echo "Results: $PASS passed, $FAIL failed"
170
+ [[ $FAIL -eq 0 ]] && exit 0 || exit 1
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ # Tests for forge-team CLI dispatcher (index.sh)
3
+ # Exit codes: 0=all pass, 1=any failure
4
+
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ DISPATCHER="$SCRIPT_DIR/../index.sh"
9
+
10
+ PASS=0
11
+ FAIL=0
12
+
13
+ assert_exit() {
14
+ local desc="$1" expected="$2"
15
+ shift 2
16
+ "$@" >/dev/null 2>&1
17
+ local actual=$?
18
+ if [[ "$actual" -eq "$expected" ]]; then
19
+ echo " PASS: $desc"
20
+ ((PASS++))
21
+ else
22
+ echo " FAIL: $desc (expected exit=$expected, got exit=$actual)"
23
+ ((FAIL++))
24
+ fi
25
+ }
26
+
27
+ assert_output_contains() {
28
+ local desc="$1" pattern="$2"
29
+ shift 2
30
+ local output
31
+ output=$("$@" 2>&1) || true
32
+ if echo "$output" | grep -q "$pattern"; then
33
+ echo " PASS: $desc"
34
+ ((PASS++))
35
+ else
36
+ echo " FAIL: $desc (expected output containing '$pattern')"
37
+ ((FAIL++))
38
+ fi
39
+ }
40
+
41
+ echo "=== forge-team dispatcher tests ==="
42
+
43
+ # Test 1: help exits 0 and shows usage
44
+ echo ""
45
+ echo "-- help subcommand --"
46
+ assert_exit "help exits 0" 0 bash "$DISPATCHER" help
47
+ assert_output_contains "help shows usage" "Usage: forge team" bash "$DISPATCHER" help
48
+
49
+ # Test 2: No args exits 1
50
+ echo ""
51
+ echo "-- no args --"
52
+ assert_exit "no args exits 1" 1 bash "$DISPATCHER"
53
+
54
+ # Test 3: Unknown subcommand exits 1 with error
55
+ echo ""
56
+ echo "-- unknown subcommand --"
57
+ assert_exit "unknown subcommand exits 1" 1 bash "$DISPATCHER" bogus
58
+ assert_output_contains "unknown subcommand shows error" "unknown subcommand" bash "$DISPATCHER" bogus
59
+
60
+ # Test 4: Each stub subcommand is reachable (exits 0)
61
+ echo ""
62
+ echo "-- stub subcommands reachable --"
63
+ for cmd in workload epic dashboard add verify sync claim; do
64
+ assert_exit "$cmd exits 0" 0 bash "$DISPATCHER" "$cmd"
65
+ assert_output_contains "$cmd outputs stub message" "not implemented" bash "$DISPATCHER" "$cmd"
66
+ done
67
+
68
+ # Test 5: Sources libs without error (script runs successfully)
69
+ echo ""
70
+ echo "-- sources libs gracefully --"
71
+ assert_exit "dispatcher sources libs without error" 0 bash "$DISPATCHER" help
72
+
73
+ echo ""
74
+ echo "=== Results: $PASS passed, $FAIL failed ==="
75
+
76
+ if [[ "$FAIL" -gt 0 ]]; then
77
+ exit 1
78
+ fi
79
+ exit 0
@@ -0,0 +1,176 @@
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
+ # ── Create mock bd ───────────────────────────────────────────────────────
11
+ mock_dir="$(mktemp -d)"
12
+
13
+ cat > "$mock_dir/bd" << 'MOCK'
14
+ #!/usr/bin/env bash
15
+ case "$1 $2" in
16
+ "show forge-epic1")
17
+ cat << 'SHOW'
18
+ ◐ forge-epic1 · Test Epic [● P2 · IN_PROGRESS]
19
+ Owner: devone
20
+
21
+ BLOCKS
22
+ ← ✓ forge-child1: Child 1 (closed)
23
+ ← ◐ forge-child2: Child 2 (in_progress)
24
+ ← ○ forge-child3: Child 3 (open)
25
+ SHOW
26
+ ;;
27
+ "show forge-child1")
28
+ echo "✓ forge-child1 · Child 1 [● P2 · CLOSED]"
29
+ echo "Owner: devone"
30
+ ;;
31
+ "show forge-child2")
32
+ echo "◐ forge-child2 · Child 2 [● P2 · IN_PROGRESS]"
33
+ echo "Owner: devtwo"
34
+ ;;
35
+ "show forge-child3")
36
+ echo "○ forge-child3 · Child 3 [● P3 · OPEN]"
37
+ echo "Owner: devone"
38
+ ;;
39
+ "show forge-empty")
40
+ cat << 'SHOW'
41
+ ○ forge-empty · Empty Epic [● P2 · OPEN]
42
+ Owner: devone
43
+ SHOW
44
+ ;;
45
+ "show forge-blocked")
46
+ cat << 'SHOW'
47
+ ◐ forge-blocked · Blocked Epic [● P2 · IN_PROGRESS]
48
+ Owner: devone
49
+
50
+ BLOCKS
51
+ ← ✓ forge-bchild1: Bchild 1 (closed)
52
+ ← ◐ forge-bchild2: Bchild 2 (in_progress)
53
+ ← ○ forge-bchild3: Bchild 3 (open)
54
+ SHOW
55
+ ;;
56
+ "show forge-bchild1")
57
+ echo "✓ forge-bchild1 · Bchild 1 [● P2 · CLOSED]"
58
+ echo "Owner: devone"
59
+ ;;
60
+ "show forge-bchild2")
61
+ cat << 'SHOW'
62
+ ◐ forge-bchild2 · Bchild 2 [● P2 · IN_PROGRESS]
63
+ Owner: devtwo
64
+
65
+ BLOCKED_BY
66
+ → forge-bchild1: Bchild 1
67
+ SHOW
68
+ ;;
69
+ "show forge-bchild3")
70
+ echo "○ forge-bchild3 · Bchild 3 [● P3 · OPEN]"
71
+ echo "Owner: devone"
72
+ ;;
73
+ esac
74
+ MOCK
75
+ chmod +x "$mock_dir/bd"
76
+
77
+ export BD_CMD="$mock_dir/bd"
78
+
79
+ # Source the library under test
80
+ source "$SCRIPT_DIR/lib/epic.sh"
81
+
82
+ PASS=0
83
+ FAIL=0
84
+
85
+ assert_eq() {
86
+ local label="$1" expected="$2" actual="$3"
87
+ if [[ "$actual" == "$expected" ]]; then
88
+ PASS=$((PASS + 1)); echo " PASS: $label"
89
+ else
90
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected '$expected', got '$actual')"
91
+ fi
92
+ }
93
+
94
+ assert_contains() {
95
+ local label="$1" expected="$2" actual="$3"
96
+ if [[ "$actual" == *"$expected"* ]]; then
97
+ PASS=$((PASS + 1)); echo " PASS: $label"
98
+ else
99
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected to contain '$expected', got '$actual')"
100
+ fi
101
+ }
102
+
103
+ assert_exit() {
104
+ local label="$1" expected="$2" actual="$3"
105
+ if [[ "$actual" -eq "$expected" ]]; then
106
+ PASS=$((PASS + 1)); echo " PASS: $label"
107
+ else
108
+ FAIL=$((FAIL + 1)); echo " FAIL: $label (expected exit $expected, got $actual)"
109
+ fi
110
+ }
111
+
112
+ # ── Test 1: Epic with mixed status children → correct progress count ─────
113
+ echo "── Test 1: Epic with mixed status children ──"
114
+ rc=0
115
+ output="$(cmd_epic "forge-epic1" 2>&1)" || rc=$?
116
+ assert_exit "exits 0" 0 "$rc"
117
+ assert_contains "shows epic id" "forge-epic1" "$output"
118
+ assert_contains "progress shows 1/3" "1/3" "$output"
119
+ assert_contains "shows closed child marker" "forge-child1" "$output"
120
+ assert_contains "shows in_progress child" "forge-child2" "$output"
121
+ assert_contains "shows open child" "forge-child3" "$output"
122
+
123
+ # ── Test 2: Per-developer breakdown correct ──────────────────────────────
124
+ echo ""
125
+ echo "── Test 2: Per-developer breakdown correct ──"
126
+ assert_contains "devone breakdown" "devone" "$output"
127
+ assert_contains "devtwo breakdown" "devtwo" "$output"
128
+
129
+ # ── Test 3: Completion percentage correct ────────────────────────────────
130
+ echo ""
131
+ echo "── Test 3: Completion percentage correct ──"
132
+ assert_contains "shows 33%" "33%" "$output"
133
+
134
+ # ── Test 4: Empty epic → No child issues ─────────────────────────────────
135
+ echo ""
136
+ echo "── Test 4: Empty epic ──"
137
+ rc=0
138
+ output_empty="$(cmd_epic "forge-empty" 2>&1)" || rc=$?
139
+ assert_exit "exits 0 for empty epic" 0 "$rc"
140
+ assert_contains "shows no child issues" "No child issues" "$output_empty"
141
+
142
+ # ── Test 5: --format=json → valid JSON with all fields ───────────────────
143
+ echo ""
144
+ echo "── Test 5: --format=json ──"
145
+ rc=0
146
+ output_json="$(cmd_epic "forge-epic1" "--format=json" 2>&1)" || rc=$?
147
+ assert_exit "json exits 0" 0 "$rc"
148
+ # Validate it's actual JSON (check for opening brace and key fields)
149
+ assert_contains "json has epic_id" "epic_id" "$output_json"
150
+ assert_contains "json has total" "total" "$output_json"
151
+ assert_contains "json has done" "done" "$output_json"
152
+ assert_contains "json has percentage" "percentage" "$output_json"
153
+ assert_contains "json has children" "children" "$output_json"
154
+ assert_contains "json has by_developer" "by_developer" "$output_json"
155
+
156
+ # ── Test 6: Missing issue-id → error ────────────────────────────────────
157
+ echo ""
158
+ echo "── Test 6: Missing issue-id ──"
159
+ rc=0
160
+ output_missing="$(cmd_epic 2>&1)" || rc=$?
161
+ assert_exit "missing id returns 1" 1 "$rc"
162
+ assert_contains "error about usage" "Usage" "$output_missing"
163
+
164
+ # ── Test 7: Blocked children shown ───────────────────────────────────────
165
+ echo ""
166
+ echo "── Test 7: Blocked children shown ──"
167
+ rc=0
168
+ output_blocked="$(cmd_epic "forge-blocked" 2>&1)" || rc=$?
169
+ assert_exit "blocked epic exits 0" 0 "$rc"
170
+ assert_contains "blocked section present" "Blocked" "$output_blocked"
171
+ assert_contains "blocked child mentioned" "forge-bchild2" "$output_blocked"
172
+
173
+ # ── Results ──────────────────────────────────────────────────────────────
174
+ echo ""
175
+ echo "Results: $PASS passed, $FAIL failed"
176
+ [[ $FAIL -eq 0 ]] && exit 0 || exit 1