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