shipwright-cli 2.4.0 → 3.0.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/README.md +16 -11
- package/completions/_shipwright +1 -1
- package/completions/shipwright.bash +3 -8
- package/completions/shipwright.fish +1 -1
- package/config/defaults.json +111 -0
- package/config/event-schema.json +81 -0
- package/config/policy.json +13 -18
- package/dashboard/coverage/coverage-summary.json +14 -0
- package/dashboard/public/index.html +1 -1
- package/dashboard/server.ts +306 -17
- package/dashboard/src/components/charts/bar.test.ts +79 -0
- package/dashboard/src/components/charts/donut.test.ts +68 -0
- package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
- package/dashboard/src/components/charts/sparkline.test.ts +125 -0
- package/dashboard/src/core/api.test.ts +309 -0
- package/dashboard/src/core/helpers.test.ts +301 -0
- package/dashboard/src/core/router.test.ts +307 -0
- package/dashboard/src/core/router.ts +7 -0
- package/dashboard/src/core/sse.test.ts +144 -0
- package/dashboard/src/views/metrics.test.ts +186 -0
- package/dashboard/src/views/overview.test.ts +173 -0
- package/dashboard/src/views/pipelines.test.ts +183 -0
- package/dashboard/src/views/team.test.ts +253 -0
- package/dashboard/vitest.config.ts +14 -5
- package/docs/TIPS.md +1 -1
- package/docs/patterns/README.md +1 -1
- package/package.json +5 -7
- package/scripts/adapters/docker-deploy.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +11 -1
- package/scripts/adapters/wezterm-adapter.sh +1 -1
- package/scripts/check-version-consistency.sh +1 -1
- package/scripts/lib/architecture.sh +126 -0
- package/scripts/lib/bootstrap.sh +75 -0
- package/scripts/lib/compat.sh +89 -6
- package/scripts/lib/config.sh +91 -0
- package/scripts/lib/daemon-adaptive.sh +3 -3
- package/scripts/lib/daemon-dispatch.sh +39 -16
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +24 -12
- package/scripts/lib/daemon-poll.sh +37 -25
- package/scripts/lib/daemon-state.sh +115 -23
- package/scripts/lib/daemon-triage.sh +30 -8
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +30 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +85 -35
- package/scripts/lib/pipeline-quality-checks.sh +16 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +242 -28
- package/scripts/lib/pipeline-state.sh +40 -4
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +3 -11
- package/scripts/sw +10 -4
- package/scripts/sw-activity.sh +1 -11
- package/scripts/sw-adaptive.sh +109 -85
- package/scripts/sw-adversarial.sh +4 -14
- package/scripts/sw-architecture-enforcer.sh +1 -11
- package/scripts/sw-auth.sh +8 -17
- package/scripts/sw-autonomous.sh +111 -49
- package/scripts/sw-changelog.sh +1 -11
- package/scripts/sw-checkpoint.sh +144 -20
- package/scripts/sw-ci.sh +2 -12
- package/scripts/sw-cleanup.sh +13 -17
- package/scripts/sw-code-review.sh +16 -36
- package/scripts/sw-connect.sh +5 -12
- package/scripts/sw-context.sh +9 -26
- package/scripts/sw-cost.sh +6 -16
- package/scripts/sw-daemon.sh +75 -70
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +506 -15
- package/scripts/sw-decompose.sh +1 -11
- package/scripts/sw-deps.sh +15 -25
- package/scripts/sw-developer-simulation.sh +1 -11
- package/scripts/sw-discovery.sh +112 -30
- package/scripts/sw-doc-fleet.sh +7 -17
- package/scripts/sw-docs-agent.sh +6 -16
- package/scripts/sw-docs.sh +4 -12
- package/scripts/sw-doctor.sh +134 -43
- package/scripts/sw-dora.sh +11 -19
- package/scripts/sw-durable.sh +35 -52
- package/scripts/sw-e2e-orchestrator.sh +11 -27
- package/scripts/sw-eventbus.sh +115 -115
- package/scripts/sw-evidence.sh +114 -30
- package/scripts/sw-feedback.sh +3 -13
- package/scripts/sw-fix.sh +2 -20
- package/scripts/sw-fleet-discover.sh +1 -11
- package/scripts/sw-fleet-viz.sh +10 -18
- package/scripts/sw-fleet.sh +13 -17
- package/scripts/sw-github-app.sh +6 -16
- package/scripts/sw-github-checks.sh +1 -11
- package/scripts/sw-github-deploy.sh +1 -11
- package/scripts/sw-github-graphql.sh +2 -12
- package/scripts/sw-guild.sh +1 -11
- package/scripts/sw-heartbeat.sh +49 -12
- package/scripts/sw-hygiene.sh +45 -43
- package/scripts/sw-incident.sh +48 -74
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +362 -51
- package/scripts/sw-jira.sh +5 -14
- package/scripts/sw-launchd.sh +2 -12
- package/scripts/sw-linear.sh +8 -17
- package/scripts/sw-logs.sh +4 -12
- package/scripts/sw-loop.sh +641 -90
- package/scripts/sw-memory.sh +243 -17
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +11 -21
- package/scripts/sw-oversight.sh +1 -11
- package/scripts/sw-patrol-meta.sh +5 -11
- package/scripts/sw-pipeline-composer.sh +7 -17
- package/scripts/sw-pipeline-vitals.sh +1 -11
- package/scripts/sw-pipeline.sh +478 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +27 -25
- package/scripts/sw-predictive.sh +16 -22
- package/scripts/sw-prep.sh +6 -16
- package/scripts/sw-ps.sh +1 -11
- package/scripts/sw-public-dashboard.sh +2 -12
- package/scripts/sw-quality.sh +77 -10
- package/scripts/sw-reaper.sh +1 -11
- package/scripts/sw-recruit.sh +15 -25
- package/scripts/sw-regression.sh +11 -21
- package/scripts/sw-release-manager.sh +19 -28
- package/scripts/sw-release.sh +8 -16
- package/scripts/sw-remote.sh +1 -11
- package/scripts/sw-replay.sh +48 -44
- package/scripts/sw-retro.sh +70 -92
- package/scripts/sw-review-rerun.sh +1 -1
- package/scripts/sw-scale.sh +109 -32
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +3 -13
- package/scripts/sw-setup.sh +8 -18
- package/scripts/sw-standup.sh +5 -15
- package/scripts/sw-status.sh +32 -23
- package/scripts/sw-strategic.sh +129 -13
- package/scripts/sw-stream.sh +1 -11
- package/scripts/sw-swarm.sh +76 -36
- package/scripts/sw-team-stages.sh +10 -20
- package/scripts/sw-templates.sh +4 -14
- package/scripts/sw-testgen.sh +3 -13
- package/scripts/sw-tmux-pipeline.sh +1 -19
- package/scripts/sw-tmux-role-color.sh +0 -10
- package/scripts/sw-tmux-status.sh +3 -11
- package/scripts/sw-tmux.sh +2 -20
- package/scripts/sw-trace.sh +1 -19
- package/scripts/sw-tracker-github.sh +0 -10
- package/scripts/sw-tracker-jira.sh +1 -11
- package/scripts/sw-tracker-linear.sh +1 -11
- package/scripts/sw-tracker.sh +7 -24
- package/scripts/sw-triage.sh +24 -34
- package/scripts/sw-upgrade.sh +5 -23
- package/scripts/sw-ux.sh +1 -19
- package/scripts/sw-webhook.sh +18 -32
- package/scripts/sw-widgets.sh +3 -21
- package/scripts/sw-worktree.sh +11 -27
- package/scripts/update-homebrew-sha.sh +67 -0
- package/templates/pipelines/tdd.json +72 -0
- package/scripts/sw-pipeline.sh.mock +0 -7
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -21,24 +21,6 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
21
21
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
22
22
|
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
23
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 emit_event 2>/dev/null)" != "function" ]]; then
|
|
25
|
-
emit_event() {
|
|
26
|
-
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
27
|
-
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
28
|
-
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
29
|
-
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
30
|
-
}
|
|
31
|
-
fi
|
|
32
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
33
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
34
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
35
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
36
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
37
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
38
|
-
DIM="${DIM:-\033[2m}"
|
|
39
|
-
BOLD="${BOLD:-\033[1m}"
|
|
40
|
-
RESET="${RESET:-\033[0m}"
|
|
41
|
-
|
|
42
24
|
# ─── Configuration ───────────────────────────────────────────────────────
|
|
43
25
|
REVIEW_CONFIG="${REPO_DIR}/.claude/code-review.json"
|
|
44
26
|
QUALITY_METRICS_FILE="${REPO_DIR}/.claude/pipeline-artifacts/quality-metrics.json"
|
|
@@ -335,17 +317,16 @@ auto_fix() {
|
|
|
335
317
|
local backup="${target_file}.review-backup"
|
|
336
318
|
cp "$target_file" "$backup"
|
|
337
319
|
|
|
338
|
-
#
|
|
339
|
-
if command -v shellcheck
|
|
340
|
-
local shellcheck_fixes=0
|
|
320
|
+
# Report shellcheck issues (informational — auto-fix is limited to whitespace)
|
|
321
|
+
if command -v shellcheck >/dev/null 2>&1; then
|
|
341
322
|
local warnings_file
|
|
342
323
|
warnings_file=$(mktemp)
|
|
343
324
|
shellcheck -f json "$target_file" > "$warnings_file" 2>/dev/null || true
|
|
344
325
|
|
|
345
326
|
if [[ -s "$warnings_file" ]]; then
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
327
|
+
local shellcheck_count
|
|
328
|
+
shellcheck_count=$(jq 'length' "$warnings_file" 2>/dev/null || echo "0")
|
|
329
|
+
[[ "$shellcheck_count" -gt 0 ]] && info "shellcheck found $shellcheck_count issues in $target_file (manual review recommended)"
|
|
349
330
|
fi
|
|
350
331
|
rm -f "$warnings_file"
|
|
351
332
|
fi
|
|
@@ -381,7 +362,7 @@ run_claude_semantic_review() {
|
|
|
381
362
|
local diff_content="$1"
|
|
382
363
|
local requirements="${2:-}"
|
|
383
364
|
[[ -z "$diff_content" ]] && return 0
|
|
384
|
-
if ! command -v claude
|
|
365
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
385
366
|
return 0
|
|
386
367
|
fi
|
|
387
368
|
|
|
@@ -437,13 +418,12 @@ review_changes() {
|
|
|
437
418
|
local review_output="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"scope\":\"$review_scope\",\"findings\":{}}"
|
|
438
419
|
local total_issues=0
|
|
439
420
|
|
|
440
|
-
# Get changed files
|
|
421
|
+
# Get changed files (Bash 3.2 compatible — no mapfile)
|
|
441
422
|
local changed_files=()
|
|
442
423
|
if [[ "$review_scope" == "staged" ]]; then
|
|
443
|
-
|
|
424
|
+
while IFS= read -r _f; do [[ -n "$_f" ]] && changed_files+=("$_f"); done < <(cd "$REPO_DIR" && git diff --cached --name-only 2>/dev/null || true)
|
|
444
425
|
else
|
|
445
|
-
|
|
446
|
-
mapfile -t changed_files < <(cd "$REPO_DIR" && git diff main...HEAD --name-only 2>/dev/null || true)
|
|
426
|
+
while IFS= read -r _f; do [[ -n "$_f" ]] && changed_files+=("$_f"); done < <(cd "$REPO_DIR" && git diff main...HEAD --name-only 2>/dev/null || true)
|
|
447
427
|
fi
|
|
448
428
|
|
|
449
429
|
[[ ${#changed_files[@]} -eq 0 ]] && { success "No changes to review"; return 0; }
|
|
@@ -456,9 +436,9 @@ review_changes() {
|
|
|
456
436
|
diff_content=$(cd "$REPO_DIR" && git diff main...HEAD 2>/dev/null || true)
|
|
457
437
|
fi
|
|
458
438
|
local semantic_issues=()
|
|
459
|
-
if [[ -n "$diff_content" ]] && command -v claude
|
|
439
|
+
if [[ -n "$diff_content" ]] && command -v claude >/dev/null 2>&1; then
|
|
460
440
|
info "Running Claude semantic review (logic, race conditions, API usage)..."
|
|
461
|
-
|
|
441
|
+
while IFS= read -r _si; do [[ -n "$_si" ]] && semantic_issues+=("$_si"); done < <(run_claude_semantic_review "$diff_content" "${REVIEW_REQUIREMENTS:-}" || true)
|
|
462
442
|
if [[ ${#semantic_issues[@]} -gt 0 ]]; then
|
|
463
443
|
total_issues=$((total_issues + ${#semantic_issues[@]}))
|
|
464
444
|
review_output=$(echo "$review_output" | jq --argjson arr "$(printf '%s\n' "${semantic_issues[@]}" | jq -R . | jq -s .)" '.semantic_findings = $arr' 2>/dev/null || echo "$review_output")
|
|
@@ -476,10 +456,10 @@ review_changes() {
|
|
|
476
456
|
local arch_issues=()
|
|
477
457
|
local style_issues=()
|
|
478
458
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
459
|
+
while IFS= read -r _s; do [[ -n "$_s" ]] && smells+=("$_s"); done < <(detect_code_smells "$file_path")
|
|
460
|
+
while IFS= read -r _s; do [[ -n "$_s" ]] && solids+=("$_s"); done < <(check_solid_principles "$file_path")
|
|
461
|
+
while IFS= read -r _s; do [[ -n "$_s" ]] && arch_issues+=("$_s"); done < <(check_architecture_boundaries "$file_path")
|
|
462
|
+
while IFS= read -r _s; do [[ -n "$_s" ]] && style_issues+=("$_s"); done < <(check_style_consistency "$file_path")
|
|
483
463
|
|
|
484
464
|
local file_issues=$((${#smells[@]} + ${#solids[@]} + ${#arch_issues[@]} + ${#style_issues[@]}))
|
|
485
465
|
total_issues=$((total_issues + file_issues))
|
package/scripts/sw-connect.sh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
10
|
|
|
11
|
-
VERSION="
|
|
11
|
+
VERSION="3.0.0"
|
|
12
12
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
13
|
|
|
14
14
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
@@ -18,8 +18,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
18
18
|
# Canonical helpers (colors, output, events)
|
|
19
19
|
# shellcheck source=lib/helpers.sh
|
|
20
20
|
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
21
|
+
[[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
|
|
21
22
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
22
23
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
24
|
+
# Color fallbacks when helpers not loaded
|
|
25
|
+
: "${CYAN:=}" "${BOLD:=}" "${RESET:=}" "${DIM:=}" "${GREEN:=}" "${RED:=}" "${YELLOW:=}" "${PURPLE:=}" "${WHITE:=}" "${BLUE:=}"
|
|
23
26
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
24
27
|
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
25
28
|
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
@@ -35,16 +38,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
35
38
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
36
39
|
}
|
|
37
40
|
fi
|
|
38
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
39
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
40
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
41
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
42
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
43
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
44
|
-
DIM="${DIM:-\033[2m}"
|
|
45
|
-
BOLD="${BOLD:-\033[1m}"
|
|
46
|
-
RESET="${RESET:-\033[0m}"
|
|
47
|
-
|
|
48
41
|
# ─── Constants ──────────────────────────────────────────────────────────────
|
|
49
42
|
SHIPWRIGHT_DIR="$HOME/.shipwright"
|
|
50
43
|
PID_FILE="$SHIPWRIGHT_DIR/connect.pid"
|
|
@@ -53,7 +46,7 @@ DAEMON_PID_FILE="$SHIPWRIGHT_DIR/daemon.pid"
|
|
|
53
46
|
DAEMON_STATE_FILE="$SHIPWRIGHT_DIR/daemon-state.json"
|
|
54
47
|
EVENTS_FILE="$SHIPWRIGHT_DIR/events.jsonl"
|
|
55
48
|
CONNECT_LOG="$SHIPWRIGHT_DIR/connect.log"
|
|
56
|
-
DEFAULT_URL="http://localhost
|
|
49
|
+
DEFAULT_URL="http://localhost:$(_config_get_int "dashboard.port" 8767)"
|
|
57
50
|
HEARTBEAT_INTERVAL=10
|
|
58
51
|
|
|
59
52
|
ensure_dir() {
|
package/scripts/sw-context.sh
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
-
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
REPO_DIR="${SHIPWRIGHT_REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
12
12
|
|
|
13
13
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
14
14
|
# shellcheck source=lib/compat.sh
|
|
@@ -26,24 +26,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
|
26
26
|
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
27
27
|
now_epoch() { date +%s; }
|
|
28
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}"
|
|
46
|
-
|
|
47
29
|
# ─── Paths ────────────────────────────────────────────────────────────────
|
|
48
30
|
ARTIFACTS_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
|
|
49
31
|
CONTEXT_BUNDLE="${ARTIFACTS_DIR}/context-bundle.md"
|
|
@@ -124,7 +106,7 @@ extract_recent_prs() {
|
|
|
124
106
|
return
|
|
125
107
|
fi
|
|
126
108
|
|
|
127
|
-
if ! command -v gh
|
|
109
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
128
110
|
echo "(gh CLI not available, using git log)"
|
|
129
111
|
echo ""
|
|
130
112
|
local recent_commits
|
|
@@ -405,6 +387,7 @@ gather_context() {
|
|
|
405
387
|
|
|
406
388
|
local tmp_file
|
|
407
389
|
tmp_file=$(mktemp "${TMPDIR:-/tmp}/sw-context-bundle.XXXXXX")
|
|
390
|
+
trap "rm -f '$tmp_file'" RETURN
|
|
408
391
|
|
|
409
392
|
# Write bundle header
|
|
410
393
|
{
|
|
@@ -484,9 +467,9 @@ gather_context() {
|
|
|
484
467
|
# ─── Show current bundle ───────────────────────────────────────────────────
|
|
485
468
|
show_context() {
|
|
486
469
|
if [[ ! -f "$CONTEXT_BUNDLE" ]]; then
|
|
487
|
-
|
|
488
|
-
echo "Run '${CYAN}shipwright context gather --goal \"...\" --stage plan${RESET}'
|
|
489
|
-
return
|
|
470
|
+
echo "Pipeline Context — No bundle generated yet"
|
|
471
|
+
echo "Run '${CYAN}shipwright context gather --goal \"...\" --stage plan${RESET}' to create one"
|
|
472
|
+
return 0
|
|
490
473
|
fi
|
|
491
474
|
|
|
492
475
|
cat "$CONTEXT_BUNDLE"
|
|
@@ -498,7 +481,7 @@ clear_context() {
|
|
|
498
481
|
rm -f "$CONTEXT_BUNDLE"
|
|
499
482
|
success "Context bundle cleared"
|
|
500
483
|
else
|
|
501
|
-
|
|
484
|
+
info "No context bundle to clear — already cleared"
|
|
502
485
|
fi
|
|
503
486
|
}
|
|
504
487
|
|
|
@@ -584,7 +567,7 @@ main() {
|
|
|
584
567
|
|
|
585
568
|
# If issue provided, fetch from GitHub
|
|
586
569
|
if [[ -n "$issue" ]]; then
|
|
587
|
-
if [[ "${NO_GITHUB:-}" == "true" ]] || ! command -v gh
|
|
570
|
+
if [[ "${NO_GITHUB:-}" == "true" ]] || ! command -v gh >/dev/null 2>&1; then
|
|
588
571
|
goal="GitHub issue #$issue (fetch unavailable)"
|
|
589
572
|
else
|
|
590
573
|
goal=$(gh issue view "$issue" --json title,body --template '{{.title}}: {{.body}}' 2>/dev/null || echo "GitHub issue #$issue")
|
package/scripts/sw-cost.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -34,16 +34,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
34
34
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
35
|
}
|
|
36
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}"
|
|
46
|
-
|
|
47
37
|
format_duration() {
|
|
48
38
|
local secs="$1"
|
|
49
39
|
if [[ "$secs" -ge 3600 ]]; then
|
|
@@ -153,13 +143,13 @@ cost_record() {
|
|
|
153
143
|
cost_usd=$(cost_calculate "$input_tokens" "$output_tokens" "$model")
|
|
154
144
|
|
|
155
145
|
# Try SQLite first
|
|
156
|
-
if type db_record_cost
|
|
146
|
+
if type db_record_cost >/dev/null 2>&1; then
|
|
157
147
|
db_record_cost "$input_tokens" "$output_tokens" "$model" "$stage" "$cost_usd" "$issue" 2>/dev/null || true
|
|
158
148
|
fi
|
|
159
149
|
|
|
160
150
|
# Always write to JSON (dual-write period)
|
|
161
151
|
(
|
|
162
|
-
if command -v flock
|
|
152
|
+
if command -v flock >/dev/null 2>&1; then
|
|
163
153
|
flock -w 10 200 2>/dev/null || { warn "Cost lock timeout"; }
|
|
164
154
|
fi
|
|
165
155
|
local tmp_file
|
|
@@ -202,7 +192,7 @@ cost_check_budget() {
|
|
|
202
192
|
|
|
203
193
|
# Try DB for budget info
|
|
204
194
|
local budget_enabled budget_usd
|
|
205
|
-
if type db_get_budget
|
|
195
|
+
if type db_get_budget >/dev/null 2>&1 && type db_available >/dev/null 2>&1 && db_available 2>/dev/null; then
|
|
206
196
|
local db_budget
|
|
207
197
|
db_budget=$(db_get_budget 2>/dev/null || true)
|
|
208
198
|
if [[ -n "$db_budget" ]]; then
|
|
@@ -259,7 +249,7 @@ cost_remaining_budget() {
|
|
|
259
249
|
ensure_cost_dir
|
|
260
250
|
|
|
261
251
|
# Try DB for remaining budget (single query)
|
|
262
|
-
if type db_remaining_budget
|
|
252
|
+
if type db_remaining_budget >/dev/null 2>&1 && type db_available >/dev/null 2>&1 && db_available 2>/dev/null; then
|
|
263
253
|
local db_result
|
|
264
254
|
db_result=$(db_remaining_budget 2>/dev/null || true)
|
|
265
255
|
if [[ -n "$db_result" ]]; then
|
|
@@ -818,7 +808,7 @@ budget_set() {
|
|
|
818
808
|
ensure_cost_dir
|
|
819
809
|
|
|
820
810
|
# Write to DB if available
|
|
821
|
-
if type db_set_budget
|
|
811
|
+
if type db_set_budget >/dev/null 2>&1; then
|
|
822
812
|
db_set_budget "$amount" 2>/dev/null || true
|
|
823
813
|
fi
|
|
824
814
|
|
package/scripts/sw-daemon.sh
CHANGED
|
@@ -9,7 +9,7 @@ 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="
|
|
12
|
+
VERSION="3.0.0"
|
|
13
13
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
14
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
15
15
|
|
|
@@ -20,6 +20,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
20
20
|
# Canonical helpers (colors, output, events)
|
|
21
21
|
# shellcheck source=lib/helpers.sh
|
|
22
22
|
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
23
|
+
[[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
|
|
23
24
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
24
25
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
25
26
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -29,23 +30,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
|
29
30
|
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
30
31
|
now_epoch() { date +%s; }
|
|
31
32
|
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
33
|
|
|
50
34
|
# Policy (config/policy.json) — daemon defaults when daemon-config.json missing or silent
|
|
51
35
|
[[ -f "$SCRIPT_DIR/lib/policy.sh" ]] && source "$SCRIPT_DIR/lib/policy.sh"
|
|
@@ -109,28 +93,6 @@ format_duration() {
|
|
|
109
93
|
fi
|
|
110
94
|
}
|
|
111
95
|
|
|
112
|
-
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
113
|
-
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
114
|
-
|
|
115
|
-
emit_event() {
|
|
116
|
-
local event_type="$1"
|
|
117
|
-
shift
|
|
118
|
-
local json_fields=""
|
|
119
|
-
for kv in "$@"; do
|
|
120
|
-
local key="${kv%%=*}"
|
|
121
|
-
local val="${kv#*=}"
|
|
122
|
-
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
123
|
-
json_fields="${json_fields},\"${key}\":${val}"
|
|
124
|
-
else
|
|
125
|
-
local escaped_val
|
|
126
|
-
escaped_val=$(printf '%s' "$val" | jq -Rs '.' 2>/dev/null || printf '"%s"' "${val//\"/\\\"}")
|
|
127
|
-
json_fields="${json_fields},\"${key}\":${escaped_val}"
|
|
128
|
-
fi
|
|
129
|
-
done
|
|
130
|
-
mkdir -p "${HOME}/.shipwright"
|
|
131
|
-
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
132
|
-
}
|
|
133
|
-
|
|
134
96
|
# ─── Event Log Rotation ─────────────────────────────────────────────────────
|
|
135
97
|
rotate_event_log() {
|
|
136
98
|
local max_size=$((50 * 1024 * 1024)) # 50MB
|
|
@@ -167,8 +129,8 @@ rotate_event_log() {
|
|
|
167
129
|
daemon_github_context() {
|
|
168
130
|
# Skip if no GitHub
|
|
169
131
|
[[ "${NO_GITHUB:-false}" == "true" ]] && return 0
|
|
170
|
-
type gh_repo_context
|
|
171
|
-
type _gh_detect_repo
|
|
132
|
+
type gh_repo_context >/dev/null 2>&1 || return 0
|
|
133
|
+
type _gh_detect_repo >/dev/null 2>&1 || return 0
|
|
172
134
|
|
|
173
135
|
_gh_detect_repo 2>/dev/null || return 0
|
|
174
136
|
local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
|
|
@@ -192,8 +154,8 @@ gh_retry() {
|
|
|
192
154
|
|
|
193
155
|
while [[ $attempt -lt $max_retries ]]; do
|
|
194
156
|
attempt=$((attempt + 1))
|
|
195
|
-
# Run the gh command; capture exit code
|
|
196
|
-
if output=$("$@" 2>&1); then
|
|
157
|
+
# Run the gh command with per-call timeout; capture exit code
|
|
158
|
+
if output=$(_timeout "$(_config_get_int "network.gh_timeout" 30 2>/dev/null || echo 30)" "$@" 2>&1); then
|
|
197
159
|
echo "$output"
|
|
198
160
|
return 0
|
|
199
161
|
fi
|
|
@@ -227,17 +189,18 @@ LOG_DIR=""
|
|
|
227
189
|
WORKTREE_DIR=""
|
|
228
190
|
|
|
229
191
|
# Config defaults (overridden by daemon-config.json; policy overrides when present)
|
|
230
|
-
WATCH_LABEL="
|
|
231
|
-
POLL_INTERVAL
|
|
232
|
-
if type policy_get
|
|
192
|
+
WATCH_LABEL="$(_config_get "labels.watch" "shipwright" 2>/dev/null || echo "shipwright")"
|
|
193
|
+
POLL_INTERVAL=$(_config_get_int "daemon.poll_interval" 60 2>/dev/null || echo 60)
|
|
194
|
+
if type policy_get >/dev/null 2>&1; then
|
|
233
195
|
POLL_INTERVAL=$(policy_get ".daemon.poll_interval_seconds" "60")
|
|
234
196
|
fi
|
|
235
|
-
MAX_PARALLEL
|
|
197
|
+
MAX_PARALLEL=$(_config_get_int "daemon.max_parallel" 4 2>/dev/null || echo 4)
|
|
198
|
+
ISSUE_LIMIT=$(_config_get_int "daemon.issue_limit" 100 2>/dev/null || echo 100)
|
|
236
199
|
PIPELINE_TEMPLATE="autonomous"
|
|
237
200
|
SKIP_GATES=true
|
|
238
201
|
MODEL="opus"
|
|
239
202
|
BASE_BRANCH="main"
|
|
240
|
-
ON_SUCCESS_REMOVE_LABEL="
|
|
203
|
+
ON_SUCCESS_REMOVE_LABEL="shipwright"
|
|
241
204
|
ON_SUCCESS_ADD_LABEL="pipeline/complete"
|
|
242
205
|
ON_SUCCESS_CLOSE_ISSUE=false
|
|
243
206
|
ON_FAILURE_ADD_LABEL="pipeline/failed"
|
|
@@ -257,7 +220,7 @@ REPO_FILTER=""
|
|
|
257
220
|
# Auto-scaling defaults (policy overrides when present)
|
|
258
221
|
AUTO_SCALE=false
|
|
259
222
|
AUTO_SCALE_INTERVAL=5
|
|
260
|
-
if type policy_get
|
|
223
|
+
if type policy_get >/dev/null 2>&1; then
|
|
261
224
|
AUTO_SCALE_INTERVAL=$(policy_get ".daemon.auto_scale_interval_cycles" "5")
|
|
262
225
|
fi
|
|
263
226
|
MAX_WORKERS=8
|
|
@@ -280,7 +243,7 @@ PATROL_RETRY_THRESHOLD=2
|
|
|
280
243
|
LAST_PATROL_EPOCH=0
|
|
281
244
|
|
|
282
245
|
# Team dashboard coordination
|
|
283
|
-
DASHBOARD_URL="${DASHBOARD_URL
|
|
246
|
+
DASHBOARD_URL="${DASHBOARD_URL:-$(_config_get "dashboard.url" "http://localhost:8767" 2>/dev/null || echo "http://localhost:8767")}"
|
|
284
247
|
|
|
285
248
|
# Runtime
|
|
286
249
|
NO_GITHUB=false
|
|
@@ -370,7 +333,7 @@ show_help() {
|
|
|
370
333
|
echo -e " ${DIM}shipwright daemon patrol --once${RESET} # Run patrol once and exit"
|
|
371
334
|
echo ""
|
|
372
335
|
echo -e "${BOLD}CONFIG FILE${RESET} ${DIM}(.claude/daemon-config.json)${RESET}"
|
|
373
|
-
echo -e " ${DIM}watch_label${RESET} GitHub label to watch for ${DIM}(default:
|
|
336
|
+
echo -e " ${DIM}watch_label${RESET} GitHub label to watch for ${DIM}(default: shipwright)${RESET}"
|
|
374
337
|
echo -e " ${DIM}poll_interval${RESET} Seconds between polls ${DIM}(default: 60)${RESET}"
|
|
375
338
|
echo -e " ${DIM}max_parallel${RESET} Max concurrent pipeline jobs ${DIM}(default: 2)${RESET}"
|
|
376
339
|
echo -e " ${DIM}pipeline_template${RESET} Pipeline template to use ${DIM}(default: autonomous)${RESET}"
|
|
@@ -411,16 +374,17 @@ load_config() {
|
|
|
411
374
|
|
|
412
375
|
info "Loading config: ${DIM}${config_file}${RESET}"
|
|
413
376
|
|
|
414
|
-
WATCH_LABEL=$(jq -r '.watch_label // "
|
|
415
|
-
POLL_INTERVAL=$(jq -r '.poll_interval // '"$(type policy_get
|
|
377
|
+
WATCH_LABEL=$(jq -r '.watch_label // "shipwright"' "$config_file")
|
|
378
|
+
POLL_INTERVAL=$(jq -r '.poll_interval // '"$(type policy_get >/dev/null 2>&1 && policy_get ".daemon.poll_interval_seconds" "60" || echo "60")"'' "$config_file")
|
|
416
379
|
MAX_PARALLEL=$(jq -r '.max_parallel // 2' "$config_file")
|
|
380
|
+
ISSUE_LIMIT=$(jq -r '.issue_limit // 100' "$config_file")
|
|
417
381
|
PIPELINE_TEMPLATE=$(jq -r '.pipeline_template // "autonomous"' "$config_file")
|
|
418
382
|
SKIP_GATES=$(jq -r '.skip_gates // true' "$config_file")
|
|
419
383
|
MODEL=$(jq -r '.model // "opus"' "$config_file")
|
|
420
384
|
BASE_BRANCH=$(jq -r '.base_branch // "main"' "$config_file")
|
|
421
385
|
|
|
422
386
|
# on_success settings
|
|
423
|
-
ON_SUCCESS_REMOVE_LABEL=$(jq -r '.on_success.remove_label // "
|
|
387
|
+
ON_SUCCESS_REMOVE_LABEL=$(jq -r '.on_success.remove_label // "shipwright"' "$config_file")
|
|
424
388
|
ON_SUCCESS_ADD_LABEL=$(jq -r '.on_success.add_label // "pipeline/complete"' "$config_file")
|
|
425
389
|
ON_SUCCESS_CLOSE_ISSUE=$(jq -r '.on_success.close_issue // false' "$config_file")
|
|
426
390
|
|
|
@@ -472,12 +436,41 @@ load_config() {
|
|
|
472
436
|
|
|
473
437
|
# self-optimization
|
|
474
438
|
SELF_OPTIMIZE=$(jq -r '.self_optimize // false' "$config_file")
|
|
475
|
-
OPTIMIZE_INTERVAL=$(jq -r '.optimize_interval // '"$(type policy_get
|
|
439
|
+
OPTIMIZE_INTERVAL=$(jq -r '.optimize_interval // '"$(type policy_get >/dev/null 2>&1 && policy_get ".daemon.optimize_interval_cycles" "10" || echo "10")"'' "$config_file")
|
|
476
440
|
|
|
477
|
-
# intelligence engine settings
|
|
478
|
-
INTELLIGENCE_ENABLED=$(jq -r '.intelligence.enabled //
|
|
441
|
+
# intelligence engine settings (default "auto" = enable when Claude CLI available)
|
|
442
|
+
INTELLIGENCE_ENABLED=$(jq -r '.intelligence.enabled // "auto"' "$config_file")
|
|
479
443
|
INTELLIGENCE_CACHE_TTL=$(jq -r '.intelligence.cache_ttl_seconds // 3600' "$config_file")
|
|
480
|
-
COMPOSER_ENABLED=$(jq -r '.intelligence.composer_enabled //
|
|
444
|
+
COMPOSER_ENABLED=$(jq -r '.intelligence.composer_enabled // "auto"' "$config_file")
|
|
445
|
+
|
|
446
|
+
# Auto-enable intelligence when Claude is available (unless explicitly disabled)
|
|
447
|
+
if [[ "${INTELLIGENCE_ENABLED}" == "false" && "${INTELLIGENCE_EXPLICIT_DISABLE:-false}" != "true" ]]; then
|
|
448
|
+
if command -v claude &>/dev/null; then
|
|
449
|
+
INTELLIGENCE_ENABLED=true
|
|
450
|
+
type daemon_log &>/dev/null && daemon_log INFO "Intelligence auto-enabled (Claude CLI detected). Disable with intelligence.enabled=false in daemon-config.json"
|
|
451
|
+
fi
|
|
452
|
+
elif [[ "${INTELLIGENCE_ENABLED}" == "auto" ]]; then
|
|
453
|
+
if command -v claude &>/dev/null; then
|
|
454
|
+
INTELLIGENCE_ENABLED=true
|
|
455
|
+
type daemon_log &>/dev/null && daemon_log INFO "Intelligence enabled (auto: Claude CLI detected)"
|
|
456
|
+
else
|
|
457
|
+
INTELLIGENCE_ENABLED=false
|
|
458
|
+
fi
|
|
459
|
+
fi
|
|
460
|
+
|
|
461
|
+
# Auto-enable composer when Claude is available (unless explicitly disabled)
|
|
462
|
+
if [[ "${COMPOSER_ENABLED}" == "false" && "${COMPOSER_EXPLICIT_DISABLE:-false}" != "true" ]]; then
|
|
463
|
+
if command -v claude &>/dev/null; then
|
|
464
|
+
COMPOSER_ENABLED=true
|
|
465
|
+
type daemon_log &>/dev/null && daemon_log INFO "Composer auto-enabled (Claude CLI detected). Disable with intelligence.composer_enabled=false in daemon-config.json"
|
|
466
|
+
fi
|
|
467
|
+
elif [[ "${COMPOSER_ENABLED}" == "auto" ]]; then
|
|
468
|
+
if command -v claude &>/dev/null; then
|
|
469
|
+
COMPOSER_ENABLED=true
|
|
470
|
+
else
|
|
471
|
+
COMPOSER_ENABLED=false
|
|
472
|
+
fi
|
|
473
|
+
fi
|
|
481
474
|
OPTIMIZATION_ENABLED=$(jq -r '.intelligence.optimization_enabled // false' "$config_file")
|
|
482
475
|
PREDICTION_ENABLED=$(jq -r '.intelligence.prediction_enabled // false' "$config_file")
|
|
483
476
|
ANOMALY_THRESHOLD=$(jq -r '.intelligence.anomaly_threshold // 3.0' "$config_file")
|
|
@@ -491,7 +484,7 @@ load_config() {
|
|
|
491
484
|
|
|
492
485
|
# stale state reaper: clean old worktrees, artifacts, state entries
|
|
493
486
|
STALE_REAPER_ENABLED=$(jq -r '.stale_reaper // true' "$config_file")
|
|
494
|
-
STALE_REAPER_INTERVAL=$(jq -r '.stale_reaper_interval // '"$(type policy_get
|
|
487
|
+
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")
|
|
495
488
|
STALE_REAPER_AGE_DAYS=$(jq -r '.stale_reaper_age_days // 7' "$config_file")
|
|
496
489
|
|
|
497
490
|
# priority lane settings
|
|
@@ -508,14 +501,14 @@ load_config() {
|
|
|
508
501
|
|
|
509
502
|
# auto-scaling
|
|
510
503
|
AUTO_SCALE=$(jq -r '.auto_scale // false' "$config_file")
|
|
511
|
-
AUTO_SCALE_INTERVAL=$(jq -r '.auto_scale_interval // '"$(type policy_get
|
|
504
|
+
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")
|
|
512
505
|
MAX_WORKERS=$(jq -r '.max_workers // 8' "$config_file")
|
|
513
506
|
MIN_WORKERS=$(jq -r '.min_workers // 1' "$config_file")
|
|
514
507
|
WORKER_MEM_GB=$(jq -r '.worker_mem_gb // 4' "$config_file")
|
|
515
508
|
EST_COST_PER_JOB=$(jq -r '.estimated_cost_per_job_usd // 5.0' "$config_file")
|
|
516
509
|
|
|
517
510
|
# heartbeat + checkpoint recovery (policy fallback when config silent)
|
|
518
|
-
HEALTH_HEARTBEAT_TIMEOUT=$(jq -r '.health.heartbeat_timeout_s // '"$(type policy_get
|
|
511
|
+
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")
|
|
519
512
|
CHECKPOINT_ENABLED=$(jq -r '.health.checkpoint_enabled // true' "$config_file")
|
|
520
513
|
|
|
521
514
|
# progress-based health monitoring (replaces static timeouts)
|
|
@@ -603,6 +596,18 @@ cleanup_on_exit() {
|
|
|
603
596
|
done <<< "$child_pids"
|
|
604
597
|
fi
|
|
605
598
|
fi
|
|
599
|
+
|
|
600
|
+
# Release claims on active issues before exit
|
|
601
|
+
local active_issues
|
|
602
|
+
active_issues=$(jq -r '.active_jobs[].issue // empty' "$STATE_FILE" 2>/dev/null || true)
|
|
603
|
+
if [[ -n "$active_issues" ]]; then
|
|
604
|
+
local machine_name
|
|
605
|
+
machine_name=$(jq -r '.machines[] | select(.role == "primary") | .name' "$HOME/.shipwright/machines.json" 2>/dev/null || hostname -s)
|
|
606
|
+
for issue_num in $active_issues; do
|
|
607
|
+
[[ -z "$issue_num" ]] && continue
|
|
608
|
+
release_claim "$issue_num" "$machine_name" 2>/dev/null || true
|
|
609
|
+
done
|
|
610
|
+
fi
|
|
606
611
|
fi
|
|
607
612
|
|
|
608
613
|
rm -f "$PID_FILE" "$SHUTDOWN_FLAG"
|
|
@@ -647,7 +652,7 @@ daemon_start() {
|
|
|
647
652
|
|
|
648
653
|
# Detach mode: re-exec in a tmux session
|
|
649
654
|
if [[ "$DETACH" == "true" ]]; then
|
|
650
|
-
if ! command -v tmux
|
|
655
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
651
656
|
error "tmux required for --detach mode"
|
|
652
657
|
exit 1
|
|
653
658
|
fi
|
|
@@ -689,7 +694,7 @@ daemon_start() {
|
|
|
689
694
|
rm -f "$SHUTDOWN_FLAG"
|
|
690
695
|
|
|
691
696
|
# Initialize SQLite database (if available)
|
|
692
|
-
if type init_schema
|
|
697
|
+
if type init_schema >/dev/null 2>&1; then
|
|
693
698
|
init_schema 2>/dev/null || true
|
|
694
699
|
fi
|
|
695
700
|
|
|
@@ -761,7 +766,7 @@ daemon_start() {
|
|
|
761
766
|
if [[ -f "$STATE_FILE" ]]; then
|
|
762
767
|
if ! jq '.' "$STATE_FILE" >/dev/null 2>&1; then
|
|
763
768
|
daemon_log WARN "Watchdog: state file corrupt — recovering from backup"
|
|
764
|
-
type validate_json
|
|
769
|
+
type validate_json >/dev/null 2>&1 && validate_json "$STATE_FILE" || true
|
|
765
770
|
fi
|
|
766
771
|
fi
|
|
767
772
|
done
|
|
@@ -931,7 +936,7 @@ daemon_init() {
|
|
|
931
936
|
|
|
932
937
|
cat > "$config_file" << 'CONFIGEOF'
|
|
933
938
|
{
|
|
934
|
-
"watch_label": "
|
|
939
|
+
"watch_label": "shipwright",
|
|
935
940
|
"poll_interval": 60,
|
|
936
941
|
"max_parallel": 2,
|
|
937
942
|
"pipeline_template": "autonomous",
|
|
@@ -939,7 +944,7 @@ daemon_init() {
|
|
|
939
944
|
"model": "opus",
|
|
940
945
|
"base_branch": "main",
|
|
941
946
|
"on_success": {
|
|
942
|
-
"remove_label": "
|
|
947
|
+
"remove_label": "shipwright",
|
|
943
948
|
"add_label": "pipeline/complete",
|
|
944
949
|
"close_issue": false
|
|
945
950
|
},
|
|
@@ -993,9 +998,9 @@ daemon_init() {
|
|
|
993
998
|
"worker_mem_gb": 4,
|
|
994
999
|
"estimated_cost_per_job_usd": 5.0,
|
|
995
1000
|
"intelligence": {
|
|
996
|
-
"enabled":
|
|
1001
|
+
"enabled": "auto",
|
|
997
1002
|
"cache_ttl_seconds": 3600,
|
|
998
|
-
"composer_enabled":
|
|
1003
|
+
"composer_enabled": "auto",
|
|
999
1004
|
"optimization_enabled": true,
|
|
1000
1005
|
"prediction_enabled": true,
|
|
1001
1006
|
"adversarial_enabled": false,
|
|
@@ -1052,7 +1057,7 @@ daemon_metrics() {
|
|
|
1052
1057
|
exit 1
|
|
1053
1058
|
fi
|
|
1054
1059
|
|
|
1055
|
-
if ! command -v jq
|
|
1060
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
1056
1061
|
error "jq is required for metrics. Install: brew install jq"
|
|
1057
1062
|
exit 1
|
|
1058
1063
|
fi
|