forge-workflow 0.0.5 → 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 +6 -1
- package/.claude/commands/plan.md +59 -14
- package/.claude/commands/premerge.md +10 -0
- package/.claude/commands/review.md +7 -1
- package/.claude/commands/ship.md +95 -47
- package/.claude/commands/status.md +42 -0
- package/.claude/commands/validate.md +7 -1
- package/.claude/commands/verify.md +52 -4
- package/.claude/rules/workflow.md +16 -0
- package/.claude/scripts/greptile-resolve.sh +32 -0
- package/.cline/workflows/dev.md +6 -1
- package/.cline/workflows/plan.md +59 -14
- package/.cline/workflows/premerge.md +10 -0
- package/.cline/workflows/review.md +7 -1
- package/.cline/workflows/ship.md +95 -47
- package/.cline/workflows/status.md +42 -0
- package/.cline/workflows/validate.md +7 -1
- package/.cline/workflows/verify.md +52 -4
- package/.codex/skills/dev/SKILL.md +6 -1
- package/.codex/skills/plan/SKILL.md +59 -14
- package/.codex/skills/premerge/SKILL.md +10 -0
- package/.codex/skills/review/SKILL.md +7 -1
- package/.codex/skills/ship/SKILL.md +95 -47
- package/.codex/skills/status/SKILL.md +42 -0
- package/.codex/skills/validate/SKILL.md +7 -1
- package/.codex/skills/verify/SKILL.md +52 -4
- package/.cursor/commands/dev.md +6 -1
- package/.cursor/commands/plan.md +59 -14
- package/.cursor/commands/premerge.md +10 -0
- package/.cursor/commands/review.md +7 -1
- package/.cursor/commands/ship.md +95 -47
- package/.cursor/commands/status.md +42 -0
- package/.cursor/commands/validate.md +7 -1
- package/.cursor/commands/verify.md +52 -4
- package/.github/prompts/dev.prompt.md +6 -1
- package/.github/prompts/plan.prompt.md +59 -14
- package/.github/prompts/premerge.prompt.md +10 -0
- package/.github/prompts/review.prompt.md +7 -1
- package/.github/prompts/ship.prompt.md +95 -47
- package/.github/prompts/status.prompt.md +42 -0
- package/.github/prompts/validate.prompt.md +7 -1
- package/.github/prompts/verify.prompt.md +52 -4
- package/.kilocode/workflows/dev.md +6 -1
- package/.kilocode/workflows/plan.md +59 -14
- package/.kilocode/workflows/premerge.md +10 -0
- package/.kilocode/workflows/review.md +7 -1
- package/.kilocode/workflows/ship.md +95 -47
- package/.kilocode/workflows/status.md +42 -0
- package/.kilocode/workflows/validate.md +7 -1
- package/.kilocode/workflows/verify.md +52 -4
- package/.opencode/commands/dev.md +6 -1
- package/.opencode/commands/plan.md +59 -14
- package/.opencode/commands/premerge.md +10 -0
- package/.opencode/commands/review.md +7 -1
- package/.opencode/commands/ship.md +95 -47
- package/.opencode/commands/status.md +42 -0
- package/.opencode/commands/validate.md +7 -1
- package/.opencode/commands/verify.md +52 -4
- package/.roo/commands/dev.md +6 -1
- package/.roo/commands/plan.md +59 -14
- package/.roo/commands/premerge.md +10 -0
- package/.roo/commands/review.md +7 -1
- package/.roo/commands/ship.md +95 -47
- package/.roo/commands/status.md +42 -0
- package/.roo/commands/validate.md +7 -1
- package/.roo/commands/verify.md +52 -4
- package/AGENTS.md +97 -0
- package/CLAUDE.md +10 -0
- package/README.md +2 -2
- package/bin/forge-cmd.js +5 -1
- package/bin/forge-preflight.js +15 -2
- package/bin/forge.js +178 -9
- package/docs/ENHANCED_ONBOARDING.md +96 -86
- package/docs/ROADMAP.md +2 -2
- package/docs/TOOLCHAIN.md +23 -0
- package/docs/VALIDATION.md +1 -1
- package/lefthook.yml +11 -0
- package/lib/agents-config.js +2 -2
- package/lib/commands/_registry.js +134 -0
- package/lib/commands/clean.js +181 -0
- package/lib/commands/dev.js +58 -0
- package/lib/commands/push.js +196 -0
- package/lib/commands/recommend.js +1 -1
- package/lib/commands/sync.js +55 -0
- package/lib/commands/team.js +37 -0
- package/lib/commands/test.js +207 -0
- package/lib/commands/worktree.js +310 -0
- package/lib/docs-command.js +51 -0
- package/lib/docs-copy.js +50 -0
- package/lib/freshness-token.js +148 -0
- package/lib/greptile-match.js +80 -0
- package/lib/reset.js +309 -0
- package/lib/task-ownership.js +117 -0
- package/package.json +2 -2
- package/scripts/beads-context.sh +157 -22
- package/scripts/beads-context.test.js +5 -1
- package/scripts/check-forge-token.js +98 -0
- package/scripts/conflict-detect.sh +2 -2
- package/scripts/dep-guard.sh +6 -28
- package/scripts/file-index.sh +117 -23
- package/scripts/forge-team/index.sh +86 -0
- package/scripts/forge-team/lib/agent-prompt.sh +52 -0
- package/scripts/forge-team/lib/claim.sh +256 -0
- package/scripts/forge-team/lib/dashboard.sh +341 -0
- package/scripts/forge-team/lib/epic.sh +332 -0
- package/scripts/forge-team/lib/hooks.sh +253 -0
- package/scripts/forge-team/lib/identity.sh +235 -0
- package/scripts/forge-team/lib/sync-github.sh +317 -0
- package/scripts/forge-team/lib/verify.sh +284 -0
- package/scripts/forge-team/lib/workload.sh +296 -0
- package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
- package/scripts/forge-team/tests/claim.test.sh +179 -0
- package/scripts/forge-team/tests/dashboard.test.sh +170 -0
- package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
- package/scripts/forge-team/tests/epic.test.sh +176 -0
- package/scripts/forge-team/tests/hooks.test.sh +239 -0
- package/scripts/forge-team/tests/identity.test.sh +176 -0
- package/scripts/forge-team/tests/integration.test.sh +371 -0
- package/scripts/forge-team/tests/sync-github.test.sh +209 -0
- package/scripts/forge-team/tests/verify.test.sh +314 -0
- package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
- package/scripts/forge-team/tests/workload.test.sh +209 -0
- package/scripts/lib/eval-runner.js +39 -0
- package/scripts/lib/jsonl-lock.sh +48 -0
- package/scripts/lib/sanitize.sh +116 -0
- package/scripts/pr-coordinator.sh +706 -0
- package/scripts/smart-status.sh +37 -10
- package/scripts/sync-utils.sh +24 -29
- package/scripts/test.js +3 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# forge-team — Team orchestration plugin for Forge.
|
|
3
|
+
#
|
|
4
|
+
# Subcommands:
|
|
5
|
+
# workload Show team workload by developer
|
|
6
|
+
# epic Epic progress rollup
|
|
7
|
+
# dashboard Team health dashboard
|
|
8
|
+
# add Add developer to team map
|
|
9
|
+
# verify Check 1:1 Beads<>GitHub enforcement
|
|
10
|
+
# sync Manual GitHub<>Beads sync
|
|
11
|
+
# claim Claim an issue with pre-check
|
|
12
|
+
# help Show usage
|
|
13
|
+
#
|
|
14
|
+
# Exit codes: 0=success, 1=error, 2=validation error
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
|
|
20
|
+
# Source libs (graceful if missing — stubs used in early dev)
|
|
21
|
+
[[ -f "$SCRIPT_DIR/lib/agent-prompt.sh" ]] && source "$SCRIPT_DIR/lib/agent-prompt.sh"
|
|
22
|
+
[[ -f "$SCRIPT_DIR/lib/identity.sh" ]] && source "$SCRIPT_DIR/lib/identity.sh"
|
|
23
|
+
[[ -f "$SCRIPT_DIR/lib/workload.sh" ]] && source "$SCRIPT_DIR/lib/workload.sh"
|
|
24
|
+
[[ -f "$SCRIPT_DIR/lib/epic.sh" ]] && source "$SCRIPT_DIR/lib/epic.sh"
|
|
25
|
+
[[ -f "$SCRIPT_DIR/lib/dashboard.sh" ]] && source "$SCRIPT_DIR/lib/dashboard.sh"
|
|
26
|
+
[[ -f "$SCRIPT_DIR/lib/hooks.sh" ]] && source "$SCRIPT_DIR/lib/hooks.sh"
|
|
27
|
+
[[ -f "$SCRIPT_DIR/lib/verify.sh" ]] && source "$SCRIPT_DIR/lib/verify.sh"
|
|
28
|
+
[[ -f "$SCRIPT_DIR/lib/claim.sh" ]] && source "$SCRIPT_DIR/lib/claim.sh"
|
|
29
|
+
|
|
30
|
+
# Source shared forge utilities
|
|
31
|
+
FORGE_SCRIPTS="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
32
|
+
[[ -f "$FORGE_SCRIPTS/lib/sanitize.sh" ]] && source "$FORGE_SCRIPTS/lib/sanitize.sh"
|
|
33
|
+
|
|
34
|
+
# ── Stub implementations ──
|
|
35
|
+
# cmd_workload provided by lib/workload.sh
|
|
36
|
+
# cmd_epic provided by lib/epic.sh
|
|
37
|
+
# cmd_dashboard provided by lib/dashboard.sh
|
|
38
|
+
cmd_add() { auto_detect_identity "$@"; }
|
|
39
|
+
# cmd_verify provided by lib/verify.sh
|
|
40
|
+
cmd_sync() { forge_team_sync "$@"; }
|
|
41
|
+
cmd_claim() { forge_team_claim "$@"; }
|
|
42
|
+
|
|
43
|
+
cmd_help() {
|
|
44
|
+
cat <<'EOF'
|
|
45
|
+
Usage: forge team <subcommand> [args...]
|
|
46
|
+
|
|
47
|
+
Subcommands:
|
|
48
|
+
workload [--developer=<user>] [--me] Show team workload
|
|
49
|
+
epic <issue-id> Epic progress rollup
|
|
50
|
+
dashboard Team health dashboard
|
|
51
|
+
add [--github=<user>] Add developer to team map
|
|
52
|
+
verify Check 1:1 Beads<>GitHub sync
|
|
53
|
+
sync Manual GitHub<>Beads sync
|
|
54
|
+
claim <issue-id> [--force] Claim issue with pre-check
|
|
55
|
+
help Show this message
|
|
56
|
+
EOF
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# ── Main dispatcher ──
|
|
60
|
+
main() {
|
|
61
|
+
if [[ $# -lt 1 ]]; then
|
|
62
|
+
cmd_help >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
local subcommand="$1"
|
|
67
|
+
shift
|
|
68
|
+
|
|
69
|
+
case "$subcommand" in
|
|
70
|
+
workload) cmd_workload "$@" ;;
|
|
71
|
+
epic) cmd_epic "$@" ;;
|
|
72
|
+
dashboard) cmd_dashboard "$@" ;;
|
|
73
|
+
add) cmd_add "$@" ;;
|
|
74
|
+
verify) cmd_verify "$@" ;;
|
|
75
|
+
sync) cmd_sync "$@" ;;
|
|
76
|
+
claim) cmd_claim "$@" ;;
|
|
77
|
+
help) cmd_help ;;
|
|
78
|
+
*)
|
|
79
|
+
echo "Error: unknown subcommand '$subcommand'" >&2
|
|
80
|
+
cmd_help >&2
|
|
81
|
+
exit 1
|
|
82
|
+
;;
|
|
83
|
+
esac
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main "$@"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# agent-prompt.sh — Shared library for structured agent communication
|
|
2
|
+
# All user-facing output from `forge team` goes through these helpers.
|
|
3
|
+
# Uses non-guessable prefix to prevent injection from GitHub data.
|
|
4
|
+
#
|
|
5
|
+
# Usage: source this file from other scripts.
|
|
6
|
+
# This library does NOT set errexit/pipefail.
|
|
7
|
+
|
|
8
|
+
# Guard against double-sourcing (readonly would error on second source)
|
|
9
|
+
if [[ -n "${_AGENT_PROMPT_LIB_LOADED:-}" ]]; then
|
|
10
|
+
return 0 2>/dev/null || true
|
|
11
|
+
fi
|
|
12
|
+
_AGENT_PROMPT_LIB_LOADED=1
|
|
13
|
+
|
|
14
|
+
readonly FORGE_AGENT_PREFIX="FORGE_AGENT_7f3a"
|
|
15
|
+
|
|
16
|
+
# Output a prompt for the AI agent to ask the user.
|
|
17
|
+
# Format: FORGE_AGENT_7f3a:PROMPT: <message> (to stderr)
|
|
18
|
+
agent_prompt() {
|
|
19
|
+
echo "${FORGE_AGENT_PREFIX}:PROMPT: $1" >&2
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Informational output.
|
|
23
|
+
# Format: FORGE_AGENT_7f3a:INFO: <message> (to stderr)
|
|
24
|
+
agent_info() {
|
|
25
|
+
echo "${FORGE_AGENT_PREFIX}:INFO: $1" >&2
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Error output.
|
|
29
|
+
# Format: FORGE_AGENT_7f3a:ERROR: <message> (to stderr)
|
|
30
|
+
agent_error() {
|
|
31
|
+
echo "${FORGE_AGENT_PREFIX}:ERROR: $1" >&2
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Strip any occurrence of the FORGE_AGENT_7f3a: prefix from input strings.
|
|
35
|
+
# Prevents injection from GitHub issue titles or other external data.
|
|
36
|
+
# Takes input via stdin or as $1 arg, outputs sanitized text to stdout.
|
|
37
|
+
sanitize_for_agent() {
|
|
38
|
+
local input
|
|
39
|
+
if [[ $# -gt 0 ]]; then
|
|
40
|
+
input="$1"
|
|
41
|
+
else
|
|
42
|
+
input="$(cat)"
|
|
43
|
+
fi
|
|
44
|
+
# Loop until no more prefix occurrences (prevents split-prefix bypass)
|
|
45
|
+
local prev
|
|
46
|
+
while true; do
|
|
47
|
+
prev="$input"
|
|
48
|
+
input="${input//${FORGE_AGENT_PREFIX}:/}"
|
|
49
|
+
[[ "$input" == "$prev" ]] && break
|
|
50
|
+
done
|
|
51
|
+
printf '%s' "$input"
|
|
52
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/forge-team/lib/claim.sh — Claim locking with pre-claim check
|
|
3
|
+
#
|
|
4
|
+
# Before allowing issue claim, checks GitHub assignee and uses flock/mkdir
|
|
5
|
+
# to prevent race conditions between concurrent agents.
|
|
6
|
+
#
|
|
7
|
+
# Functions:
|
|
8
|
+
# pre_claim_check — Check if issue is claimable
|
|
9
|
+
# claim_with_lock — Atomic claim with file locking
|
|
10
|
+
# forge_team_claim — Top-level dispatcher entry point
|
|
11
|
+
#
|
|
12
|
+
# Env overrides (for testing):
|
|
13
|
+
# GH_CMD — Path to gh binary
|
|
14
|
+
# BD_CMD — Path to bd binary
|
|
15
|
+
#
|
|
16
|
+
# This file does NOT set errexit/pipefail — callers manage their own shell options.
|
|
17
|
+
|
|
18
|
+
# Guard against double-sourcing
|
|
19
|
+
if [[ -n "${_CLAIM_LIB_LOADED:-}" ]]; then
|
|
20
|
+
return 0 2>/dev/null || true
|
|
21
|
+
fi
|
|
22
|
+
_CLAIM_LIB_LOADED=1
|
|
23
|
+
|
|
24
|
+
_CLAIM_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
|
|
26
|
+
# Source dependencies
|
|
27
|
+
if [[ -f "$_CLAIM_DIR/agent-prompt.sh" ]]; then
|
|
28
|
+
source "$_CLAIM_DIR/agent-prompt.sh"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
if [[ -f "$_CLAIM_DIR/identity.sh" ]]; then
|
|
32
|
+
source "$_CLAIM_DIR/identity.sh"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# ── Internal helpers ─────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
# _claim_error <message>
|
|
38
|
+
_claim_error() {
|
|
39
|
+
if declare -f agent_error &>/dev/null; then
|
|
40
|
+
agent_error "$1"
|
|
41
|
+
else
|
|
42
|
+
echo "ERROR: $1" >&2
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# _claim_info <message>
|
|
47
|
+
_claim_info() {
|
|
48
|
+
if declare -f agent_info &>/dev/null; then
|
|
49
|
+
agent_info "$1"
|
|
50
|
+
else
|
|
51
|
+
echo "INFO: $1" >&2
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# _claim_prompt <message>
|
|
56
|
+
_claim_prompt() {
|
|
57
|
+
if declare -f agent_prompt &>/dev/null; then
|
|
58
|
+
agent_prompt "$1"
|
|
59
|
+
else
|
|
60
|
+
echo "PROMPT: $1" >&2
|
|
61
|
+
fi
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# _get_github_issue_number <beads-id>
|
|
65
|
+
# Extracts github_issue:N from bd show output. Returns number or empty.
|
|
66
|
+
_get_github_issue_number() {
|
|
67
|
+
local beads_id="$1"
|
|
68
|
+
local bd_cmd="${BD_CMD:-bd}"
|
|
69
|
+
local bd_output
|
|
70
|
+
bd_output="$("$bd_cmd" show "$beads_id" 2>/dev/null)" || return 1
|
|
71
|
+
|
|
72
|
+
local issue_num
|
|
73
|
+
issue_num="$(printf '%s' "$bd_output" | grep -oP 'github_issue:\K[0-9]+' || true)"
|
|
74
|
+
if [[ -z "$issue_num" ]]; then
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
77
|
+
printf '%s' "$issue_num"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# _get_github_assignee <issue-number>
|
|
81
|
+
# Returns the GitHub login of the assignee, or empty if unassigned.
|
|
82
|
+
_get_github_assignee() {
|
|
83
|
+
local issue_num="$1"
|
|
84
|
+
local gh_cmd="${GH_CMD:-gh}"
|
|
85
|
+
local assignee
|
|
86
|
+
assignee="$("$gh_cmd" issue view "$issue_num" --json assignees --jq '.assignees[0].login // empty' 2>/dev/null)" || return 1
|
|
87
|
+
printf '%s' "$assignee"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# _claim_lock_dir — Returns the path to the claim lock directory
|
|
91
|
+
_claim_lock_dir() {
|
|
92
|
+
local root="${TEAM_MAP_ROOT:-.}"
|
|
93
|
+
printf '%s' "$root/.beads"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# ── Public API ───────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
# pre_claim_check <beads-id>
|
|
99
|
+
# Checks if issue is claimable. Returns 0 if clear, 1 if blocked.
|
|
100
|
+
pre_claim_check() {
|
|
101
|
+
local beads_id="${1:-}"
|
|
102
|
+
if [[ -z "$beads_id" ]]; then
|
|
103
|
+
_claim_error "Usage: pre_claim_check <beads-id>"
|
|
104
|
+
return 1
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Get current user first (validates gh auth)
|
|
108
|
+
local current_user
|
|
109
|
+
if ! current_user="$(get_github_user)"; then
|
|
110
|
+
_claim_error "GitHub CLI not authenticated. Run 'gh auth login' first."
|
|
111
|
+
return 1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Get GitHub issue number from beads
|
|
115
|
+
local issue_num
|
|
116
|
+
if ! issue_num="$(_get_github_issue_number "$beads_id")"; then
|
|
117
|
+
_claim_error "No github_issue number found for $beads_id"
|
|
118
|
+
return 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
if [[ -z "$issue_num" ]]; then
|
|
122
|
+
_claim_error "No github_issue number found for $beads_id"
|
|
123
|
+
return 1
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Query GitHub assignee
|
|
127
|
+
local assignee
|
|
128
|
+
assignee="$(_get_github_assignee "$issue_num")" || true
|
|
129
|
+
|
|
130
|
+
# If unassigned or assigned to current user: proceed
|
|
131
|
+
if [[ -z "$assignee" ]] || [[ "$assignee" == "$current_user" ]]; then
|
|
132
|
+
return 0
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Assigned to someone else: prompt
|
|
136
|
+
_claim_prompt "$beads_id is claimed by $assignee. Override? Run: forge team claim $beads_id --force"
|
|
137
|
+
return 1
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# claim_with_lock <beads-id> [--force]
|
|
141
|
+
# Atomic claim using flock/mkdir locking pattern.
|
|
142
|
+
# With --force, skips the re-check of assignee inside the lock.
|
|
143
|
+
claim_with_lock() {
|
|
144
|
+
local beads_id="${1:-}"
|
|
145
|
+
local force="${2:-}"
|
|
146
|
+
if [[ -z "$beads_id" ]]; then
|
|
147
|
+
_claim_error "Usage: claim_with_lock <beads-id>"
|
|
148
|
+
return 1
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
local gh_cmd="${GH_CMD:-gh}"
|
|
152
|
+
local bd_cmd="${BD_CMD:-bd}"
|
|
153
|
+
local lock_dir
|
|
154
|
+
lock_dir="$(_claim_lock_dir)"
|
|
155
|
+
mkdir -p "$lock_dir"
|
|
156
|
+
|
|
157
|
+
local lock_file="$lock_dir/claim.lock"
|
|
158
|
+
|
|
159
|
+
# Get current user
|
|
160
|
+
local current_user
|
|
161
|
+
if ! current_user="$(get_github_user)"; then
|
|
162
|
+
_claim_error "GitHub CLI not authenticated."
|
|
163
|
+
return 1
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Get GitHub issue number
|
|
167
|
+
local issue_num
|
|
168
|
+
if ! issue_num="$(_get_github_issue_number "$beads_id")"; then
|
|
169
|
+
_claim_error "No github_issue number found for $beads_id"
|
|
170
|
+
return 1
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
if command -v flock &>/dev/null; then
|
|
174
|
+
# flock-based locking
|
|
175
|
+
(
|
|
176
|
+
flock -w 5 200 || {
|
|
177
|
+
_claim_error "Claim lock timeout after 5s"
|
|
178
|
+
exit 1
|
|
179
|
+
}
|
|
180
|
+
# Re-check assignee inside lock (skip if --force)
|
|
181
|
+
if [[ "$force" != "--force" ]]; then
|
|
182
|
+
local assignee
|
|
183
|
+
assignee="$(_get_github_assignee "$issue_num")" || true
|
|
184
|
+
if [[ -n "$assignee" ]] && [[ "$assignee" != "$current_user" ]]; then
|
|
185
|
+
_claim_error "$beads_id was claimed by $assignee while waiting for lock"
|
|
186
|
+
exit 1
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
# Claim on both Beads and GitHub
|
|
190
|
+
"$bd_cmd" update "$beads_id" --claim >/dev/null 2>&1 || true
|
|
191
|
+
"$gh_cmd" issue edit "$issue_num" --add-assignee "$current_user" >/dev/null 2>&1 || true
|
|
192
|
+
) 200>"$lock_file"
|
|
193
|
+
local subshell_rc=$?
|
|
194
|
+
[[ $subshell_rc -ne 0 ]] && return $subshell_rc
|
|
195
|
+
else
|
|
196
|
+
# mkdir-based fallback
|
|
197
|
+
local lock_mkdir="$lock_dir/claim.lock.d"
|
|
198
|
+
local attempts=0
|
|
199
|
+
while ! mkdir "$lock_mkdir" 2>/dev/null; do
|
|
200
|
+
attempts=$((attempts + 1))
|
|
201
|
+
if [[ $attempts -ge 50 ]]; then
|
|
202
|
+
_claim_error "Claim lock timeout after 5s"
|
|
203
|
+
return 1
|
|
204
|
+
fi
|
|
205
|
+
sleep 0.1
|
|
206
|
+
done
|
|
207
|
+
trap 'rmdir "$lock_mkdir" 2>/dev/null; trap - RETURN' RETURN
|
|
208
|
+
|
|
209
|
+
# Re-check assignee inside lock (skip if --force)
|
|
210
|
+
if [[ "$force" != "--force" ]]; then
|
|
211
|
+
local assignee
|
|
212
|
+
assignee="$(_get_github_assignee "$issue_num")" || true
|
|
213
|
+
if [[ -n "$assignee" ]] && [[ "$assignee" != "$current_user" ]]; then
|
|
214
|
+
_claim_error "$beads_id was claimed by $assignee while waiting for lock"
|
|
215
|
+
return 1
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
# Claim on both Beads and GitHub
|
|
219
|
+
"$bd_cmd" update "$beads_id" --claim >/dev/null 2>&1 || true
|
|
220
|
+
"$gh_cmd" issue edit "$issue_num" --add-assignee "$current_user" >/dev/null 2>&1 || true
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
return 0
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# forge_team_claim <beads-id> [--force]
|
|
227
|
+
# Top-level function called by the dispatcher.
|
|
228
|
+
forge_team_claim() {
|
|
229
|
+
local beads_id="${1:-}"
|
|
230
|
+
local force_flag="${2:-}"
|
|
231
|
+
|
|
232
|
+
if [[ -z "$beads_id" ]]; then
|
|
233
|
+
_claim_error "Usage: forge team claim <beads-id> [--force]"
|
|
234
|
+
return 1
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
local bd_cmd="${BD_CMD:-bd}"
|
|
238
|
+
|
|
239
|
+
if [[ "$force_flag" == "--force" ]]; then
|
|
240
|
+
# Skip pre-claim check, claim directly (pass --force to skip lock re-check too)
|
|
241
|
+
claim_with_lock "$beads_id" "--force" || return 1
|
|
242
|
+
# Log override via bd comments
|
|
243
|
+
"$bd_cmd" comments add "$beads_id" "Force-claimed by $(get_github_user 2>/dev/null || echo 'unknown')" >/dev/null 2>&1 || true
|
|
244
|
+
_claim_info "$beads_id force-claimed successfully"
|
|
245
|
+
return 0
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
# Normal flow: pre-claim check then lock-claim
|
|
249
|
+
if ! pre_claim_check "$beads_id"; then
|
|
250
|
+
return 1
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
claim_with_lock "$beads_id" || return 1
|
|
254
|
+
_claim_info "$beads_id claimed successfully"
|
|
255
|
+
return 0
|
|
256
|
+
}
|