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,332 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/forge-team/lib/epic.sh — Epic rollup view
|
|
3
|
+
#
|
|
4
|
+
# Shows epic progress with per-developer breakdown and blocked issue tracking.
|
|
5
|
+
#
|
|
6
|
+
# Functions:
|
|
7
|
+
# cmd_epic — Top-level dispatcher entry point
|
|
8
|
+
#
|
|
9
|
+
# Env overrides (for testing):
|
|
10
|
+
# BD_CMD — Path to bd binary
|
|
11
|
+
#
|
|
12
|
+
# This file does NOT set errexit/pipefail — callers manage their own shell options.
|
|
13
|
+
|
|
14
|
+
# Guard against double-sourcing
|
|
15
|
+
if [[ -n "${_EPIC_LIB_LOADED:-}" ]]; then
|
|
16
|
+
return 0 2>/dev/null || true
|
|
17
|
+
fi
|
|
18
|
+
_EPIC_LIB_LOADED=1
|
|
19
|
+
|
|
20
|
+
# ── Internal helpers ─────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
# _epic_get_bd — Returns the bd command path
|
|
23
|
+
_epic_get_bd() {
|
|
24
|
+
printf '%s' "${BD_CMD:-bd}"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# _epic_parse_blocks <bd-show-output>
|
|
28
|
+
# Extracts child issue IDs from the BLOCKS section.
|
|
29
|
+
# Each line in BLOCKS looks like: ← ✓ forge-child1: Child 1 (closed)
|
|
30
|
+
# Returns one ID per line.
|
|
31
|
+
_epic_parse_blocks() {
|
|
32
|
+
local bd_output="$1"
|
|
33
|
+
local in_blocks=0
|
|
34
|
+
|
|
35
|
+
while IFS= read -r line; do
|
|
36
|
+
if [[ "$line" == "BLOCKS" ]]; then
|
|
37
|
+
in_blocks=1
|
|
38
|
+
continue
|
|
39
|
+
fi
|
|
40
|
+
# Stop at next section header (non-indented, non-empty line after BLOCKS)
|
|
41
|
+
if [[ $in_blocks -eq 1 ]]; then
|
|
42
|
+
if [[ -z "$line" ]]; then
|
|
43
|
+
continue
|
|
44
|
+
fi
|
|
45
|
+
if [[ "$line" =~ ^[A-Z] ]]; then
|
|
46
|
+
break
|
|
47
|
+
fi
|
|
48
|
+
# Extract issue ID: matches forge-XXXX pattern after the arrow+status marker
|
|
49
|
+
local child_id
|
|
50
|
+
child_id="$(printf '%s' "$line" | sed -n 's/.*← [^ ]* \([a-z0-9-]*\):.*/\1/p')"
|
|
51
|
+
if [[ -n "$child_id" ]]; then
|
|
52
|
+
printf '%s\n' "$child_id"
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
done <<< "$bd_output"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# _epic_get_child_info <child-id>
|
|
59
|
+
# Returns: id|status|owner|title|blocked_by
|
|
60
|
+
_epic_get_child_info() {
|
|
61
|
+
local child_id="$1"
|
|
62
|
+
local bd_cmd
|
|
63
|
+
bd_cmd="$(_epic_get_bd)"
|
|
64
|
+
|
|
65
|
+
local child_output
|
|
66
|
+
child_output="$("$bd_cmd" show "$child_id" 2>/dev/null)" || return 1
|
|
67
|
+
|
|
68
|
+
local first_line
|
|
69
|
+
first_line="$(printf '%s' "$child_output" | head -1)"
|
|
70
|
+
|
|
71
|
+
# Parse status from the first character/marker
|
|
72
|
+
local status="open"
|
|
73
|
+
if [[ "$first_line" == *"CLOSED"* ]] || [[ "$first_line" == *"✓"* ]]; then
|
|
74
|
+
status="closed"
|
|
75
|
+
elif [[ "$first_line" == *"IN_PROGRESS"* ]] || [[ "$first_line" == *"◐"* ]]; then
|
|
76
|
+
status="in_progress"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Parse title — between · and [
|
|
80
|
+
local title
|
|
81
|
+
title="$(printf '%s' "$first_line" | sed -n 's/^[^ ]* [^ ]* · \(.*\) \[.*/\1/p')"
|
|
82
|
+
if [[ -z "$title" ]]; then
|
|
83
|
+
title="$child_id"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Parse owner from "Owner: xxx" line
|
|
87
|
+
local owner=""
|
|
88
|
+
owner="$(printf '%s' "$child_output" | sed -n 's/^Owner: *//p' | head -1)"
|
|
89
|
+
|
|
90
|
+
# Check for BLOCKED_BY section
|
|
91
|
+
local blocked_by=""
|
|
92
|
+
blocked_by="$(printf '%s' "$child_output" | sed -n '/^BLOCKED_BY$/,/^[A-Z]/{ /^ /{ s/.*→ \([a-z0-9-]*\):.*/\1/p; }; }')"
|
|
93
|
+
|
|
94
|
+
printf '%s|%s|%s|%s|%s' "$child_id" "$status" "$owner" "$title" "$blocked_by"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# _epic_status_icon <status>
|
|
98
|
+
_epic_status_icon() {
|
|
99
|
+
case "$1" in
|
|
100
|
+
closed) printf '%s' "✓" ;;
|
|
101
|
+
in_progress) printf '%s' "◐" ;;
|
|
102
|
+
*) printf '%s' "○" ;;
|
|
103
|
+
esac
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# ── Public API ───────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
# cmd_epic <issue-id> [--format=json]
|
|
109
|
+
cmd_epic() {
|
|
110
|
+
local issue_id="${1:-}"
|
|
111
|
+
local format_flag="${2:-}"
|
|
112
|
+
|
|
113
|
+
if [[ -z "$issue_id" ]]; then
|
|
114
|
+
echo "ERROR: Usage: forge team epic <issue-id> [--format=json]" >&2
|
|
115
|
+
return 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
local bd_cmd
|
|
119
|
+
bd_cmd="$(_epic_get_bd)"
|
|
120
|
+
|
|
121
|
+
# 1. Get epic details
|
|
122
|
+
local epic_output
|
|
123
|
+
epic_output="$("$bd_cmd" show "$issue_id" 2>/dev/null)" || {
|
|
124
|
+
echo "ERROR: Could not fetch epic $issue_id" >&2
|
|
125
|
+
return 1
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Parse epic title from first line
|
|
129
|
+
local epic_first_line
|
|
130
|
+
epic_first_line="$(printf '%s' "$epic_output" | head -1)"
|
|
131
|
+
local epic_title
|
|
132
|
+
epic_title="$(printf '%s' "$epic_first_line" | sed -n 's/^[^ ]* [^ ]* · \(.*\) \[.*/\1/p')"
|
|
133
|
+
if [[ -z "$epic_title" ]]; then
|
|
134
|
+
epic_title="$issue_id"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# 2. Parse BLOCKS section to find child issues
|
|
138
|
+
local children_ids
|
|
139
|
+
children_ids="$(_epic_parse_blocks "$epic_output")"
|
|
140
|
+
|
|
141
|
+
# Handle empty epic
|
|
142
|
+
if [[ -z "$children_ids" ]]; then
|
|
143
|
+
if [[ "$format_flag" == "--format=json" ]]; then
|
|
144
|
+
cat <<ENDJSON
|
|
145
|
+
{"epic_id":"$issue_id","title":"$epic_title","total":0,"done":0,"in_progress":0,"open_count":0,"percentage":0,"children":[],"by_developer":{},"blocked":[]}
|
|
146
|
+
ENDJSON
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
echo "Epic: $issue_id — $epic_title"
|
|
150
|
+
echo "No child issues"
|
|
151
|
+
return 0
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# 3. For each child: get status, assignee
|
|
155
|
+
local total=0
|
|
156
|
+
local done_count=0
|
|
157
|
+
local in_progress_count=0
|
|
158
|
+
local open_count=0
|
|
159
|
+
local blocked_list=""
|
|
160
|
+
|
|
161
|
+
# Arrays for child data (using indexed approach for bash 3 compat)
|
|
162
|
+
local child_lines=""
|
|
163
|
+
# Developer tracking: accumulate "dev:status" pairs
|
|
164
|
+
local dev_entries=""
|
|
165
|
+
|
|
166
|
+
while IFS= read -r child_id; do
|
|
167
|
+
[[ -z "$child_id" ]] && continue
|
|
168
|
+
|
|
169
|
+
local info
|
|
170
|
+
info="$(_epic_get_child_info "$child_id")" || continue
|
|
171
|
+
|
|
172
|
+
local c_id c_status c_owner c_title c_blocked
|
|
173
|
+
IFS='|' read -r c_id c_status c_owner c_title c_blocked <<< "$info"
|
|
174
|
+
|
|
175
|
+
total=$((total + 1))
|
|
176
|
+
|
|
177
|
+
case "$c_status" in
|
|
178
|
+
closed) done_count=$((done_count + 1)) ;;
|
|
179
|
+
in_progress) in_progress_count=$((in_progress_count + 1)) ;;
|
|
180
|
+
*) open_count=$((open_count + 1)) ;;
|
|
181
|
+
esac
|
|
182
|
+
|
|
183
|
+
# Track blocked children
|
|
184
|
+
if [[ -n "$c_blocked" ]]; then
|
|
185
|
+
if [[ -n "$blocked_list" ]]; then
|
|
186
|
+
blocked_list="$blocked_list"$'\n'"$c_id blocked by $c_blocked"
|
|
187
|
+
else
|
|
188
|
+
blocked_list="$c_id blocked by $c_blocked"
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
# Build child line for display
|
|
193
|
+
local icon
|
|
194
|
+
icon="$(_epic_status_icon "$c_status")"
|
|
195
|
+
local display_owner="${c_owner:-unassigned}"
|
|
196
|
+
child_lines="${child_lines} $icon $c_id [$display_owner] $c_title ($c_status)"$'\n'
|
|
197
|
+
|
|
198
|
+
# Track developer entries
|
|
199
|
+
if [[ -n "$c_owner" ]]; then
|
|
200
|
+
dev_entries="${dev_entries}${c_owner}:${c_status}"$'\n'
|
|
201
|
+
fi
|
|
202
|
+
done <<< "$children_ids"
|
|
203
|
+
|
|
204
|
+
# 4. Calculate completion percentage
|
|
205
|
+
local percentage=0
|
|
206
|
+
if [[ $total -gt 0 ]]; then
|
|
207
|
+
percentage=$(( (done_count * 100) / total ))
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# 5. Build per-developer breakdown
|
|
211
|
+
# Collect unique developers
|
|
212
|
+
local devs=""
|
|
213
|
+
while IFS= read -r entry; do
|
|
214
|
+
[[ -z "$entry" ]] && continue
|
|
215
|
+
local dev="${entry%%:*}"
|
|
216
|
+
if [[ -z "$devs" ]] || ! printf '%s' "$devs" | grep -qxF "$dev"; then
|
|
217
|
+
if [[ -n "$devs" ]]; then
|
|
218
|
+
devs="$devs"$'\n'"$dev"
|
|
219
|
+
else
|
|
220
|
+
devs="$dev"
|
|
221
|
+
fi
|
|
222
|
+
fi
|
|
223
|
+
done <<< "$dev_entries"
|
|
224
|
+
|
|
225
|
+
# JSON output
|
|
226
|
+
if [[ "$format_flag" == "--format=json" ]]; then
|
|
227
|
+
local children_json="["
|
|
228
|
+
local first_child=1
|
|
229
|
+
while IFS= read -r child_id; do
|
|
230
|
+
[[ -z "$child_id" ]] && continue
|
|
231
|
+
local info
|
|
232
|
+
info="$(_epic_get_child_info "$child_id")" || continue
|
|
233
|
+
local c_id c_status c_owner c_title c_blocked
|
|
234
|
+
IFS='|' read -r c_id c_status c_owner c_title c_blocked <<< "$info"
|
|
235
|
+
|
|
236
|
+
if [[ $first_child -eq 0 ]]; then
|
|
237
|
+
children_json="$children_json,"
|
|
238
|
+
fi
|
|
239
|
+
first_child=0
|
|
240
|
+
children_json="$children_json{\"id\":\"$c_id\",\"status\":\"$c_status\",\"owner\":\"$c_owner\",\"title\":\"$c_title\",\"blocked_by\":\"$c_blocked\"}"
|
|
241
|
+
done <<< "$children_ids"
|
|
242
|
+
children_json="$children_json]"
|
|
243
|
+
|
|
244
|
+
local dev_json="{"
|
|
245
|
+
local first_dev=1
|
|
246
|
+
while IFS= read -r dev; do
|
|
247
|
+
[[ -z "$dev" ]] && continue
|
|
248
|
+
local d_done=0 d_in_progress=0 d_open=0 d_total=0
|
|
249
|
+
while IFS= read -r entry; do
|
|
250
|
+
[[ -z "$entry" ]] && continue
|
|
251
|
+
local e_dev="${entry%%:*}"
|
|
252
|
+
local e_status="${entry#*:}"
|
|
253
|
+
if [[ "$e_dev" == "$dev" ]]; then
|
|
254
|
+
d_total=$((d_total + 1))
|
|
255
|
+
case "$e_status" in
|
|
256
|
+
closed) d_done=$((d_done + 1)) ;;
|
|
257
|
+
in_progress) d_in_progress=$((d_in_progress + 1)) ;;
|
|
258
|
+
*) d_open=$((d_open + 1)) ;;
|
|
259
|
+
esac
|
|
260
|
+
fi
|
|
261
|
+
done <<< "$dev_entries"
|
|
262
|
+
|
|
263
|
+
if [[ $first_dev -eq 0 ]]; then
|
|
264
|
+
dev_json="$dev_json,"
|
|
265
|
+
fi
|
|
266
|
+
first_dev=0
|
|
267
|
+
dev_json="$dev_json\"$dev\":{\"total\":$d_total,\"done\":$d_done,\"in_progress\":$d_in_progress,\"open\":$d_open}"
|
|
268
|
+
done <<< "$devs"
|
|
269
|
+
dev_json="$dev_json}"
|
|
270
|
+
|
|
271
|
+
local blocked_json="["
|
|
272
|
+
if [[ -n "$blocked_list" ]]; then
|
|
273
|
+
local first_blocked=1
|
|
274
|
+
while IFS= read -r bline; do
|
|
275
|
+
[[ -z "$bline" ]] && continue
|
|
276
|
+
if [[ $first_blocked -eq 0 ]]; then
|
|
277
|
+
blocked_json="$blocked_json,"
|
|
278
|
+
fi
|
|
279
|
+
first_blocked=0
|
|
280
|
+
blocked_json="$blocked_json\"$bline\""
|
|
281
|
+
done <<< "$blocked_list"
|
|
282
|
+
fi
|
|
283
|
+
blocked_json="$blocked_json]"
|
|
284
|
+
|
|
285
|
+
printf '{"epic_id":"%s","title":"%s","total":%d,"done":%d,"in_progress":%d,"open_count":%d,"percentage":%d,"children":%s,"by_developer":%s,"blocked":%s}\n' \
|
|
286
|
+
"$issue_id" "$epic_title" "$total" "$done_count" "$in_progress_count" "$open_count" "$percentage" \
|
|
287
|
+
"$children_json" "$dev_json" "$blocked_json"
|
|
288
|
+
return 0
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
# Text output
|
|
292
|
+
echo "Epic: $issue_id — $epic_title"
|
|
293
|
+
echo "Progress: $done_count/$total ($percentage%)"
|
|
294
|
+
printf '%s' "$child_lines"
|
|
295
|
+
|
|
296
|
+
echo ""
|
|
297
|
+
echo "By developer:"
|
|
298
|
+
while IFS= read -r dev; do
|
|
299
|
+
[[ -z "$dev" ]] && continue
|
|
300
|
+
local d_done=0 d_in_progress=0 d_open=0 d_total=0
|
|
301
|
+
while IFS= read -r entry; do
|
|
302
|
+
[[ -z "$entry" ]] && continue
|
|
303
|
+
local e_dev="${entry%%:*}"
|
|
304
|
+
local e_status="${entry#*:}"
|
|
305
|
+
if [[ "$e_dev" == "$dev" ]]; then
|
|
306
|
+
d_total=$((d_total + 1))
|
|
307
|
+
case "$e_status" in
|
|
308
|
+
closed) d_done=$((d_done + 1)) ;;
|
|
309
|
+
in_progress) d_in_progress=$((d_in_progress + 1)) ;;
|
|
310
|
+
*) d_open=$((d_open + 1)) ;;
|
|
311
|
+
esac
|
|
312
|
+
fi
|
|
313
|
+
done <<< "$dev_entries"
|
|
314
|
+
|
|
315
|
+
local dev_parts=""
|
|
316
|
+
dev_parts="$d_done/$d_total done"
|
|
317
|
+
[[ $d_in_progress -gt 0 ]] && dev_parts="$dev_parts, $d_in_progress in progress"
|
|
318
|
+
[[ $d_open -gt 0 ]] && dev_parts="$dev_parts, $d_open open"
|
|
319
|
+
echo " $dev: $dev_parts"
|
|
320
|
+
done <<< "$devs"
|
|
321
|
+
|
|
322
|
+
echo ""
|
|
323
|
+
if [[ -n "$blocked_list" ]]; then
|
|
324
|
+
echo "Blocked:"
|
|
325
|
+
while IFS= read -r bline; do
|
|
326
|
+
[[ -z "$bline" ]] && continue
|
|
327
|
+
echo " $bline"
|
|
328
|
+
done <<< "$blocked_list"
|
|
329
|
+
else
|
|
330
|
+
echo "Blocked: none"
|
|
331
|
+
fi
|
|
332
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/forge-team/lib/hooks.sh — Hook-based sync (pre-push + stage transitions)
|
|
3
|
+
#
|
|
4
|
+
# Functions:
|
|
5
|
+
# forge_team_sync [--quiet] — Top-level sync for hooks/manual
|
|
6
|
+
# forge_team_sync_on_stage <beads-id> <from> <to> — Stage transition sync
|
|
7
|
+
# _check_auto_sync_enabled — Check config for auto-sync
|
|
8
|
+
#
|
|
9
|
+
# Env overrides (for testing):
|
|
10
|
+
# GH_CMD — Path to gh binary (default: gh)
|
|
11
|
+
# BD_CMD — Path to bd binary (default: bd)
|
|
12
|
+
# BEADS_ROOT — Path to .beads/ parent directory (default: .)
|
|
13
|
+
#
|
|
14
|
+
# This file does NOT set errexit/pipefail — callers manage their own shell options.
|
|
15
|
+
|
|
16
|
+
# Guard against double-sourcing
|
|
17
|
+
if [[ -n "${_HOOKS_LIB_LOADED:-}" ]]; then
|
|
18
|
+
return 0 2>/dev/null || true
|
|
19
|
+
fi
|
|
20
|
+
_HOOKS_LIB_LOADED=1
|
|
21
|
+
|
|
22
|
+
_HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
|
|
24
|
+
# Source dependencies
|
|
25
|
+
if [[ -f "$_HOOKS_DIR/agent-prompt.sh" ]]; then
|
|
26
|
+
source "$_HOOKS_DIR/agent-prompt.sh"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if [[ -f "$_HOOKS_DIR/sync-github.sh" ]]; then
|
|
30
|
+
source "$_HOOKS_DIR/sync-github.sh"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# ── Internal helpers ─────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
# _hooks_error <message>
|
|
36
|
+
_hooks_error() {
|
|
37
|
+
if declare -f agent_error &>/dev/null; then
|
|
38
|
+
agent_error "$1"
|
|
39
|
+
else
|
|
40
|
+
echo "ERROR: $1" >&2
|
|
41
|
+
fi
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# _hooks_info <message>
|
|
45
|
+
_hooks_info() {
|
|
46
|
+
if declare -f agent_info &>/dev/null; then
|
|
47
|
+
agent_info "$1"
|
|
48
|
+
else
|
|
49
|
+
echo "INFO: $1" >&2
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# _hooks_warn <message>
|
|
54
|
+
_hooks_warn() {
|
|
55
|
+
echo "FORGE_AGENT_7f3a:WARN: $1" >&2
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ── _check_auto_sync_enabled ─────────────────────────────────────────────
|
|
59
|
+
# Reads .beads/config.yaml for team.auto-sync setting.
|
|
60
|
+
# Returns 0 if enabled (default), 1 if disabled.
|
|
61
|
+
_check_auto_sync_enabled() {
|
|
62
|
+
local beads_root="${BEADS_ROOT:-.}"
|
|
63
|
+
local config_file="$beads_root/.beads/config.yaml"
|
|
64
|
+
|
|
65
|
+
if [[ ! -f "$config_file" ]]; then
|
|
66
|
+
# Default: enabled
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Parse auto-sync value from YAML (simple grep, no yq dependency)
|
|
71
|
+
local auto_sync_line
|
|
72
|
+
auto_sync_line="$(grep -E 'auto-sync:' "$config_file" 2>/dev/null || true)"
|
|
73
|
+
|
|
74
|
+
if [[ -z "$auto_sync_line" ]]; then
|
|
75
|
+
# Not set → default enabled
|
|
76
|
+
return 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
local value
|
|
80
|
+
value="$(printf '%s' "$auto_sync_line" | sed -E 's/.*auto-sync:[[:space:]]*//' | tr -d '[:space:]')"
|
|
81
|
+
|
|
82
|
+
if [[ "$value" == "false" ]]; then
|
|
83
|
+
return 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
return 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# ── Public API ───────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
# forge_team_sync [--quiet]
|
|
92
|
+
# Top-level sync function called by hooks and manual `forge team sync`.
|
|
93
|
+
# 1. Check gh auth status
|
|
94
|
+
# 2. Check auto-sync config
|
|
95
|
+
# 3. Get in_progress issues with github_issue state
|
|
96
|
+
# 4. Sync each to GitHub
|
|
97
|
+
# 5. Report count (unless --quiet)
|
|
98
|
+
# Non-blocking: warns on failure, doesn't prevent push.
|
|
99
|
+
forge_team_sync() {
|
|
100
|
+
local quiet=false
|
|
101
|
+
|
|
102
|
+
# Parse arguments
|
|
103
|
+
while [[ $# -gt 0 ]]; do
|
|
104
|
+
case "$1" in
|
|
105
|
+
--quiet) quiet=true; shift ;;
|
|
106
|
+
*) shift ;;
|
|
107
|
+
esac
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
local gh_cmd="${GH_CMD:-gh}"
|
|
111
|
+
local bd_cmd="${BD_CMD:-bd}"
|
|
112
|
+
|
|
113
|
+
# 1. Check gh auth status
|
|
114
|
+
if ! "$gh_cmd" auth status >/dev/null 2>&1; then
|
|
115
|
+
_hooks_error "GitHub CLI not authenticated. Run 'gh auth login'."
|
|
116
|
+
return 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# 2. Check auto-sync config
|
|
120
|
+
if ! _check_auto_sync_enabled; then
|
|
121
|
+
# auto-sync disabled, skip silently
|
|
122
|
+
return 0
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# 3. Get all in_progress issues
|
|
126
|
+
local list_output
|
|
127
|
+
list_output="$("$bd_cmd" list --status=in_progress 2>/dev/null)" || {
|
|
128
|
+
if [[ "$quiet" == "false" ]]; then
|
|
129
|
+
_hooks_warn "Failed to list issues, sync skipped"
|
|
130
|
+
fi
|
|
131
|
+
return 0
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Filter empty lines
|
|
135
|
+
if [[ -z "$list_output" ]] || [[ -z "$(echo "$list_output" | tr -d '[:space:]')" ]]; then
|
|
136
|
+
if [[ "$quiet" == "false" ]]; then
|
|
137
|
+
_hooks_info "Nothing to sync"
|
|
138
|
+
fi
|
|
139
|
+
return 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# 4. For each issue, check for github_issue state and sync
|
|
143
|
+
local synced=0
|
|
144
|
+
local warnings=0
|
|
145
|
+
|
|
146
|
+
while IFS= read -r line; do
|
|
147
|
+
[[ -z "$line" ]] && continue
|
|
148
|
+
|
|
149
|
+
# Extract issue id
|
|
150
|
+
local issue_id
|
|
151
|
+
issue_id="$(echo "$line" | grep -oE 'forge-[a-zA-Z0-9]+' | head -1)"
|
|
152
|
+
[[ -z "$issue_id" ]] && continue
|
|
153
|
+
|
|
154
|
+
# Get details to check for github_issue state
|
|
155
|
+
local show_output
|
|
156
|
+
show_output="$("$bd_cmd" show "$issue_id" 2>/dev/null)" || continue
|
|
157
|
+
|
|
158
|
+
# Check if it has github_issue:N state
|
|
159
|
+
local gh_issue_num
|
|
160
|
+
gh_issue_num="$(printf '%s' "$show_output" | grep -oP 'github_issue:\K[0-9]+' | head -1 || true)"
|
|
161
|
+
|
|
162
|
+
if [[ -z "$gh_issue_num" ]]; then
|
|
163
|
+
# No GitHub issue linked, skip but warn
|
|
164
|
+
warnings=$((warnings + 1))
|
|
165
|
+
if [[ "$quiet" == "false" ]]; then
|
|
166
|
+
_hooks_warn "No github_issue for $issue_id, sync skipped"
|
|
167
|
+
fi
|
|
168
|
+
continue
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# Sync status to in_progress on GitHub
|
|
172
|
+
if sync_issue_status "$issue_id" "in_progress" 2>/dev/null; then
|
|
173
|
+
synced=$((synced + 1))
|
|
174
|
+
else
|
|
175
|
+
warnings=$((warnings + 1))
|
|
176
|
+
if [[ "$quiet" == "false" ]]; then
|
|
177
|
+
_hooks_warn "Failed to sync $issue_id to GitHub"
|
|
178
|
+
fi
|
|
179
|
+
fi
|
|
180
|
+
done <<< "$list_output"
|
|
181
|
+
|
|
182
|
+
# 5. Report
|
|
183
|
+
if [[ "$quiet" == "false" ]]; then
|
|
184
|
+
if [[ "$synced" -gt 0 ]]; then
|
|
185
|
+
_hooks_info "Synced $synced issues to GitHub"
|
|
186
|
+
elif [[ "$warnings" -gt 0 ]]; then
|
|
187
|
+
_hooks_warn "Sync completed with $warnings warnings"
|
|
188
|
+
fi
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
return 0
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# forge_team_sync_on_stage <beads-id> <from-stage> <to-stage>
|
|
195
|
+
# Called after stage transitions. Syncs the specific issue.
|
|
196
|
+
# Stages: plan → dev → validate → ship → review → premerge → verify
|
|
197
|
+
forge_team_sync_on_stage() {
|
|
198
|
+
local beads_id="${1:-}"
|
|
199
|
+
local from_stage="${2:-}"
|
|
200
|
+
local to_stage="${3:-}"
|
|
201
|
+
|
|
202
|
+
if [[ -z "$beads_id" ]] || [[ -z "$from_stage" ]] || [[ -z "$to_stage" ]]; then
|
|
203
|
+
_hooks_error "Usage: forge_team_sync_on_stage <beads-id> <from-stage> <to-stage>"
|
|
204
|
+
return 1
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Check auto-sync
|
|
208
|
+
if ! _check_auto_sync_enabled; then
|
|
209
|
+
return 0
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
local status=""
|
|
213
|
+
case "$to_stage" in
|
|
214
|
+
dev)
|
|
215
|
+
status="in_progress"
|
|
216
|
+
;;
|
|
217
|
+
ship)
|
|
218
|
+
status="in_progress"
|
|
219
|
+
;;
|
|
220
|
+
verify)
|
|
221
|
+
# Will be closed by /verify, skip sync
|
|
222
|
+
_hooks_info "Stage verify: sync skipped (closed by /verify)"
|
|
223
|
+
return 0
|
|
224
|
+
;;
|
|
225
|
+
*)
|
|
226
|
+
# Other stages: sync as in_progress if moving forward
|
|
227
|
+
status="in_progress"
|
|
228
|
+
;;
|
|
229
|
+
esac
|
|
230
|
+
|
|
231
|
+
if [[ -n "$status" ]]; then
|
|
232
|
+
if ! sync_issue_status "$beads_id" "$status" 2>/dev/null; then
|
|
233
|
+
_hooks_warn "Failed to sync $beads_id status on stage transition $from_stage → $to_stage"
|
|
234
|
+
fi
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
return 0
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# ── CLI entrypoint (for lefthook) ────────────────────────────────────────
|
|
241
|
+
# When called directly: bash hooks.sh sync [--quiet]
|
|
242
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
243
|
+
case "${1:-}" in
|
|
244
|
+
sync)
|
|
245
|
+
shift
|
|
246
|
+
forge_team_sync "$@"
|
|
247
|
+
;;
|
|
248
|
+
*)
|
|
249
|
+
echo "Usage: hooks.sh sync [--quiet]" >&2
|
|
250
|
+
exit 1
|
|
251
|
+
;;
|
|
252
|
+
esac
|
|
253
|
+
fi
|