forge-workflow 0.0.9 → 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/ship.md +2 -2
- package/.claude/commands/status.md +4 -4
- package/.cline/workflows/dev.md +2 -2
- package/.cline/workflows/plan.md +2 -2
- package/.cline/workflows/ship.md +2 -2
- 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/ship/SKILL.md +2 -2
- 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/ship.md +2 -2
- 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/ship.prompt.md +2 -2
- package/.github/prompts/status.prompt.md +4 -4
- package/.github/workflows/beads-to-github.yml +43 -10
- package/.github/workflows/github-to-beads.yml +10 -7
- package/.kilocode/workflows/dev.md +2 -2
- package/.kilocode/workflows/plan.md +2 -2
- package/.kilocode/workflows/ship.md +2 -2
- package/.kilocode/workflows/status.md +4 -4
- package/.opencode/commands/dev.md +2 -2
- package/.opencode/commands/plan.md +2 -2
- package/.opencode/commands/ship.md +2 -2
- package/.opencode/commands/status.md +4 -4
- package/.roo/commands/dev.md +2 -2
- package/.roo/commands/plan.md +2 -2
- package/.roo/commands/ship.md +2 -2
- package/.roo/commands/status.md +4 -4
- package/AGENTS.md +1 -0
- package/CLAUDE.md +12 -0
- package/bin/forge.js +10 -5
- package/docs/BEADS_GITHUB_SYNC.md +26 -0
- package/docs/TOOLCHAIN.md +130 -148
- 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/commands/_issue.js +11 -1
- package/lib/commands/issues.js +49 -0
- package/lib/commands/recommend.js +22 -1
- package/lib/commands/setup.js +16 -10
- package/lib/commands/status.js +181 -0
- package/lib/commands/team.js +11 -1
- package/lib/commands/test.js +37 -2
- package/lib/commands/validate.js +14 -8
- 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/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 +3 -1
- package/lib/workflow/state-manager.js +164 -8
- 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 -232
- package/scripts/forge-team/lib/sync-github.sh +160 -28
- package/scripts/forge-team/tests/sync-github.test.sh +195 -58
- 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/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 +74 -329
- package/scripts/sync-agentic-workflow.js +48 -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 +219 -28
- 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
|
|
@@ -75,7 +76,7 @@ if [ -n "$_jq_version" ]; then
|
|
|
75
76
|
_jq_major="${_jq_version%%.*}"
|
|
76
77
|
_jq_minor="${_jq_version#*.}"; _jq_minor="${_jq_minor%%.*}"
|
|
77
78
|
if [ "${_jq_major:-0}" -lt 1 ] || { [ "${_jq_major:-0}" -eq 1 ] && [ "${_jq_minor:-0}" -lt 6 ]; }; then
|
|
78
|
-
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
|
|
79
80
|
fi
|
|
80
81
|
fi
|
|
81
82
|
|
|
@@ -86,7 +87,7 @@ jq() {
|
|
|
86
87
|
command jq "$@" | tr -d '\r'
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
#
|
|
90
|
+
# ââ€â‚¬Ã¢â€â‚¬ Configuration ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
90
91
|
|
|
91
92
|
BD="${BD_CMD:-bd}"
|
|
92
93
|
GIT="${GIT_CMD:-git}"
|
|
@@ -103,7 +104,7 @@ else
|
|
|
103
104
|
BASE_BRANCH="master"
|
|
104
105
|
fi
|
|
105
106
|
|
|
106
|
-
# Parse arguments (bash 3.2 compatible
|
|
107
|
+
# Parse arguments (bash 3.2 compatible  no associative arrays)
|
|
107
108
|
for arg in "$@"; do
|
|
108
109
|
case "$arg" in
|
|
109
110
|
--json) JSON_MODE=1 ;;
|
|
@@ -124,7 +125,7 @@ for arg in "$@"; do
|
|
|
124
125
|
esac
|
|
125
126
|
done
|
|
126
127
|
|
|
127
|
-
#
|
|
128
|
+
# ââ€â‚¬Ã¢â€â‚¬ Fetch issues ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
128
129
|
|
|
129
130
|
# Auto-recover beads database if Dolt server lost the database (e.g. branch
|
|
130
131
|
# switch, fresh clone, server restart). Checks for the specific "database
|
|
@@ -138,10 +139,18 @@ fi
|
|
|
138
139
|
|
|
139
140
|
_bd_list_err="$("$BD" list --json --limit 0 2>&1 >/dev/null || true)"
|
|
140
141
|
if printf '%s' "$_bd_list_err" | grep -qi "database.*not found"; then
|
|
141
|
-
# Derive prefix from repo root directory name (not pwd, which could be a subdirectory)
|
|
142
142
|
_repo_root="$("$GIT" rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
145
154
|
if "$BD" init --force --prefix "$_bd_prefix" >/dev/null 2>&1; then
|
|
146
155
|
if [ -d "$_repo_root/.beads/backup" ] && ls "$_repo_root/.beads/backup"/*.jsonl >/dev/null 2>&1; then
|
|
147
156
|
"$BD" backup restore >/dev/null 2>&1 && echo "Beads: restored from backup." >&2 || echo "Beads: backup restore failed." >&2
|
|
@@ -149,7 +158,7 @@ if printf '%s' "$_bd_list_err" | grep -qi "database.*not found"; then
|
|
|
149
158
|
echo "Beads: initialized fresh (no backup found)." >&2
|
|
150
159
|
fi
|
|
151
160
|
else
|
|
152
|
-
echo "Beads: auto-recovery failed
|
|
161
|
+
echo "Beads: auto-recovery failed - run 'bd doctor' manually." >&2
|
|
153
162
|
fi
|
|
154
163
|
fi
|
|
155
164
|
|
|
@@ -165,7 +174,7 @@ if [ "$(printf '%s' "$ISSUES_JSON" | jq 'length')" = "0" ]; then
|
|
|
165
174
|
exit 0
|
|
166
175
|
fi
|
|
167
176
|
|
|
168
|
-
#
|
|
177
|
+
# ââ€â‚¬Ã¢â€â‚¬ Identify epics and fetch their children ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
169
178
|
|
|
170
179
|
# Extract epic IDs (type == "epic")
|
|
171
180
|
EPIC_IDS="$(printf '%s' "$ISSUES_JSON" | jq -r '.[] | select(.type == "epic") | .id')"
|
|
@@ -173,7 +182,7 @@ EPIC_IDS="$(printf '%s' "$ISSUES_JSON" | jq -r '.[] | select(.type == "epic") |
|
|
|
173
182
|
# Build a JSON object mapping epic_id -> { closed, total }
|
|
174
183
|
EPIC_STATS="{}"
|
|
175
184
|
if [ -n "$EPIC_IDS" ]; then
|
|
176
|
-
# Process each epic (bash 3.2 compatible
|
|
185
|
+
# Process each epic (bash 3.2 compatible  read line by line)
|
|
177
186
|
while IFS= read -r epic_id; do
|
|
178
187
|
[ -z "$epic_id" ] && continue
|
|
179
188
|
CHILDREN_JSON="$("$BD" children "$epic_id" --json 2>/dev/null || echo '[]')"
|
|
@@ -185,92 +194,16 @@ $EPIC_IDS
|
|
|
185
194
|
EOF
|
|
186
195
|
fi
|
|
187
196
|
|
|
188
|
-
#
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
[.[] | . as $issue |
|
|
200
|
-
|
|
201
|
-
# Priority weight: P0=5, P1=4, P2=3, P3=2, P4=1, default=1
|
|
202
|
-
# Normalize priority to number (beads 0.62+ sends numbers, older sends strings)
|
|
203
|
-
((.priority // 2) | if type == "string" then
|
|
204
|
-
(if . == "P0" then 0 elif . == "P1" then 1 elif . == "P2" then 2 elif . == "P3" then 3 elif . == "P4" then 4 else 5 end)
|
|
205
|
-
else . end) as $pri_num |
|
|
206
|
-
(if $pri_num == 0 then 5
|
|
207
|
-
elif $pri_num == 1 then 4
|
|
208
|
-
elif $pri_num == 2 then 3
|
|
209
|
-
elif $pri_num == 3 then 2
|
|
210
|
-
elif $pri_num == 4 then 1
|
|
211
|
-
else 1 end) as $priority_weight |
|
|
212
|
-
|
|
213
|
-
# Unblock chain: dependent_count + 1 (min 1).
|
|
214
|
-
# NOTE: dependent_count is the authoritative server-side count. The "Unblocks:"
|
|
215
|
-
# annotation (computed below from local response) may show fewer items when
|
|
216
|
-
# bd list omits closed or filtered-out issues. This is an intentional trade-off.
|
|
217
|
-
(((.dependent_count // 0) + 1) | if . < 1 then 1 else . end) as $unblock_chain |
|
|
218
|
-
|
|
219
|
-
# Type weight: bug=1.2, feature=1.0, task=0.8, default=1.0
|
|
220
|
-
# Handle null type from beads 0.62+
|
|
221
|
-
(if (.type // "task") == "bug" then 1.2
|
|
222
|
-
elif (.type // "task") == "feature" then 1.0
|
|
223
|
-
elif (.type // "task") == "task" then 0.8
|
|
224
|
-
else 1.0 end) as $type_weight |
|
|
225
|
-
|
|
226
|
-
# Status boost: in_progress=1.5, open=1.0, default=1.0
|
|
227
|
-
(if .status == "in_progress" then 1.5
|
|
228
|
-
elif .status == "open" then 1.0
|
|
229
|
-
else 1.0 end) as $status_boost |
|
|
230
|
-
|
|
231
|
-
# Epic proximity: if issue has parent_id that is an epic, compute
|
|
232
|
-
# 1.0 + (closed_siblings / total_siblings) * 0.5
|
|
233
|
-
(if (.parent_id // "") != "" and ($epic_stats[.parent_id] // null) != null then
|
|
234
|
-
$epic_stats[.parent_id] as $es |
|
|
235
|
-
if $es.total > 0 then
|
|
236
|
-
1.0 + (($es.closed / $es.total) * 0.5)
|
|
237
|
-
else 1.0 end
|
|
238
|
-
else 1.0 end) as $epic_proximity |
|
|
239
|
-
|
|
240
|
-
# Staleness boost based on updated_at
|
|
241
|
-
# 0-7d=1.0, 7-14d=1.1, 14-30d=1.2, 30+d=1.5
|
|
242
|
-
# Truncate to first 19 chars (YYYY-MM-DDTHH:MM:SS) + "Z" for fromdateiso8601.
|
|
243
|
-
# Handles fractional seconds (.NNN), timezone offsets (+05:30), or both.
|
|
244
|
-
(if .updated_at then
|
|
245
|
-
((.updated_at[:19] + "Z") | fromdateiso8601) as $ts |
|
|
246
|
-
((now - $ts) / 86400) as $days |
|
|
247
|
-
if $days >= 30 then 1.5
|
|
248
|
-
elif $days >= 14 then 1.2
|
|
249
|
-
elif $days >= 7 then 1.1
|
|
250
|
-
else 1.0 end
|
|
251
|
-
else 1.0 end) as $staleness_boost |
|
|
252
|
-
|
|
253
|
-
# Composite score
|
|
254
|
-
($priority_weight * $unblock_chain * $type_weight * $status_boost * $epic_proximity * $staleness_boost) as $score |
|
|
255
|
-
|
|
256
|
-
# Compute dependents list (which issues does this one unblock?)
|
|
257
|
-
($dependents_map[$issue.id] // []) as $dependents_list |
|
|
258
|
-
|
|
259
|
-
# Output scored issue
|
|
260
|
-
$issue + {
|
|
261
|
-
score: ($score * 100 | round / 100),
|
|
262
|
-
priority_weight: $priority_weight,
|
|
263
|
-
unblock_chain: $unblock_chain,
|
|
264
|
-
type_weight: $type_weight,
|
|
265
|
-
status_boost: $status_boost,
|
|
266
|
-
epic_proximity: (($epic_proximity * 100 | round) / 100),
|
|
267
|
-
staleness_boost: $staleness_boost,
|
|
268
|
-
dependents: $dependents_list
|
|
269
|
-
}
|
|
270
|
-
] | sort_by(-.score)
|
|
271
|
-
')"
|
|
272
|
-
|
|
273
|
-
# ── 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 ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
274
207
|
|
|
275
208
|
# Parse git worktree list --porcelain to find active sessions
|
|
276
209
|
# Format: blocks separated by blank lines, each block has:
|
|
@@ -280,198 +213,51 @@ SCORED_JSON="$(printf '%s' "$ISSUES_JSON" | jq --argjson epic_stats "$EPIC_STATS
|
|
|
280
213
|
|
|
281
214
|
WORKTREE_PORCELAIN="$("$GIT" worktree list --porcelain 2>/dev/null || echo '')"
|
|
282
215
|
|
|
283
|
-
# Collect non-default-branch worktree branches and paths
|
|
284
|
-
# Stored as newline-delimited "branch<TAB>path" entries — safe for paths with |, spaces, etc.
|
|
285
|
-
SESSION_ENTRIES=""
|
|
286
|
-
SESSION_COUNT=0
|
|
287
|
-
|
|
288
|
-
_wt_path=""
|
|
289
|
-
_wt_branch=""
|
|
290
|
-
while IFS= read -r line; do
|
|
291
|
-
case "$line" in
|
|
292
|
-
worktree\ *)
|
|
293
|
-
_wt_path="${line#worktree }"
|
|
294
|
-
;;
|
|
295
|
-
branch\ *)
|
|
296
|
-
_wt_branch="${line#branch refs/heads/}"
|
|
297
|
-
;;
|
|
298
|
-
"")
|
|
299
|
-
# End of block — process if we have a branch that is not the default branch
|
|
300
|
-
if [ -n "$_wt_branch" ] && [ "$_wt_branch" != "$BASE_BRANCH" ]; then
|
|
301
|
-
SESSION_COUNT=$((SESSION_COUNT + 1))
|
|
302
|
-
if [ -n "$SESSION_ENTRIES" ]; then
|
|
303
|
-
SESSION_ENTRIES="${SESSION_ENTRIES}
|
|
304
|
-
${_wt_branch} ${_wt_path}"
|
|
305
|
-
else
|
|
306
|
-
SESSION_ENTRIES="${_wt_branch} ${_wt_path}"
|
|
307
|
-
fi
|
|
308
|
-
fi
|
|
309
|
-
_wt_path=""
|
|
310
|
-
_wt_branch=""
|
|
311
|
-
;;
|
|
312
|
-
esac
|
|
313
|
-
done <<EOF
|
|
314
|
-
${WORKTREE_PORCELAIN}
|
|
315
|
-
|
|
316
|
-
EOF
|
|
317
216
|
|
|
318
|
-
#
|
|
217
|
+
# Build session/conflict data with the shared JS helper so the shell layer
|
|
218
|
+
# only performs command execution and rendering.
|
|
319
219
|
IN_PROGRESS_JSON="[]"
|
|
320
|
-
if [ "$
|
|
220
|
+
if [ -n "$WORKTREE_PORCELAIN" ]; then
|
|
321
221
|
IN_PROGRESS_JSON="$("$BD" list --status in_progress --json --limit 0 2>/dev/null || echo '')"
|
|
322
|
-
# Fallback: only if bd command failed (empty output), not if result is legitimately empty array
|
|
323
222
|
if [ -z "$IN_PROGRESS_JSON" ] || ! printf '%s' "$IN_PROGRESS_JSON" | jq empty 2>/dev/null; then
|
|
324
223
|
IN_PROGRESS_JSON="$(printf '%s' "$ISSUES_JSON" | jq '[.[] | select(.status == "in_progress")]')"
|
|
325
224
|
fi
|
|
326
225
|
fi
|
|
327
226
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
while IFS=' ' read -r _branch _path; do
|
|
334
|
-
[ -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')"
|
|
335
232
|
|
|
336
|
-
# Extract slug from branch name (strip any <prefix>/ convention)
|
|
337
|
-
_slug="$_branch"
|
|
338
|
-
case "$_slug" in
|
|
339
|
-
*/*) _slug="${_slug##*/}" ;;
|
|
340
|
-
esac
|
|
341
|
-
|
|
342
|
-
# Convert slug hyphens to spaces for title matching
|
|
343
|
-
_slug_spaces="$(printf '%s' "$_slug" | tr '-' ' ')"
|
|
344
|
-
|
|
345
|
-
# Find matching in-progress issues: title contains slug words (case-insensitive)
|
|
346
|
-
_matched_ids="$(printf '%s' "$IN_PROGRESS_JSON" | jq -r --arg slug "$_slug_spaces" '
|
|
347
|
-
[.[] | select(
|
|
348
|
-
(.title | ascii_downcase | contains($slug | ascii_downcase))
|
|
349
|
-
) | .id] | join(",")
|
|
350
|
-
')"
|
|
351
|
-
|
|
352
|
-
_issue_count=0
|
|
353
|
-
if [ -n "$_matched_ids" ]; then
|
|
354
|
-
# Count commas + 1
|
|
355
|
-
_issue_count="$(printf '%s' "$_matched_ids" | tr -cd ',' | wc -c)"
|
|
356
|
-
_issue_count=$((_issue_count + 1))
|
|
357
|
-
fi
|
|
358
|
-
|
|
359
|
-
# Build session JSON entry
|
|
360
|
-
if [ -n "$_matched_ids" ]; then
|
|
361
|
-
_ids_json="$(printf '%s' "$_matched_ids" | jq -R 'split(",")')"
|
|
362
|
-
else
|
|
363
|
-
_ids_json="[]"
|
|
364
|
-
fi
|
|
365
|
-
|
|
366
|
-
SESSIONS_JSON="$(printf '%s' "$SESSIONS_JSON" | jq \
|
|
367
|
-
--arg branch "$_branch" \
|
|
368
|
-
--arg path "$_path" \
|
|
369
|
-
--argjson ids "$_ids_json" \
|
|
370
|
-
--argjson count "$_issue_count" \
|
|
371
|
-
'. + [{branch: $branch, path: $path, issue_ids: $ids, issue_count: $count}]'
|
|
372
|
-
)"
|
|
373
|
-
|
|
374
|
-
done <<SESSIONS_EOF
|
|
375
|
-
$SESSION_ENTRIES
|
|
376
|
-
SESSIONS_EOF
|
|
377
|
-
fi
|
|
378
|
-
|
|
379
|
-
# ── File-level conflict detection ────────────────────────────────────────
|
|
380
|
-
# For each active worktree branch, get changed files via:
|
|
381
|
-
# git diff <base-branch>...<branch> --name-only --
|
|
382
|
-
# Uses -- separator to prevent argument injection (OWASP A03)
|
|
383
|
-
|
|
384
|
-
# ALL_BRANCH_FILES: newline-delimited list of "branch<TAB>file1<TAB>file2..."
|
|
385
|
-
ALL_BRANCH_FILES=""
|
|
386
233
|
if [ "$SESSION_COUNT" -ge 2 ]; then
|
|
387
|
-
|
|
388
|
-
|
|
234
|
+
BRANCH_FILES_JSON="[]"
|
|
235
|
+
_session_branches="$(printf '%s' "$SESSIONS_JSON" | jq -r '.[].branch')"
|
|
236
|
+
while IFS= read -r _branch; do
|
|
389
237
|
[ -z "$_branch" ] && continue
|
|
390
|
-
|
|
391
|
-
# Get changed files for this branch vs BASE_BRANCH (-- prevents injection)
|
|
392
238
|
_files="$("$GIT" diff "${BASE_BRANCH}...${_branch}" --name-only -- 2>/dev/null || echo '')"
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if [ -n "$ALL_BRANCH_FILES" ]; then
|
|
397
|
-
ALL_BRANCH_FILES="${ALL_BRANCH_FILES}
|
|
398
|
-
${_branch}:${_files_csv}"
|
|
399
|
-
else
|
|
400
|
-
ALL_BRANCH_FILES="${_branch}:${_files_csv}"
|
|
401
|
-
fi
|
|
402
|
-
done <<CONFLICT_EOF
|
|
403
|
-
$SESSION_ENTRIES
|
|
404
|
-
CONFLICT_EOF
|
|
405
|
-
|
|
406
|
-
# Build conflict map: find files that appear in multiple branches
|
|
407
|
-
# and add changed_files + conflicts to SESSIONS_JSON
|
|
408
|
-
while IFS= read -r _entry; do
|
|
409
|
-
[ -z "$_entry" ] && continue
|
|
410
|
-
_branch="${_entry%%:*}"
|
|
411
|
-
_files_csv="${_entry#*:}"
|
|
412
|
-
|
|
413
|
-
# Convert tab-delimited files to JSON array
|
|
414
|
-
if [ -n "$_files_csv" ]; then
|
|
415
|
-
_files_json="$(printf '%s' "$_files_csv" | jq -R 'split("\t")')"
|
|
416
|
-
else
|
|
417
|
-
_files_json="[]"
|
|
418
|
-
fi
|
|
419
|
-
|
|
420
|
-
# Find conflicts: files in this branch that also appear in other branches
|
|
421
|
-
_conflicts_json="[]"
|
|
422
|
-
while IFS= read -r _other_entry; do
|
|
423
|
-
[ -z "$_other_entry" ] && continue
|
|
424
|
-
_other_branch="${_other_entry%%:*}"
|
|
425
|
-
_other_files="${_other_entry#*:}"
|
|
426
|
-
|
|
427
|
-
# Skip self
|
|
428
|
-
[ "$_other_branch" = "$_branch" ] && continue
|
|
429
|
-
[ -z "$_other_files" ] && continue
|
|
430
|
-
|
|
431
|
-
# Find intersection
|
|
432
|
-
_other_json="$(printf '%s' "$_other_files" | jq -R 'split("\t")')"
|
|
433
|
-
_overlap="$(jq -n --argjson a "$_files_json" --argjson b "$_other_json" \
|
|
434
|
-
'[$a[] as $f | select($b | index($f))] | unique')"
|
|
435
|
-
_overlap_len="$(printf '%s' "$_overlap" | jq 'length')"
|
|
436
|
-
if [ "$_overlap_len" -gt 0 ]; then
|
|
437
|
-
_conflicts_json="$(jq -n \
|
|
438
|
-
--argjson existing "$_conflicts_json" \
|
|
439
|
-
--arg other_branch "$_other_branch" \
|
|
440
|
-
--argjson files "$_overlap" \
|
|
441
|
-
'$existing + [{branch: $other_branch, files: $files}]')"
|
|
442
|
-
fi
|
|
443
|
-
done <<INNER_EOF
|
|
444
|
-
$ALL_BRANCH_FILES
|
|
445
|
-
INNER_EOF
|
|
446
|
-
|
|
447
|
-
# Update SESSIONS_JSON: add changed_files and conflicts to matching branch entry
|
|
448
|
-
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 \
|
|
449
241
|
--arg branch "$_branch" \
|
|
450
242
|
--argjson files "$_files_json" \
|
|
451
|
-
|
|
452
|
-
'[.[] | if .branch == $branch then . + {changed_files: $files, conflicts: $conflicts} else . end]'
|
|
243
|
+
'. + [{branch: $branch, files: $files}]'
|
|
453
244
|
)"
|
|
454
|
-
done <<
|
|
455
|
-
$
|
|
456
|
-
|
|
457
|
-
fi
|
|
245
|
+
done <<BRANCHEOF
|
|
246
|
+
$_session_branches
|
|
247
|
+
BRANCHEOF
|
|
458
248
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
# 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
|
|
462
251
|
|
|
463
252
|
TIER2_ENABLED=0
|
|
464
253
|
if [ "$SESSION_COUNT" -ge 2 ]; then
|
|
465
|
-
# Check git version >= 2.38 (merge-tree --write-tree requires it)
|
|
466
254
|
_git_ver="$("$GIT" --version 2>/dev/null || echo '')"
|
|
467
255
|
_git_major=0
|
|
468
256
|
_git_minor=0
|
|
469
|
-
# Parse "git version X.Y.Z..." -> extract major and minor
|
|
470
257
|
_ver_nums="${_git_ver#git version }"
|
|
471
258
|
_git_major="${_ver_nums%%.*}"
|
|
472
259
|
_ver_rest="${_ver_nums#*.}"
|
|
473
260
|
_git_minor="${_ver_rest%%.*}"
|
|
474
|
-
# Validate they are numbers
|
|
475
261
|
case "$_git_major" in ''|*[!0-9]*) _git_major=0 ;; esac
|
|
476
262
|
case "$_git_minor" in ''|*[!0-9]*) _git_minor=0 ;; esac
|
|
477
263
|
|
|
@@ -480,79 +266,38 @@ if [ "$SESSION_COUNT" -ge 2 ]; then
|
|
|
480
266
|
fi
|
|
481
267
|
fi
|
|
482
268
|
|
|
483
|
-
# For each pair of branches with file overlaps, run merge-tree
|
|
484
269
|
if [ "$TIER2_ENABLED" = "1" ]; then
|
|
485
|
-
# Collect all branch pairs that have conflicts in SESSIONS_JSON
|
|
486
270
|
_conflict_pairs="$(printf '%s' "$SESSIONS_JSON" | jq -r '
|
|
487
271
|
[.[] | select((.conflicts // []) | length > 0) |
|
|
488
272
|
.branch as $b |
|
|
489
273
|
.conflicts[] |
|
|
490
|
-
[$b, .branch] | sort | join("
|
|
274
|
+
[$b, .branch] | sort | join("\t")
|
|
491
275
|
] | unique | .[]
|
|
492
276
|
')"
|
|
493
277
|
|
|
494
278
|
if [ -n "$_conflict_pairs" ]; then
|
|
495
|
-
|
|
496
|
-
while IFS= read -r
|
|
497
|
-
[ -z "$
|
|
498
|
-
|
|
499
|
-
_b2="${_pair#* }"
|
|
500
|
-
|
|
501
|
-
# 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
|
|
502
283
|
_mt_output=""
|
|
503
284
|
_mt_exit=0
|
|
504
285
|
_mt_output="$("$GIT" merge-tree --write-tree --name-only --no-messages -- "$_b1" "$_b2" 2>/dev/null)" || _mt_exit=$?
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
while IFS= read -r _cf_line; do
|
|
513
|
-
_line_num=$((_line_num + 1))
|
|
514
|
-
# Skip the first line (tree SHA output from merge-tree)
|
|
515
|
-
if [ "$_line_num" -eq 1 ]; then continue; fi
|
|
516
|
-
# Skip empty lines
|
|
517
|
-
if [ -z "$_cf_line" ]; then continue; fi
|
|
518
|
-
if [ -n "$_conflict_files" ]; then
|
|
519
|
-
_conflict_files="${_conflict_files} ${_cf_line}"
|
|
520
|
-
else
|
|
521
|
-
_conflict_files="${_cf_line}"
|
|
522
|
-
fi
|
|
523
|
-
done <<MTEOF
|
|
524
|
-
$_mt_output
|
|
525
|
-
MTEOF
|
|
526
|
-
|
|
527
|
-
if [ -n "$_conflict_files" ]; then
|
|
528
|
-
_cf_json="$(printf '%s' "$_conflict_files" | jq -R 'split("\t")')"
|
|
529
|
-
|
|
530
|
-
# Add merge_conflicts to both branches in SESSIONS_JSON
|
|
531
|
-
for _target_branch in "$_b1" "$_b2"; do
|
|
532
|
-
_other_branch="$_b1"
|
|
533
|
-
[ "$_target_branch" = "$_b1" ] && _other_branch="$_b2"
|
|
534
|
-
|
|
535
|
-
SESSIONS_JSON="$(printf '%s' "$SESSIONS_JSON" | jq \
|
|
536
|
-
--arg branch "$_target_branch" \
|
|
537
|
-
--arg other "$_other_branch" \
|
|
538
|
-
--argjson files "$_cf_json" \
|
|
539
|
-
'[.[] | if .branch == $branch then
|
|
540
|
-
. + {merge_conflicts: ((.merge_conflicts // []) + [{branch: $other, files: $files}])}
|
|
541
|
-
else . end]'
|
|
542
|
-
)"
|
|
543
|
-
done
|
|
544
|
-
fi
|
|
545
|
-
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
|
+
)"
|
|
546
293
|
done <<PAIRSEOF
|
|
547
294
|
$_conflict_pairs
|
|
548
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")"
|
|
549
298
|
fi
|
|
550
299
|
fi
|
|
551
300
|
|
|
552
|
-
# ── Cross-developer team activity ────────────────────────────────────────
|
|
553
|
-
# Read file index entries from all developers, identify OTHER developers'
|
|
554
|
-
# in-progress work, compute module overlaps, and flag stale claims.
|
|
555
|
-
|
|
556
301
|
TEAM_ACTIVITY_JSON="[]"
|
|
557
302
|
|
|
558
303
|
# Only proceed if file_index_read and get_session_identity are available
|
|
@@ -620,7 +365,7 @@ if command -v file_index_read &>/dev/null && command -v get_session_identity &>/
|
|
|
620
365
|
fi
|
|
621
366
|
fi
|
|
622
367
|
|
|
623
|
-
#
|
|
368
|
+
# ââ€â‚¬Ã¢â€â‚¬ Output ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
624
369
|
|
|
625
370
|
if [ "$JSON_MODE" = "1" ]; then
|
|
626
371
|
# Always output consistent shape: {sessions: [...], issues: [...], team_activity: [...]}
|
|
@@ -637,7 +382,7 @@ else
|
|
|
637
382
|
C_RED=$'\033[31m' C_DIM=$'\033[2m' C_BOLD=$'\033[1m' C_CYAN=$'\033[36m'
|
|
638
383
|
fi
|
|
639
384
|
|
|
640
|
-
#
|
|
385
|
+
# ââ€â‚¬Ã¢â€â‚¬ Active Sessions (before grouped output) ââ€â‚¬Ã¢â€â‚¬
|
|
641
386
|
if [ "$SESSION_COUNT" -gt 0 ]; then
|
|
642
387
|
printf '%s%s=== ACTIVE SESSIONS ===%s\n' "$C_BOLD" "$C_CYAN" "$C_RESET"
|
|
643
388
|
printf '%s' "$SESSIONS_JSON" | jq -r '.[] |
|
|
@@ -687,10 +432,10 @@ else
|
|
|
687
432
|
printf '\n'
|
|
688
433
|
fi
|
|
689
434
|
|
|
690
|
-
#
|
|
435
|
+
# ââ€â‚¬Ã¢â€â‚¬ Team Activity (cross-developer visibility) ââ€â‚¬Ã¢â€â‚¬
|
|
691
436
|
_ta_len="$(printf '%s' "$TEAM_ACTIVITY_JSON" | jq 'length')"
|
|
692
437
|
if [ "$_ta_len" -gt 0 ]; then
|
|
693
|
-
printf '%s%s
|
|
438
|
+
printf '%s%s--- Team Activity ----------------------------------------%s\n' "$C_BOLD" "$C_CYAN" "$C_RESET"
|
|
694
439
|
printf '%s' "$TEAM_ACTIVITY_JSON" | jq -r '
|
|
695
440
|
.[] |
|
|
696
441
|
.developer as $dev |
|
|
@@ -707,7 +452,7 @@ else
|
|
|
707
452
|
else
|
|
708
453
|
" " + $iid + " (in_progress)" +
|
|
709
454
|
(if ($mods | length) > 0 then
|
|
710
|
-
"
|
|
455
|
+
" - touching " + ($mods | join(", "))
|
|
711
456
|
else "" end)
|
|
712
457
|
end),
|
|
713
458
|
# Overlap or no-overlap line
|