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.
- package/.claude/commands/dev.md +345 -340
- package/.claude/commands/plan.md +566 -521
- package/.claude/commands/premerge.md +186 -176
- package/.claude/commands/research.md +42 -42
- package/.claude/commands/review.md +448 -442
- package/.claude/commands/rollback.md +721 -721
- package/.claude/commands/ship.md +212 -164
- package/.claude/commands/sonarcloud.md +152 -152
- package/.claude/commands/status.md +90 -48
- package/.claude/commands/validate.md +288 -282
- package/.claude/commands/verify.md +269 -221
- package/.claude/rules/greptile-review-process.md +285 -285
- package/.claude/rules/workflow.md +121 -105
- package/.claude/scripts/greptile-resolve.sh +558 -526
- package/.claude/scripts/load-env.sh +32 -32
- package/.cline/workflows/dev.md +342 -337
- package/.cline/workflows/plan.md +563 -518
- package/.cline/workflows/premerge.md +183 -173
- package/.cline/workflows/research.md +39 -39
- package/.cline/workflows/review.md +445 -439
- package/.cline/workflows/rollback.md +718 -718
- package/.cline/workflows/ship.md +209 -161
- package/.cline/workflows/sonarcloud.md +146 -146
- package/.cline/workflows/status.md +87 -45
- package/.cline/workflows/validate.md +285 -279
- package/.cline/workflows/verify.md +266 -218
- package/.codex/config.toml +11 -11
- package/.codex/skills/dev/SKILL.md +345 -340
- package/.codex/skills/plan/SKILL.md +566 -521
- package/.codex/skills/premerge/SKILL.md +186 -176
- package/.codex/skills/research/SKILL.md +42 -42
- package/.codex/skills/review/SKILL.md +448 -442
- package/.codex/skills/rollback/SKILL.md +721 -721
- package/.codex/skills/ship/SKILL.md +212 -164
- package/.codex/skills/sonarcloud/SKILL.md +149 -149
- package/.codex/skills/status/SKILL.md +90 -48
- package/.codex/skills/validate/SKILL.md +288 -282
- package/.codex/skills/verify/SKILL.md +269 -221
- package/.cursor/commands/dev.md +342 -337
- package/.cursor/commands/plan.md +563 -518
- package/.cursor/commands/premerge.md +183 -173
- package/.cursor/commands/research.md +39 -39
- package/.cursor/commands/review.md +445 -439
- package/.cursor/commands/rollback.md +718 -718
- package/.cursor/commands/ship.md +209 -161
- package/.cursor/commands/sonarcloud.md +146 -146
- package/.cursor/commands/status.md +87 -45
- package/.cursor/commands/validate.md +285 -279
- package/.cursor/commands/verify.md +266 -218
- package/.cursor/rules/permissions-guidance.mdc +37 -37
- package/.forge/hooks/check-tdd.js +240 -240
- package/.github/PLUGIN_TEMPLATE.json +32 -32
- package/.github/prompts/dev.prompt.md +347 -342
- package/.github/prompts/plan.prompt.md +568 -523
- package/.github/prompts/premerge.prompt.md +188 -178
- package/.github/prompts/research.prompt.md +44 -44
- package/.github/prompts/review.prompt.md +450 -444
- package/.github/prompts/rollback.prompt.md +723 -723
- package/.github/prompts/ship.prompt.md +214 -166
- package/.github/prompts/sonarcloud.prompt.md +151 -151
- package/.github/prompts/status.prompt.md +92 -50
- package/.github/prompts/validate.prompt.md +290 -284
- package/.github/prompts/verify.prompt.md +271 -223
- package/.github/workflows/beads-to-github.yml +56 -0
- package/.github/workflows/github-to-beads.yml +97 -0
- package/.kilocode/workflows/dev.md +346 -341
- package/.kilocode/workflows/plan.md +567 -522
- package/.kilocode/workflows/premerge.md +187 -177
- package/.kilocode/workflows/research.md +43 -43
- package/.kilocode/workflows/review.md +449 -443
- package/.kilocode/workflows/rollback.md +722 -722
- package/.kilocode/workflows/ship.md +213 -165
- package/.kilocode/workflows/sonarcloud.md +150 -150
- package/.kilocode/workflows/status.md +91 -49
- package/.kilocode/workflows/validate.md +289 -283
- package/.kilocode/workflows/verify.md +270 -222
- package/.mcp.json.example +12 -12
- package/.opencode/commands/dev.md +345 -340
- package/.opencode/commands/plan.md +566 -521
- package/.opencode/commands/premerge.md +186 -176
- package/.opencode/commands/research.md +42 -42
- package/.opencode/commands/review.md +448 -442
- package/.opencode/commands/rollback.md +721 -721
- package/.opencode/commands/ship.md +212 -164
- package/.opencode/commands/sonarcloud.md +149 -149
- package/.opencode/commands/status.md +90 -48
- package/.opencode/commands/validate.md +288 -282
- package/.opencode/commands/verify.md +269 -221
- package/.roo/commands/dev.md +346 -341
- package/.roo/commands/plan.md +567 -522
- package/.roo/commands/premerge.md +187 -177
- package/.roo/commands/research.md +43 -43
- package/.roo/commands/review.md +449 -443
- package/.roo/commands/rollback.md +722 -722
- package/.roo/commands/ship.md +213 -165
- package/.roo/commands/sonarcloud.md +150 -150
- package/.roo/commands/status.md +91 -49
- package/.roo/commands/validate.md +289 -283
- package/.roo/commands/verify.md +270 -222
- package/AGENTS.md +272 -175
- package/CLAUDE.md +110 -100
- package/README.md +429 -416
- package/bin/forge-cmd.js +317 -313
- package/bin/forge-preflight.js +322 -309
- package/bin/forge.js +4765 -4303
- package/docs/AGENT_INSTALL_PROMPT.md +342 -342
- package/docs/BEADS_GITHUB_SYNC.md +251 -251
- package/docs/ENHANCED_ONBOARDING.md +612 -602
- package/docs/EXAMPLES.md +482 -482
- package/docs/GREPTILE_SETUP.md +400 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +106 -106
- package/docs/ROADMAP.md +359 -359
- package/docs/SETUP.md +663 -631
- package/docs/TOOLCHAIN.md +653 -630
- package/docs/VALIDATION.md +363 -363
- package/install.sh +40 -1056
- package/lefthook.yml +50 -39
- package/lib/agents/README.md +198 -198
- package/lib/agents/claude.plugin.json +28 -28
- package/lib/agents/cline.plugin.json +22 -22
- package/lib/agents/codex.plugin.json +19 -19
- package/lib/agents/copilot.plugin.json +24 -24
- package/lib/agents/cursor.plugin.json +25 -25
- package/lib/agents/kilocode.plugin.json +22 -22
- package/lib/agents/opencode.plugin.json +20 -20
- package/lib/agents/roo.plugin.json +23 -23
- package/lib/agents-config.js +2112 -2112
- package/lib/beads-health-check.js +143 -0
- package/lib/beads-setup.js +341 -0
- package/lib/beads-sync-scaffold.js +260 -0
- package/lib/commands/_registry.js +134 -0
- package/lib/commands/clean.js +181 -0
- package/lib/commands/dev.js +571 -513
- package/lib/commands/plan.js +692 -692
- package/lib/commands/push.js +196 -0
- package/lib/commands/recommend.js +119 -119
- package/lib/commands/ship.js +377 -377
- package/lib/commands/status.js +378 -378
- package/lib/commands/sync.js +55 -0
- package/lib/commands/team.js +37 -0
- package/lib/commands/test.js +207 -0
- package/lib/commands/validate.js +602 -602
- package/lib/commands/worktree.js +310 -0
- package/lib/context-merge.js +359 -359
- package/lib/dep-guard/analyzer.js +294 -294
- package/lib/dep-guard/behavior-detector.js +98 -98
- package/lib/dep-guard/contract-detector.js +162 -162
- package/lib/dep-guard/import-detector.js +498 -498
- package/lib/dep-guard/path-utils.js +13 -13
- package/lib/dep-guard/rubric.js +120 -120
- package/lib/dep-guard/task-parser.js +318 -318
- package/lib/detect-agent.js +191 -191
- package/lib/detect-worktree.js +47 -47
- package/lib/docs-command.js +51 -0
- package/lib/docs-copy.js +50 -0
- package/lib/file-hash.js +26 -26
- package/lib/freshness-token.js +148 -0
- package/lib/greptile-match.js +80 -0
- package/lib/husky-migration.js +450 -0
- package/lib/lefthook-check.js +65 -0
- package/lib/pat-setup.js +207 -0
- package/lib/plugin-catalog.js +350 -350
- package/lib/plugin-manager.js +166 -166
- package/lib/plugin-recommender.js +141 -141
- package/lib/project-discovery.js +491 -491
- package/lib/reset.js +309 -0
- package/lib/setup-action-log.js +139 -139
- package/lib/setup-summary-renderer.js +106 -106
- package/lib/setup-utils.js +96 -0
- package/lib/setup.js +192 -192
- package/lib/smart-merge.js +64 -0
- package/lib/symlink-utils.js +81 -0
- package/lib/task-ownership.js +117 -0
- package/lib/workflow-profiles.js +197 -197
- package/package.json +131 -128
- package/scripts/beads-context.sh +426 -0
- package/scripts/beads-context.test.js +567 -0
- package/scripts/behavioral-judge.sh +378 -0
- package/scripts/benchmark.js +85 -0
- package/scripts/branch-protection.js +183 -0
- package/scripts/check-agents.js +172 -0
- package/scripts/check-forge-token.js +98 -0
- package/scripts/commitlint.js +42 -0
- package/scripts/conflict-detect.sh +323 -0
- package/scripts/dep-guard-analyze.js +71 -0
- package/scripts/dep-guard.sh +789 -0
- package/scripts/eval_win.py +249 -0
- package/scripts/file-index.sh +493 -0
- package/scripts/forge-team/index.sh +86 -0
- package/scripts/forge-team/lib/agent-prompt.sh +52 -0
- package/scripts/forge-team/lib/claim.sh +256 -0
- package/scripts/forge-team/lib/dashboard.sh +341 -0
- package/scripts/forge-team/lib/epic.sh +332 -0
- package/scripts/forge-team/lib/hooks.sh +253 -0
- package/scripts/forge-team/lib/identity.sh +235 -0
- package/scripts/forge-team/lib/sync-github.sh +317 -0
- package/scripts/forge-team/lib/verify.sh +284 -0
- package/scripts/forge-team/lib/workload.sh +296 -0
- package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
- package/scripts/forge-team/tests/claim.test.sh +179 -0
- package/scripts/forge-team/tests/dashboard.test.sh +170 -0
- package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
- package/scripts/forge-team/tests/epic.test.sh +176 -0
- package/scripts/forge-team/tests/hooks.test.sh +239 -0
- package/scripts/forge-team/tests/identity.test.sh +176 -0
- package/scripts/forge-team/tests/integration.test.sh +371 -0
- package/scripts/forge-team/tests/sync-github.test.sh +209 -0
- package/scripts/forge-team/tests/verify.test.sh +314 -0
- package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
- package/scripts/forge-team/tests/workload.test.sh +209 -0
- package/scripts/github-beads-sync/comment.mjs +64 -0
- package/scripts/github-beads-sync/config.mjs +148 -0
- package/scripts/github-beads-sync/github-api.mjs +131 -0
- package/scripts/github-beads-sync/index.mjs +332 -0
- package/scripts/github-beads-sync/label-mapper.mjs +54 -0
- package/scripts/github-beads-sync/mapping.mjs +78 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +31 -0
- package/scripts/github-beads-sync/reverse-sync.mjs +138 -0
- package/scripts/github-beads-sync/run-bd.mjs +159 -0
- package/scripts/github-beads-sync/sanitize.mjs +121 -0
- package/scripts/github-beads-sync.config.json +26 -0
- package/scripts/improve-command.js +375 -0
- package/scripts/lib/eval-runner.js +268 -0
- package/scripts/lib/eval-schema.js +135 -0
- package/scripts/lib/eval-storage.js +78 -0
- package/scripts/lib/grading.js +203 -0
- package/scripts/lib/jsonl-lock.sh +48 -0
- package/scripts/lib/sanitize.sh +116 -0
- package/scripts/lib/transcript-parser.js +63 -0
- package/scripts/lint.js +47 -0
- package/scripts/migrate-to-bun-test.js +412 -0
- package/scripts/pr-coordinator.sh +706 -0
- package/scripts/run-command-eval.js +236 -0
- package/scripts/smart-status.sh +809 -0
- package/scripts/sync-commands.js +571 -0
- package/scripts/sync-utils.sh +455 -0
- package/scripts/test-dashboard.js +123 -0
- package/scripts/test.js +46 -0
- package/scripts/validate.sh +94 -0
- package/skills/parallel-deep-research/SKILL.md +108 -108
- package/skills/parallel-deep-research/evals/README.md +27 -27
- package/skills/parallel-deep-research/evals/evals.json +62 -62
- package/skills/sonarcloud-analysis/SKILL.md +171 -171
- package/skills/sonarcloud-analysis/evals/README.md +27 -27
- package/skills/sonarcloud-analysis/evals/evals.json +50 -50
- package/skills/sonarcloud-analysis/references/api-reference.md +466 -466
- package/.cursor/hooks/state/continual-learning-index.json +0 -19
- 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
|