shipwright-cli 3.1.0 → 3.3.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/code-reviewer.md +2 -0
- package/.claude/agents/devops-engineer.md +2 -0
- package/.claude/agents/doc-fleet-agent.md +2 -0
- package/.claude/agents/pipeline-agent.md +2 -0
- package/.claude/agents/shell-script-specialist.md +2 -0
- package/.claude/agents/test-specialist.md +2 -0
- package/.claude/hooks/agent-crash-capture.sh +32 -0
- package/.claude/hooks/post-tool-use.sh +3 -2
- package/.claude/hooks/pre-tool-use.sh +35 -3
- package/README.md +22 -8
- package/claude-code/hooks/config-change.sh +18 -0
- package/claude-code/hooks/instructions-reloaded.sh +7 -0
- package/claude-code/hooks/worktree-create.sh +25 -0
- package/claude-code/hooks/worktree-remove.sh +20 -0
- package/config/code-constitution.json +130 -0
- package/config/defaults.json +25 -2
- package/config/policy.json +1 -1
- package/dashboard/middleware/auth.ts +134 -0
- package/dashboard/middleware/constants.ts +21 -0
- package/dashboard/public/index.html +8 -6
- package/dashboard/public/styles.css +176 -97
- package/dashboard/routes/auth.ts +38 -0
- package/dashboard/server.ts +117 -25
- package/dashboard/services/config.ts +26 -0
- package/dashboard/services/db.ts +118 -0
- package/dashboard/src/canvas/pixel-agent.ts +298 -0
- package/dashboard/src/canvas/pixel-sprites.ts +440 -0
- package/dashboard/src/canvas/shipyard-effects.ts +367 -0
- package/dashboard/src/canvas/shipyard-scene.ts +616 -0
- package/dashboard/src/canvas/submarine-layout.ts +267 -0
- package/dashboard/src/components/header.ts +8 -7
- package/dashboard/src/core/api.ts +5 -0
- package/dashboard/src/core/router.ts +1 -0
- package/dashboard/src/design/submarine-theme.ts +253 -0
- package/dashboard/src/main.ts +2 -0
- package/dashboard/src/types/api.ts +12 -1
- package/dashboard/src/views/activity.ts +2 -1
- package/dashboard/src/views/metrics.ts +69 -1
- package/dashboard/src/views/shipyard.ts +39 -0
- package/dashboard/types/index.ts +166 -0
- package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
- package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
- package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
- package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
- package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
- package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
- package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
- package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
- package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
- package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
- package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
- package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
- package/docs/research/RESEARCH_INDEX.md +439 -0
- package/docs/research/RESEARCH_SOURCES.md +440 -0
- package/docs/research/RESEARCH_SUMMARY.txt +275 -0
- package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
- package/package.json +2 -2
- package/scripts/lib/adaptive-model.sh +427 -0
- package/scripts/lib/adaptive-timeout.sh +316 -0
- package/scripts/lib/audit-trail.sh +309 -0
- package/scripts/lib/auto-recovery.sh +471 -0
- package/scripts/lib/bandit-selector.sh +431 -0
- package/scripts/lib/bootstrap.sh +104 -2
- package/scripts/lib/causal-graph.sh +455 -0
- package/scripts/lib/compat.sh +126 -0
- package/scripts/lib/compound-audit.sh +337 -0
- package/scripts/lib/constitutional.sh +454 -0
- package/scripts/lib/context-budget.sh +359 -0
- package/scripts/lib/convergence.sh +594 -0
- package/scripts/lib/cost-optimizer.sh +634 -0
- package/scripts/lib/daemon-adaptive.sh +14 -2
- package/scripts/lib/daemon-dispatch.sh +106 -17
- package/scripts/lib/daemon-failure.sh +34 -4
- package/scripts/lib/daemon-patrol.sh +25 -4
- package/scripts/lib/daemon-poll-github.sh +361 -0
- package/scripts/lib/daemon-poll-health.sh +299 -0
- package/scripts/lib/daemon-poll.sh +27 -611
- package/scripts/lib/daemon-state.sh +119 -66
- package/scripts/lib/daemon-triage.sh +10 -0
- package/scripts/lib/dod-scorecard.sh +442 -0
- package/scripts/lib/error-actionability.sh +300 -0
- package/scripts/lib/formal-spec.sh +461 -0
- package/scripts/lib/helpers.sh +180 -5
- package/scripts/lib/intent-analysis.sh +409 -0
- package/scripts/lib/loop-convergence.sh +350 -0
- package/scripts/lib/loop-iteration.sh +682 -0
- package/scripts/lib/loop-progress.sh +48 -0
- package/scripts/lib/loop-restart.sh +185 -0
- package/scripts/lib/memory-effectiveness.sh +506 -0
- package/scripts/lib/mutation-executor.sh +352 -0
- package/scripts/lib/outcome-feedback.sh +521 -0
- package/scripts/lib/pipeline-cli.sh +336 -0
- package/scripts/lib/pipeline-commands.sh +1216 -0
- package/scripts/lib/pipeline-detection.sh +101 -3
- package/scripts/lib/pipeline-execution.sh +897 -0
- package/scripts/lib/pipeline-github.sh +28 -3
- package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
- package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
- package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
- package/scripts/lib/pipeline-intelligence.sh +104 -1138
- package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
- package/scripts/lib/pipeline-quality-checks.sh +17 -711
- package/scripts/lib/pipeline-quality-gates.sh +563 -0
- package/scripts/lib/pipeline-stages-build.sh +730 -0
- package/scripts/lib/pipeline-stages-delivery.sh +965 -0
- package/scripts/lib/pipeline-stages-intake.sh +1133 -0
- package/scripts/lib/pipeline-stages-monitor.sh +407 -0
- package/scripts/lib/pipeline-stages-review.sh +1022 -0
- package/scripts/lib/pipeline-stages.sh +161 -2901
- package/scripts/lib/pipeline-state.sh +36 -5
- package/scripts/lib/pipeline-util.sh +487 -0
- package/scripts/lib/policy-learner.sh +438 -0
- package/scripts/lib/process-reward.sh +493 -0
- package/scripts/lib/project-detect.sh +649 -0
- package/scripts/lib/quality-profile.sh +334 -0
- package/scripts/lib/recruit-commands.sh +885 -0
- package/scripts/lib/recruit-learning.sh +739 -0
- package/scripts/lib/recruit-roles.sh +648 -0
- package/scripts/lib/reward-aggregator.sh +458 -0
- package/scripts/lib/rl-optimizer.sh +362 -0
- package/scripts/lib/root-cause.sh +427 -0
- package/scripts/lib/scope-enforcement.sh +445 -0
- package/scripts/lib/session-restart.sh +493 -0
- package/scripts/lib/skill-memory.sh +300 -0
- package/scripts/lib/skill-registry.sh +775 -0
- package/scripts/lib/spec-driven.sh +476 -0
- package/scripts/lib/test-helpers.sh +18 -7
- package/scripts/lib/test-holdout.sh +429 -0
- package/scripts/lib/test-optimizer.sh +511 -0
- package/scripts/shipwright-file-suggest.sh +45 -0
- package/scripts/skills/adversarial-quality.md +61 -0
- package/scripts/skills/api-design.md +44 -0
- package/scripts/skills/architecture-design.md +50 -0
- package/scripts/skills/brainstorming.md +43 -0
- package/scripts/skills/data-pipeline.md +44 -0
- package/scripts/skills/deploy-safety.md +64 -0
- package/scripts/skills/documentation.md +38 -0
- package/scripts/skills/frontend-design.md +45 -0
- package/scripts/skills/generated/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
- package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
- package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
- package/scripts/skills/generated/cli-version-management.md +29 -0
- package/scripts/skills/generated/collection-system-validation.md +99 -0
- package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
- package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
- package/scripts/skills/generated/test-parallelization-detection.md +65 -0
- package/scripts/skills/observability.md +79 -0
- package/scripts/skills/performance.md +48 -0
- package/scripts/skills/pr-quality.md +49 -0
- package/scripts/skills/product-thinking.md +43 -0
- package/scripts/skills/security-audit.md +49 -0
- package/scripts/skills/systematic-debugging.md +40 -0
- package/scripts/skills/testing-strategy.md +47 -0
- package/scripts/skills/two-stage-review.md +52 -0
- package/scripts/skills/validation-thoroughness.md +55 -0
- package/scripts/sw +9 -3
- package/scripts/sw-activity.sh +9 -8
- package/scripts/sw-adaptive.sh +8 -7
- package/scripts/sw-adversarial.sh +2 -1
- package/scripts/sw-architecture-enforcer.sh +3 -1
- package/scripts/sw-auth.sh +12 -2
- package/scripts/sw-autonomous.sh +5 -1
- package/scripts/sw-changelog.sh +4 -1
- package/scripts/sw-checkpoint.sh +2 -1
- package/scripts/sw-ci.sh +15 -6
- package/scripts/sw-cleanup.sh +4 -26
- package/scripts/sw-code-review.sh +45 -20
- package/scripts/sw-connect.sh +2 -1
- package/scripts/sw-context.sh +2 -1
- package/scripts/sw-cost.sh +107 -5
- package/scripts/sw-daemon.sh +71 -11
- package/scripts/sw-dashboard.sh +3 -1
- package/scripts/sw-db.sh +71 -20
- package/scripts/sw-decide.sh +8 -2
- package/scripts/sw-decompose.sh +360 -17
- package/scripts/sw-deps.sh +4 -1
- package/scripts/sw-developer-simulation.sh +4 -1
- package/scripts/sw-discovery.sh +378 -5
- package/scripts/sw-doc-fleet.sh +4 -1
- package/scripts/sw-docs-agent.sh +3 -1
- package/scripts/sw-docs.sh +2 -1
- package/scripts/sw-doctor.sh +453 -2
- package/scripts/sw-dora.sh +4 -1
- package/scripts/sw-durable.sh +12 -7
- package/scripts/sw-e2e-orchestrator.sh +17 -16
- package/scripts/sw-eventbus.sh +13 -4
- package/scripts/sw-evidence.sh +364 -12
- package/scripts/sw-feedback.sh +550 -9
- package/scripts/sw-fix.sh +20 -1
- package/scripts/sw-fleet-discover.sh +6 -2
- package/scripts/sw-fleet-viz.sh +9 -4
- package/scripts/sw-fleet.sh +5 -1
- package/scripts/sw-github-app.sh +18 -4
- package/scripts/sw-github-checks.sh +3 -2
- package/scripts/sw-github-deploy.sh +3 -2
- package/scripts/sw-github-graphql.sh +18 -7
- package/scripts/sw-guild.sh +5 -1
- package/scripts/sw-heartbeat.sh +5 -30
- package/scripts/sw-hello.sh +67 -0
- package/scripts/sw-hygiene.sh +10 -3
- package/scripts/sw-incident.sh +273 -5
- package/scripts/sw-init.sh +18 -2
- package/scripts/sw-instrument.sh +10 -2
- package/scripts/sw-intelligence.sh +44 -7
- package/scripts/sw-jira.sh +5 -1
- package/scripts/sw-launchd.sh +2 -1
- package/scripts/sw-linear.sh +4 -1
- package/scripts/sw-logs.sh +4 -1
- package/scripts/sw-loop.sh +436 -1076
- package/scripts/sw-memory.sh +357 -3
- package/scripts/sw-mission-control.sh +6 -1
- package/scripts/sw-model-router.sh +483 -27
- package/scripts/sw-otel.sh +15 -4
- package/scripts/sw-oversight.sh +14 -5
- package/scripts/sw-patrol-meta.sh +334 -0
- package/scripts/sw-pipeline-composer.sh +7 -1
- package/scripts/sw-pipeline-vitals.sh +12 -6
- package/scripts/sw-pipeline.sh +54 -2653
- package/scripts/sw-pm.sh +16 -8
- package/scripts/sw-pr-lifecycle.sh +2 -1
- package/scripts/sw-predictive.sh +17 -5
- package/scripts/sw-prep.sh +185 -2
- package/scripts/sw-ps.sh +5 -25
- package/scripts/sw-public-dashboard.sh +17 -4
- package/scripts/sw-quality.sh +14 -6
- package/scripts/sw-reaper.sh +8 -25
- package/scripts/sw-recruit.sh +156 -2303
- package/scripts/sw-regression.sh +19 -12
- package/scripts/sw-release-manager.sh +3 -1
- package/scripts/sw-release.sh +4 -1
- package/scripts/sw-remote.sh +3 -1
- package/scripts/sw-replay.sh +7 -1
- package/scripts/sw-retro.sh +158 -1
- package/scripts/sw-review-rerun.sh +3 -1
- package/scripts/sw-scale.sh +14 -5
- package/scripts/sw-security-audit.sh +6 -1
- package/scripts/sw-self-optimize.sh +173 -6
- package/scripts/sw-session.sh +9 -3
- package/scripts/sw-setup.sh +3 -1
- package/scripts/sw-stall-detector.sh +406 -0
- package/scripts/sw-standup.sh +15 -7
- package/scripts/sw-status.sh +3 -1
- package/scripts/sw-strategic.sh +14 -6
- package/scripts/sw-stream.sh +13 -4
- package/scripts/sw-swarm.sh +20 -7
- package/scripts/sw-team-stages.sh +13 -6
- package/scripts/sw-templates.sh +7 -31
- package/scripts/sw-testgen.sh +17 -6
- package/scripts/sw-tmux-pipeline.sh +4 -1
- package/scripts/sw-tmux-role-color.sh +2 -0
- package/scripts/sw-tmux-status.sh +1 -1
- package/scripts/sw-tmux.sh +37 -1
- package/scripts/sw-trace.sh +3 -1
- package/scripts/sw-tracker-github.sh +3 -0
- package/scripts/sw-tracker-jira.sh +3 -0
- package/scripts/sw-tracker-linear.sh +3 -0
- package/scripts/sw-tracker.sh +3 -1
- package/scripts/sw-triage.sh +3 -2
- package/scripts/sw-upgrade.sh +3 -1
- package/scripts/sw-ux.sh +5 -2
- package/scripts/sw-webhook.sh +5 -2
- package/scripts/sw-widgets.sh +9 -4
- package/scripts/sw-worktree.sh +15 -3
- package/scripts/test-skill-injection.sh +1233 -0
- package/templates/pipelines/autonomous.json +27 -3
- package/templates/pipelines/cost-aware.json +34 -8
- package/templates/pipelines/deployed.json +12 -0
- package/templates/pipelines/enterprise.json +12 -0
- package/templates/pipelines/fast.json +6 -0
- package/templates/pipelines/full.json +27 -3
- package/templates/pipelines/hotfix.json +6 -0
- package/templates/pipelines/standard.json +12 -0
- package/templates/pipelines/tdd.json +12 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Module guard - prevent double-sourcing
|
|
3
|
+
[[ -n "${_CAUSAL_GRAPH_LOADED:-}" ]] && return 0
|
|
4
|
+
_CAUSAL_GRAPH_LOADED=1
|
|
5
|
+
|
|
6
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
7
|
+
# ║ shipwright causal-graph — Causal Dependency Graph for Root-Cause ║
|
|
8
|
+
# ║ Builds entity-relationship graph from pipeline context ║
|
|
9
|
+
# ║ Traces failure chains: test → function → variable → config ║
|
|
10
|
+
# ║ Enables causal debugging instead of blind pattern matching ║
|
|
11
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
12
|
+
|
|
13
|
+
# shellcheck disable=SC2034
|
|
14
|
+
VERSION="3.3.0"
|
|
15
|
+
|
|
16
|
+
# ─── Output Helpers ──────────────────────────────────────────────────────────
|
|
17
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
18
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
19
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
20
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
21
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
22
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# ─── Configuration ───────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
CAUSAL_GRAPH_FILE="${CAUSAL_GRAPH_FILE:-.claude/causal-graph.json}"
|
|
28
|
+
CAUSAL_MAX_DEPTH="${CAUSAL_MAX_DEPTH:-5}"
|
|
29
|
+
|
|
30
|
+
# ─── Node ID Generation ─────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
_causal_node_id() {
|
|
33
|
+
local type="$1" name="$2"
|
|
34
|
+
printf '%s:%s' "$type" "$name"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ─── Build Graph ─────────────────────────────────────────────────────────────
|
|
38
|
+
# Build entity-relationship graph from current pipeline state.
|
|
39
|
+
# Analyzes: changed files, functions defined/called, test files, configs.
|
|
40
|
+
|
|
41
|
+
causal_build_graph() {
|
|
42
|
+
local project_dir="${1:-.}"
|
|
43
|
+
local base_ref="${2:-HEAD~1}"
|
|
44
|
+
|
|
45
|
+
info "Building causal dependency graph..."
|
|
46
|
+
|
|
47
|
+
# Get changed files
|
|
48
|
+
local changed_files
|
|
49
|
+
changed_files=$(git -C "$project_dir" diff --name-only "$base_ref" 2>/dev/null || true)
|
|
50
|
+
|
|
51
|
+
if [[ -z "$changed_files" ]]; then
|
|
52
|
+
changed_files=$(git -C "$project_dir" diff --cached --name-only 2>/dev/null || true)
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
if [[ -z "$changed_files" ]]; then
|
|
56
|
+
warn "No changed files detected — building minimal graph"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
local nodes=""
|
|
60
|
+
local edges=""
|
|
61
|
+
local node_count=0
|
|
62
|
+
local edge_count=0
|
|
63
|
+
|
|
64
|
+
# ─── Pass 1: Create file nodes ───────────────────────────────────────
|
|
65
|
+
while IFS= read -r file; do
|
|
66
|
+
[[ -z "$file" ]] && continue
|
|
67
|
+
[[ ! -f "${project_dir}/${file}" ]] && continue
|
|
68
|
+
|
|
69
|
+
local file_type="source"
|
|
70
|
+
if echo "$file" | grep -qiE '(test|spec)'; then
|
|
71
|
+
file_type="test"
|
|
72
|
+
elif echo "$file" | grep -qiE '(config|\.json$|\.ya?ml$|\.toml$|\.env)'; then
|
|
73
|
+
file_type="config"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
local node
|
|
77
|
+
node=$(printf '{"id":"file:%s","type":"file","subtype":"%s","name":"%s"}' \
|
|
78
|
+
"$file" "$file_type" "$file")
|
|
79
|
+
|
|
80
|
+
if [[ -n "$nodes" ]]; then
|
|
81
|
+
nodes="${nodes},${node}"
|
|
82
|
+
else
|
|
83
|
+
nodes="${node}"
|
|
84
|
+
fi
|
|
85
|
+
node_count=$((node_count + 1))
|
|
86
|
+
done <<< "$changed_files"
|
|
87
|
+
|
|
88
|
+
# ─── Pass 2: Extract functions from changed files ────────────────────
|
|
89
|
+
while IFS= read -r file; do
|
|
90
|
+
[[ -z "$file" ]] && continue
|
|
91
|
+
[[ ! -f "${project_dir}/${file}" ]] && continue
|
|
92
|
+
|
|
93
|
+
local ext="${file##*.}"
|
|
94
|
+
local func_pattern=""
|
|
95
|
+
|
|
96
|
+
case "$ext" in
|
|
97
|
+
sh|bash)
|
|
98
|
+
func_pattern='^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)'
|
|
99
|
+
;;
|
|
100
|
+
ts|js|tsx|jsx)
|
|
101
|
+
func_pattern='(function\s+[a-zA-Z_]\w*|const\s+[a-zA-Z_]\w*\s*=\s*(async\s+)?\(|export\s+(async\s+)?function)'
|
|
102
|
+
;;
|
|
103
|
+
py)
|
|
104
|
+
func_pattern='^\s*def\s+[a-zA-Z_]\w*'
|
|
105
|
+
;;
|
|
106
|
+
go)
|
|
107
|
+
func_pattern='^\s*func\s+[a-zA-Z_]\w*'
|
|
108
|
+
;;
|
|
109
|
+
rs)
|
|
110
|
+
func_pattern='^\s*(pub\s+)?fn\s+[a-zA-Z_]\w*'
|
|
111
|
+
;;
|
|
112
|
+
*)
|
|
113
|
+
continue
|
|
114
|
+
;;
|
|
115
|
+
esac
|
|
116
|
+
|
|
117
|
+
local funcs
|
|
118
|
+
funcs=$(grep -oE "$func_pattern" "${project_dir}/${file}" 2>/dev/null | \
|
|
119
|
+
sed 's/.*function\s\+//' | sed 's/.*def\s\+//' | sed 's/.*func\s\+//' | \
|
|
120
|
+
sed 's/.*fn\s\+//' | sed 's/.*const\s\+//' | \
|
|
121
|
+
sed 's/\s*[=(].*//' | tr -d '(){}' | sort -u | head -20 || true)
|
|
122
|
+
|
|
123
|
+
while IFS= read -r func; do
|
|
124
|
+
[[ -z "$func" ]] && continue
|
|
125
|
+
func=$(echo "$func" | tr -d ' ')
|
|
126
|
+
[[ -z "$func" ]] && continue
|
|
127
|
+
|
|
128
|
+
local node
|
|
129
|
+
node=$(printf '{"id":"func:%s:%s","type":"function","name":"%s","file":"%s"}' \
|
|
130
|
+
"$file" "$func" "$func" "$file")
|
|
131
|
+
nodes="${nodes},${node}"
|
|
132
|
+
node_count=$((node_count + 1))
|
|
133
|
+
|
|
134
|
+
# Edge: file contains function
|
|
135
|
+
local edge
|
|
136
|
+
edge=$(printf '{"from":"file:%s","to":"func:%s:%s","type":"defines"}' \
|
|
137
|
+
"$file" "$file" "$func")
|
|
138
|
+
if [[ -n "$edges" ]]; then
|
|
139
|
+
edges="${edges},${edge}"
|
|
140
|
+
else
|
|
141
|
+
edges="${edge}"
|
|
142
|
+
fi
|
|
143
|
+
edge_count=$((edge_count + 1))
|
|
144
|
+
done <<< "$funcs"
|
|
145
|
+
|
|
146
|
+
done <<< "$changed_files"
|
|
147
|
+
|
|
148
|
+
# ─── Pass 3: Find cross-file dependencies ───────────────────────────
|
|
149
|
+
while IFS= read -r file; do
|
|
150
|
+
[[ -z "$file" ]] && continue
|
|
151
|
+
[[ ! -f "${project_dir}/${file}" ]] && continue
|
|
152
|
+
|
|
153
|
+
# Find imports/requires/sources from this file
|
|
154
|
+
local imports
|
|
155
|
+
imports=$(grep -oE "(import|require|source|from)\s+['\"]([^'\"]+)['\"]" "${project_dir}/${file}" 2>/dev/null | \
|
|
156
|
+
sed "s/.*['\"]//;s/['\"].*//" | head -20 || true)
|
|
157
|
+
|
|
158
|
+
while IFS= read -r imp; do
|
|
159
|
+
[[ -z "$imp" ]] && continue
|
|
160
|
+
|
|
161
|
+
# Check if imported file is in our changed set
|
|
162
|
+
local match
|
|
163
|
+
match=$(echo "$changed_files" | grep -F "$imp" | head -1 || true)
|
|
164
|
+
|
|
165
|
+
if [[ -n "$match" ]]; then
|
|
166
|
+
local edge
|
|
167
|
+
edge=$(printf '{"from":"file:%s","to":"file:%s","type":"imports"}' "$file" "$match")
|
|
168
|
+
if [[ -n "$edges" ]]; then
|
|
169
|
+
edges="${edges},${edge}"
|
|
170
|
+
else
|
|
171
|
+
edges="${edge}"
|
|
172
|
+
fi
|
|
173
|
+
edge_count=$((edge_count + 1))
|
|
174
|
+
fi
|
|
175
|
+
done <<< "$imports"
|
|
176
|
+
|
|
177
|
+
done <<< "$changed_files"
|
|
178
|
+
|
|
179
|
+
# ─── Pass 4: Find test → source relationships ───────────────────────
|
|
180
|
+
while IFS= read -r file; do
|
|
181
|
+
[[ -z "$file" ]] && continue
|
|
182
|
+
if ! echo "$file" | grep -qiE '(test|spec)'; then
|
|
183
|
+
continue
|
|
184
|
+
fi
|
|
185
|
+
[[ ! -f "${project_dir}/${file}" ]] && continue
|
|
186
|
+
|
|
187
|
+
# Test files typically import/source the file they test
|
|
188
|
+
local tested_files
|
|
189
|
+
tested_files=$(grep -oE "(import|require|source)\s+['\"]([^'\"]+)['\"]" "${project_dir}/${file}" 2>/dev/null | \
|
|
190
|
+
sed "s/.*['\"]//;s/['\"].*//" | head -10 || true)
|
|
191
|
+
|
|
192
|
+
while IFS= read -r tf; do
|
|
193
|
+
[[ -z "$tf" ]] && continue
|
|
194
|
+
local match
|
|
195
|
+
match=$(echo "$changed_files" | grep -F "$tf" | head -1 || true)
|
|
196
|
+
if [[ -n "$match" ]]; then
|
|
197
|
+
local edge
|
|
198
|
+
edge=$(printf '{"from":"file:%s","to":"file:%s","type":"tests"}' "$file" "$match")
|
|
199
|
+
if [[ -n "$edges" ]]; then
|
|
200
|
+
edges="${edges},${edge}"
|
|
201
|
+
else
|
|
202
|
+
edges="${edge}"
|
|
203
|
+
fi
|
|
204
|
+
edge_count=$((edge_count + 1))
|
|
205
|
+
fi
|
|
206
|
+
done <<< "$tested_files"
|
|
207
|
+
done <<< "$changed_files"
|
|
208
|
+
|
|
209
|
+
# ─── Write Graph ─────────────────────────────────────────────────────
|
|
210
|
+
mkdir -p "$(dirname "$CAUSAL_GRAPH_FILE")"
|
|
211
|
+
|
|
212
|
+
local tmp_file
|
|
213
|
+
tmp_file=$(mktemp 2>/dev/null || echo "${CAUSAL_GRAPH_FILE}.raw")
|
|
214
|
+
cat > "$tmp_file" <<EOF
|
|
215
|
+
{
|
|
216
|
+
"built_at": "$(now_iso)",
|
|
217
|
+
"base_ref": "${base_ref}",
|
|
218
|
+
"node_count": ${node_count},
|
|
219
|
+
"edge_count": ${edge_count},
|
|
220
|
+
"nodes": [${nodes}],
|
|
221
|
+
"edges": [${edges}]
|
|
222
|
+
}
|
|
223
|
+
EOF
|
|
224
|
+
|
|
225
|
+
# Pretty-print if jq available, then atomic move
|
|
226
|
+
if command -v jq >/dev/null 2>&1; then
|
|
227
|
+
if jq '.' "$tmp_file" > "${CAUSAL_GRAPH_FILE}.pp" 2>/dev/null; then
|
|
228
|
+
mv "${CAUSAL_GRAPH_FILE}.pp" "$CAUSAL_GRAPH_FILE"
|
|
229
|
+
else
|
|
230
|
+
mv "$tmp_file" "$CAUSAL_GRAPH_FILE"
|
|
231
|
+
fi
|
|
232
|
+
else
|
|
233
|
+
mv "$tmp_file" "$CAUSAL_GRAPH_FILE"
|
|
234
|
+
fi
|
|
235
|
+
rm -f "$tmp_file" "${CAUSAL_GRAPH_FILE}.pp" "${CAUSAL_GRAPH_FILE}.raw" 2>/dev/null || true
|
|
236
|
+
|
|
237
|
+
success "Causal graph: ${node_count} nodes, ${edge_count} edges"
|
|
238
|
+
|
|
239
|
+
if type emit_event >/dev/null 2>&1; then
|
|
240
|
+
emit_event "causal_graph_built" \
|
|
241
|
+
"nodes=${node_count}" \
|
|
242
|
+
"edges=${edge_count}" \
|
|
243
|
+
"base_ref=${base_ref}"
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
return 0
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# ─── Trace Failure ───────────────────────────────────────────────────────────
|
|
250
|
+
# Given a failing test file, trace the causal chain to identify root cause.
|
|
251
|
+
# Returns JSON with the causal chain and suggested root cause.
|
|
252
|
+
|
|
253
|
+
causal_trace_failure() {
|
|
254
|
+
local failing_test="${1:-}"
|
|
255
|
+
local project_dir="${2:-.}"
|
|
256
|
+
|
|
257
|
+
if [[ -z "$failing_test" ]]; then
|
|
258
|
+
error "causal_trace_failure requires a failing test file path"
|
|
259
|
+
return 1
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
if [[ ! -f "$CAUSAL_GRAPH_FILE" ]]; then
|
|
263
|
+
warn "No causal graph — building now..."
|
|
264
|
+
causal_build_graph "$project_dir"
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
268
|
+
error "jq required for causal tracing"
|
|
269
|
+
return 1
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
info "Tracing causal chain for: ${failing_test}"
|
|
273
|
+
|
|
274
|
+
local chain=""
|
|
275
|
+
local current="file:${failing_test}"
|
|
276
|
+
local visited=""
|
|
277
|
+
local depth=0
|
|
278
|
+
|
|
279
|
+
# BFS through graph following edges from test → source → dependencies
|
|
280
|
+
while [[ "$depth" -lt "$CAUSAL_MAX_DEPTH" ]]; do
|
|
281
|
+
# Find all edges FROM current node
|
|
282
|
+
local targets
|
|
283
|
+
targets=$(jq -r --arg from "$current" \
|
|
284
|
+
'.edges[] | select(.from == $from) | "\(.to)|\(.type)"' \
|
|
285
|
+
"$CAUSAL_GRAPH_FILE" 2>/dev/null || true)
|
|
286
|
+
|
|
287
|
+
if [[ -z "$targets" ]]; then
|
|
288
|
+
break
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
while IFS='|' read -r target edge_type; do
|
|
292
|
+
[[ -z "$target" ]] && continue
|
|
293
|
+
|
|
294
|
+
# Skip already visited
|
|
295
|
+
if echo "$visited" | grep -qF "$target" 2>/dev/null; then
|
|
296
|
+
continue
|
|
297
|
+
fi
|
|
298
|
+
visited="${visited} ${target}"
|
|
299
|
+
|
|
300
|
+
local node_info
|
|
301
|
+
node_info=$(jq -c --arg id "$target" '.nodes[] | select(.id == $id)' \
|
|
302
|
+
"$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "{}")
|
|
303
|
+
|
|
304
|
+
local entry
|
|
305
|
+
entry=$(printf '{"node":"%s","edge_type":"%s","depth":%d,"info":%s}' \
|
|
306
|
+
"$target" "$edge_type" "$depth" "${node_info:-\"{}\"}")
|
|
307
|
+
|
|
308
|
+
if [[ -n "$chain" ]]; then
|
|
309
|
+
chain="${chain},${entry}"
|
|
310
|
+
else
|
|
311
|
+
chain="${entry}"
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
# Follow the chain deeper for source files
|
|
315
|
+
if echo "$target" | grep -q "^file:"; then
|
|
316
|
+
current="$target"
|
|
317
|
+
fi
|
|
318
|
+
done <<< "$targets"
|
|
319
|
+
|
|
320
|
+
depth=$((depth + 1))
|
|
321
|
+
done
|
|
322
|
+
|
|
323
|
+
# Identify likely root cause (deepest source file in chain)
|
|
324
|
+
local root_cause=""
|
|
325
|
+
if [[ -n "$chain" ]]; then
|
|
326
|
+
root_cause=$(echo "[${chain}]" | jq -r \
|
|
327
|
+
'[.[] | select(.edge_type != "tests")] | last | .node // "unknown"' 2>/dev/null || echo "unknown")
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
# Build trace result
|
|
331
|
+
local trace_file="${CAUSAL_GRAPH_FILE%.json}-trace.json"
|
|
332
|
+
cat > "$trace_file" <<EOF
|
|
333
|
+
{
|
|
334
|
+
"traced_at": "$(now_iso)",
|
|
335
|
+
"failing_test": "${failing_test}",
|
|
336
|
+
"root_cause": "${root_cause}",
|
|
337
|
+
"chain_depth": ${depth},
|
|
338
|
+
"chain": [${chain}]
|
|
339
|
+
}
|
|
340
|
+
EOF
|
|
341
|
+
|
|
342
|
+
if [[ "$root_cause" != "unknown" && -n "$root_cause" ]]; then
|
|
343
|
+
success "Root cause identified: ${root_cause}"
|
|
344
|
+
else
|
|
345
|
+
warn "Could not determine root cause from graph (may need deeper analysis)"
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
if type emit_event >/dev/null 2>&1; then
|
|
349
|
+
emit_event "causal_trace_completed" \
|
|
350
|
+
"test=${failing_test}" \
|
|
351
|
+
"root_cause=${root_cause}" \
|
|
352
|
+
"depth=${depth}"
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
echo "$trace_file"
|
|
356
|
+
return 0
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# ─── Find Dependencies ──────────────────────────────────────────────────────
|
|
360
|
+
# Find all entities affected by a change to a given file/function.
|
|
361
|
+
|
|
362
|
+
causal_find_dependencies() {
|
|
363
|
+
local node_id="${1:-}"
|
|
364
|
+
|
|
365
|
+
if [[ -z "$node_id" ]]; then
|
|
366
|
+
error "causal_find_dependencies requires a node ID (e.g., file:src/foo.ts)"
|
|
367
|
+
return 1
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if [[ ! -f "$CAUSAL_GRAPH_FILE" ]]; then
|
|
371
|
+
warn "No causal graph available"
|
|
372
|
+
return 1
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
# Find all nodes that depend on this one (reverse edge traversal)
|
|
376
|
+
local dependents
|
|
377
|
+
dependents=$(jq -r --arg to "$node_id" \
|
|
378
|
+
'.edges[] | select(.to == $to) | .from' \
|
|
379
|
+
"$CAUSAL_GRAPH_FILE" 2>/dev/null || true)
|
|
380
|
+
|
|
381
|
+
if [[ -z "$dependents" ]]; then
|
|
382
|
+
info "No dependents found for ${node_id}"
|
|
383
|
+
return 0
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
echo "Dependents of ${node_id}:"
|
|
387
|
+
echo "$dependents" | while IFS= read -r dep; do
|
|
388
|
+
local dep_info
|
|
389
|
+
dep_info=$(jq -r --arg id "$dep" '.nodes[] | select(.id == $id) | "\(.type): \(.name)"' \
|
|
390
|
+
"$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "$dep")
|
|
391
|
+
echo " - ${dep_info}"
|
|
392
|
+
done
|
|
393
|
+
|
|
394
|
+
return 0
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
# ─── Suggest Fix ─────────────────────────────────────────────────────────────
|
|
398
|
+
# Given a causal trace, suggest what to fix based on the root cause type.
|
|
399
|
+
|
|
400
|
+
causal_suggest_fix() {
|
|
401
|
+
local trace_file="${1:-}"
|
|
402
|
+
|
|
403
|
+
if [[ ! -f "$trace_file" ]]; then
|
|
404
|
+
error "Trace file not found: ${trace_file}"
|
|
405
|
+
return 1
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
local root_cause
|
|
409
|
+
root_cause=$(jq -r '.root_cause // "unknown"' "$trace_file" 2>/dev/null)
|
|
410
|
+
|
|
411
|
+
if [[ "$root_cause" == "unknown" ]]; then
|
|
412
|
+
echo "Unable to suggest fix — root cause not identified"
|
|
413
|
+
return 1
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
# Extract file type from root cause
|
|
417
|
+
local cause_file
|
|
418
|
+
cause_file=$(echo "$root_cause" | sed 's/^file://')
|
|
419
|
+
|
|
420
|
+
echo "Suggested fix approach:"
|
|
421
|
+
echo " Root cause: ${cause_file}"
|
|
422
|
+
|
|
423
|
+
if echo "$cause_file" | grep -qiE '(config|\.json$|\.ya?ml$|\.env)'; then
|
|
424
|
+
echo " Type: Configuration issue"
|
|
425
|
+
echo " Action: Check config values, defaults, and schema validation"
|
|
426
|
+
elif echo "$cause_file" | grep -qiE '(test|spec)'; then
|
|
427
|
+
echo " Type: Test issue (test itself may be wrong)"
|
|
428
|
+
echo " Action: Review test assertions and expected values"
|
|
429
|
+
else
|
|
430
|
+
echo " Type: Source code issue"
|
|
431
|
+
echo " Action: Review recent changes to ${cause_file}"
|
|
432
|
+
echo " Hint: Check git diff for this file and verify logic correctness"
|
|
433
|
+
fi
|
|
434
|
+
|
|
435
|
+
# Show the causal chain for context
|
|
436
|
+
echo ""
|
|
437
|
+
echo "Causal chain:"
|
|
438
|
+
jq -r '.chain[] | " \(.edge_type): \(.node)"' "$trace_file" 2>/dev/null
|
|
439
|
+
|
|
440
|
+
return 0
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# ─── Graph Status ────────────────────────────────────────────────────────────
|
|
444
|
+
|
|
445
|
+
causal_status() {
|
|
446
|
+
if [[ -f "$CAUSAL_GRAPH_FILE" ]]; then
|
|
447
|
+
local node_count edge_count built_at
|
|
448
|
+
node_count=$(jq -r '.node_count // 0' "$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "0")
|
|
449
|
+
edge_count=$(jq -r '.edge_count // 0' "$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "0")
|
|
450
|
+
built_at=$(jq -r '.built_at // "unknown"' "$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "unknown")
|
|
451
|
+
echo "causal_graph=active nodes=${node_count} edges=${edge_count} built=${built_at}"
|
|
452
|
+
else
|
|
453
|
+
echo "causal_graph=none"
|
|
454
|
+
fi
|
|
455
|
+
}
|
package/scripts/lib/compat.sh
CHANGED
|
@@ -281,3 +281,129 @@ compute_md5() {
|
|
|
281
281
|
md5 -q "$file" 2>/dev/null || md5sum "$file" 2>/dev/null | awk '{print $1}'
|
|
282
282
|
fi
|
|
283
283
|
}
|
|
284
|
+
|
|
285
|
+
# ─── Intelligent Model Selection ──────────────────────────────────────────
|
|
286
|
+
# _smart_model <purpose> [default]
|
|
287
|
+
# Returns model name from config chain: env var → daemon-config.json → default
|
|
288
|
+
# Purpose: "classification", "detection", "validation", "commit_quality",
|
|
289
|
+
# "default", or any custom key under model_routing.{purpose}
|
|
290
|
+
_smart_model() {
|
|
291
|
+
local purpose="${1:-default}" default="${2:-haiku}"
|
|
292
|
+
|
|
293
|
+
# 1. Environment override (e.g., SW_MODEL_CLASSIFICATION=sonnet)
|
|
294
|
+
local env_key
|
|
295
|
+
env_key="SW_MODEL_$(echo "$purpose" | tr '[:lower:]' '[:upper:]')"
|
|
296
|
+
local env_val=""
|
|
297
|
+
eval 'env_val="${'"$env_key"':-}"' 2>/dev/null || true
|
|
298
|
+
if [[ -n "$env_val" ]]; then
|
|
299
|
+
echo "$env_val"
|
|
300
|
+
return
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
# 2. Daemon config: model_routing.{purpose}
|
|
304
|
+
local cfg="${DAEMON_CONFIG:-${WORK_DIR:-.}/.claude/daemon-config.json}"
|
|
305
|
+
if [[ -f "$cfg" ]]; then
|
|
306
|
+
local cfg_val
|
|
307
|
+
cfg_val=$(jq -r --arg p "$purpose" '.model_routing[$p] // empty' "$cfg" 2>/dev/null || true)
|
|
308
|
+
if [[ -n "$cfg_val" && "$cfg_val" != "null" ]]; then
|
|
309
|
+
echo "$cfg_val"
|
|
310
|
+
return
|
|
311
|
+
fi
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
# 3. User-level config: ~/.shipwright/model-routing.json
|
|
315
|
+
local user_cfg="${HOME}/.shipwright/model-routing.json"
|
|
316
|
+
if [[ -f "$user_cfg" ]]; then
|
|
317
|
+
local user_val
|
|
318
|
+
user_val=$(jq -r --arg p "$purpose" '.[$p] // empty' "$user_cfg" 2>/dev/null || true)
|
|
319
|
+
if [[ -n "$user_val" && "$user_val" != "null" ]]; then
|
|
320
|
+
echo "$user_val"
|
|
321
|
+
return
|
|
322
|
+
fi
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
# 4. Default
|
|
326
|
+
echo "$default"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
# _smart_int <config_key> <default>
|
|
330
|
+
# Read int from daemon-config with env override and default fallback
|
|
331
|
+
_smart_int() {
|
|
332
|
+
local key="$1" default="$2"
|
|
333
|
+
|
|
334
|
+
# Env override: config.key.path → SW_KEY_PATH
|
|
335
|
+
local env_key
|
|
336
|
+
env_key="SW_$(echo "$key" | tr '[:lower:].' '[:upper:]_')"
|
|
337
|
+
local env_val=""
|
|
338
|
+
eval 'env_val="${'"$env_key"':-}"' 2>/dev/null || true
|
|
339
|
+
if [[ -n "$env_val" ]]; then
|
|
340
|
+
echo "$env_val"
|
|
341
|
+
return
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
# Daemon config
|
|
345
|
+
local cfg="${DAEMON_CONFIG:-${WORK_DIR:-.}/.claude/daemon-config.json}"
|
|
346
|
+
if [[ -f "$cfg" ]]; then
|
|
347
|
+
local cfg_val
|
|
348
|
+
cfg_val=$(jq -r --arg k "$key" 'getpath($k | split(".")) // empty' "$cfg" 2>/dev/null || true)
|
|
349
|
+
if [[ -n "$cfg_val" && "$cfg_val" != "null" ]]; then
|
|
350
|
+
echo "$cfg_val"
|
|
351
|
+
return
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
echo "$default"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
# _smart_effort <stage>
|
|
359
|
+
# Read effort level from config, with per-stage defaults
|
|
360
|
+
_smart_effort() {
|
|
361
|
+
local stage="$1"
|
|
362
|
+
|
|
363
|
+
# 1. Explicit override
|
|
364
|
+
if [[ -n "${EFFORT_LEVEL_OVERRIDE:-}" ]]; then
|
|
365
|
+
echo "$EFFORT_LEVEL_OVERRIDE"
|
|
366
|
+
return
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
# 2. Config: effort_levels.{stage}
|
|
370
|
+
local cfg="${DAEMON_CONFIG:-${WORK_DIR:-.}/.claude/daemon-config.json}"
|
|
371
|
+
if [[ -f "$cfg" ]]; then
|
|
372
|
+
local cfg_val
|
|
373
|
+
cfg_val=$(jq -r --arg s "$stage" '.effort_levels[$s] // empty' "$cfg" 2>/dev/null || true)
|
|
374
|
+
if [[ -n "$cfg_val" && "$cfg_val" != "null" ]]; then
|
|
375
|
+
echo "$cfg_val"
|
|
376
|
+
return
|
|
377
|
+
fi
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
# 3. Intelligent defaults (same as before, but now overridable)
|
|
381
|
+
case "$stage" in
|
|
382
|
+
intake) echo "low" ;;
|
|
383
|
+
plan|design) echo "high" ;;
|
|
384
|
+
build|test) echo "medium" ;;
|
|
385
|
+
review|compound_quality) echo "high" ;;
|
|
386
|
+
pr|merge) echo "low" ;;
|
|
387
|
+
deploy|validate|monitor) echo "medium" ;;
|
|
388
|
+
*) echo "medium" ;;
|
|
389
|
+
esac
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# _exponential_backoff <attempt> [base_seconds] [max_seconds]
|
|
393
|
+
# Returns sleep duration with jitter for retry loops
|
|
394
|
+
_exponential_backoff() {
|
|
395
|
+
local attempt="${1:-1}" base="${2:-2}" max="${3:-60}"
|
|
396
|
+
local delay=$base
|
|
397
|
+
local i=1
|
|
398
|
+
while [ "$i" -lt "$attempt" ]; do
|
|
399
|
+
delay=$((delay * 2))
|
|
400
|
+
i=$((i + 1))
|
|
401
|
+
done
|
|
402
|
+
# Cap at max
|
|
403
|
+
[ "$delay" -gt "$max" ] && delay=$max
|
|
404
|
+
# Add jitter: ±25%
|
|
405
|
+
local jitter=$(( (RANDOM % (delay / 2 + 1)) - delay / 4 ))
|
|
406
|
+
delay=$((delay + jitter))
|
|
407
|
+
[ "$delay" -lt 1 ] && delay=1
|
|
408
|
+
echo "$delay"
|
|
409
|
+
}
|