shipwright-cli 3.1.0 → 3.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/README.md +21 -7
- package/config/defaults.json +25 -2
- package/config/policy.json +1 -1
- package/dashboard/public/index.html +6 -0
- package/dashboard/public/styles.css +76 -0
- package/dashboard/server.ts +51 -0
- package/dashboard/src/core/api.ts +5 -0
- package/dashboard/src/types/api.ts +10 -0
- package/dashboard/src/views/metrics.ts +69 -1
- package/package.json +1 -1
- package/scripts/lib/daemon-adaptive.sh +4 -2
- package/scripts/lib/daemon-patrol.sh +2 -2
- package/scripts/lib/daemon-state.sh +7 -0
- package/scripts/lib/helpers.sh +3 -1
- package/scripts/lib/pipeline-detection.sh +1 -1
- package/scripts/lib/pipeline-intelligence.sh +5 -3
- package/scripts/lib/pipeline-quality-checks.sh +8 -4
- package/scripts/lib/pipeline-stages.sh +132 -2
- package/scripts/sw +1 -1
- package/scripts/sw-activity.sh +1 -7
- package/scripts/sw-adaptive.sh +7 -7
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +1 -1
- package/scripts/sw-autonomous.sh +1 -1
- package/scripts/sw-changelog.sh +1 -1
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +11 -6
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +36 -17
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +1 -1
- package/scripts/sw-cost.sh +60 -3
- package/scripts/sw-daemon.sh +5 -2
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +13 -5
- package/scripts/sw-decide.sh +1 -1
- package/scripts/sw-decompose.sh +1 -1
- package/scripts/sw-deps.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +54 -4
- package/scripts/sw-doc-fleet.sh +1 -1
- package/scripts/sw-docs-agent.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +1 -1
- package/scripts/sw-dora.sh +1 -1
- package/scripts/sw-durable.sh +9 -5
- package/scripts/sw-e2e-orchestrator.sh +1 -1
- package/scripts/sw-eventbus.sh +7 -4
- package/scripts/sw-evidence.sh +1 -1
- package/scripts/sw-feedback.sh +1 -1
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +1 -1
- package/scripts/sw-fleet-viz.sh +6 -4
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-app.sh +3 -2
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +5 -3
- package/scripts/sw-incident.sh +9 -5
- package/scripts/sw-init.sh +1 -1
- package/scripts/sw-instrument.sh +1 -1
- package/scripts/sw-intelligence.sh +3 -2
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +1 -1
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +72 -16
- package/scripts/sw-memory.sh +2 -2
- package/scripts/sw-mission-control.sh +1 -1
- package/scripts/sw-model-router.sh +3 -2
- package/scripts/sw-otel.sh +4 -2
- package/scripts/sw-oversight.sh +1 -1
- package/scripts/sw-pipeline-composer.sh +3 -1
- package/scripts/sw-pipeline-vitals.sh +11 -6
- package/scripts/sw-pipeline.sh +20 -8
- package/scripts/sw-pm.sh +5 -4
- package/scripts/sw-pr-lifecycle.sh +1 -1
- package/scripts/sw-predictive.sh +11 -5
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +3 -2
- package/scripts/sw-quality.sh +13 -6
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +1 -1
- package/scripts/sw-regression.sh +1 -1
- package/scripts/sw-release-manager.sh +1 -1
- package/scripts/sw-release.sh +1 -1
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +1 -1
- package/scripts/sw-retro.sh +1 -1
- package/scripts/sw-review-rerun.sh +1 -1
- package/scripts/sw-scale.sh +5 -3
- package/scripts/sw-security-audit.sh +1 -1
- package/scripts/sw-self-optimize.sh +168 -4
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +1 -1
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +11 -6
- package/scripts/sw-stream.sh +7 -4
- package/scripts/sw-swarm.sh +3 -2
- package/scripts/sw-team-stages.sh +1 -1
- package/scripts/sw-templates.sh +3 -3
- package/scripts/sw-testgen.sh +11 -6
- package/scripts/sw-tmux-pipeline.sh +1 -1
- package/scripts/sw-tmux.sh +35 -1
- package/scripts/sw-trace.sh +1 -1
- package/scripts/sw-tracker.sh +1 -1
- package/scripts/sw-triage.sh +2 -2
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +1 -1
- package/scripts/sw-webhook.sh +3 -2
- package/scripts/sw-widgets.sh +7 -4
- package/scripts/sw-worktree.sh +1 -1
package/scripts/sw-init.sh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# ║ ║
|
|
9
9
|
# ║ --deploy Detect platform and generate deployed.json template ║
|
|
10
10
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
11
|
-
VERSION="3.
|
|
11
|
+
VERSION="3.2.0"
|
|
12
12
|
set -euo pipefail
|
|
13
13
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
14
14
|
trap 'rm -f "${tmp:-}"' EXIT
|
package/scripts/sw-instrument.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="3.
|
|
9
|
+
VERSION="3.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
12
12
|
|
|
@@ -395,7 +395,8 @@ _intelligence_fallback_analyze() {
|
|
|
395
395
|
local outcomes_file="$HOME/.shipwright/optimization/outcomes.jsonl"
|
|
396
396
|
if [[ -f "$outcomes_file" ]] && command -v jq &>/dev/null; then
|
|
397
397
|
local sample_count
|
|
398
|
-
sample_count=$(wc -l < "$outcomes_file" 2>/dev/null ||
|
|
398
|
+
sample_count=$(wc -l < "$outcomes_file" 2>/dev/null || true)
|
|
399
|
+
sample_count="${sample_count:-0}"
|
|
399
400
|
|
|
400
401
|
if [[ "$sample_count" -gt 5 ]]; then
|
|
401
402
|
# Compute average complexity from past outcomes
|
package/scripts/sw-jira.sh
CHANGED
package/scripts/sw-launchd.sh
CHANGED
package/scripts/sw-linear.sh
CHANGED
package/scripts/sw-logs.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# ║ ║
|
|
5
5
|
# ║ Captures tmux pane scrollback and provides log browsing/search. ║
|
|
6
6
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
-
VERSION="3.
|
|
7
|
+
VERSION="3.2.0"
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
10
|
|
package/scripts/sw-loop.sh
CHANGED
|
@@ -14,6 +14,7 @@ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
|
14
14
|
unset CLAUDECODE 2>/dev/null || true
|
|
15
15
|
# Ignore SIGHUP so tmux attach/detach doesn't kill long-running agent sessions
|
|
16
16
|
trap '' HUP
|
|
17
|
+
trap '' SIGPIPE
|
|
17
18
|
|
|
18
19
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
20
|
|
|
@@ -30,6 +31,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
30
31
|
if [[ -f "$SCRIPT_DIR/sw-db.sh" ]]; then
|
|
31
32
|
source "$SCRIPT_DIR/sw-db.sh" 2>/dev/null || true
|
|
32
33
|
fi
|
|
34
|
+
# Cross-pipeline discovery (learnings from other pipeline runs)
|
|
35
|
+
[[ -f "$SCRIPT_DIR/sw-discovery.sh" ]] && source "$SCRIPT_DIR/sw-discovery.sh" 2>/dev/null || true
|
|
33
36
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
34
37
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
35
38
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -69,7 +72,7 @@ MAX_RESTARTS=$(_config_get_int "loop.max_restarts" 0 2>/dev/null || echo 0)
|
|
|
69
72
|
SESSION_RESTART=false
|
|
70
73
|
RESTART_COUNT=0
|
|
71
74
|
REPO_OVERRIDE=""
|
|
72
|
-
VERSION="3.
|
|
75
|
+
VERSION="3.2.0"
|
|
73
76
|
|
|
74
77
|
# ─── Token Tracking ─────────────────────────────────────────────────────────
|
|
75
78
|
LOOP_INPUT_TOKENS=0
|
|
@@ -975,7 +978,7 @@ check_fatal_error() {
|
|
|
975
978
|
local match
|
|
976
979
|
match=$(grep -iE "$fatal_patterns" "$log_file" 2>/dev/null | head -1 | cut -c1-120)
|
|
977
980
|
error "Fatal CLI error: $match"
|
|
978
|
-
return
|
|
981
|
+
return 1 # fatal error detected
|
|
979
982
|
fi
|
|
980
983
|
|
|
981
984
|
# Non-zero exit + tiny output = likely CLI crash
|
|
@@ -1661,13 +1664,20 @@ HOLISTIC_PROMPT
|
|
|
1661
1664
|
# Prevents prompt from exceeding Claude's context limit (~200K tokens).
|
|
1662
1665
|
# Trims least-critical sections first when over budget.
|
|
1663
1666
|
|
|
1664
|
-
CONTEXT_BUDGET_CHARS="${CONTEXT_BUDGET_CHARS
|
|
1667
|
+
CONTEXT_BUDGET_CHARS="${CONTEXT_BUDGET_CHARS:-$(_config_get_int "loop.context_budget_chars" 180000 2>/dev/null || echo 180000)}" # ~45K tokens at 4 chars/token
|
|
1665
1668
|
|
|
1666
1669
|
manage_context_window() {
|
|
1667
1670
|
local prompt="$1"
|
|
1668
1671
|
local budget="${CONTEXT_BUDGET_CHARS}"
|
|
1669
1672
|
local current_len=${#prompt}
|
|
1670
1673
|
|
|
1674
|
+
# Read trimming tunables from config (env > daemon-config > policy > defaults.json)
|
|
1675
|
+
local trim_memory_chars trim_git_entries trim_hotspot_files trim_test_lines
|
|
1676
|
+
trim_memory_chars=$(_config_get_int "loop.context_trim_memory_chars" 20000 2>/dev/null || echo 20000)
|
|
1677
|
+
trim_git_entries=$(_config_get_int "loop.context_trim_git_entries" 10 2>/dev/null || echo 10)
|
|
1678
|
+
trim_hotspot_files=$(_config_get_int "loop.context_trim_hotspot_files" 5 2>/dev/null || echo 5)
|
|
1679
|
+
trim_test_lines=$(_config_get_int "loop.context_trim_test_lines" 50 2>/dev/null || echo 50)
|
|
1680
|
+
|
|
1671
1681
|
if [[ "$current_len" -le "$budget" ]]; then
|
|
1672
1682
|
echo "$prompt"
|
|
1673
1683
|
return
|
|
@@ -1681,19 +1691,19 @@ manage_context_window() {
|
|
|
1681
1691
|
trimmed=$(echo "$trimmed" | awk '/^## Performance Baselines/{skip=1; next} skip && /^## [^#]/{skip=0} !skip{print}')
|
|
1682
1692
|
fi
|
|
1683
1693
|
|
|
1684
|
-
# 2. Trim file hotspots to top
|
|
1694
|
+
# 2. Trim file hotspots to top N
|
|
1685
1695
|
if [[ "${#trimmed}" -gt "$budget" ]]; then
|
|
1686
|
-
trimmed=$(echo "$trimmed" | awk '/## File Hotspots/{p=1; c=0} p && /^- /{c++; if(c>
|
|
1696
|
+
trimmed=$(echo "$trimmed" | awk -v max="$trim_hotspot_files" '/## File Hotspots/{p=1; c=0} p && /^- /{c++; if(c>max) next} {print}')
|
|
1687
1697
|
fi
|
|
1688
1698
|
|
|
1689
|
-
# 3. Trim git log to last
|
|
1699
|
+
# 3. Trim git log to last N entries
|
|
1690
1700
|
if [[ "${#trimmed}" -gt "$budget" ]]; then
|
|
1691
|
-
trimmed=$(echo "$trimmed" | awk '/## Recent Git Activity/{p=1; c=0} p && /^[a-f0-9]/{c++; if(c>
|
|
1701
|
+
trimmed=$(echo "$trimmed" | awk -v max="$trim_git_entries" '/## Recent Git Activity/{p=1; c=0} p && /^[a-f0-9]/{c++; if(c>max) next} {print}')
|
|
1692
1702
|
fi
|
|
1693
1703
|
|
|
1694
|
-
# 4. Truncate memory context to first
|
|
1704
|
+
# 4. Truncate memory context to first N chars
|
|
1695
1705
|
if [[ "${#trimmed}" -gt "$budget" ]]; then
|
|
1696
|
-
trimmed=$(echo "$trimmed" | awk -v max=
|
|
1706
|
+
trimmed=$(echo "$trimmed" | awk -v max="$trim_memory_chars" '
|
|
1697
1707
|
/## Memory Context/{mem=1; skip_rest=0; chars=0; print; next}
|
|
1698
1708
|
mem && /^## [^#]/{mem=0; print; next}
|
|
1699
1709
|
mem{chars+=length($0)+1; if(chars>max){print "... (memory truncated for context budget)"; skip_rest=1; mem=0; next}}
|
|
@@ -1703,11 +1713,11 @@ manage_context_window() {
|
|
|
1703
1713
|
')
|
|
1704
1714
|
fi
|
|
1705
1715
|
|
|
1706
|
-
# 5. Truncate test output to last
|
|
1716
|
+
# 5. Truncate test output to last N lines
|
|
1707
1717
|
if [[ "${#trimmed}" -gt "$budget" ]]; then
|
|
1708
|
-
trimmed=$(echo "$trimmed" | awk '
|
|
1718
|
+
trimmed=$(echo "$trimmed" | awk -v max="$trim_test_lines" '
|
|
1709
1719
|
/## Test Results/{found=1; buf=""; print; next}
|
|
1710
|
-
found && /^## [^#]/{found=0; n=split(buf,arr,"\n"); start=(n>
|
|
1720
|
+
found && /^## [^#]/{found=0; n=split(buf,arr,"\n"); start=(n>max)?(n-max+1):1; for(i=start;i<=n;i++) if(arr[i]!="") print arr[i]; print; next}
|
|
1711
1721
|
found{buf=buf $0 "\n"; next}
|
|
1712
1722
|
{print}
|
|
1713
1723
|
')
|
|
@@ -1786,6 +1796,16 @@ Fix these specific errors. Each line above is one distinct error from the test o
|
|
|
1786
1796
|
memory_section="$("$SCRIPT_DIR/sw-memory.sh" inject build 2>/dev/null || true)"
|
|
1787
1797
|
fi
|
|
1788
1798
|
|
|
1799
|
+
# Cross-pipeline discovery injection (learnings from other pipeline runs)
|
|
1800
|
+
local discovery_section=""
|
|
1801
|
+
if type inject_discoveries >/dev/null 2>&1; then
|
|
1802
|
+
local disc_output
|
|
1803
|
+
disc_output="$(inject_discoveries "${GOAL:-}" 2>/dev/null || true)"
|
|
1804
|
+
if [[ -n "$disc_output" ]]; then
|
|
1805
|
+
discovery_section="$disc_output"
|
|
1806
|
+
fi
|
|
1807
|
+
fi
|
|
1808
|
+
|
|
1789
1809
|
# DORA baselines for context
|
|
1790
1810
|
local dora_section=""
|
|
1791
1811
|
if type memory_get_dora_baseline >/dev/null 2>&1; then
|
|
@@ -1990,6 +2010,9 @@ ${error_summary_section:+$error_summary_section
|
|
|
1990
2010
|
${memory_section:+## Memory Context
|
|
1991
2011
|
$memory_section
|
|
1992
2012
|
}
|
|
2013
|
+
${discovery_section:+## Cross-Pipeline Learnings
|
|
2014
|
+
$discovery_section
|
|
2015
|
+
}
|
|
1993
2016
|
${dora_section:+$dora_section
|
|
1994
2017
|
}
|
|
1995
2018
|
${intelligence_section:+$intelligence_section
|
|
@@ -2004,6 +2027,13 @@ ${restart_section:+$restart_section
|
|
|
2004
2027
|
5. Commit your work with a descriptive message
|
|
2005
2028
|
6. When the goal is FULLY achieved, output exactly: LOOP_COMPLETE
|
|
2006
2029
|
|
|
2030
|
+
## Context Efficiency
|
|
2031
|
+
- Batch independent tool calls in parallel — avoid sequential round-trips
|
|
2032
|
+
- Use targeted file reads (offset/limit) instead of reading entire large files
|
|
2033
|
+
- Delegate large searches to subagents — only import the summary
|
|
2034
|
+
- Filter tool results with grep/jq before reasoning over them
|
|
2035
|
+
- Keep working memory lean — summarize completed steps, don't preserve full outputs
|
|
2036
|
+
|
|
2007
2037
|
${audit_section}
|
|
2008
2038
|
|
|
2009
2039
|
${audit_feedback_section}
|
|
@@ -2102,7 +2132,8 @@ detect_stuckness() {
|
|
|
2102
2132
|
local stuckness_reasons=()
|
|
2103
2133
|
local tracking_file="${STUCKNESS_TRACKING_FILE:-$LOG_DIR/stuckness-tracking.txt}"
|
|
2104
2134
|
local tracking_lines
|
|
2105
|
-
tracking_lines=$(wc -l < "$tracking_file" 2>/dev/null ||
|
|
2135
|
+
tracking_lines=$(wc -l < "$tracking_file" 2>/dev/null || true)
|
|
2136
|
+
tracking_lines="${tracking_lines:-0}"
|
|
2106
2137
|
|
|
2107
2138
|
# Signal 1: Text overlap (existing logic) — compare last 2 iteration logs
|
|
2108
2139
|
if [[ "$iteration" -ge 3 ]]; then
|
|
@@ -2117,7 +2148,8 @@ detect_stuckness() {
|
|
|
2117
2148
|
|
|
2118
2149
|
if [[ -n "$lines1" && -n "$lines2" ]]; then
|
|
2119
2150
|
total=$(echo "$lines1" | wc -l | tr -d ' ')
|
|
2120
|
-
common=$(comm -12 <(echo "$lines1") <(echo "$lines2") 2>/dev/null | wc -l | tr -d ' ' ||
|
|
2151
|
+
common=$(comm -12 <(echo "$lines1") <(echo "$lines2") 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
2152
|
+
common="${common:-0}"
|
|
2121
2153
|
if [[ "$total" -gt 0 ]]; then
|
|
2122
2154
|
overlap_pct=$(( common * 100 / total ))
|
|
2123
2155
|
else
|
|
@@ -2189,7 +2221,8 @@ detect_stuckness() {
|
|
|
2189
2221
|
|
|
2190
2222
|
# Signal 6: Git diff size — no or minimal code changes (existing)
|
|
2191
2223
|
local diff_lines
|
|
2192
|
-
diff_lines=$(git -C "${PROJECT_ROOT:-.}" diff HEAD 2>/dev/null | wc -l | tr -d ' ' ||
|
|
2224
|
+
diff_lines=$(git -C "${PROJECT_ROOT:-.}" diff HEAD 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
2225
|
+
diff_lines="${diff_lines:-0}"
|
|
2193
2226
|
if [[ "${diff_lines:-0}" -lt 5 ]] && [[ "$iteration" -gt 2 ]]; then
|
|
2194
2227
|
stuckness_signals=$((stuckness_signals + 1))
|
|
2195
2228
|
stuckness_reasons+=("no code changes in last iteration")
|
|
@@ -2356,7 +2389,7 @@ compose_worker_prompt() {
|
|
|
2356
2389
|
role_desc="$recruit_desc"
|
|
2357
2390
|
fi
|
|
2358
2391
|
fi
|
|
2359
|
-
# Fallback to
|
|
2392
|
+
# Fallback to built-in role descriptions
|
|
2360
2393
|
if [[ -z "$role_desc" ]]; then
|
|
2361
2394
|
case "$role" in
|
|
2362
2395
|
builder) role_desc="Focus on implementation — writing code, fixing bugs, building features. You are the primary builder." ;;
|
|
@@ -2412,10 +2445,33 @@ run_claude_iteration() {
|
|
|
2412
2445
|
local final_prompt
|
|
2413
2446
|
final_prompt=$(manage_context_window "$prompt")
|
|
2414
2447
|
|
|
2448
|
+
local raw_prompt_chars=${#prompt}
|
|
2415
2449
|
local prompt_chars=${#final_prompt}
|
|
2416
2450
|
local approx_tokens=$((prompt_chars / 4))
|
|
2417
2451
|
info "Prompt: ~${approx_tokens} tokens (${prompt_chars} chars)"
|
|
2418
2452
|
|
|
2453
|
+
# Emit context efficiency metrics
|
|
2454
|
+
if type emit_event >/dev/null 2>&1; then
|
|
2455
|
+
local trim_ratio=0
|
|
2456
|
+
local budget_utilization=0
|
|
2457
|
+
if [[ "$raw_prompt_chars" -gt 0 ]]; then
|
|
2458
|
+
trim_ratio=$(awk -v raw="$raw_prompt_chars" -v trimmed="$prompt_chars" \
|
|
2459
|
+
'BEGIN { printf "%.1f", ((raw - trimmed) / raw) * 100 }')
|
|
2460
|
+
fi
|
|
2461
|
+
if [[ "${CONTEXT_BUDGET_CHARS:-0}" -gt 0 ]]; then
|
|
2462
|
+
budget_utilization=$(awk -v used="$prompt_chars" -v budget="${CONTEXT_BUDGET_CHARS}" \
|
|
2463
|
+
'BEGIN { printf "%.1f", (used / budget) * 100 }')
|
|
2464
|
+
fi
|
|
2465
|
+
emit_event "loop.context_efficiency" \
|
|
2466
|
+
"iteration=$ITERATION" \
|
|
2467
|
+
"raw_prompt_chars=$raw_prompt_chars" \
|
|
2468
|
+
"trimmed_prompt_chars=$prompt_chars" \
|
|
2469
|
+
"trim_ratio=$trim_ratio" \
|
|
2470
|
+
"budget_utilization=$budget_utilization" \
|
|
2471
|
+
"budget_chars=${CONTEXT_BUDGET_CHARS:-0}" \
|
|
2472
|
+
"job_id=${PIPELINE_JOB_ID:-loop-$$}" 2>/dev/null || true
|
|
2473
|
+
fi
|
|
2474
|
+
|
|
2419
2475
|
local flags
|
|
2420
2476
|
flags="$(build_claude_flags)"
|
|
2421
2477
|
|
package/scripts/sw-memory.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="3.
|
|
9
|
+
VERSION="3.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
12
12
|
|
|
@@ -725,7 +725,7 @@ memory_analyze_failure() {
|
|
|
725
725
|
"$failures_file" 2>/dev/null || true)
|
|
726
726
|
fi
|
|
727
727
|
|
|
728
|
-
# Build valid categories list (from compat.sh if available, else
|
|
728
|
+
# Build valid categories list (from compat.sh if available, else built-in defaults)
|
|
729
729
|
local valid_cats="test_failure, build_error, lint_error, timeout, dependency, flaky, config"
|
|
730
730
|
if [[ -n "${SW_ERROR_CATEGORIES:-}" ]]; then
|
|
731
731
|
valid_cats=$(echo "$SW_ERROR_CATEGORIES" | tr ' ' ', ')
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
set -euo pipefail
|
|
8
8
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
9
9
|
|
|
10
|
-
VERSION="3.
|
|
10
|
+
VERSION="3.2.0"
|
|
11
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
12
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
13
|
|
|
@@ -416,7 +416,8 @@ show_report() {
|
|
|
416
416
|
|
|
417
417
|
# Summary stats
|
|
418
418
|
local total_runs
|
|
419
|
-
total_runs=$(wc -l < "$MODEL_USAGE_LOG" ||
|
|
419
|
+
total_runs=$(wc -l < "$MODEL_USAGE_LOG" || true)
|
|
420
|
+
total_runs="${total_runs:-0}"
|
|
420
421
|
|
|
421
422
|
local haiku_runs
|
|
422
423
|
haiku_runs=$(grep -c '"model":"haiku"' "$MODEL_USAGE_LOG" || true)
|
package/scripts/sw-otel.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="3.
|
|
9
|
+
VERSION="3.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -77,6 +77,7 @@ cmd_metrics() {
|
|
|
77
77
|
|
|
78
78
|
# Parse events.jsonl
|
|
79
79
|
if [[ -f "$EVENTS_FILE" ]]; then
|
|
80
|
+
{
|
|
80
81
|
while IFS= read -r line; do
|
|
81
82
|
[[ -z "$line" ]] && continue
|
|
82
83
|
|
|
@@ -462,7 +463,8 @@ cmd_report() {
|
|
|
462
463
|
local last_event_ts=""
|
|
463
464
|
|
|
464
465
|
if [[ -f "$EVENTS_FILE" ]]; then
|
|
465
|
-
event_count=$(wc -l < "$EVENTS_FILE" ||
|
|
466
|
+
event_count=$(wc -l < "$EVENTS_FILE" || true)
|
|
467
|
+
event_count="${event_count:-0}"
|
|
466
468
|
export_count=$(grep -c '"type":"otel_export"' "$EVENTS_FILE" 2>/dev/null || true)
|
|
467
469
|
export_count="${export_count:-0}"
|
|
468
470
|
webhook_count=$(grep -c '"type":"webhook_sent"' "$EVENTS_FILE" 2>/dev/null || true)
|
package/scripts/sw-oversight.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="3.
|
|
9
|
+
VERSION="3.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -17,6 +17,8 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
17
17
|
# Canonical helpers (colors, output, events)
|
|
18
18
|
# shellcheck source=lib/helpers.sh
|
|
19
19
|
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
20
|
+
# shellcheck source=lib/config.sh
|
|
21
|
+
[[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
|
|
20
22
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
21
23
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
22
24
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -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="3.
|
|
9
|
+
VERSION="3.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -150,7 +150,8 @@ _compute_convergence() {
|
|
|
150
150
|
fi
|
|
151
151
|
|
|
152
152
|
local total_errors
|
|
153
|
-
total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' ||
|
|
153
|
+
total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
|
|
154
|
+
total_errors="${total_errors:-0}"
|
|
154
155
|
total_errors=$(_safe_num "$total_errors")
|
|
155
156
|
|
|
156
157
|
if [[ "$total_errors" -eq 0 ]]; then
|
|
@@ -270,7 +271,8 @@ _compute_error_maturity() {
|
|
|
270
271
|
fi
|
|
271
272
|
|
|
272
273
|
local total_errors
|
|
273
|
-
total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' ||
|
|
274
|
+
total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
|
|
275
|
+
total_errors="${total_errors:-0}"
|
|
274
276
|
total_errors=$(_safe_num "$total_errors")
|
|
275
277
|
|
|
276
278
|
if [[ "$total_errors" -eq 0 ]]; then
|
|
@@ -280,7 +282,8 @@ _compute_error_maturity() {
|
|
|
280
282
|
|
|
281
283
|
# Count unique error signatures
|
|
282
284
|
local unique_errors
|
|
283
|
-
unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' ||
|
|
285
|
+
unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || true)
|
|
286
|
+
unique_errors="${unique_errors:-0}"
|
|
284
287
|
unique_errors=$(_safe_num "$unique_errors")
|
|
285
288
|
|
|
286
289
|
if [[ "$unique_errors" -eq 0 ]]; then
|
|
@@ -507,8 +510,10 @@ pipeline_compute_vitals() {
|
|
|
507
510
|
# ── Error counts ──
|
|
508
511
|
local total_errors=0 unique_errors=0
|
|
509
512
|
if [[ -f "$error_log" ]]; then
|
|
510
|
-
total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' ||
|
|
511
|
-
|
|
513
|
+
total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
|
|
514
|
+
total_errors="${total_errors:-0}"
|
|
515
|
+
unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || true)
|
|
516
|
+
unique_errors="${unique_errors:-0}"
|
|
512
517
|
fi
|
|
513
518
|
|
|
514
519
|
# ── Output JSON ──
|
package/scripts/sw-pipeline.sh
CHANGED
|
@@ -10,8 +10,9 @@ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
|
10
10
|
unset CLAUDECODE 2>/dev/null || true
|
|
11
11
|
# Ignore SIGHUP so tmux attach/detach doesn't kill long-running plan/design/review stages
|
|
12
12
|
trap '' HUP
|
|
13
|
+
trap '' SIGPIPE
|
|
13
14
|
|
|
14
|
-
VERSION="3.
|
|
15
|
+
VERSION="3.2.0"
|
|
15
16
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
17
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
18
|
|
|
@@ -149,7 +150,8 @@ rotate_event_log_if_needed() {
|
|
|
149
150
|
local max_lines=10000
|
|
150
151
|
[[ ! -f "$events_file" ]] && return
|
|
151
152
|
local lines
|
|
152
|
-
lines=$(wc -l < "$events_file" 2>/dev/null ||
|
|
153
|
+
lines=$(wc -l < "$events_file" 2>/dev/null || true)
|
|
154
|
+
lines="${lines:-0}"
|
|
153
155
|
if [[ "$lines" -gt "$max_lines" ]]; then
|
|
154
156
|
local tmp="${events_file}.rotating"
|
|
155
157
|
if tail -5000 "$events_file" > "$tmp" 2>/dev/null && mv "$tmp" "$events_file" 2>/dev/null; then
|
|
@@ -506,14 +508,16 @@ load_pipeline_config() {
|
|
|
506
508
|
# Check for intelligence-composed pipeline first
|
|
507
509
|
local composed_pipeline="${ARTIFACTS_DIR}/composed-pipeline.json"
|
|
508
510
|
if [[ -f "$composed_pipeline" ]] && type composer_validate_pipeline >/dev/null 2>&1; then
|
|
509
|
-
# Use composed pipeline if fresh (
|
|
511
|
+
# Use composed pipeline if fresh (within cache TTL)
|
|
512
|
+
local composed_cache_ttl
|
|
513
|
+
composed_cache_ttl=$(_config_get_int "pipeline.composed_cache_ttl" 3600 2>/dev/null || echo 3600)
|
|
510
514
|
local composed_age=99999
|
|
511
515
|
local composed_mtime
|
|
512
516
|
composed_mtime=$(file_mtime "$composed_pipeline")
|
|
513
517
|
if [[ "$composed_mtime" -gt 0 ]]; then
|
|
514
518
|
composed_age=$(( $(now_epoch) - composed_mtime ))
|
|
515
519
|
fi
|
|
516
|
-
if [[ "$composed_age" -lt
|
|
520
|
+
if [[ "$composed_age" -lt "$composed_cache_ttl" ]]; then
|
|
517
521
|
local validate_json
|
|
518
522
|
validate_json=$(cat "$composed_pipeline" 2>/dev/null || echo "")
|
|
519
523
|
if [[ -n "$validate_json" ]] && composer_validate_pipeline "$validate_json" 2>/dev/null; then
|
|
@@ -1791,10 +1795,14 @@ pipeline_cleanup_worktree() {
|
|
|
1791
1795
|
# Extract branch name before removing worktree
|
|
1792
1796
|
local _wt_branch=""
|
|
1793
1797
|
_wt_branch=$(git worktree list --porcelain 2>/dev/null | grep -A1 "worktree ${worktree_path}$" | grep "^branch " | sed 's|^branch refs/heads/||' || true)
|
|
1794
|
-
git worktree remove --force "$worktree_path" 2>/dev/null
|
|
1798
|
+
if ! git worktree remove --force "$worktree_path" 2>/dev/null; then
|
|
1799
|
+
warn "Failed to remove worktree at ${worktree_path} — may need manual cleanup"
|
|
1800
|
+
fi
|
|
1795
1801
|
# Clean up the local branch
|
|
1796
1802
|
if [[ -n "$_wt_branch" ]]; then
|
|
1797
|
-
git branch -D "$_wt_branch" 2>/dev/null
|
|
1803
|
+
if ! git branch -D "$_wt_branch" 2>/dev/null; then
|
|
1804
|
+
warn "Failed to delete local branch ${_wt_branch}"
|
|
1805
|
+
fi
|
|
1798
1806
|
fi
|
|
1799
1807
|
# Clean up the remote branch (if it was pushed)
|
|
1800
1808
|
if [[ -n "$_wt_branch" && "${NO_GITHUB:-}" != "true" ]]; then
|
|
@@ -2258,11 +2266,15 @@ pipeline_start() {
|
|
|
2258
2266
|
if [[ -z "$GIT_BRANCH" ]]; then
|
|
2259
2267
|
local ci_branch="ci/issue-${ISSUE_NUMBER}"
|
|
2260
2268
|
info "CI resume: creating branch ${ci_branch} from current HEAD"
|
|
2261
|
-
git checkout -b "$ci_branch" 2>/dev/null
|
|
2269
|
+
if ! git checkout -b "$ci_branch" 2>/dev/null && ! git checkout "$ci_branch" 2>/dev/null; then
|
|
2270
|
+
warn "CI resume: failed to create or checkout branch ${ci_branch}"
|
|
2271
|
+
fi
|
|
2262
2272
|
GIT_BRANCH="$ci_branch"
|
|
2263
2273
|
elif [[ "$(git branch --show-current 2>/dev/null)" != "$GIT_BRANCH" ]]; then
|
|
2264
2274
|
info "CI resume: checking out branch ${GIT_BRANCH}"
|
|
2265
|
-
git checkout -b "$GIT_BRANCH" 2>/dev/null
|
|
2275
|
+
if ! git checkout -b "$GIT_BRANCH" 2>/dev/null && ! git checkout "$GIT_BRANCH" 2>/dev/null; then
|
|
2276
|
+
warn "CI resume: failed to create or checkout branch ${GIT_BRANCH}"
|
|
2277
|
+
fi
|
|
2266
2278
|
fi
|
|
2267
2279
|
write_state 2>/dev/null || true
|
|
2268
2280
|
fi
|
package/scripts/sw-pm.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="3.
|
|
9
|
+
VERSION="3.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
@@ -121,7 +121,8 @@ analyze_issue() {
|
|
|
121
121
|
# Count estimated files affected by analyzing body content
|
|
122
122
|
local file_scope complexity risk estimated_hours
|
|
123
123
|
local files_mentioned
|
|
124
|
-
files_mentioned=$(echo "$body" | grep -o '\b[a-zA-Z0-9_.-]*\.[a-z]*' | sort -u | wc -l ||
|
|
124
|
+
files_mentioned=$(echo "$body" | grep -o '\b[a-zA-Z0-9_.-]*\.[a-z]*' | sort -u | wc -l || true)
|
|
125
|
+
files_mentioned="${files_mentioned:-0}"
|
|
125
126
|
files_mentioned=$((files_mentioned + 1)) # At least 1 file
|
|
126
127
|
|
|
127
128
|
# Determine file scope
|
|
@@ -203,7 +204,7 @@ analyze_issue() {
|
|
|
203
204
|
|
|
204
205
|
# ─── recommend_team <analysis_json> ──────────────────────────────────────────
|
|
205
206
|
# Based on analysis, recommend team composition
|
|
206
|
-
# Tries recruit's AI/heuristic team composition first, falls back to
|
|
207
|
+
# Tries recruit's AI/heuristic team composition first, falls back to built-in rules.
|
|
207
208
|
recommend_team() {
|
|
208
209
|
local analysis="$1"
|
|
209
210
|
|
|
@@ -253,7 +254,7 @@ recommend_team() {
|
|
|
253
254
|
fi
|
|
254
255
|
fi
|
|
255
256
|
|
|
256
|
-
# ── Fallback:
|
|
257
|
+
# ── Fallback: heuristic team composition ──
|
|
257
258
|
local complexity risk is_security is_perf file_scope
|
|
258
259
|
complexity=$(echo "$analysis" | jq -r '.complexity')
|
|
259
260
|
risk=$(echo "$analysis" | jq -r '.risk')
|
package/scripts/sw-predictive.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="3.
|
|
9
|
+
VERSION="3.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -17,6 +17,8 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
17
17
|
# Canonical helpers (colors, output, events)
|
|
18
18
|
# shellcheck source=lib/helpers.sh
|
|
19
19
|
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
20
|
+
# shellcheck source=lib/config.sh
|
|
21
|
+
[[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
|
|
20
22
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
21
23
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
22
24
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -384,10 +386,12 @@ Return JSON format:
|
|
|
384
386
|
fi
|
|
385
387
|
|
|
386
388
|
# Fallback: heuristic risk assessment
|
|
387
|
-
local
|
|
389
|
+
local default_risk
|
|
390
|
+
default_risk=$(_config_get_int "predictive.default_risk_score" 50 2>/dev/null || echo 50)
|
|
391
|
+
local risk=$default_risk
|
|
388
392
|
local reason="Default medium risk — no AI analysis available"
|
|
389
393
|
|
|
390
|
-
# Check for learned keyword weights first, fall back to
|
|
394
|
+
# Check for learned keyword weights first, fall back to config defaults
|
|
391
395
|
local keywords_json
|
|
392
396
|
keywords_json=$(_predictive_get_risk_keywords)
|
|
393
397
|
|
|
@@ -416,9 +420,11 @@ Return JSON format:
|
|
|
416
420
|
reason="Learned keyword weights: ${matched_keywords%%, }"
|
|
417
421
|
fi
|
|
418
422
|
else
|
|
419
|
-
#
|
|
423
|
+
# Config-driven keyword risk elevation
|
|
424
|
+
local keyword_risk
|
|
425
|
+
keyword_risk=$(_config_get_int "predictive.keyword_risk_score" 70 2>/dev/null || echo 70)
|
|
420
426
|
if echo "$issue_json" | grep -qiE "refactor|migration|breaking|security|deploy"; then
|
|
421
|
-
risk
|
|
427
|
+
risk=$keyword_risk
|
|
422
428
|
reason="Keywords suggest elevated complexity"
|
|
423
429
|
fi
|
|
424
430
|
fi
|
package/scripts/sw-prep.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="3.
|
|
9
|
+
VERSION="3.2.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Handle subcommands ───────────────────────────────────────────────────────
|
package/scripts/sw-ps.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ║ Displays a table of agents running in claude-* tmux windows with ║
|
|
6
6
|
# ║ PID, status, idle time, and pane references. ║
|
|
7
7
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
-
VERSION="3.
|
|
8
|
+
VERSION="3.2.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|