shipwright-cli 2.1.1 → 2.2.0
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/agents/devops-engineer.md +14 -12
- package/.claude/agents/doc-fleet-agent.md +99 -0
- package/.claude/agents/test-specialist.md +5 -3
- package/README.md +48 -27
- package/claude-code/CLAUDE.md.shipwright +2 -2
- package/config/policy.json +73 -0
- package/config/policy.schema.json +75 -0
- package/docs/AGI-PLATFORM-PLAN.md +122 -0
- package/docs/AGI-WHATS-NEXT.md +69 -0
- package/docs/KNOWN-ISSUES.md +1 -23
- package/docs/PLATFORM-TODO-BACKLOG.md +41 -0
- package/docs/PLATFORM-TODO-TRIAGE.md +56 -0
- package/docs/README.md +83 -0
- package/docs/TIPS.md +39 -2
- package/docs/config-policy.md +40 -0
- package/docs/definition-of-done.example.md +2 -0
- package/docs/patterns/README.md +5 -0
- package/docs/strategy/02-mission-and-brand.md +3 -3
- package/docs/strategy/README.md +4 -3
- package/docs/tmux-research/TMUX-AUDIT.md +2 -0
- package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +17 -0
- package/package.json +3 -2
- package/scripts/lib/daemon-health.sh +32 -0
- package/scripts/lib/pipeline-quality.sh +23 -0
- package/scripts/lib/policy.sh +32 -0
- package/scripts/sw +5 -1
- package/scripts/sw-activity.sh +35 -46
- package/scripts/sw-adaptive.sh +30 -39
- package/scripts/sw-adversarial.sh +30 -36
- package/scripts/sw-architecture-enforcer.sh +30 -33
- package/scripts/sw-auth.sh +30 -42
- package/scripts/sw-autonomous.sh +60 -40
- package/scripts/sw-changelog.sh +29 -30
- package/scripts/sw-checkpoint.sh +30 -18
- package/scripts/sw-ci.sh +30 -42
- package/scripts/sw-cleanup.sh +32 -15
- package/scripts/sw-code-review.sh +26 -32
- package/scripts/sw-connect.sh +30 -19
- package/scripts/sw-context.sh +30 -19
- package/scripts/sw-cost.sh +30 -40
- package/scripts/sw-daemon.sh +150 -39
- package/scripts/sw-dashboard.sh +31 -40
- package/scripts/sw-db.sh +30 -20
- package/scripts/sw-decompose.sh +30 -38
- package/scripts/sw-deps.sh +30 -41
- package/scripts/sw-developer-simulation.sh +30 -36
- package/scripts/sw-discovery.sh +36 -19
- package/scripts/sw-doc-fleet.sh +822 -0
- package/scripts/sw-docs-agent.sh +30 -36
- package/scripts/sw-docs.sh +29 -31
- package/scripts/sw-doctor.sh +52 -20
- package/scripts/sw-dora.sh +29 -34
- package/scripts/sw-durable.sh +30 -20
- package/scripts/sw-e2e-orchestrator.sh +36 -21
- package/scripts/sw-eventbus.sh +30 -17
- package/scripts/sw-feedback.sh +30 -41
- package/scripts/sw-fix.sh +30 -40
- package/scripts/sw-fleet-discover.sh +30 -41
- package/scripts/sw-fleet-viz.sh +30 -20
- package/scripts/sw-fleet.sh +30 -40
- package/scripts/sw-github-app.sh +30 -41
- package/scripts/sw-github-checks.sh +30 -41
- package/scripts/sw-github-deploy.sh +30 -41
- package/scripts/sw-github-graphql.sh +30 -38
- package/scripts/sw-guild.sh +30 -37
- package/scripts/sw-heartbeat.sh +30 -19
- package/scripts/sw-hygiene.sh +134 -42
- package/scripts/sw-incident.sh +30 -39
- package/scripts/sw-init.sh +31 -14
- package/scripts/sw-instrument.sh +30 -41
- package/scripts/sw-intelligence.sh +39 -44
- package/scripts/sw-jira.sh +31 -41
- package/scripts/sw-launchd.sh +30 -17
- package/scripts/sw-linear.sh +31 -41
- package/scripts/sw-logs.sh +32 -17
- package/scripts/sw-loop.sh +55 -26
- package/scripts/sw-memory.sh +90 -99
- package/scripts/sw-mission-control.sh +31 -40
- package/scripts/sw-model-router.sh +30 -20
- package/scripts/sw-otel.sh +30 -20
- package/scripts/sw-oversight.sh +30 -36
- package/scripts/sw-patrol-meta.sh +31 -0
- package/scripts/sw-pipeline-composer.sh +30 -39
- package/scripts/sw-pipeline-vitals.sh +30 -44
- package/scripts/sw-pipeline.sh +315 -6388
- package/scripts/sw-pm.sh +31 -41
- package/scripts/sw-pr-lifecycle.sh +30 -42
- package/scripts/sw-predictive.sh +32 -34
- package/scripts/sw-prep.sh +47 -32
- package/scripts/sw-ps.sh +32 -17
- package/scripts/sw-public-dashboard.sh +30 -40
- package/scripts/sw-quality.sh +42 -40
- package/scripts/sw-reaper.sh +32 -15
- package/scripts/sw-recruit.sh +428 -48
- package/scripts/sw-regression.sh +30 -38
- package/scripts/sw-release-manager.sh +30 -38
- package/scripts/sw-release.sh +29 -31
- package/scripts/sw-remote.sh +31 -40
- package/scripts/sw-replay.sh +30 -18
- package/scripts/sw-retro.sh +33 -42
- package/scripts/sw-scale.sh +41 -24
- package/scripts/sw-security-audit.sh +30 -20
- package/scripts/sw-self-optimize.sh +33 -37
- package/scripts/sw-session.sh +31 -15
- package/scripts/sw-setup.sh +30 -16
- package/scripts/sw-standup.sh +30 -20
- package/scripts/sw-status.sh +33 -13
- package/scripts/sw-strategic.sh +55 -43
- package/scripts/sw-stream.sh +33 -37
- package/scripts/sw-swarm.sh +30 -21
- package/scripts/sw-team-stages.sh +30 -38
- package/scripts/sw-templates.sh +31 -16
- package/scripts/sw-testgen.sh +30 -31
- package/scripts/sw-tmux-pipeline.sh +29 -31
- package/scripts/sw-tmux-role-color.sh +31 -0
- package/scripts/sw-tmux-status.sh +31 -0
- package/scripts/sw-tmux.sh +31 -15
- package/scripts/sw-trace.sh +30 -19
- package/scripts/sw-tracker-github.sh +31 -0
- package/scripts/sw-tracker-jira.sh +31 -0
- package/scripts/sw-tracker-linear.sh +31 -0
- package/scripts/sw-tracker.sh +30 -40
- package/scripts/sw-triage.sh +68 -61
- package/scripts/sw-upgrade.sh +30 -16
- package/scripts/sw-ux.sh +30 -35
- package/scripts/sw-webhook.sh +30 -25
- package/scripts/sw-widgets.sh +30 -19
- package/scripts/sw-worktree.sh +32 -15
- package/tmux/templates/doc-fleet.json +43 -0
package/scripts/sw-cost.sh
CHANGED
|
@@ -6,32 +6,43 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="2.
|
|
9
|
+
VERSION="2.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
13
|
-
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
14
|
-
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
15
|
-
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
16
|
-
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
17
|
-
GREEN='\033[38;2;74;222;128m' # success
|
|
18
|
-
YELLOW='\033[38;2;250;204;21m' # warning
|
|
19
|
-
RED='\033[38;2;248;113;113m' # error
|
|
20
|
-
DIM='\033[2m'
|
|
21
|
-
BOLD='\033[1m'
|
|
22
|
-
RESET='\033[0m'
|
|
23
|
-
|
|
24
13
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
25
14
|
# shellcheck source=lib/compat.sh
|
|
26
15
|
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
27
|
-
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
28
|
-
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
29
|
-
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
30
|
-
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
31
|
-
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
# Canonical helpers (colors, output, events)
|
|
18
|
+
# shellcheck source=lib/helpers.sh
|
|
19
|
+
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
20
|
+
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
21
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
22
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
23
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
24
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
25
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
26
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
27
|
+
now_epoch() { date +%s; }
|
|
28
|
+
fi
|
|
29
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
30
|
+
emit_event() {
|
|
31
|
+
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
32
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
33
|
+
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
34
|
+
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
|
+
}
|
|
36
|
+
fi
|
|
37
|
+
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
38
|
+
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
39
|
+
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
40
|
+
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
41
|
+
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
42
|
+
RED="${RED:-\033[38;2;248;113;113m}"
|
|
43
|
+
DIM="${DIM:-\033[2m}"
|
|
44
|
+
BOLD="${BOLD:-\033[1m}"
|
|
45
|
+
RESET="${RESET:-\033[0m}"
|
|
35
46
|
|
|
36
47
|
format_duration() {
|
|
37
48
|
local secs="$1"
|
|
@@ -44,27 +55,6 @@ format_duration() {
|
|
|
44
55
|
fi
|
|
45
56
|
}
|
|
46
57
|
|
|
47
|
-
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
48
|
-
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
49
|
-
|
|
50
|
-
emit_event() {
|
|
51
|
-
local event_type="$1"
|
|
52
|
-
shift
|
|
53
|
-
local json_fields=""
|
|
54
|
-
for kv in "$@"; do
|
|
55
|
-
local key="${kv%%=*}"
|
|
56
|
-
local val="${kv#*=}"
|
|
57
|
-
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
58
|
-
json_fields="${json_fields},\"${key}\":${val}"
|
|
59
|
-
else
|
|
60
|
-
val="${val//\"/\\\"}"
|
|
61
|
-
json_fields="${json_fields},\"${key}\":\"${val}\""
|
|
62
|
-
fi
|
|
63
|
-
done
|
|
64
|
-
mkdir -p "${HOME}/.shipwright"
|
|
65
|
-
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
66
|
-
}
|
|
67
|
-
|
|
68
58
|
# ─── Cost Storage ──────────────────────────────────────────────────────────
|
|
69
59
|
COST_DIR="${HOME}/.shipwright"
|
|
70
60
|
COST_FILE="${COST_DIR}/costs.json"
|
package/scripts/sw-daemon.sh
CHANGED
|
@@ -9,25 +9,49 @@ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
|
9
9
|
# Allow spawning Claude CLI from within a Claude Code session (daemon, fleet, etc.)
|
|
10
10
|
unset CLAUDECODE 2>/dev/null || true
|
|
11
11
|
|
|
12
|
-
VERSION="2.
|
|
12
|
+
VERSION="2.2.0"
|
|
13
13
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
14
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
15
15
|
|
|
16
|
-
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
17
|
-
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
18
|
-
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
19
|
-
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
20
|
-
GREEN='\033[38;2;74;222;128m' # success
|
|
21
|
-
YELLOW='\033[38;2;250;204;21m' # warning
|
|
22
|
-
RED='\033[38;2;248;113;113m' # error
|
|
23
|
-
DIM='\033[2m'
|
|
24
|
-
BOLD='\033[1m'
|
|
25
|
-
RESET='\033[0m'
|
|
26
|
-
|
|
27
16
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
28
17
|
# shellcheck source=lib/compat.sh
|
|
29
18
|
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
30
19
|
|
|
20
|
+
# Canonical helpers (colors, output, events)
|
|
21
|
+
# shellcheck source=lib/helpers.sh
|
|
22
|
+
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
23
|
+
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
24
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
25
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
26
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
27
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
28
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
29
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
30
|
+
now_epoch() { date +%s; }
|
|
31
|
+
fi
|
|
32
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
33
|
+
emit_event() {
|
|
34
|
+
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
35
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
36
|
+
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
37
|
+
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
38
|
+
}
|
|
39
|
+
fi
|
|
40
|
+
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
41
|
+
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
42
|
+
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
43
|
+
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
44
|
+
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
45
|
+
RED="${RED:-\033[38;2;248;113;113m}"
|
|
46
|
+
DIM="${DIM:-\033[2m}"
|
|
47
|
+
BOLD="${BOLD:-\033[1m}"
|
|
48
|
+
RESET="${RESET:-\033[0m}"
|
|
49
|
+
|
|
50
|
+
# Policy (config/policy.json) — daemon defaults when daemon-config.json missing or silent
|
|
51
|
+
[[ -f "$SCRIPT_DIR/lib/policy.sh" ]] && source "$SCRIPT_DIR/lib/policy.sh"
|
|
52
|
+
# Daemon health timeouts from policy (lib/daemon-health.sh)
|
|
53
|
+
[[ -f "$SCRIPT_DIR/lib/daemon-health.sh" ]] && source "$SCRIPT_DIR/lib/daemon-health.sh"
|
|
54
|
+
|
|
31
55
|
# ─── Intelligence Engine (optional) ──────────────────────────────────────────
|
|
32
56
|
# shellcheck source=sw-intelligence.sh
|
|
33
57
|
[[ -f "$SCRIPT_DIR/sw-intelligence.sh" ]] && source "$SCRIPT_DIR/sw-intelligence.sh"
|
|
@@ -52,15 +76,6 @@ RESET='\033[0m'
|
|
|
52
76
|
# shellcheck source=sw-github-deploy.sh
|
|
53
77
|
[[ -f "$SCRIPT_DIR/sw-github-deploy.sh" ]] && source "$SCRIPT_DIR/sw-github-deploy.sh"
|
|
54
78
|
|
|
55
|
-
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
56
|
-
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
57
|
-
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
58
|
-
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
59
|
-
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
60
|
-
|
|
61
|
-
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
62
|
-
now_epoch() { date +%s; }
|
|
63
|
-
|
|
64
79
|
epoch_to_iso() {
|
|
65
80
|
local epoch="$1"
|
|
66
81
|
date -u -r "$epoch" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
|
@@ -197,9 +212,12 @@ LOG_FILE=""
|
|
|
197
212
|
LOG_DIR=""
|
|
198
213
|
WORKTREE_DIR=""
|
|
199
214
|
|
|
200
|
-
# Config defaults (overridden by daemon-config.json)
|
|
215
|
+
# Config defaults (overridden by daemon-config.json; policy overrides when present)
|
|
201
216
|
WATCH_LABEL="ready-to-build"
|
|
202
217
|
POLL_INTERVAL=60
|
|
218
|
+
if type policy_get &>/dev/null 2>&1; then
|
|
219
|
+
POLL_INTERVAL=$(policy_get ".daemon.poll_interval_seconds" "60")
|
|
220
|
+
fi
|
|
203
221
|
MAX_PARALLEL=2
|
|
204
222
|
PIPELINE_TEMPLATE="autonomous"
|
|
205
223
|
SKIP_GATES=true
|
|
@@ -222,9 +240,12 @@ WATCH_MODE="repo"
|
|
|
222
240
|
ORG=""
|
|
223
241
|
REPO_FILTER=""
|
|
224
242
|
|
|
225
|
-
# Auto-scaling defaults
|
|
243
|
+
# Auto-scaling defaults (policy overrides when present)
|
|
226
244
|
AUTO_SCALE=false
|
|
227
245
|
AUTO_SCALE_INTERVAL=5
|
|
246
|
+
if type policy_get &>/dev/null 2>&1; then
|
|
247
|
+
AUTO_SCALE_INTERVAL=$(policy_get ".daemon.auto_scale_interval_cycles" "5")
|
|
248
|
+
fi
|
|
228
249
|
MAX_WORKERS=8
|
|
229
250
|
MIN_WORKERS=1
|
|
230
251
|
WORKER_MEM_GB=4
|
|
@@ -377,7 +398,7 @@ load_config() {
|
|
|
377
398
|
info "Loading config: ${DIM}${config_file}${RESET}"
|
|
378
399
|
|
|
379
400
|
WATCH_LABEL=$(jq -r '.watch_label // "ready-to-build"' "$config_file")
|
|
380
|
-
POLL_INTERVAL=$(jq -r '.poll_interval // 60' "$config_file")
|
|
401
|
+
POLL_INTERVAL=$(jq -r '.poll_interval // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.poll_interval_seconds" "60" || echo "60")"'' "$config_file")
|
|
381
402
|
MAX_PARALLEL=$(jq -r '.max_parallel // 2' "$config_file")
|
|
382
403
|
PIPELINE_TEMPLATE=$(jq -r '.pipeline_template // "autonomous"' "$config_file")
|
|
383
404
|
SKIP_GATES=$(jq -r '.skip_gates // true' "$config_file")
|
|
@@ -437,7 +458,7 @@ load_config() {
|
|
|
437
458
|
|
|
438
459
|
# self-optimization
|
|
439
460
|
SELF_OPTIMIZE=$(jq -r '.self_optimize // false' "$config_file")
|
|
440
|
-
OPTIMIZE_INTERVAL=$(jq -r '.optimize_interval // 10' "$config_file")
|
|
461
|
+
OPTIMIZE_INTERVAL=$(jq -r '.optimize_interval // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.optimize_interval_cycles" "10" || echo "10")"'' "$config_file")
|
|
441
462
|
|
|
442
463
|
# intelligence engine settings
|
|
443
464
|
INTELLIGENCE_ENABLED=$(jq -r '.intelligence.enabled // false' "$config_file")
|
|
@@ -456,7 +477,7 @@ load_config() {
|
|
|
456
477
|
|
|
457
478
|
# stale state reaper: clean old worktrees, artifacts, state entries
|
|
458
479
|
STALE_REAPER_ENABLED=$(jq -r '.stale_reaper // true' "$config_file")
|
|
459
|
-
STALE_REAPER_INTERVAL=$(jq -r '.stale_reaper_interval // 10' "$config_file")
|
|
480
|
+
STALE_REAPER_INTERVAL=$(jq -r '.stale_reaper_interval // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.stale_reaper_interval_cycles" "10" || echo "10")"'' "$config_file")
|
|
460
481
|
STALE_REAPER_AGE_DAYS=$(jq -r '.stale_reaper_age_days // 7' "$config_file")
|
|
461
482
|
|
|
462
483
|
# priority lane settings
|
|
@@ -473,14 +494,14 @@ load_config() {
|
|
|
473
494
|
|
|
474
495
|
# auto-scaling
|
|
475
496
|
AUTO_SCALE=$(jq -r '.auto_scale // false' "$config_file")
|
|
476
|
-
AUTO_SCALE_INTERVAL=$(jq -r '.auto_scale_interval // 5' "$config_file")
|
|
497
|
+
AUTO_SCALE_INTERVAL=$(jq -r '.auto_scale_interval // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.auto_scale_interval_cycles" "5" || echo "5")"'' "$config_file")
|
|
477
498
|
MAX_WORKERS=$(jq -r '.max_workers // 8' "$config_file")
|
|
478
499
|
MIN_WORKERS=$(jq -r '.min_workers // 1' "$config_file")
|
|
479
500
|
WORKER_MEM_GB=$(jq -r '.worker_mem_gb // 4' "$config_file")
|
|
480
501
|
EST_COST_PER_JOB=$(jq -r '.estimated_cost_per_job_usd // 5.0' "$config_file")
|
|
481
502
|
|
|
482
|
-
# heartbeat + checkpoint recovery
|
|
483
|
-
HEALTH_HEARTBEAT_TIMEOUT=$(jq -r '.health.heartbeat_timeout_s // 120' "$config_file")
|
|
503
|
+
# heartbeat + checkpoint recovery (policy fallback when config silent)
|
|
504
|
+
HEALTH_HEARTBEAT_TIMEOUT=$(jq -r '.health.heartbeat_timeout_s // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.health_heartbeat_timeout" "120" || echo "120")"'' "$config_file")
|
|
484
505
|
CHECKPOINT_ENABLED=$(jq -r '.health.checkpoint_enabled // true' "$config_file")
|
|
485
506
|
|
|
486
507
|
# progress-based health monitoring (replaces static timeouts)
|
|
@@ -612,14 +633,23 @@ get_adaptive_heartbeat_timeout() {
|
|
|
612
633
|
return
|
|
613
634
|
fi
|
|
614
635
|
|
|
615
|
-
# Stage-specific defaults (
|
|
636
|
+
# Stage-specific defaults (daemon-health.sh when sourced, else policy_get, else literal)
|
|
616
637
|
local default_timeout="${HEALTH_HEARTBEAT_TIMEOUT:-120}"
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
638
|
+
if type daemon_health_timeout_for_stage &>/dev/null 2>&1; then
|
|
639
|
+
default_timeout=$(daemon_health_timeout_for_stage "$stage" "$default_timeout")
|
|
640
|
+
elif type policy_get &>/dev/null 2>&1; then
|
|
641
|
+
local policy_stage
|
|
642
|
+
policy_stage=$(policy_get ".daemon.stage_timeouts.$stage" "")
|
|
643
|
+
[[ -n "$policy_stage" && "$policy_stage" =~ ^[0-9]+$ ]] && default_timeout="$policy_stage"
|
|
644
|
+
else
|
|
645
|
+
case "$stage" in
|
|
646
|
+
build) default_timeout=300 ;;
|
|
647
|
+
test) default_timeout=180 ;;
|
|
648
|
+
review|compound_quality) default_timeout=180 ;;
|
|
649
|
+
lint|format|intake|plan|design) default_timeout=60 ;;
|
|
650
|
+
esac
|
|
651
|
+
fi
|
|
652
|
+
[[ "$default_timeout" =~ ^[0-9]+$ ]] || default_timeout="${HEALTH_HEARTBEAT_TIMEOUT:-120}"
|
|
623
653
|
|
|
624
654
|
local durations_file="$HOME/.shipwright/optimization/stage-durations.json"
|
|
625
655
|
if [[ ! -f "$durations_file" ]]; then
|
|
@@ -4049,6 +4079,27 @@ Auto-detected by \`shipwright daemon patrol\` on $(now_iso)." \
|
|
|
4049
4079
|
fi
|
|
4050
4080
|
echo ""
|
|
4051
4081
|
|
|
4082
|
+
echo -e " ${BOLD}Dead Pane Reaping${RESET}"
|
|
4083
|
+
pre_check_findings=$total_findings
|
|
4084
|
+
if [[ -x "$SCRIPT_DIR/sw-reaper.sh" ]] && [[ -n "${TMUX:-}" ]]; then
|
|
4085
|
+
local reaper_output
|
|
4086
|
+
reaper_output=$(bash "$SCRIPT_DIR/sw-reaper.sh" --once 2>/dev/null) || true
|
|
4087
|
+
local reaped_count=0
|
|
4088
|
+
reaped_count=$(echo "$reaper_output" | grep -c "Reaped" 2>/dev/null || true)
|
|
4089
|
+
if [[ "${reaped_count:-0}" -gt 0 ]]; then
|
|
4090
|
+
total_findings=$((total_findings + reaped_count))
|
|
4091
|
+
echo -e " ${CYAN}●${RESET} Reaped ${reaped_count} dead agent pane(s)"
|
|
4092
|
+
else
|
|
4093
|
+
echo -e " ${GREEN}●${RESET} No dead panes found"
|
|
4094
|
+
fi
|
|
4095
|
+
else
|
|
4096
|
+
echo -e " ${DIM}●${RESET} Skipped (no tmux session or reaper not found)"
|
|
4097
|
+
fi
|
|
4098
|
+
if [[ "$total_findings" -gt "$pre_check_findings" ]]; then
|
|
4099
|
+
patrol_findings_summary="${patrol_findings_summary}reaper: $((total_findings - pre_check_findings)) finding(s); "
|
|
4100
|
+
fi
|
|
4101
|
+
echo ""
|
|
4102
|
+
|
|
4052
4103
|
# ── Stage 2: AI-Powered Confirmation (if enabled) ──
|
|
4053
4104
|
if [[ "${PREDICTION_ENABLED:-false}" == "true" ]] && type patrol_ai_analyze &>/dev/null 2>&1; then
|
|
4054
4105
|
daemon_log INFO "Intelligence: using AI patrol analysis (prediction enabled)"
|
|
@@ -4592,9 +4643,24 @@ NUDGE_EOF
|
|
|
4592
4643
|
local stale_timeout
|
|
4593
4644
|
stale_timeout=$(get_adaptive_stale_timeout "$PIPELINE_TEMPLATE")
|
|
4594
4645
|
if [[ "$elapsed" -gt "$stale_timeout" ]]; then
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4646
|
+
# Check if process is still alive
|
|
4647
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
4648
|
+
# Kill at 2x stale timeout — the process is truly hung
|
|
4649
|
+
local kill_threshold=$(( stale_timeout * 2 ))
|
|
4650
|
+
if [[ "$elapsed" -gt "$kill_threshold" ]]; then
|
|
4651
|
+
daemon_log WARN "Killing stale job (legacy): issue #${issue_num} (${elapsed}s > ${kill_threshold}s kill threshold, PID $pid)"
|
|
4652
|
+
emit_event "daemon.stale_kill" "issue=$issue_num" "elapsed_s=$elapsed" "pid=$pid"
|
|
4653
|
+
kill "$pid" 2>/dev/null || true
|
|
4654
|
+
sleep 2
|
|
4655
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
4656
|
+
else
|
|
4657
|
+
daemon_log WARN "Stale job (legacy): issue #${issue_num} (${elapsed}s > ${stale_timeout}s, PID $pid) — will kill at ${kill_threshold}s"
|
|
4658
|
+
emit_event "daemon.stale_warning" "issue=$issue_num" "elapsed_s=$elapsed" "pid=$pid"
|
|
4659
|
+
fi
|
|
4660
|
+
else
|
|
4661
|
+
daemon_log WARN "Stale job with dead process: issue #${issue_num} (PID $pid no longer exists)"
|
|
4662
|
+
emit_event "daemon.stale_dead" "issue=$issue_num" "pid=$pid"
|
|
4663
|
+
fi
|
|
4598
4664
|
findings=$((findings + 1))
|
|
4599
4665
|
fi
|
|
4600
4666
|
fi
|
|
@@ -5167,6 +5233,51 @@ daemon_cleanup_stale() {
|
|
|
5167
5233
|
fi
|
|
5168
5234
|
fi
|
|
5169
5235
|
|
|
5236
|
+
# ── 6. Detect stale pipeline-state.md stuck in "running" ──
|
|
5237
|
+
local pipeline_state=".claude/pipeline-state.md"
|
|
5238
|
+
if [[ -f "$pipeline_state" ]]; then
|
|
5239
|
+
local ps_status=""
|
|
5240
|
+
ps_status=$(sed -n 's/^status: *//p' "$pipeline_state" 2>/dev/null | head -1 | tr -d ' ')
|
|
5241
|
+
if [[ "$ps_status" == "running" ]]; then
|
|
5242
|
+
local ps_mtime
|
|
5243
|
+
ps_mtime=$(stat -f '%m' "$pipeline_state" 2>/dev/null || stat -c '%Y' "$pipeline_state" 2>/dev/null || echo "0")
|
|
5244
|
+
local ps_age=$((now_e - ps_mtime))
|
|
5245
|
+
# If pipeline-state.md has been "running" for more than 2 hours and no active job
|
|
5246
|
+
if [[ "$ps_age" -gt 7200 ]]; then
|
|
5247
|
+
local has_active=false
|
|
5248
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
5249
|
+
local active_count
|
|
5250
|
+
active_count=$(jq '.active_jobs | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
5251
|
+
[[ "${active_count:-0}" -gt 0 ]] && has_active=true
|
|
5252
|
+
fi
|
|
5253
|
+
if [[ "$has_active" == "false" ]]; then
|
|
5254
|
+
daemon_log WARN "Stale pipeline-state.md stuck in 'running' for ${ps_age}s with no active jobs — marking failed"
|
|
5255
|
+
# Atomically update status to failed
|
|
5256
|
+
local tmp_ps="${pipeline_state}.tmp.$$"
|
|
5257
|
+
sed 's/^status: *running/status: failed (stale — cleaned by daemon)/' "$pipeline_state" > "$tmp_ps" 2>/dev/null && mv "$tmp_ps" "$pipeline_state" || rm -f "$tmp_ps"
|
|
5258
|
+
emit_event "daemon.stale_pipeline_state" "age_s=$ps_age"
|
|
5259
|
+
cleaned=$((cleaned + 1))
|
|
5260
|
+
fi
|
|
5261
|
+
fi
|
|
5262
|
+
fi
|
|
5263
|
+
fi
|
|
5264
|
+
|
|
5265
|
+
# ── 7. Clean remote branches for merged pipeline/* branches ──
|
|
5266
|
+
if command -v git &>/dev/null && [[ "${NO_GITHUB:-}" != "true" ]]; then
|
|
5267
|
+
while IFS= read -r branch; do
|
|
5268
|
+
[[ -z "$branch" ]] && continue
|
|
5269
|
+
branch="${branch## }"
|
|
5270
|
+
[[ "$branch" == pipeline/* ]] || continue
|
|
5271
|
+
local br_issue="${branch#pipeline/pipeline-issue-}"
|
|
5272
|
+
if ! daemon_is_inflight "$br_issue" 2>/dev/null; then
|
|
5273
|
+
daemon_log INFO "Removing orphaned pipeline branch: ${branch}"
|
|
5274
|
+
git branch -D "$branch" 2>/dev/null || true
|
|
5275
|
+
git push origin --delete "$branch" 2>/dev/null || true
|
|
5276
|
+
cleaned=$((cleaned + 1))
|
|
5277
|
+
fi
|
|
5278
|
+
done < <(git branch --list 'pipeline/*' 2>/dev/null)
|
|
5279
|
+
fi
|
|
5280
|
+
|
|
5170
5281
|
if [[ "$cleaned" -gt 0 ]]; then
|
|
5171
5282
|
emit_event "daemon.cleanup" "cleaned=$cleaned" "age_days=$age_days"
|
|
5172
5283
|
daemon_log SUCCESS "Stale reaper cleaned ${cleaned} item(s)"
|
package/scripts/sw-dashboard.sh
CHANGED
|
@@ -6,33 +6,43 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="2.
|
|
9
|
+
VERSION="2.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
|
-
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
13
|
-
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
14
|
-
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
15
|
-
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
16
|
-
GREEN='\033[38;2;74;222;128m' # success
|
|
17
|
-
YELLOW='\033[38;2;250;204;21m' # warning
|
|
18
|
-
RED='\033[38;2;248;113;113m' # error
|
|
19
|
-
DIM='\033[2m'
|
|
20
|
-
BOLD='\033[1m'
|
|
21
|
-
RESET='\033[0m'
|
|
22
|
-
|
|
23
12
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
24
13
|
# shellcheck source=lib/compat.sh
|
|
25
14
|
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
26
|
-
UNDERLINE='\033[4m'
|
|
27
|
-
|
|
28
|
-
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
29
|
-
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
30
|
-
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
31
|
-
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
32
|
-
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
33
15
|
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
# Canonical helpers (colors, output, events)
|
|
17
|
+
# shellcheck source=lib/helpers.sh
|
|
18
|
+
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
19
|
+
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
20
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
21
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
22
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
23
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
24
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
25
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
26
|
+
now_epoch() { date +%s; }
|
|
27
|
+
fi
|
|
28
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
29
|
+
emit_event() {
|
|
30
|
+
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
31
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
32
|
+
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
33
|
+
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
34
|
+
}
|
|
35
|
+
fi
|
|
36
|
+
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
37
|
+
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
38
|
+
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
39
|
+
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
40
|
+
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
41
|
+
RED="${RED:-\033[38;2;248;113;113m}"
|
|
42
|
+
DIM="${DIM:-\033[2m}"
|
|
43
|
+
BOLD="${BOLD:-\033[1m}"
|
|
44
|
+
RESET="${RESET:-\033[0m}"
|
|
45
|
+
UNDERLINE='\033[4m'
|
|
36
46
|
|
|
37
47
|
# ─── Paths ──────────────────────────────────────────────────────────────────
|
|
38
48
|
TEAMS_DIR="${HOME}/.shipwright"
|
|
@@ -42,25 +52,6 @@ LOG_FILE="${LOG_DIR}/dashboard.log"
|
|
|
42
52
|
EVENTS_FILE="${TEAMS_DIR}/events.jsonl"
|
|
43
53
|
DEFAULT_PORT=8767
|
|
44
54
|
|
|
45
|
-
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
46
|
-
emit_event() {
|
|
47
|
-
local event_type="$1"
|
|
48
|
-
shift
|
|
49
|
-
local json_fields=""
|
|
50
|
-
for kv in "$@"; do
|
|
51
|
-
local key="${kv%%=*}"
|
|
52
|
-
local val="${kv#*=}"
|
|
53
|
-
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
54
|
-
json_fields="${json_fields},\"${key}\":${val}"
|
|
55
|
-
else
|
|
56
|
-
val="${val//\"/\\\"}"
|
|
57
|
-
json_fields="${json_fields},\"${key}\":\"${val}\""
|
|
58
|
-
fi
|
|
59
|
-
done
|
|
60
|
-
mkdir -p "${TEAMS_DIR}"
|
|
61
|
-
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
62
|
-
}
|
|
63
|
-
|
|
64
55
|
# ─── Header ────────────────────────────────────────────────────────────────
|
|
65
56
|
dashboard_header() {
|
|
66
57
|
echo ""
|
package/scripts/sw-db.sh
CHANGED
|
@@ -14,33 +14,43 @@ if [[ -n "${_SW_DB_LOADED:-}" ]] && [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
|
|
|
14
14
|
fi
|
|
15
15
|
_SW_DB_LOADED=1
|
|
16
16
|
|
|
17
|
-
VERSION="2.
|
|
17
|
+
VERSION="2.2.0"
|
|
18
18
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
19
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
20
20
|
|
|
21
|
-
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
22
|
-
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
23
|
-
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
24
|
-
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
25
|
-
GREEN='\033[38;2;74;222;128m' # success
|
|
26
|
-
YELLOW='\033[38;2;250;204;21m' # warning
|
|
27
|
-
RED='\033[38;2;248;113;113m' # error
|
|
28
|
-
DIM='\033[2m'
|
|
29
|
-
BOLD='\033[1m'
|
|
30
|
-
RESET='\033[0m'
|
|
31
|
-
|
|
32
21
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
33
22
|
# shellcheck source=lib/compat.sh
|
|
34
23
|
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
35
24
|
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
25
|
+
# Canonical helpers (colors, output, events)
|
|
26
|
+
# shellcheck source=lib/helpers.sh
|
|
27
|
+
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
28
|
+
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
29
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
30
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
31
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
32
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
33
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
34
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
35
|
+
now_epoch() { date +%s; }
|
|
36
|
+
fi
|
|
37
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
38
|
+
emit_event() {
|
|
39
|
+
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
40
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
41
|
+
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
42
|
+
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
43
|
+
}
|
|
44
|
+
fi
|
|
45
|
+
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
46
|
+
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
47
|
+
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
48
|
+
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
49
|
+
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
50
|
+
RED="${RED:-\033[38;2;248;113;113m}"
|
|
51
|
+
DIM="${DIM:-\033[2m}"
|
|
52
|
+
BOLD="${BOLD:-\033[1m}"
|
|
53
|
+
RESET="${RESET:-\033[0m}"
|
|
44
54
|
|
|
45
55
|
# ─── Database Configuration ──────────────────────────────────────────────────
|
|
46
56
|
DB_DIR="${HOME}/.shipwright"
|
package/scripts/sw-decompose.sh
CHANGED
|
@@ -6,55 +6,47 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="2.
|
|
9
|
+
VERSION="2.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
13
|
-
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
14
|
-
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
15
|
-
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
16
|
-
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
17
|
-
GREEN='\033[38;2;74;222;128m' # success
|
|
18
|
-
YELLOW='\033[38;2;250;204;21m' # warning
|
|
19
|
-
RED='\033[38;2;248;113;113m' # error
|
|
20
|
-
DIM='\033[2m'
|
|
21
|
-
BOLD='\033[1m'
|
|
22
|
-
RESET='\033[0m'
|
|
23
|
-
|
|
24
13
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
25
14
|
# shellcheck source=lib/compat.sh
|
|
26
15
|
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
27
16
|
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
17
|
+
# Canonical helpers (colors, output, events)
|
|
18
|
+
# shellcheck source=lib/helpers.sh
|
|
19
|
+
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
20
|
+
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
21
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
22
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
23
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
24
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
25
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
26
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
27
|
+
now_epoch() { date +%s; }
|
|
28
|
+
fi
|
|
29
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
30
|
+
emit_event() {
|
|
31
|
+
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
32
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
33
|
+
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
34
|
+
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
|
+
}
|
|
36
|
+
fi
|
|
37
|
+
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
38
|
+
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
39
|
+
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
40
|
+
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
41
|
+
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
42
|
+
RED="${RED:-\033[38;2;248;113;113m}"
|
|
43
|
+
DIM="${DIM:-\033[2m}"
|
|
44
|
+
BOLD="${BOLD:-\033[1m}"
|
|
45
|
+
RESET="${RESET:-\033[0m}"
|
|
36
46
|
|
|
37
47
|
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
38
48
|
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
39
49
|
|
|
40
|
-
emit_event() {
|
|
41
|
-
local event_type="$1"
|
|
42
|
-
shift
|
|
43
|
-
local json_fields=""
|
|
44
|
-
for kv in "$@"; do
|
|
45
|
-
local key="${kv%%=*}"
|
|
46
|
-
local val="${kv#*=}"
|
|
47
|
-
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
48
|
-
json_fields="${json_fields},\"${key}\":${val}"
|
|
49
|
-
else
|
|
50
|
-
val="${val//\"/\\\"}"
|
|
51
|
-
json_fields="${json_fields},\"${key}\":\"${val}\""
|
|
52
|
-
fi
|
|
53
|
-
done
|
|
54
|
-
mkdir -p "${HOME}/.shipwright"
|
|
55
|
-
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
50
|
# ─── Configuration ─────────────────────────────────────────────────────────
|
|
59
51
|
COMPLEXITY_THRESHOLD=70 # Decompose if complexity > this
|
|
60
52
|
HOURS_THRESHOLD=8 # Decompose if estimated hours > this
|