forge-workflow 0.0.8 → 0.0.10
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 +2 -2
- package/.claude/commands/plan.md +2 -2
- package/.claude/commands/premerge.md +2 -2
- package/.claude/commands/review.md +5 -2
- package/.claude/commands/ship.md +6 -5
- package/.claude/commands/status.md +4 -4
- package/.claude/rules/greptile-review-process.md +4 -4
- package/.cline/workflows/dev.md +2 -2
- package/.cline/workflows/plan.md +2 -2
- package/.cline/workflows/premerge.md +2 -2
- package/.cline/workflows/review.md +5 -2
- package/.cline/workflows/ship.md +6 -5
- package/.cline/workflows/status.md +4 -4
- package/.codex/skills/dev/SKILL.md +2 -2
- package/.codex/skills/plan/SKILL.md +2 -2
- package/.codex/skills/premerge/SKILL.md +2 -2
- package/.codex/skills/review/SKILL.md +5 -2
- package/.codex/skills/ship/SKILL.md +6 -5
- package/.codex/skills/status/SKILL.md +4 -4
- package/.cursor/commands/dev.md +2 -2
- package/.cursor/commands/plan.md +2 -2
- package/.cursor/commands/premerge.md +2 -2
- package/.cursor/commands/review.md +5 -2
- package/.cursor/commands/ship.md +6 -5
- package/.cursor/commands/status.md +4 -4
- package/.github/prompts/dev.prompt.md +2 -2
- package/.github/prompts/plan.prompt.md +2 -2
- package/.github/prompts/premerge.prompt.md +2 -2
- package/.github/prompts/review.prompt.md +5 -2
- package/.github/prompts/ship.prompt.md +6 -5
- package/.github/prompts/status.prompt.md +4 -4
- package/.github/workflows/beads-to-github.yml +44 -11
- package/.github/workflows/github-to-beads.yml +11 -8
- package/.kilocode/workflows/dev.md +2 -2
- package/.kilocode/workflows/plan.md +2 -2
- package/.kilocode/workflows/premerge.md +2 -2
- package/.kilocode/workflows/review.md +5 -2
- package/.kilocode/workflows/ship.md +6 -5
- package/.kilocode/workflows/status.md +4 -4
- package/.opencode/commands/dev.md +2 -2
- package/.opencode/commands/plan.md +2 -2
- package/.opencode/commands/premerge.md +2 -2
- package/.opencode/commands/review.md +5 -2
- package/.opencode/commands/ship.md +6 -5
- package/.opencode/commands/status.md +4 -4
- package/.roo/commands/dev.md +2 -2
- package/.roo/commands/plan.md +2 -2
- package/.roo/commands/premerge.md +2 -2
- package/.roo/commands/review.md +5 -2
- package/.roo/commands/ship.md +6 -5
- package/.roo/commands/status.md +4 -4
- package/AGENTS.md +10 -9
- package/CLAUDE.md +12 -0
- package/README.md +12 -6
- package/bin/forge.js +24 -8
- package/docs/BEADS_GITHUB_SYNC.md +32 -2
- package/docs/EXAMPLES.md +22 -22
- package/docs/ROADMAP.md +3 -3
- package/docs/TOOLCHAIN.md +150 -160
- package/lib/agents/codex.plugin.json +3 -0
- package/lib/agents-config.js +18 -12
- package/lib/beads-bootstrap.js +225 -0
- package/lib/beads-health-check.js +55 -10
- package/lib/beads-setup.js +104 -28
- package/lib/beads-sync-scaffold.js +11 -6
- package/lib/codex-skills.js +54 -1
- package/lib/commands/_issue.js +11 -1
- package/lib/commands/issues.js +49 -0
- package/lib/commands/plan.js +5 -2
- package/lib/commands/recommend.js +22 -1
- package/lib/commands/setup.js +247 -27
- package/lib/commands/ship.js +188 -5
- package/lib/commands/status.js +187 -19
- package/lib/commands/team.js +11 -1
- package/lib/commands/test.js +125 -25
- package/lib/commands/validate.js +232 -9
- package/lib/commands/worktree.js +27 -54
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/detect-worktree.js +9 -10
- package/lib/forge-issues.js +326 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/lefthook-check.js +5 -2
- package/lib/project-memory.js +564 -0
- package/lib/runtime-health.js +100 -12
- package/lib/setup-action-log.js +2 -0
- package/lib/setup-summary-renderer.js +15 -11
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +177 -0
- package/lib/status/beads-snapshot.js +102 -0
- package/lib/status/presenter.js +65 -0
- package/lib/workflow/enforce-stage.js +14 -8
- package/lib/workflow/state-manager.js +349 -0
- package/package.json +12 -4
- package/scripts/beads-context.sh +124 -5
- package/scripts/beads-context.test.js +21 -4
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +263 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +45 -222
- package/scripts/forge-team/lib/hooks.sh +1 -1
- package/scripts/forge-team/lib/sync-github.sh +160 -28
- package/scripts/forge-team/lib/verify.sh +1 -1
- package/scripts/forge-team/lib/workload.sh +56 -27
- package/scripts/forge-team/tests/sync-github.test.sh +195 -58
- package/scripts/forge-team/tests/workload.test.sh +35 -4
- package/scripts/github-beads-sync/index.mjs +122 -98
- package/scripts/github-beads-sync/mapping.mjs +54 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +2 -2
- package/scripts/github-beads-sync/reverse-sync.mjs +31 -7
- package/scripts/github-beads-sync/run-bd.mjs +4 -2
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/preflight.sh +181 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +84 -330
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +39 -0
- package/scripts/test-ci-shard.js +244 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +335 -38
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
applyMergeTreeConflicts,
|
|
8
|
+
computeFileConflicts,
|
|
9
|
+
matchInProgressIssues,
|
|
10
|
+
parseWorktreePorcelain,
|
|
11
|
+
} = require('../lib/smart-status/conflicts.js');
|
|
12
|
+
|
|
13
|
+
function readPayload() {
|
|
14
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
15
|
+
if (!raw.trim()) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildSessions(payload) {
|
|
23
|
+
let sessions = Array.isArray(payload.sessions)
|
|
24
|
+
? payload.sessions
|
|
25
|
+
: matchInProgressIssues(
|
|
26
|
+
parseWorktreePorcelain(payload.worktreePorcelain || '', payload.baseBranch || 'master'),
|
|
27
|
+
Array.isArray(payload.inProgressIssues) ? payload.inProgressIssues : [],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (payload.branchFiles) {
|
|
31
|
+
sessions = computeFileConflicts(sessions, payload.branchFiles);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (payload.mergeTreeResults) {
|
|
35
|
+
sessions = applyMergeTreeConflicts(sessions, payload.mergeTreeResults);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return sessions;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function main() {
|
|
42
|
+
try {
|
|
43
|
+
const payload = readPayload();
|
|
44
|
+
process.stdout.write(JSON.stringify(buildSessions(payload)));
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`smart-status-sessions: ${error.message}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
main();
|
package/scripts/smart-status.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# smart-status.sh
|
|
2
|
+
# smart-status.sh  Workflow intelligence scoring engine
|
|
3
3
|
#
|
|
4
4
|
# Reads issues via `bd list --json --limit 0`, computes a composite score
|
|
5
5
|
# for each issue, and outputs them sorted by score descending.
|
|
@@ -11,20 +11,21 @@
|
|
|
11
11
|
# smart-status.sh [--json]
|
|
12
12
|
#
|
|
13
13
|
# Environment:
|
|
14
|
-
# BD_CMD
|
|
15
|
-
# GIT_CMD
|
|
16
|
-
# DEFAULT_BRANCH
|
|
14
|
+
# BD_CMD  override the bd command (for testing with mocks)
|
|
15
|
+
# GIT_CMD  override the git command (for testing with mocks)
|
|
16
|
+
# DEFAULT_BRANCH  override the default branch name (default: auto-detect)
|
|
17
17
|
#
|
|
18
18
|
# Cross-platform: bash 3.2 compatible (no associative arrays, no mapfile).
|
|
19
19
|
# OWASP A03: All variables quoted, sanitize() strips injection patterns.
|
|
20
20
|
|
|
21
21
|
set -euo pipefail
|
|
22
22
|
|
|
23
|
-
#
|
|
23
|
+
# ââ€â‚¬Ã¢â€â‚¬ Source cross-dev awareness scripts ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
24
24
|
# file-index.sh: file_index_read for reading file index entries
|
|
25
25
|
# sync-utils.sh: get_session_identity for current developer identity
|
|
26
26
|
|
|
27
27
|
_SMART_STATUS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
NODE_CMD="${NODE_CMD:-node}"
|
|
28
29
|
if [ -f "$_SMART_STATUS_DIR/lib/sanitize.sh" ]; then
|
|
29
30
|
source "$_SMART_STATUS_DIR/lib/sanitize.sh"
|
|
30
31
|
fi
|
|
@@ -35,7 +36,7 @@ if [ -f "$_SMART_STATUS_DIR/sync-utils.sh" ]; then
|
|
|
35
36
|
source "$_SMART_STATUS_DIR/sync-utils.sh"
|
|
36
37
|
fi
|
|
37
38
|
|
|
38
|
-
#
|
|
39
|
+
# ââ€â‚¬Ã¢â€â‚¬ Helpers ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
39
40
|
|
|
40
41
|
if ! declare -F sanitize >/dev/null; then
|
|
41
42
|
# Sanitize a string: strip shell-injection patterns (OWASP A03)
|
|
@@ -56,7 +57,7 @@ sanitize() {
|
|
|
56
57
|
}
|
|
57
58
|
fi
|
|
58
59
|
|
|
59
|
-
#
|
|
60
|
+
# ââ€â‚¬Ã¢â€â‚¬ Dependency check ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
60
61
|
|
|
61
62
|
if ! command -v jq &>/dev/null; then
|
|
62
63
|
echo "Error: jq is required but not found." >&2
|
|
@@ -68,16 +69,25 @@ if ! command -v jq &>/dev/null; then
|
|
|
68
69
|
fi
|
|
69
70
|
|
|
70
71
|
# Warn if jq < 1.6 (fromdateiso8601 and sub/2 require 1.6+)
|
|
71
|
-
|
|
72
|
+
# Strip CR here as well because Windows jq.exe (or a CRLF-emitting wrapper in
|
|
73
|
+
# tests) can affect the version probe before the jq() shim is defined below.
|
|
74
|
+
_jq_version="$(command jq --version 2>/dev/null | tr -d '\r' | sed 's/jq-//')" || true
|
|
72
75
|
if [ -n "$_jq_version" ]; then
|
|
73
76
|
_jq_major="${_jq_version%%.*}"
|
|
74
77
|
_jq_minor="${_jq_version#*.}"; _jq_minor="${_jq_minor%%.*}"
|
|
75
78
|
if [ "${_jq_major:-0}" -lt 1 ] || { [ "${_jq_major:-0}" -eq 1 ] && [ "${_jq_minor:-0}" -lt 6 ]; }; then
|
|
76
|
-
echo "Warning: jq $_jq_version detected
|
|
79
|
+
echo "Warning: jq $_jq_version detected - staleness features require jq 1.6+. Team Activity may not display." >&2
|
|
77
80
|
fi
|
|
78
81
|
fi
|
|
79
82
|
|
|
80
|
-
#
|
|
83
|
+
# On Windows/WSL we may be invoking jq.exe, which emits CRLF and breaks
|
|
84
|
+
# numeric comparisons like `[ "$count" -gt 0 ]`. Strip trailing CRs from
|
|
85
|
+
# all jq output after the dependency/version checks above.
|
|
86
|
+
jq() {
|
|
87
|
+
command jq "$@" | tr -d '\r'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# ââ€â‚¬Ã¢â€â‚¬ Configuration ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
81
91
|
|
|
82
92
|
BD="${BD_CMD:-bd}"
|
|
83
93
|
GIT="${GIT_CMD:-git}"
|
|
@@ -94,7 +104,7 @@ else
|
|
|
94
104
|
BASE_BRANCH="master"
|
|
95
105
|
fi
|
|
96
106
|
|
|
97
|
-
# Parse arguments (bash 3.2 compatible
|
|
107
|
+
# Parse arguments (bash 3.2 compatible  no associative arrays)
|
|
98
108
|
for arg in "$@"; do
|
|
99
109
|
case "$arg" in
|
|
100
110
|
--json) JSON_MODE=1 ;;
|
|
@@ -115,7 +125,7 @@ for arg in "$@"; do
|
|
|
115
125
|
esac
|
|
116
126
|
done
|
|
117
127
|
|
|
118
|
-
#
|
|
128
|
+
# ââ€â‚¬Ã¢â€â‚¬ Fetch issues ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
119
129
|
|
|
120
130
|
# Auto-recover beads database if Dolt server lost the database (e.g. branch
|
|
121
131
|
# switch, fresh clone, server restart). Checks for the specific "database
|
|
@@ -129,10 +139,18 @@ fi
|
|
|
129
139
|
|
|
130
140
|
_bd_list_err="$("$BD" list --json --limit 0 2>&1 >/dev/null || true)"
|
|
131
141
|
if printf '%s' "$_bd_list_err" | grep -qi "database.*not found"; then
|
|
132
|
-
# Derive prefix from repo root directory name (not pwd, which could be a subdirectory)
|
|
133
142
|
_repo_root="$("$GIT" rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
134
|
-
|
|
135
|
-
|
|
143
|
+
_metadata_path="$_repo_root/.beads/metadata.json"
|
|
144
|
+
_bd_prefix=""
|
|
145
|
+
if [ -f "$_metadata_path" ]; then
|
|
146
|
+
if _metadata_prefix="$(jq -r '(.dolt_database // "") | strings' "$_metadata_path" 2>/dev/null)"; then
|
|
147
|
+
_bd_prefix="$(printf '%s' "$_metadata_prefix" | tr -d '\r')"
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
if [ -z "$_bd_prefix" ]; then
|
|
151
|
+
_bd_prefix="$(basename "$_repo_root" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]-' '-' | sed 's/^-//;s/-$//')"
|
|
152
|
+
fi
|
|
153
|
+
echo "Beads database not found - attempting auto-recovery..." >&2
|
|
136
154
|
if "$BD" init --force --prefix "$_bd_prefix" >/dev/null 2>&1; then
|
|
137
155
|
if [ -d "$_repo_root/.beads/backup" ] && ls "$_repo_root/.beads/backup"/*.jsonl >/dev/null 2>&1; then
|
|
138
156
|
"$BD" backup restore >/dev/null 2>&1 && echo "Beads: restored from backup." >&2 || echo "Beads: backup restore failed." >&2
|
|
@@ -140,7 +158,7 @@ if printf '%s' "$_bd_list_err" | grep -qi "database.*not found"; then
|
|
|
140
158
|
echo "Beads: initialized fresh (no backup found)." >&2
|
|
141
159
|
fi
|
|
142
160
|
else
|
|
143
|
-
echo "Beads: auto-recovery failed
|
|
161
|
+
echo "Beads: auto-recovery failed - run 'bd doctor' manually." >&2
|
|
144
162
|
fi
|
|
145
163
|
fi
|
|
146
164
|
|
|
@@ -156,7 +174,7 @@ if [ "$(printf '%s' "$ISSUES_JSON" | jq 'length')" = "0" ]; then
|
|
|
156
174
|
exit 0
|
|
157
175
|
fi
|
|
158
176
|
|
|
159
|
-
#
|
|
177
|
+
# ââ€â‚¬Ã¢â€â‚¬ Identify epics and fetch their children ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
160
178
|
|
|
161
179
|
# Extract epic IDs (type == "epic")
|
|
162
180
|
EPIC_IDS="$(printf '%s' "$ISSUES_JSON" | jq -r '.[] | select(.type == "epic") | .id')"
|
|
@@ -164,7 +182,7 @@ EPIC_IDS="$(printf '%s' "$ISSUES_JSON" | jq -r '.[] | select(.type == "epic") |
|
|
|
164
182
|
# Build a JSON object mapping epic_id -> { closed, total }
|
|
165
183
|
EPIC_STATS="{}"
|
|
166
184
|
if [ -n "$EPIC_IDS" ]; then
|
|
167
|
-
# Process each epic (bash 3.2 compatible
|
|
185
|
+
# Process each epic (bash 3.2 compatible  read line by line)
|
|
168
186
|
while IFS= read -r epic_id; do
|
|
169
187
|
[ -z "$epic_id" ] && continue
|
|
170
188
|
CHILDREN_JSON="$("$BD" children "$epic_id" --json 2>/dev/null || echo '[]')"
|
|
@@ -176,92 +194,16 @@ $EPIC_IDS
|
|
|
176
194
|
EOF
|
|
177
195
|
fi
|
|
178
196
|
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
[.[] | . as $issue |
|
|
191
|
-
|
|
192
|
-
# Priority weight: P0=5, P1=4, P2=3, P3=2, P4=1, default=1
|
|
193
|
-
# Normalize priority to number (beads 0.62+ sends numbers, older sends strings)
|
|
194
|
-
((.priority // 2) | if type == "string" then
|
|
195
|
-
(if . == "P0" then 0 elif . == "P1" then 1 elif . == "P2" then 2 elif . == "P3" then 3 elif . == "P4" then 4 else 5 end)
|
|
196
|
-
else . end) as $pri_num |
|
|
197
|
-
(if $pri_num == 0 then 5
|
|
198
|
-
elif $pri_num == 1 then 4
|
|
199
|
-
elif $pri_num == 2 then 3
|
|
200
|
-
elif $pri_num == 3 then 2
|
|
201
|
-
elif $pri_num == 4 then 1
|
|
202
|
-
else 1 end) as $priority_weight |
|
|
203
|
-
|
|
204
|
-
# Unblock chain: dependent_count + 1 (min 1).
|
|
205
|
-
# NOTE: dependent_count is the authoritative server-side count. The "Unblocks:"
|
|
206
|
-
# annotation (computed below from local response) may show fewer items when
|
|
207
|
-
# bd list omits closed or filtered-out issues. This is an intentional trade-off.
|
|
208
|
-
(((.dependent_count // 0) + 1) | if . < 1 then 1 else . end) as $unblock_chain |
|
|
209
|
-
|
|
210
|
-
# Type weight: bug=1.2, feature=1.0, task=0.8, default=1.0
|
|
211
|
-
# Handle null type from beads 0.62+
|
|
212
|
-
(if (.type // "task") == "bug" then 1.2
|
|
213
|
-
elif (.type // "task") == "feature" then 1.0
|
|
214
|
-
elif (.type // "task") == "task" then 0.8
|
|
215
|
-
else 1.0 end) as $type_weight |
|
|
216
|
-
|
|
217
|
-
# Status boost: in_progress=1.5, open=1.0, default=1.0
|
|
218
|
-
(if .status == "in_progress" then 1.5
|
|
219
|
-
elif .status == "open" then 1.0
|
|
220
|
-
else 1.0 end) as $status_boost |
|
|
221
|
-
|
|
222
|
-
# Epic proximity: if issue has parent_id that is an epic, compute
|
|
223
|
-
# 1.0 + (closed_siblings / total_siblings) * 0.5
|
|
224
|
-
(if (.parent_id // "") != "" and ($epic_stats[.parent_id] // null) != null then
|
|
225
|
-
$epic_stats[.parent_id] as $es |
|
|
226
|
-
if $es.total > 0 then
|
|
227
|
-
1.0 + (($es.closed / $es.total) * 0.5)
|
|
228
|
-
else 1.0 end
|
|
229
|
-
else 1.0 end) as $epic_proximity |
|
|
230
|
-
|
|
231
|
-
# Staleness boost based on updated_at
|
|
232
|
-
# 0-7d=1.0, 7-14d=1.1, 14-30d=1.2, 30+d=1.5
|
|
233
|
-
# Truncate to first 19 chars (YYYY-MM-DDTHH:MM:SS) + "Z" for fromdateiso8601.
|
|
234
|
-
# Handles fractional seconds (.NNN), timezone offsets (+05:30), or both.
|
|
235
|
-
(if .updated_at then
|
|
236
|
-
((.updated_at[:19] + "Z") | fromdateiso8601) as $ts |
|
|
237
|
-
((now - $ts) / 86400) as $days |
|
|
238
|
-
if $days >= 30 then 1.5
|
|
239
|
-
elif $days >= 14 then 1.2
|
|
240
|
-
elif $days >= 7 then 1.1
|
|
241
|
-
else 1.0 end
|
|
242
|
-
else 1.0 end) as $staleness_boost |
|
|
243
|
-
|
|
244
|
-
# Composite score
|
|
245
|
-
($priority_weight * $unblock_chain * $type_weight * $status_boost * $epic_proximity * $staleness_boost) as $score |
|
|
246
|
-
|
|
247
|
-
# Compute dependents list (which issues does this one unblock?)
|
|
248
|
-
($dependents_map[$issue.id] // []) as $dependents_list |
|
|
249
|
-
|
|
250
|
-
# Output scored issue
|
|
251
|
-
$issue + {
|
|
252
|
-
score: ($score * 100 | round / 100),
|
|
253
|
-
priority_weight: $priority_weight,
|
|
254
|
-
unblock_chain: $unblock_chain,
|
|
255
|
-
type_weight: $type_weight,
|
|
256
|
-
status_boost: $status_boost,
|
|
257
|
-
epic_proximity: (($epic_proximity * 100 | round) / 100),
|
|
258
|
-
staleness_boost: $staleness_boost,
|
|
259
|
-
dependents: $dependents_list
|
|
260
|
-
}
|
|
261
|
-
] | sort_by(-.score)
|
|
262
|
-
')"
|
|
263
|
-
|
|
264
|
-
# ── Session detection ─────────────────────────────────────────────────
|
|
197
|
+
# ââ€â‚¬Ã¢â€â‚¬ Score issues via shared JS helper ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
198
|
+
|
|
199
|
+
if ! command -v "$NODE_CMD" >/dev/null 2>&1; then
|
|
200
|
+
echo "Error: node is required but not found." >&2
|
|
201
|
+
exit 1
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
SCORED_JSON="$(printf '{"issues":%s,"epicStats":%s}' "$ISSUES_JSON" "$EPIC_STATS" | "$NODE_CMD" "$_SMART_STATUS_DIR/smart-status-score.js")"
|
|
205
|
+
|
|
206
|
+
# ââ€â‚¬Ã¢â€â‚¬ Session detection ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
265
207
|
|
|
266
208
|
# Parse git worktree list --porcelain to find active sessions
|
|
267
209
|
# Format: blocks separated by blank lines, each block has:
|
|
@@ -271,198 +213,51 @@ SCORED_JSON="$(printf '%s' "$ISSUES_JSON" | jq --argjson epic_stats "$EPIC_STATS
|
|
|
271
213
|
|
|
272
214
|
WORKTREE_PORCELAIN="$("$GIT" worktree list --porcelain 2>/dev/null || echo '')"
|
|
273
215
|
|
|
274
|
-
# Collect non-default-branch worktree branches and paths
|
|
275
|
-
# Stored as newline-delimited "branch<TAB>path" entries — safe for paths with |, spaces, etc.
|
|
276
|
-
SESSION_ENTRIES=""
|
|
277
|
-
SESSION_COUNT=0
|
|
278
|
-
|
|
279
|
-
_wt_path=""
|
|
280
|
-
_wt_branch=""
|
|
281
|
-
while IFS= read -r line; do
|
|
282
|
-
case "$line" in
|
|
283
|
-
worktree\ *)
|
|
284
|
-
_wt_path="${line#worktree }"
|
|
285
|
-
;;
|
|
286
|
-
branch\ *)
|
|
287
|
-
_wt_branch="${line#branch refs/heads/}"
|
|
288
|
-
;;
|
|
289
|
-
"")
|
|
290
|
-
# End of block — process if we have a branch that is not the default branch
|
|
291
|
-
if [ -n "$_wt_branch" ] && [ "$_wt_branch" != "$BASE_BRANCH" ]; then
|
|
292
|
-
SESSION_COUNT=$((SESSION_COUNT + 1))
|
|
293
|
-
if [ -n "$SESSION_ENTRIES" ]; then
|
|
294
|
-
SESSION_ENTRIES="${SESSION_ENTRIES}
|
|
295
|
-
${_wt_branch} ${_wt_path}"
|
|
296
|
-
else
|
|
297
|
-
SESSION_ENTRIES="${_wt_branch} ${_wt_path}"
|
|
298
|
-
fi
|
|
299
|
-
fi
|
|
300
|
-
_wt_path=""
|
|
301
|
-
_wt_branch=""
|
|
302
|
-
;;
|
|
303
|
-
esac
|
|
304
|
-
done <<EOF
|
|
305
|
-
${WORKTREE_PORCELAIN}
|
|
306
216
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
# Get in-progress issues for matching (only if we have sessions)
|
|
217
|
+
# Build session/conflict data with the shared JS helper so the shell layer
|
|
218
|
+
# only performs command execution and rendering.
|
|
310
219
|
IN_PROGRESS_JSON="[]"
|
|
311
|
-
if [ "$
|
|
220
|
+
if [ -n "$WORKTREE_PORCELAIN" ]; then
|
|
312
221
|
IN_PROGRESS_JSON="$("$BD" list --status in_progress --json --limit 0 2>/dev/null || echo '')"
|
|
313
|
-
# Fallback: only if bd command failed (empty output), not if result is legitimately empty array
|
|
314
222
|
if [ -z "$IN_PROGRESS_JSON" ] || ! printf '%s' "$IN_PROGRESS_JSON" | jq empty 2>/dev/null; then
|
|
315
223
|
IN_PROGRESS_JSON="$(printf '%s' "$ISSUES_JSON" | jq '[.[] | select(.status == "in_progress")]')"
|
|
316
224
|
fi
|
|
317
225
|
fi
|
|
318
226
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
while IFS=' ' read -r _branch _path; do
|
|
325
|
-
[ -z "$_branch" ] && continue
|
|
227
|
+
SESSIONS_JSON="$(printf '{"baseBranch":%s,"worktreePorcelain":%s,"inProgressIssues":%s}' \
|
|
228
|
+
"$(printf '%s' "$BASE_BRANCH" | jq -R '.')" \
|
|
229
|
+
"$(printf '%s' "$WORKTREE_PORCELAIN" | jq -Rs '.')" \
|
|
230
|
+
"$IN_PROGRESS_JSON" | "$NODE_CMD" "$_SMART_STATUS_DIR/smart-status-sessions.js")"
|
|
231
|
+
SESSION_COUNT="$(printf '%s' "$SESSIONS_JSON" | jq 'length')"
|
|
326
232
|
|
|
327
|
-
# Extract slug from branch name (strip any <prefix>/ convention)
|
|
328
|
-
_slug="$_branch"
|
|
329
|
-
case "$_slug" in
|
|
330
|
-
*/*) _slug="${_slug##*/}" ;;
|
|
331
|
-
esac
|
|
332
|
-
|
|
333
|
-
# Convert slug hyphens to spaces for title matching
|
|
334
|
-
_slug_spaces="$(printf '%s' "$_slug" | tr '-' ' ')"
|
|
335
|
-
|
|
336
|
-
# Find matching in-progress issues: title contains slug words (case-insensitive)
|
|
337
|
-
_matched_ids="$(printf '%s' "$IN_PROGRESS_JSON" | jq -r --arg slug "$_slug_spaces" '
|
|
338
|
-
[.[] | select(
|
|
339
|
-
(.title | ascii_downcase | contains($slug | ascii_downcase))
|
|
340
|
-
) | .id] | join(",")
|
|
341
|
-
')"
|
|
342
|
-
|
|
343
|
-
_issue_count=0
|
|
344
|
-
if [ -n "$_matched_ids" ]; then
|
|
345
|
-
# Count commas + 1
|
|
346
|
-
_issue_count="$(printf '%s' "$_matched_ids" | tr -cd ',' | wc -c)"
|
|
347
|
-
_issue_count=$((_issue_count + 1))
|
|
348
|
-
fi
|
|
349
|
-
|
|
350
|
-
# Build session JSON entry
|
|
351
|
-
if [ -n "$_matched_ids" ]; then
|
|
352
|
-
_ids_json="$(printf '%s' "$_matched_ids" | jq -R 'split(",")')"
|
|
353
|
-
else
|
|
354
|
-
_ids_json="[]"
|
|
355
|
-
fi
|
|
356
|
-
|
|
357
|
-
SESSIONS_JSON="$(printf '%s' "$SESSIONS_JSON" | jq \
|
|
358
|
-
--arg branch "$_branch" \
|
|
359
|
-
--arg path "$_path" \
|
|
360
|
-
--argjson ids "$_ids_json" \
|
|
361
|
-
--argjson count "$_issue_count" \
|
|
362
|
-
'. + [{branch: $branch, path: $path, issue_ids: $ids, issue_count: $count}]'
|
|
363
|
-
)"
|
|
364
|
-
|
|
365
|
-
done <<SESSIONS_EOF
|
|
366
|
-
$SESSION_ENTRIES
|
|
367
|
-
SESSIONS_EOF
|
|
368
|
-
fi
|
|
369
|
-
|
|
370
|
-
# ── File-level conflict detection ────────────────────────────────────────
|
|
371
|
-
# For each active worktree branch, get changed files via:
|
|
372
|
-
# git diff <base-branch>...<branch> --name-only --
|
|
373
|
-
# Uses -- separator to prevent argument injection (OWASP A03)
|
|
374
|
-
|
|
375
|
-
# ALL_BRANCH_FILES: newline-delimited list of "branch<TAB>file1<TAB>file2..."
|
|
376
|
-
ALL_BRANCH_FILES=""
|
|
377
233
|
if [ "$SESSION_COUNT" -ge 2 ]; then
|
|
378
|
-
|
|
379
|
-
|
|
234
|
+
BRANCH_FILES_JSON="[]"
|
|
235
|
+
_session_branches="$(printf '%s' "$SESSIONS_JSON" | jq -r '.[].branch')"
|
|
236
|
+
while IFS= read -r _branch; do
|
|
380
237
|
[ -z "$_branch" ] && continue
|
|
381
|
-
|
|
382
|
-
# Get changed files for this branch vs BASE_BRANCH (-- prevents injection)
|
|
383
238
|
_files="$("$GIT" diff "${BASE_BRANCH}...${_branch}" --name-only -- 2>/dev/null || echo '')"
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if [ -n "$ALL_BRANCH_FILES" ]; then
|
|
388
|
-
ALL_BRANCH_FILES="${ALL_BRANCH_FILES}
|
|
389
|
-
${_branch}:${_files_csv}"
|
|
390
|
-
else
|
|
391
|
-
ALL_BRANCH_FILES="${_branch}:${_files_csv}"
|
|
392
|
-
fi
|
|
393
|
-
done <<CONFLICT_EOF
|
|
394
|
-
$SESSION_ENTRIES
|
|
395
|
-
CONFLICT_EOF
|
|
396
|
-
|
|
397
|
-
# Build conflict map: find files that appear in multiple branches
|
|
398
|
-
# and add changed_files + conflicts to SESSIONS_JSON
|
|
399
|
-
while IFS= read -r _entry; do
|
|
400
|
-
[ -z "$_entry" ] && continue
|
|
401
|
-
_branch="${_entry%%:*}"
|
|
402
|
-
_files_csv="${_entry#*:}"
|
|
403
|
-
|
|
404
|
-
# Convert tab-delimited files to JSON array
|
|
405
|
-
if [ -n "$_files_csv" ]; then
|
|
406
|
-
_files_json="$(printf '%s' "$_files_csv" | jq -R 'split("\t")')"
|
|
407
|
-
else
|
|
408
|
-
_files_json="[]"
|
|
409
|
-
fi
|
|
410
|
-
|
|
411
|
-
# Find conflicts: files in this branch that also appear in other branches
|
|
412
|
-
_conflicts_json="[]"
|
|
413
|
-
while IFS= read -r _other_entry; do
|
|
414
|
-
[ -z "$_other_entry" ] && continue
|
|
415
|
-
_other_branch="${_other_entry%%:*}"
|
|
416
|
-
_other_files="${_other_entry#*:}"
|
|
417
|
-
|
|
418
|
-
# Skip self
|
|
419
|
-
[ "$_other_branch" = "$_branch" ] && continue
|
|
420
|
-
[ -z "$_other_files" ] && continue
|
|
421
|
-
|
|
422
|
-
# Find intersection
|
|
423
|
-
_other_json="$(printf '%s' "$_other_files" | jq -R 'split("\t")')"
|
|
424
|
-
_overlap="$(jq -n --argjson a "$_files_json" --argjson b "$_other_json" \
|
|
425
|
-
'[$a[] as $f | select($b | index($f))] | unique')"
|
|
426
|
-
_overlap_len="$(printf '%s' "$_overlap" | jq 'length')"
|
|
427
|
-
if [ "$_overlap_len" -gt 0 ]; then
|
|
428
|
-
_conflicts_json="$(jq -n \
|
|
429
|
-
--argjson existing "$_conflicts_json" \
|
|
430
|
-
--arg other_branch "$_other_branch" \
|
|
431
|
-
--argjson files "$_overlap" \
|
|
432
|
-
'$existing + [{branch: $other_branch, files: $files}]')"
|
|
433
|
-
fi
|
|
434
|
-
done <<INNER_EOF
|
|
435
|
-
$ALL_BRANCH_FILES
|
|
436
|
-
INNER_EOF
|
|
437
|
-
|
|
438
|
-
# Update SESSIONS_JSON: add changed_files and conflicts to matching branch entry
|
|
439
|
-
SESSIONS_JSON="$(printf '%s' "$SESSIONS_JSON" | jq \
|
|
239
|
+
_files_json="$(printf '%s' "$_files" | jq -R -s 'split("\n") | map(select(length > 0))')"
|
|
240
|
+
BRANCH_FILES_JSON="$(printf '%s' "$BRANCH_FILES_JSON" | jq \
|
|
440
241
|
--arg branch "$_branch" \
|
|
441
242
|
--argjson files "$_files_json" \
|
|
442
|
-
|
|
443
|
-
'[.[] | if .branch == $branch then . + {changed_files: $files, conflicts: $conflicts} else . end]'
|
|
243
|
+
'. + [{branch: $branch, files: $files}]'
|
|
444
244
|
)"
|
|
445
|
-
done <<
|
|
446
|
-
$
|
|
447
|
-
|
|
448
|
-
fi
|
|
245
|
+
done <<BRANCHEOF
|
|
246
|
+
$_session_branches
|
|
247
|
+
BRANCHEOF
|
|
449
248
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
# to detect actual merge conflicts vs mere file overlap.
|
|
249
|
+
SESSIONS_JSON="$(printf '{"sessions":%s,"branchFiles":%s}' "$SESSIONS_JSON" "$BRANCH_FILES_JSON" | "$NODE_CMD" "$_SMART_STATUS_DIR/smart-status-sessions.js")"
|
|
250
|
+
fi
|
|
453
251
|
|
|
454
252
|
TIER2_ENABLED=0
|
|
455
253
|
if [ "$SESSION_COUNT" -ge 2 ]; then
|
|
456
|
-
# Check git version >= 2.38 (merge-tree --write-tree requires it)
|
|
457
254
|
_git_ver="$("$GIT" --version 2>/dev/null || echo '')"
|
|
458
255
|
_git_major=0
|
|
459
256
|
_git_minor=0
|
|
460
|
-
# Parse "git version X.Y.Z..." -> extract major and minor
|
|
461
257
|
_ver_nums="${_git_ver#git version }"
|
|
462
258
|
_git_major="${_ver_nums%%.*}"
|
|
463
259
|
_ver_rest="${_ver_nums#*.}"
|
|
464
260
|
_git_minor="${_ver_rest%%.*}"
|
|
465
|
-
# Validate they are numbers
|
|
466
261
|
case "$_git_major" in ''|*[!0-9]*) _git_major=0 ;; esac
|
|
467
262
|
case "$_git_minor" in ''|*[!0-9]*) _git_minor=0 ;; esac
|
|
468
263
|
|
|
@@ -471,79 +266,38 @@ if [ "$SESSION_COUNT" -ge 2 ]; then
|
|
|
471
266
|
fi
|
|
472
267
|
fi
|
|
473
268
|
|
|
474
|
-
# For each pair of branches with file overlaps, run merge-tree
|
|
475
269
|
if [ "$TIER2_ENABLED" = "1" ]; then
|
|
476
|
-
# Collect all branch pairs that have conflicts in SESSIONS_JSON
|
|
477
270
|
_conflict_pairs="$(printf '%s' "$SESSIONS_JSON" | jq -r '
|
|
478
271
|
[.[] | select((.conflicts // []) | length > 0) |
|
|
479
272
|
.branch as $b |
|
|
480
273
|
.conflicts[] |
|
|
481
|
-
[$b, .branch] | sort | join("
|
|
274
|
+
[$b, .branch] | sort | join("\t")
|
|
482
275
|
] | unique | .[]
|
|
483
276
|
')"
|
|
484
277
|
|
|
485
278
|
if [ -n "$_conflict_pairs" ]; then
|
|
486
|
-
|
|
487
|
-
while IFS= read -r
|
|
488
|
-
[ -z "$
|
|
489
|
-
|
|
490
|
-
_b2="${_pair#* }"
|
|
491
|
-
|
|
492
|
-
# Run merge-tree; exit 1 = actual conflict, exit 0 = clean merge
|
|
279
|
+
MERGE_TREE_RESULTS_JSON="[]"
|
|
280
|
+
while IFS=' ' read -r _b1 _b2; do
|
|
281
|
+
[ -z "$_b1" ] && continue
|
|
282
|
+
[ -z "$_b2" ] && continue
|
|
493
283
|
_mt_output=""
|
|
494
284
|
_mt_exit=0
|
|
495
285
|
_mt_output="$("$GIT" merge-tree --write-tree --name-only --no-messages -- "$_b1" "$_b2" 2>/dev/null)" || _mt_exit=$?
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
while IFS= read -r _cf_line; do
|
|
504
|
-
_line_num=$((_line_num + 1))
|
|
505
|
-
# Skip the first line (tree SHA output from merge-tree)
|
|
506
|
-
if [ "$_line_num" -eq 1 ]; then continue; fi
|
|
507
|
-
# Skip empty lines
|
|
508
|
-
if [ -z "$_cf_line" ]; then continue; fi
|
|
509
|
-
if [ -n "$_conflict_files" ]; then
|
|
510
|
-
_conflict_files="${_conflict_files} ${_cf_line}"
|
|
511
|
-
else
|
|
512
|
-
_conflict_files="${_cf_line}"
|
|
513
|
-
fi
|
|
514
|
-
done <<MTEOF
|
|
515
|
-
$_mt_output
|
|
516
|
-
MTEOF
|
|
517
|
-
|
|
518
|
-
if [ -n "$_conflict_files" ]; then
|
|
519
|
-
_cf_json="$(printf '%s' "$_conflict_files" | jq -R 'split("\t")')"
|
|
520
|
-
|
|
521
|
-
# Add merge_conflicts to both branches in SESSIONS_JSON
|
|
522
|
-
for _target_branch in "$_b1" "$_b2"; do
|
|
523
|
-
_other_branch="$_b1"
|
|
524
|
-
[ "$_target_branch" = "$_b1" ] && _other_branch="$_b2"
|
|
525
|
-
|
|
526
|
-
SESSIONS_JSON="$(printf '%s' "$SESSIONS_JSON" | jq \
|
|
527
|
-
--arg branch "$_target_branch" \
|
|
528
|
-
--arg other "$_other_branch" \
|
|
529
|
-
--argjson files "$_cf_json" \
|
|
530
|
-
'[.[] | if .branch == $branch then
|
|
531
|
-
. + {merge_conflicts: ((.merge_conflicts // []) + [{branch: $other, files: $files}])}
|
|
532
|
-
else . end]'
|
|
533
|
-
)"
|
|
534
|
-
done
|
|
535
|
-
fi
|
|
536
|
-
fi
|
|
286
|
+
MERGE_TREE_RESULTS_JSON="$(printf '%s' "$MERGE_TREE_RESULTS_JSON" | jq \
|
|
287
|
+
--arg left "$_b1" \
|
|
288
|
+
--arg right "$_b2" \
|
|
289
|
+
--arg output "$_mt_output" \
|
|
290
|
+
--argjson exitCode "$_mt_exit" \
|
|
291
|
+
'. + [{left: $left, right: $right, exitCode: $exitCode, output: $output}]'
|
|
292
|
+
)"
|
|
537
293
|
done <<PAIRSEOF
|
|
538
294
|
$_conflict_pairs
|
|
539
295
|
PAIRSEOF
|
|
296
|
+
|
|
297
|
+
SESSIONS_JSON="$(printf '{"sessions":%s,"mergeTreeResults":%s}' "$SESSIONS_JSON" "$MERGE_TREE_RESULTS_JSON" | "$NODE_CMD" "$_SMART_STATUS_DIR/smart-status-sessions.js")"
|
|
540
298
|
fi
|
|
541
299
|
fi
|
|
542
300
|
|
|
543
|
-
# ── Cross-developer team activity ────────────────────────────────────────
|
|
544
|
-
# Read file index entries from all developers, identify OTHER developers'
|
|
545
|
-
# in-progress work, compute module overlaps, and flag stale claims.
|
|
546
|
-
|
|
547
301
|
TEAM_ACTIVITY_JSON="[]"
|
|
548
302
|
|
|
549
303
|
# Only proceed if file_index_read and get_session_identity are available
|
|
@@ -611,7 +365,7 @@ if command -v file_index_read &>/dev/null && command -v get_session_identity &>/
|
|
|
611
365
|
fi
|
|
612
366
|
fi
|
|
613
367
|
|
|
614
|
-
#
|
|
368
|
+
# ââ€â‚¬Ã¢â€â‚¬ Output ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
615
369
|
|
|
616
370
|
if [ "$JSON_MODE" = "1" ]; then
|
|
617
371
|
# Always output consistent shape: {sessions: [...], issues: [...], team_activity: [...]}
|
|
@@ -628,7 +382,7 @@ else
|
|
|
628
382
|
C_RED=$'\033[31m' C_DIM=$'\033[2m' C_BOLD=$'\033[1m' C_CYAN=$'\033[36m'
|
|
629
383
|
fi
|
|
630
384
|
|
|
631
|
-
#
|
|
385
|
+
# ââ€â‚¬Ã¢â€â‚¬ Active Sessions (before grouped output) ââ€â‚¬Ã¢â€â‚¬
|
|
632
386
|
if [ "$SESSION_COUNT" -gt 0 ]; then
|
|
633
387
|
printf '%s%s=== ACTIVE SESSIONS ===%s\n' "$C_BOLD" "$C_CYAN" "$C_RESET"
|
|
634
388
|
printf '%s' "$SESSIONS_JSON" | jq -r '.[] |
|
|
@@ -678,10 +432,10 @@ else
|
|
|
678
432
|
printf '\n'
|
|
679
433
|
fi
|
|
680
434
|
|
|
681
|
-
#
|
|
435
|
+
# ââ€â‚¬Ã¢â€â‚¬ Team Activity (cross-developer visibility) ââ€â‚¬Ã¢â€â‚¬
|
|
682
436
|
_ta_len="$(printf '%s' "$TEAM_ACTIVITY_JSON" | jq 'length')"
|
|
683
437
|
if [ "$_ta_len" -gt 0 ]; then
|
|
684
|
-
printf '%s%s
|
|
438
|
+
printf '%s%s--- Team Activity ----------------------------------------%s\n' "$C_BOLD" "$C_CYAN" "$C_RESET"
|
|
685
439
|
printf '%s' "$TEAM_ACTIVITY_JSON" | jq -r '
|
|
686
440
|
.[] |
|
|
687
441
|
.developer as $dev |
|
|
@@ -698,7 +452,7 @@ else
|
|
|
698
452
|
else
|
|
699
453
|
" " + $iid + " (in_progress)" +
|
|
700
454
|
(if ($mods | length) > 0 then
|
|
701
|
-
"
|
|
455
|
+
" - touching " + ($mods | join(", "))
|
|
702
456
|
else "" end)
|
|
703
457
|
end),
|
|
704
458
|
# Overlap or no-overlap line
|