forge-workflow 0.0.5 → 0.0.7
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 +6 -1
- package/.claude/commands/plan.md +59 -14
- package/.claude/commands/premerge.md +10 -0
- package/.claude/commands/review.md +7 -1
- package/.claude/commands/ship.md +95 -47
- package/.claude/commands/status.md +42 -0
- package/.claude/commands/validate.md +7 -1
- package/.claude/commands/verify.md +52 -4
- package/.claude/rules/workflow.md +16 -0
- package/.claude/scripts/greptile-resolve.sh +32 -0
- package/.cline/workflows/dev.md +6 -1
- package/.cline/workflows/plan.md +59 -14
- package/.cline/workflows/premerge.md +10 -0
- package/.cline/workflows/review.md +7 -1
- package/.cline/workflows/ship.md +95 -47
- package/.cline/workflows/status.md +42 -0
- package/.cline/workflows/validate.md +7 -1
- package/.cline/workflows/verify.md +52 -4
- package/.codex/skills/dev/SKILL.md +6 -1
- package/.codex/skills/plan/SKILL.md +59 -14
- package/.codex/skills/premerge/SKILL.md +10 -0
- package/.codex/skills/review/SKILL.md +7 -1
- package/.codex/skills/ship/SKILL.md +95 -47
- package/.codex/skills/status/SKILL.md +42 -0
- package/.codex/skills/validate/SKILL.md +7 -1
- package/.codex/skills/verify/SKILL.md +52 -4
- package/.cursor/commands/dev.md +6 -1
- package/.cursor/commands/plan.md +59 -14
- package/.cursor/commands/premerge.md +10 -0
- package/.cursor/commands/review.md +7 -1
- package/.cursor/commands/ship.md +95 -47
- package/.cursor/commands/status.md +42 -0
- package/.cursor/commands/validate.md +7 -1
- package/.cursor/commands/verify.md +52 -4
- package/.cursorrules +149 -0
- package/.github/prompts/dev.prompt.md +6 -1
- package/.github/prompts/plan.prompt.md +59 -14
- package/.github/prompts/premerge.prompt.md +10 -0
- package/.github/prompts/review.prompt.md +7 -1
- package/.github/prompts/ship.prompt.md +95 -47
- package/.github/prompts/status.prompt.md +42 -0
- package/.github/prompts/validate.prompt.md +7 -1
- package/.github/prompts/verify.prompt.md +52 -4
- package/.kilocode/workflows/dev.md +6 -1
- package/.kilocode/workflows/plan.md +59 -14
- package/.kilocode/workflows/premerge.md +10 -0
- package/.kilocode/workflows/review.md +7 -1
- package/.kilocode/workflows/ship.md +95 -47
- package/.kilocode/workflows/status.md +42 -0
- package/.kilocode/workflows/validate.md +7 -1
- package/.kilocode/workflows/verify.md +52 -4
- package/.opencode/commands/dev.md +6 -1
- package/.opencode/commands/plan.md +59 -14
- package/.opencode/commands/premerge.md +10 -0
- package/.opencode/commands/review.md +7 -1
- package/.opencode/commands/ship.md +95 -47
- package/.opencode/commands/status.md +42 -0
- package/.opencode/commands/validate.md +7 -1
- package/.opencode/commands/verify.md +52 -4
- package/.roo/commands/dev.md +6 -1
- package/.roo/commands/plan.md +59 -14
- package/.roo/commands/premerge.md +10 -0
- package/.roo/commands/review.md +7 -1
- package/.roo/commands/ship.md +95 -47
- package/.roo/commands/status.md +42 -0
- package/.roo/commands/validate.md +7 -1
- package/.roo/commands/verify.md +52 -4
- package/AGENTS.md +97 -0
- package/CLAUDE.md +10 -0
- package/README.md +2 -2
- package/bin/forge-cmd.js +5 -1
- package/bin/forge-preflight.js +15 -2
- package/bin/forge.js +211 -9
- package/docs/ENHANCED_ONBOARDING.md +96 -86
- package/docs/ROADMAP.md +2 -2
- package/docs/TOOLCHAIN.md +23 -0
- package/docs/VALIDATION.md +1 -1
- package/lefthook.yml +11 -0
- package/lib/agents/README.md +46 -1
- package/lib/agents/cline.plugin.json +11 -4
- package/lib/agents/codex.plugin.json +2 -2
- package/lib/agents/copilot.plugin.json +5 -5
- package/lib/agents/cursor.plugin.json +1 -1
- package/lib/agents/kilocode.plugin.json +1 -1
- package/lib/agents/opencode.plugin.json +7 -4
- package/lib/agents/roo.plugin.json +10 -3
- package/lib/agents-config.js +129 -81
- package/lib/codex-skills.js +50 -0
- package/lib/commands/_registry.js +173 -0
- package/lib/commands/clean.js +181 -0
- package/lib/commands/commands-reset.js +147 -0
- package/lib/commands/dev.js +84 -0
- package/lib/commands/plan.js +18 -0
- package/lib/commands/push.js +196 -0
- package/lib/commands/recommend.js +1 -1
- package/lib/commands/setup.js +4295 -0
- package/lib/commands/ship.js +20 -0
- package/lib/commands/status.js +210 -44
- package/lib/commands/sync.js +71 -0
- package/lib/commands/team.js +37 -0
- package/lib/commands/test.js +207 -0
- package/lib/commands/validate.js +13 -0
- package/lib/commands/worktree.js +310 -0
- package/lib/detect-agent.js +38 -8
- package/lib/detection-utils.js +405 -0
- package/lib/docs-command.js +51 -0
- package/lib/docs-copy.js +50 -0
- package/lib/file-utils.js +260 -0
- package/lib/forge-context.js +42 -0
- package/lib/freshness-token.js +148 -0
- package/lib/frontmatter.js +79 -0
- package/lib/greptile-match.js +80 -0
- package/lib/husky-migration.js +113 -12
- package/lib/lefthook-check.js +27 -6
- package/lib/plugin-manager.js +225 -72
- package/lib/project-discovery.js +39 -5
- package/lib/reset.js +309 -0
- package/lib/runtime-health.js +305 -0
- package/lib/shell-utils.js +50 -0
- package/lib/task-ownership.js +117 -0
- package/lib/ui-utils.js +43 -0
- package/lib/validation-utils.js +163 -0
- package/lib/workflow/enforce-stage.js +179 -0
- package/lib/workflow/stages.js +201 -0
- package/lib/workflow/state.js +332 -0
- package/opencode.json +67 -0
- package/package.json +16 -6
- package/scripts/beads-context.sh +165 -22
- package/scripts/beads-context.test.js +5 -1
- package/scripts/check-agents.js +103 -0
- package/scripts/check-forge-token.js +98 -0
- package/scripts/conflict-detect.sh +2 -2
- package/scripts/dep-guard.sh +6 -28
- package/scripts/file-index.sh +117 -23
- 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/lib/eval-runner.js +39 -0
- package/scripts/lib/jsonl-lock.sh +48 -0
- package/scripts/lib/sanitize.sh +116 -0
- package/scripts/pr-coordinator.sh +756 -0
- package/scripts/smart-status.sh +58 -21
- package/scripts/sync-commands.js +49 -20
- package/scripts/sync-utils.sh +24 -29
- package/scripts/test.js +18 -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" "$mock_dir"' EXIT
|
|
9
|
+
|
|
10
|
+
# ── Create mock dir and log file ─────────────────────────────────────────
|
|
11
|
+
mock_dir="$(mktemp -d /c/tmp/forge-sync-test.XXXXXX)"
|
|
12
|
+
log_file="$mock_dir/calls.log"
|
|
13
|
+
touch "$log_file"
|
|
14
|
+
|
|
15
|
+
# ── Mock gh ──────────────────────────────────────────────────────────────
|
|
16
|
+
cat > "$mock_dir/gh" << 'MOCK'
|
|
17
|
+
#!/usr/bin/env bash
|
|
18
|
+
echo "$*" >> "$LOG_FILE"
|
|
19
|
+
case "$1 $2" in
|
|
20
|
+
"issue create") echo "https://github.com/test/repo/issues/42"; exit 0 ;;
|
|
21
|
+
"issue edit") echo ""; exit 0 ;;
|
|
22
|
+
"issue close") echo ""; exit 0 ;;
|
|
23
|
+
"issue comment") echo ""; exit 0 ;;
|
|
24
|
+
"api user") echo "testuser"; exit 0 ;;
|
|
25
|
+
esac
|
|
26
|
+
echo "unknown gh command: $*" >&2; exit 1
|
|
27
|
+
MOCK
|
|
28
|
+
chmod +x "$mock_dir/gh"
|
|
29
|
+
|
|
30
|
+
# ── Mock bd ──────────────────────────────────────────────────────────────
|
|
31
|
+
cat > "$mock_dir/bd" << 'MOCK'
|
|
32
|
+
#!/usr/bin/env bash
|
|
33
|
+
echo "$*" >> "$LOG_FILE"
|
|
34
|
+
case "$1" in
|
|
35
|
+
show)
|
|
36
|
+
echo "○ $2 · Test Issue [github_issue:42]"
|
|
37
|
+
echo "Title: Test Issue"
|
|
38
|
+
;;
|
|
39
|
+
set-state) echo "State set" ;;
|
|
40
|
+
esac
|
|
41
|
+
MOCK
|
|
42
|
+
chmod +x "$mock_dir/bd"
|
|
43
|
+
|
|
44
|
+
# ── Mock bd that returns NO github_issue ─────────────────────────────────
|
|
45
|
+
cat > "$mock_dir/bd-no-gh" << 'MOCK'
|
|
46
|
+
#!/usr/bin/env bash
|
|
47
|
+
echo "$*" >> "$LOG_FILE"
|
|
48
|
+
case "$1" in
|
|
49
|
+
show)
|
|
50
|
+
echo "○ $2 · Test Issue"
|
|
51
|
+
echo "Title: Test Issue"
|
|
52
|
+
;;
|
|
53
|
+
set-state) echo "State set" ;;
|
|
54
|
+
esac
|
|
55
|
+
MOCK
|
|
56
|
+
chmod +x "$mock_dir/bd-no-gh"
|
|
57
|
+
|
|
58
|
+
# Export env for mocks
|
|
59
|
+
export LOG_FILE="$log_file"
|
|
60
|
+
export GH_CMD="$mock_dir/gh"
|
|
61
|
+
export BD_CMD="$mock_dir/bd"
|
|
62
|
+
export TEAM_MAP_ROOT="$TEST_TMP"
|
|
63
|
+
|
|
64
|
+
# Source the library under test
|
|
65
|
+
source "$SCRIPT_DIR/lib/sync-github.sh"
|
|
66
|
+
|
|
67
|
+
PASS=0
|
|
68
|
+
FAIL=0
|
|
69
|
+
|
|
70
|
+
assert_eq() {
|
|
71
|
+
local label="$1" expected="$2" actual="$3"
|
|
72
|
+
if [[ "$actual" == "$expected" ]]; then
|
|
73
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
74
|
+
else
|
|
75
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected '$expected', got '$actual')"
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
assert_contains() {
|
|
80
|
+
local label="$1" expected="$2" actual="$3"
|
|
81
|
+
if [[ "$actual" == *"$expected"* ]]; then
|
|
82
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
83
|
+
else
|
|
84
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected to contain '$expected', got '$actual')"
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
assert_exit() {
|
|
89
|
+
local label="$1" expected="$2" actual="$3"
|
|
90
|
+
if [[ "$actual" -eq "$expected" ]]; then
|
|
91
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
92
|
+
else
|
|
93
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected exit $expected, got $actual)"
|
|
94
|
+
fi
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# ── Test 1: sync_issue_create — creates GitHub issue, calls bd set-state ─
|
|
98
|
+
echo "── Test 1: sync_issue_create ──"
|
|
99
|
+
> "$log_file" # clear log
|
|
100
|
+
rc=0
|
|
101
|
+
sync_issue_create "beads-001" || rc=$?
|
|
102
|
+
assert_exit "exit code 0" 0 "$rc"
|
|
103
|
+
# Check gh was called with issue create
|
|
104
|
+
log_contents="$(cat "$log_file")"
|
|
105
|
+
assert_contains "gh issue create called" "issue create" "$log_contents"
|
|
106
|
+
assert_contains "title includes Test Issue" "Test Issue" "$log_contents"
|
|
107
|
+
assert_contains "body includes beads-001" "beads-001" "$log_contents"
|
|
108
|
+
# Check bd set-state was called with github_issue=42
|
|
109
|
+
assert_contains "bd set-state called" "set-state" "$log_contents"
|
|
110
|
+
assert_contains "github_issue=42 set" "github_issue=42" "$log_contents"
|
|
111
|
+
|
|
112
|
+
# ── Test 2: sync_issue_claim — updates GitHub assignee ───────────────────
|
|
113
|
+
echo ""
|
|
114
|
+
echo "── Test 2: sync_issue_claim ──"
|
|
115
|
+
> "$log_file"
|
|
116
|
+
unset _GITHUB_USER_CACHE
|
|
117
|
+
rc=0
|
|
118
|
+
sync_issue_claim "beads-001" || rc=$?
|
|
119
|
+
assert_exit "exit code 0" 0 "$rc"
|
|
120
|
+
log_contents="$(cat "$log_file")"
|
|
121
|
+
assert_contains "gh issue edit called" "issue edit" "$log_contents"
|
|
122
|
+
assert_contains "add-assignee used" "--add-assignee" "$log_contents"
|
|
123
|
+
assert_contains "assignee is testuser" "testuser" "$log_contents"
|
|
124
|
+
|
|
125
|
+
# ── Test 3: sync_issue_status — removes old labels, adds new one ─────────
|
|
126
|
+
echo ""
|
|
127
|
+
echo "── Test 3: sync_issue_status ──"
|
|
128
|
+
> "$log_file"
|
|
129
|
+
rc=0
|
|
130
|
+
sync_issue_status "beads-001" "in_progress" || rc=$?
|
|
131
|
+
assert_exit "exit code 0" 0 "$rc"
|
|
132
|
+
log_contents="$(cat "$log_file")"
|
|
133
|
+
assert_contains "gh issue edit called" "issue edit" "$log_contents"
|
|
134
|
+
assert_contains "removes status/open" "--remove-label status/open" "$log_contents"
|
|
135
|
+
assert_contains "adds status/in-progress" "--add-label status/in-progress" "$log_contents"
|
|
136
|
+
|
|
137
|
+
# ── Test 4: sync_issue_close — closes GitHub issue ───────────────────────
|
|
138
|
+
echo ""
|
|
139
|
+
echo "── Test 4: sync_issue_close ──"
|
|
140
|
+
> "$log_file"
|
|
141
|
+
rc=0
|
|
142
|
+
sync_issue_close "beads-001" || rc=$?
|
|
143
|
+
assert_exit "exit code 0" 0 "$rc"
|
|
144
|
+
log_contents="$(cat "$log_file")"
|
|
145
|
+
assert_contains "gh issue close called" "issue close" "$log_contents"
|
|
146
|
+
assert_contains "closes issue 42" " 42" "$log_contents"
|
|
147
|
+
|
|
148
|
+
# ── Test 5: sync_issue_deps — adds comment on GitHub issue ───────────────
|
|
149
|
+
echo ""
|
|
150
|
+
echo "── Test 5: sync_issue_deps ──"
|
|
151
|
+
> "$log_file"
|
|
152
|
+
rc=0
|
|
153
|
+
sync_issue_deps "beads-001" "beads-002" || rc=$?
|
|
154
|
+
assert_exit "exit code 0" 0 "$rc"
|
|
155
|
+
log_contents="$(cat "$log_file")"
|
|
156
|
+
assert_contains "gh issue comment called" "issue comment" "$log_contents"
|
|
157
|
+
assert_contains "Blocked by referenced" "Blocked by #42" "$log_contents"
|
|
158
|
+
|
|
159
|
+
# ── Test 6: _get_github_issue_number — extracts number from bd show ──────
|
|
160
|
+
echo ""
|
|
161
|
+
echo "── Test 6: _get_github_issue_number ──"
|
|
162
|
+
result="$(_get_github_issue_number "beads-001")"
|
|
163
|
+
assert_eq "extracts issue number 42" "42" "$result"
|
|
164
|
+
|
|
165
|
+
# ── Test 7: Missing GitHub issue number → returns error ──────────────────
|
|
166
|
+
echo ""
|
|
167
|
+
echo "── Test 7: Missing GitHub issue number ──"
|
|
168
|
+
export BD_CMD="$mock_dir/bd-no-gh"
|
|
169
|
+
rc=0
|
|
170
|
+
result="$(_get_github_issue_number "beads-999" 2>/dev/null)" || rc=$?
|
|
171
|
+
assert_exit "returns error when no github_issue" 1 "$rc"
|
|
172
|
+
# Restore normal bd mock
|
|
173
|
+
export BD_CMD="$mock_dir/bd"
|
|
174
|
+
|
|
175
|
+
# ── Test 8: Injection in issue title → sanitized before gh call ──────────
|
|
176
|
+
echo ""
|
|
177
|
+
echo "── Test 8: Injection in title sanitized ──"
|
|
178
|
+
# Create a bd mock that returns injection-laden title
|
|
179
|
+
cat > "$mock_dir/bd-inject" << 'MOCK'
|
|
180
|
+
#!/usr/bin/env bash
|
|
181
|
+
echo "$*" >> "$LOG_FILE"
|
|
182
|
+
case "$1" in
|
|
183
|
+
show)
|
|
184
|
+
echo '○ beads-inject · Evil $(rm -rf /) Issue [github_issue:99]'
|
|
185
|
+
echo 'Title: Evil $(rm -rf /) Issue'
|
|
186
|
+
;;
|
|
187
|
+
set-state) echo "State set" ;;
|
|
188
|
+
esac
|
|
189
|
+
MOCK
|
|
190
|
+
chmod +x "$mock_dir/bd-inject"
|
|
191
|
+
export BD_CMD="$mock_dir/bd-inject"
|
|
192
|
+
> "$log_file"
|
|
193
|
+
rc=0
|
|
194
|
+
sync_issue_create "beads-inject" || rc=$?
|
|
195
|
+
assert_exit "exit code 0 with injection" 0 "$rc"
|
|
196
|
+
log_contents="$(cat "$log_file")"
|
|
197
|
+
# The $(...) pattern must NOT appear in the gh call
|
|
198
|
+
if [[ "$log_contents" == *'$(rm'* ]]; then
|
|
199
|
+
FAIL=$((FAIL + 1)); echo " FAIL: injection NOT sanitized — \$(rm found in gh call"
|
|
200
|
+
else
|
|
201
|
+
PASS=$((PASS + 1)); echo " PASS: injection sanitized — no \$(rm in gh call"
|
|
202
|
+
fi
|
|
203
|
+
# Restore normal bd mock
|
|
204
|
+
export BD_CMD="$mock_dir/bd"
|
|
205
|
+
|
|
206
|
+
# ── Results ──────────────────────────────────────────────────────────────
|
|
207
|
+
echo ""
|
|
208
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
209
|
+
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
|
@@ -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
|