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,239 @@
|
|
|
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 gh (authenticated) ──────────────────────────────────────
|
|
11
|
+
mock_dir="$(mktemp -d)"
|
|
12
|
+
|
|
13
|
+
cat > "$mock_dir/gh" << 'MOCK'
|
|
14
|
+
#!/usr/bin/env bash
|
|
15
|
+
case "$*" in
|
|
16
|
+
"auth status")
|
|
17
|
+
echo "Logged in to github.com as devone"
|
|
18
|
+
exit 0
|
|
19
|
+
;;
|
|
20
|
+
"issue edit"*)
|
|
21
|
+
# Accept any issue edit (sync_issue_status)
|
|
22
|
+
exit 0
|
|
23
|
+
;;
|
|
24
|
+
esac
|
|
25
|
+
MOCK
|
|
26
|
+
chmod +x "$mock_dir/gh"
|
|
27
|
+
|
|
28
|
+
# ── Create mock gh (not authenticated) ──────────────────────────────────
|
|
29
|
+
cat > "$mock_dir/gh-noauth" << 'MOCK'
|
|
30
|
+
#!/usr/bin/env bash
|
|
31
|
+
case "$*" in
|
|
32
|
+
"auth status")
|
|
33
|
+
echo "You are not logged in" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
37
|
+
MOCK
|
|
38
|
+
chmod +x "$mock_dir/gh-noauth"
|
|
39
|
+
|
|
40
|
+
# ── Create mock bd (with in_progress issues having github_issue state) ──
|
|
41
|
+
cat > "$mock_dir/bd" << 'MOCK'
|
|
42
|
+
#!/usr/bin/env bash
|
|
43
|
+
case "$1 $2" in
|
|
44
|
+
"list --status=in_progress")
|
|
45
|
+
echo "◐ forge-aaa · Feature A"
|
|
46
|
+
echo "◐ forge-bbb · Feature B"
|
|
47
|
+
;;
|
|
48
|
+
"show forge-aaa")
|
|
49
|
+
echo "◐ forge-aaa · Feature A [● P2 · IN_PROGRESS]"
|
|
50
|
+
echo "Owner: devone"
|
|
51
|
+
echo "Updated: 2026-03-27T10:00:00Z"
|
|
52
|
+
echo ""
|
|
53
|
+
echo "STATE"
|
|
54
|
+
echo " github_issue:42"
|
|
55
|
+
;;
|
|
56
|
+
"show forge-bbb")
|
|
57
|
+
echo "◐ forge-bbb · Feature B [● P2 · IN_PROGRESS]"
|
|
58
|
+
echo "Owner: devtwo"
|
|
59
|
+
echo "Updated: 2026-03-27T09:00:00Z"
|
|
60
|
+
echo ""
|
|
61
|
+
echo "STATE"
|
|
62
|
+
echo " github_issue:55"
|
|
63
|
+
;;
|
|
64
|
+
esac
|
|
65
|
+
MOCK
|
|
66
|
+
chmod +x "$mock_dir/bd"
|
|
67
|
+
|
|
68
|
+
# ── Create mock bd (no in_progress issues) ──────────────────────────────
|
|
69
|
+
cat > "$mock_dir/bd-empty" << 'MOCK'
|
|
70
|
+
#!/usr/bin/env bash
|
|
71
|
+
case "$1 $2" in
|
|
72
|
+
"list --status=in_progress")
|
|
73
|
+
echo ""
|
|
74
|
+
;;
|
|
75
|
+
esac
|
|
76
|
+
MOCK
|
|
77
|
+
chmod +x "$mock_dir/bd-empty"
|
|
78
|
+
|
|
79
|
+
# ── Create mock bd (issue without github_issue state) ───────────────────
|
|
80
|
+
cat > "$mock_dir/bd-no-gh" << 'MOCK'
|
|
81
|
+
#!/usr/bin/env bash
|
|
82
|
+
case "$1 $2" in
|
|
83
|
+
"list --status=in_progress")
|
|
84
|
+
echo "◐ forge-xxx · Feature X"
|
|
85
|
+
;;
|
|
86
|
+
"show forge-xxx")
|
|
87
|
+
echo "◐ forge-xxx · Feature X [● P2 · IN_PROGRESS]"
|
|
88
|
+
echo "Owner: devone"
|
|
89
|
+
echo "Updated: 2026-03-27T10:00:00Z"
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
MOCK
|
|
93
|
+
chmod +x "$mock_dir/bd-no-gh"
|
|
94
|
+
|
|
95
|
+
# ── Create .beads/config.yaml for auto-sync tests ──────────────────────
|
|
96
|
+
mkdir -p "$TEST_TMP/.beads"
|
|
97
|
+
cat > "$TEST_TMP/.beads/config.yaml" << 'YAML'
|
|
98
|
+
team:
|
|
99
|
+
auto-sync: true
|
|
100
|
+
YAML
|
|
101
|
+
|
|
102
|
+
export GH_CMD="$mock_dir/gh"
|
|
103
|
+
export BD_CMD="$mock_dir/bd"
|
|
104
|
+
export BEADS_ROOT="$TEST_TMP"
|
|
105
|
+
|
|
106
|
+
# Source the library under test
|
|
107
|
+
source "$SCRIPT_DIR/lib/hooks.sh"
|
|
108
|
+
|
|
109
|
+
PASS=0
|
|
110
|
+
FAIL=0
|
|
111
|
+
|
|
112
|
+
assert_eq() {
|
|
113
|
+
local label="$1" expected="$2" actual="$3"
|
|
114
|
+
if [[ "$actual" == "$expected" ]]; then
|
|
115
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
116
|
+
else
|
|
117
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected '$expected', got '$actual')"
|
|
118
|
+
fi
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
assert_contains() {
|
|
122
|
+
local label="$1" expected="$2" actual="$3"
|
|
123
|
+
if [[ "$actual" == *"$expected"* ]]; then
|
|
124
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
125
|
+
else
|
|
126
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected to contain '$expected', got '$actual')"
|
|
127
|
+
fi
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
assert_not_contains() {
|
|
131
|
+
local label="$1" unexpected="$2" actual="$3"
|
|
132
|
+
if [[ "$actual" != *"$unexpected"* ]]; then
|
|
133
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
134
|
+
else
|
|
135
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected NOT to contain '$unexpected', got '$actual')"
|
|
136
|
+
fi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
assert_exit() {
|
|
140
|
+
local label="$1" expected="$2" actual="$3"
|
|
141
|
+
if [[ "$actual" -eq "$expected" ]]; then
|
|
142
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
143
|
+
else
|
|
144
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected exit $expected, got $actual)"
|
|
145
|
+
fi
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# ── Test 1: forge_team_sync with mock gh + bd → syncs in_progress issues ──
|
|
149
|
+
echo "── Test 1: forge_team_sync syncs in_progress issues ──"
|
|
150
|
+
export GH_CMD="$mock_dir/gh"
|
|
151
|
+
export BD_CMD="$mock_dir/bd"
|
|
152
|
+
rc=0
|
|
153
|
+
output="$(forge_team_sync 2>&1)" || rc=$?
|
|
154
|
+
assert_exit "exits 0" 0 "$rc"
|
|
155
|
+
assert_contains "reports synced issues" "Synced 2 issues" "$output"
|
|
156
|
+
|
|
157
|
+
# ── Test 2: forge_team_sync with gh not authenticated → error, exit 1 ──
|
|
158
|
+
echo ""
|
|
159
|
+
echo "── Test 2: forge_team_sync with gh not authenticated → error, exit 1 ──"
|
|
160
|
+
export GH_CMD="$mock_dir/gh-noauth"
|
|
161
|
+
export BD_CMD="$mock_dir/bd"
|
|
162
|
+
rc=0
|
|
163
|
+
output="$(forge_team_sync 2>&1)" || rc=$?
|
|
164
|
+
assert_exit "exits 1" 1 "$rc"
|
|
165
|
+
assert_contains "error message" "FORGE_AGENT_7f3a:ERROR:" "$output"
|
|
166
|
+
|
|
167
|
+
# ── Test 3: forge_team_sync with auto-sync: false → skip, exit 0 ────────
|
|
168
|
+
echo ""
|
|
169
|
+
echo "── Test 3: forge_team_sync with auto-sync: false → skip, exit 0 ──"
|
|
170
|
+
export GH_CMD="$mock_dir/gh"
|
|
171
|
+
export BD_CMD="$mock_dir/bd"
|
|
172
|
+
cat > "$TEST_TMP/.beads/config.yaml" << 'YAML'
|
|
173
|
+
team:
|
|
174
|
+
auto-sync: false
|
|
175
|
+
YAML
|
|
176
|
+
rc=0
|
|
177
|
+
output="$(forge_team_sync 2>&1)" || rc=$?
|
|
178
|
+
assert_exit "exits 0" 0 "$rc"
|
|
179
|
+
assert_not_contains "does not sync" "Synced" "$output"
|
|
180
|
+
# Restore config
|
|
181
|
+
cat > "$TEST_TMP/.beads/config.yaml" << 'YAML'
|
|
182
|
+
team:
|
|
183
|
+
auto-sync: true
|
|
184
|
+
YAML
|
|
185
|
+
|
|
186
|
+
# ── Test 4: forge_team_sync with no in_progress issues → nothing to sync ──
|
|
187
|
+
echo ""
|
|
188
|
+
echo "── Test 4: forge_team_sync with no in_progress issues → nothing to sync ──"
|
|
189
|
+
export GH_CMD="$mock_dir/gh"
|
|
190
|
+
export BD_CMD="$mock_dir/bd-empty"
|
|
191
|
+
rc=0
|
|
192
|
+
output="$(forge_team_sync 2>&1)" || rc=$?
|
|
193
|
+
assert_exit "exits 0" 0 "$rc"
|
|
194
|
+
assert_contains "nothing to sync message" "Nothing to sync" "$output"
|
|
195
|
+
|
|
196
|
+
# ── Test 5: forge_team_sync --quiet → no output on success ──────────────
|
|
197
|
+
echo ""
|
|
198
|
+
echo "── Test 5: forge_team_sync --quiet → no output on success ──"
|
|
199
|
+
export GH_CMD="$mock_dir/gh"
|
|
200
|
+
export BD_CMD="$mock_dir/bd"
|
|
201
|
+
rc=0
|
|
202
|
+
output="$(forge_team_sync --quiet 2>&1)" || rc=$?
|
|
203
|
+
assert_exit "exits 0" 0 "$rc"
|
|
204
|
+
assert_eq "no output" "" "$output"
|
|
205
|
+
|
|
206
|
+
# ── Test 6: forge_team_sync_on_stage triggers correct status sync ────────
|
|
207
|
+
echo ""
|
|
208
|
+
echo "── Test 6: forge_team_sync_on_stage triggers correct status sync per stage ──"
|
|
209
|
+
export GH_CMD="$mock_dir/gh"
|
|
210
|
+
export BD_CMD="$mock_dir/bd"
|
|
211
|
+
# dev transition → in_progress
|
|
212
|
+
rc=0
|
|
213
|
+
output="$(forge_team_sync_on_stage forge-aaa plan dev 2>&1)" || rc=$?
|
|
214
|
+
assert_exit "dev transition exits 0" 0 "$rc"
|
|
215
|
+
# ship transition → in_progress (still active)
|
|
216
|
+
rc=0
|
|
217
|
+
output="$(forge_team_sync_on_stage forge-aaa dev ship 2>&1)" || rc=$?
|
|
218
|
+
assert_exit "ship transition exits 0" 0 "$rc"
|
|
219
|
+
# verify transition → skip (closed by /verify)
|
|
220
|
+
rc=0
|
|
221
|
+
output="$(forge_team_sync_on_stage forge-aaa ship verify 2>&1)" || rc=$?
|
|
222
|
+
assert_exit "verify transition exits 0" 0 "$rc"
|
|
223
|
+
assert_contains "verify skips sync" "skipped" "$output"
|
|
224
|
+
|
|
225
|
+
# ── Test 7: Sync failure doesn't block (non-fatal) ──────────────────────
|
|
226
|
+
echo ""
|
|
227
|
+
echo "── Test 7: Sync failure doesn't block (non-fatal warning) ──"
|
|
228
|
+
export GH_CMD="$mock_dir/gh"
|
|
229
|
+
export BD_CMD="$mock_dir/bd-no-gh"
|
|
230
|
+
rc=0
|
|
231
|
+
output="$(forge_team_sync 2>&1)" || rc=$?
|
|
232
|
+
# Should still exit 0 even with sync failures (non-blocking)
|
|
233
|
+
assert_exit "exits 0 despite sync failure" 0 "$rc"
|
|
234
|
+
assert_contains "warns about failure" "warn" "$output"
|
|
235
|
+
|
|
236
|
+
# ── Results ──────────────────────────────────────────────────────────────
|
|
237
|
+
echo ""
|
|
238
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
239
|
+
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
|
@@ -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
|
+
# 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
|
+
cat > "$mock_dir/gh" << 'MOCK'
|
|
16
|
+
#!/usr/bin/env bash
|
|
17
|
+
if [[ "$1" == "api" && "$2" == "user" ]]; then
|
|
18
|
+
echo "testuser"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
echo "unknown" >&2; exit 1
|
|
22
|
+
MOCK
|
|
23
|
+
chmod +x "$mock_dir/gh"
|
|
24
|
+
|
|
25
|
+
# Create a failing mock gh
|
|
26
|
+
cat > "$mock_dir/gh-fail" << 'MOCK'
|
|
27
|
+
#!/usr/bin/env bash
|
|
28
|
+
echo "not logged in" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
MOCK
|
|
31
|
+
chmod +x "$mock_dir/gh-fail"
|
|
32
|
+
|
|
33
|
+
# Source the library under test
|
|
34
|
+
source "$SCRIPT_DIR/lib/identity.sh"
|
|
35
|
+
|
|
36
|
+
PASS=0
|
|
37
|
+
FAIL=0
|
|
38
|
+
|
|
39
|
+
assert_eq() {
|
|
40
|
+
local label="$1" expected="$2" actual="$3"
|
|
41
|
+
if [[ "$actual" == "$expected" ]]; then
|
|
42
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
43
|
+
else
|
|
44
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected '$expected', got '$actual')"
|
|
45
|
+
fi
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
assert_contains() {
|
|
49
|
+
local label="$1" expected="$2" actual="$3"
|
|
50
|
+
if [[ "$actual" == *"$expected"* ]]; then
|
|
51
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
52
|
+
else
|
|
53
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected to contain '$expected')"
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
assert_exit() {
|
|
58
|
+
local label="$1" expected="$2" actual="$3"
|
|
59
|
+
if [[ "$actual" -eq "$expected" ]]; then
|
|
60
|
+
PASS=$((PASS + 1)); echo " PASS: $label"
|
|
61
|
+
else
|
|
62
|
+
FAIL=$((FAIL + 1)); echo " FAIL: $label (expected exit $expected, got $actual)"
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ── Test 1: get_github_user with mock gh returning "testuser" ────────────
|
|
67
|
+
echo "── Test 1: get_github_user with mock gh ──"
|
|
68
|
+
unset _GITHUB_USER_CACHE
|
|
69
|
+
export GH_CMD="$mock_dir/gh"
|
|
70
|
+
result="$(get_github_user)"
|
|
71
|
+
rc=$?
|
|
72
|
+
assert_eq "returns testuser" "testuser" "$result"
|
|
73
|
+
assert_exit "exit code 0" 0 "$rc"
|
|
74
|
+
|
|
75
|
+
# ── Test 2: get_github_user with mock gh failing ────────────────────────
|
|
76
|
+
echo ""
|
|
77
|
+
echo "── Test 2: get_github_user with failing gh ──"
|
|
78
|
+
unset _GITHUB_USER_CACHE
|
|
79
|
+
export GH_CMD="$mock_dir/gh-fail"
|
|
80
|
+
result="$(get_github_user 2>/dev/null)" || true
|
|
81
|
+
rc=0
|
|
82
|
+
get_github_user >/dev/null 2>/dev/null || rc=$?
|
|
83
|
+
assert_exit "returns exit 1 on failure" 1 "$rc"
|
|
84
|
+
|
|
85
|
+
# ── Test 3: team_map_add creates JSONL entry ────────────────────────────
|
|
86
|
+
echo ""
|
|
87
|
+
echo "── Test 3: team_map_add creates parseable JSONL ──"
|
|
88
|
+
export GH_CMD="$mock_dir/gh"
|
|
89
|
+
unset _GITHUB_USER_CACHE
|
|
90
|
+
team_map_add "testuser" "Test User"
|
|
91
|
+
mapfile="$TEAM_MAP_ROOT/.beads/team-map.jsonl"
|
|
92
|
+
# Entry should be parseable JSON
|
|
93
|
+
last_line="$(tail -1 "$mapfile")"
|
|
94
|
+
parsed="$(echo "$last_line" | jq -r '.github')"
|
|
95
|
+
assert_eq "github field is testuser" "testuser" "$parsed"
|
|
96
|
+
display="$(echo "$last_line" | jq -r '.display_name')"
|
|
97
|
+
assert_eq "display_name is Test User" "Test User" "$display"
|
|
98
|
+
is_bot_val="$(echo "$last_line" | jq -r '.is_bot')"
|
|
99
|
+
assert_eq "is_bot is false" "false" "$is_bot_val"
|
|
100
|
+
updated="$(echo "$last_line" | jq -r '.updated_at')"
|
|
101
|
+
assert_contains "updated_at has timestamp" "20" "$updated"
|
|
102
|
+
|
|
103
|
+
# ── Test 4: team_map_read with LWW resolution ──────────────────────────
|
|
104
|
+
echo ""
|
|
105
|
+
echo "── Test 4: team_map_read with LWW resolution ──"
|
|
106
|
+
# Add a second entry for same user with different display name
|
|
107
|
+
sleep 1 # ensure different timestamp
|
|
108
|
+
team_map_add "testuser" "Updated User"
|
|
109
|
+
# Add another user
|
|
110
|
+
team_map_add "otheruser" "Other"
|
|
111
|
+
result="$(team_map_read)"
|
|
112
|
+
# Should have 2 unique users, with testuser having "Updated User"
|
|
113
|
+
count="$(echo "$result" | jq 'length')"
|
|
114
|
+
assert_eq "2 unique users" "2" "$count"
|
|
115
|
+
testuser_display="$(echo "$result" | jq -r '.[] | select(.github=="testuser") | .display_name')"
|
|
116
|
+
assert_eq "LWW: last entry wins" "Updated User" "$testuser_display"
|
|
117
|
+
|
|
118
|
+
# ── Test 5: team_map_get "testuser" ─────────────────────────────────────
|
|
119
|
+
echo ""
|
|
120
|
+
echo "── Test 5: team_map_get testuser ──"
|
|
121
|
+
result="$(team_map_get "testuser")"
|
|
122
|
+
gh_val="$(echo "$result" | jq -r '.github')"
|
|
123
|
+
assert_eq "returns correct github" "testuser" "$gh_val"
|
|
124
|
+
|
|
125
|
+
# ── Test 6: team_map_get "nonexistent" ──────────────────────────────────
|
|
126
|
+
echo ""
|
|
127
|
+
echo "── Test 6: team_map_get nonexistent ──"
|
|
128
|
+
result="$(team_map_get "nonexistent")"
|
|
129
|
+
assert_eq "returns null" "null" "$result"
|
|
130
|
+
|
|
131
|
+
# ── Test 7: is_bot "dependabot[bot]" ────────────────────────────────────
|
|
132
|
+
echo ""
|
|
133
|
+
echo "── Test 7: is_bot dependabot[bot] ──"
|
|
134
|
+
is_bot "dependabot[bot]"
|
|
135
|
+
rc=$?
|
|
136
|
+
assert_exit "bot detected" 0 "$rc"
|
|
137
|
+
|
|
138
|
+
# ── Test 8: is_bot "harshanandak" ───────────────────────────────────────
|
|
139
|
+
echo ""
|
|
140
|
+
echo "── Test 8: is_bot harshanandak ──"
|
|
141
|
+
rc=0
|
|
142
|
+
is_bot "harshanandak" || rc=$?
|
|
143
|
+
assert_exit "not a bot" 1 "$rc"
|
|
144
|
+
|
|
145
|
+
# ── Test 9: auto_detect_identity with mock gh ───────────────────────────
|
|
146
|
+
echo ""
|
|
147
|
+
echo "── Test 9: auto_detect_identity ──"
|
|
148
|
+
# Clean state
|
|
149
|
+
rm -rf "$TEAM_MAP_ROOT/.beads"
|
|
150
|
+
unset _GITHUB_USER_CACHE
|
|
151
|
+
export GH_CMD="$mock_dir/gh"
|
|
152
|
+
auto_detect_identity
|
|
153
|
+
rc=$?
|
|
154
|
+
assert_exit "silent success" 0 "$rc"
|
|
155
|
+
# Verify entry was added
|
|
156
|
+
result="$(team_map_get "testuser")"
|
|
157
|
+
gh_val="$(echo "$result" | jq -r '.github')"
|
|
158
|
+
assert_eq "entry created in JSONL" "testuser" "$gh_val"
|
|
159
|
+
|
|
160
|
+
# ── Test 10: Invalid username with special chars ────────────────────────
|
|
161
|
+
echo ""
|
|
162
|
+
echo "── Test 10: Invalid username rejected ──"
|
|
163
|
+
rc=0
|
|
164
|
+
team_map_add "bad user!" "Bad" 2>/dev/null || rc=$?
|
|
165
|
+
assert_exit "rejects invalid username" 1 "$rc"
|
|
166
|
+
rc=0
|
|
167
|
+
team_map_add "-startdash" "Bad" 2>/dev/null || rc=$?
|
|
168
|
+
assert_exit "rejects leading hyphen" 1 "$rc"
|
|
169
|
+
rc=0
|
|
170
|
+
team_map_add "has spaces" "Bad" 2>/dev/null || rc=$?
|
|
171
|
+
assert_exit "rejects spaces" 1 "$rc"
|
|
172
|
+
|
|
173
|
+
# ── Results ──────────────────────────────────────────────────────────────
|
|
174
|
+
echo ""
|
|
175
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
176
|
+
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|