shipwright-cli 2.3.1 → 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 +95 -28
- 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 +155 -2
- package/config/policy.schema.json +162 -1
- 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 +15 -5
- 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 +748 -0
- 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 +284 -67
- 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 +203 -29
- 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 +220 -0
- 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
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="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
12
12
|
|
|
@@ -34,15 +34,9 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
34
34
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
35
|
}
|
|
36
36
|
fi
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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}"
|
|
37
|
+
# ─── Database (for dual-write memory to DB) ───────────────────────────────────
|
|
38
|
+
# shellcheck source=sw-db.sh
|
|
39
|
+
[[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
|
|
46
40
|
|
|
47
41
|
# ─── Intelligence Engine (optional) ──────────────────────────────────────────
|
|
48
42
|
# shellcheck source=sw-intelligence.sh
|
|
@@ -52,6 +46,186 @@ RESET="${RESET:-\033[0m}"
|
|
|
52
46
|
MEMORY_ROOT="${HOME}/.shipwright/memory"
|
|
53
47
|
GLOBAL_MEMORY="${MEMORY_ROOT}/global.json"
|
|
54
48
|
|
|
49
|
+
# ─── Domain keyword expansion (shared semantic concept) ──────────────────────
|
|
50
|
+
|
|
51
|
+
_expand_domain_keywords() {
|
|
52
|
+
local text="$1"
|
|
53
|
+
local expanded="$text"
|
|
54
|
+
|
|
55
|
+
local dom
|
|
56
|
+
for dom in auth api db ui test deploy error perf; do
|
|
57
|
+
case "$dom" in
|
|
58
|
+
auth) [[ "$text" =~ [aA]uth ]] && expanded="$expanded authentication authorization login session token credential permission access" ;;
|
|
59
|
+
api) [[ "$text" =~ [aA]pi ]] && expanded="$expanded endpoint route handler request response rest graphql" ;;
|
|
60
|
+
db) [[ "$text" =~ [dD]b ]] && expanded="$expanded database query migration schema model table sql" ;;
|
|
61
|
+
ui) [[ "$text" =~ [uU]i ]] && expanded="$expanded component view render template layout style css frontend" ;;
|
|
62
|
+
test) [[ "$text" =~ [tT]est ]] && expanded="$expanded testing assertion coverage mock stub fixture spec" ;;
|
|
63
|
+
deploy) [[ "$text" =~ [dD]eploy ]] && expanded="$expanded deployment release publish ship ci cd pipeline" ;;
|
|
64
|
+
error) [[ "$text" =~ [eE]rror ]] && expanded="$expanded exception failure crash bug issue defect" ;;
|
|
65
|
+
perf) [[ "$text" =~ [pP]erf ]] && expanded="$expanded performance optimization speed latency throughput cache" ;;
|
|
66
|
+
esac
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
echo "$expanded"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ─── Embedding & Semantic Search ───────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
# Generate content hash for deduplication
|
|
75
|
+
_memory_content_hash() {
|
|
76
|
+
echo -n "$1" | shasum -a 256 | cut -d' ' -f1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# TF-IDF-like ranked search across failures, patterns, decisions
|
|
80
|
+
# Returns JSON array of {source_type, content_text} for injection compatibility
|
|
81
|
+
memory_ranked_search() {
|
|
82
|
+
local query="$1"
|
|
83
|
+
local memory_dir="$2"
|
|
84
|
+
local max_results="${3:-5}"
|
|
85
|
+
|
|
86
|
+
# Use repo memory dir when not specified
|
|
87
|
+
if [[ -z "$memory_dir" ]] && type repo_memory_dir &>/dev/null 2>&1; then
|
|
88
|
+
memory_dir="$(repo_memory_dir)"
|
|
89
|
+
fi
|
|
90
|
+
memory_dir="${memory_dir:-$HOME/.shipwright/memory}"
|
|
91
|
+
[[ ! -d "$memory_dir" ]] && echo "[]" && return 0
|
|
92
|
+
|
|
93
|
+
# Extract and expand query keywords
|
|
94
|
+
local keywords
|
|
95
|
+
keywords=$(echo "$query" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '\n' | sort -u | \
|
|
96
|
+
grep -vxE '^.{1,2}$|^(the|and|for|not|with|this|that|from)$' || true)
|
|
97
|
+
keywords=$(_expand_domain_keywords "$keywords" 2>/dev/null || echo "$keywords")
|
|
98
|
+
|
|
99
|
+
local results_file
|
|
100
|
+
results_file=$(mktemp)
|
|
101
|
+
|
|
102
|
+
# Search failures.json
|
|
103
|
+
if [[ -f "$memory_dir/failures.json" ]]; then
|
|
104
|
+
jq -c '.failures[]? // empty' "$memory_dir/failures.json" 2>/dev/null | while IFS= read -r entry; do
|
|
105
|
+
[[ -z "$entry" ]] && continue
|
|
106
|
+
local entry_text
|
|
107
|
+
entry_text=$(echo "$entry" | jq -r '(.pattern // "") + " " + (.root_cause // "") + " " + (.fix // "")' 2>/dev/null)
|
|
108
|
+
local score=0
|
|
109
|
+
while IFS= read -r kw; do
|
|
110
|
+
[[ -z "$kw" ]] && continue
|
|
111
|
+
if echo "$entry_text" | grep -qiF "$kw" 2>/dev/null; then
|
|
112
|
+
score=$((score + 1))
|
|
113
|
+
fi
|
|
114
|
+
done <<< "$keywords"
|
|
115
|
+
|
|
116
|
+
# Boost by effectiveness
|
|
117
|
+
local effectiveness
|
|
118
|
+
effectiveness=$(echo "$entry" | jq -r '.fix_effectiveness_rate // 0' 2>/dev/null)
|
|
119
|
+
if [[ "$effectiveness" =~ ^[0-9]+$ ]] && [[ "$effectiveness" -gt 50 ]]; then
|
|
120
|
+
score=$((score + 2))
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
if [[ "$score" -gt 0 ]]; then
|
|
124
|
+
local content
|
|
125
|
+
content=$(echo "$entry" | jq -r '(.pattern // "") + " | " + (.root_cause // "") + " | " + (.fix // "")' 2>/dev/null)
|
|
126
|
+
echo "${score}|{\"source_type\":\"failure\",\"content_text\":$(echo "$content" | jq -Rs .)}" >> "$results_file"
|
|
127
|
+
fi
|
|
128
|
+
done
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Search decisions.json
|
|
132
|
+
if [[ -f "$memory_dir/decisions.json" ]]; then
|
|
133
|
+
jq -c '.decisions[]? // empty' "$memory_dir/decisions.json" 2>/dev/null | while IFS= read -r entry; do
|
|
134
|
+
[[ -z "$entry" ]] && continue
|
|
135
|
+
local entry_text
|
|
136
|
+
entry_text=$(echo "$entry" | jq -r '(.summary // "") + " " + (.detail // "") + " " + (.type // "")' 2>/dev/null)
|
|
137
|
+
local score=0
|
|
138
|
+
while IFS= read -r kw; do
|
|
139
|
+
[[ -z "$kw" ]] && continue
|
|
140
|
+
echo "$entry_text" | grep -qiF "$kw" 2>/dev/null && score=$((score + 1))
|
|
141
|
+
done <<< "$keywords"
|
|
142
|
+
if [[ "$score" -gt 0 ]]; then
|
|
143
|
+
local content
|
|
144
|
+
content=$(echo "$entry" | jq -r '(.summary // "") + " | " + (.detail // "")' 2>/dev/null)
|
|
145
|
+
echo "${score}|{\"source_type\":\"decision\",\"content_text\":$(echo "$content" | jq -Rs .)}" >> "$results_file"
|
|
146
|
+
fi
|
|
147
|
+
done
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# Search patterns.json (project, conventions, known_issues as text)
|
|
151
|
+
if [[ -f "$memory_dir/patterns.json" ]]; then
|
|
152
|
+
local entry_text
|
|
153
|
+
entry_text=$(jq -r 'to_entries | map(select(.key != "known_issues")) | from_entries | tostring' "$memory_dir/patterns.json" 2>/dev/null || echo "")
|
|
154
|
+
entry_text="$entry_text $(jq -r '.known_issues[]? // empty' "$memory_dir/patterns.json" 2>/dev/null | tr '\n' ' ')"
|
|
155
|
+
local score=0
|
|
156
|
+
while IFS= read -r kw; do
|
|
157
|
+
[[ -z "$kw" ]] && continue
|
|
158
|
+
echo "$entry_text" | grep -qiF "$kw" 2>/dev/null && score=$((score + 1))
|
|
159
|
+
done <<< "$keywords"
|
|
160
|
+
if [[ "$score" -gt 0 ]]; then
|
|
161
|
+
local content
|
|
162
|
+
content=$(jq -r 'to_entries | map("\(.key): \(.value)") | join(" | ")' "$memory_dir/patterns.json" 2>/dev/null | head -c 500)
|
|
163
|
+
echo "${score}|{\"source_type\":\"pattern\",\"content_text\":$(echo "$content" | jq -Rs .)}" >> "$results_file"
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# Sort by score and output as JSON array
|
|
168
|
+
local output
|
|
169
|
+
if [[ -s "$results_file" ]]; then
|
|
170
|
+
output=$(sort -t'|' -k1 -rn "$results_file" | head -"$max_results" | cut -d'|' -f2- | jq -s '.' 2>/dev/null || echo "[]")
|
|
171
|
+
else
|
|
172
|
+
output="[]"
|
|
173
|
+
fi
|
|
174
|
+
rm -f "$results_file" 2>/dev/null || true
|
|
175
|
+
echo "$output"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Store a memory with its text content for future embedding
|
|
179
|
+
memory_store_for_embedding() {
|
|
180
|
+
local source_type="$1" content_text="$2" repo_hash="${3:-}"
|
|
181
|
+
local content_hash
|
|
182
|
+
content_hash=$(_memory_content_hash "$content_text")
|
|
183
|
+
|
|
184
|
+
if type db_save_embedding >/dev/null 2>&1; then
|
|
185
|
+
db_save_embedding "$content_hash" "$source_type" "$content_text" "$repo_hash" 2>/dev/null || true
|
|
186
|
+
fi
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Check if vector embeddings search is available (future: SQLite vec0, etc.)
|
|
190
|
+
_has_embeddings() {
|
|
191
|
+
return 1 # No embedding-based search yet
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Semantic search: embeddings when available, else TF-IDF-like ranked keyword search
|
|
195
|
+
memory_semantic_search() {
|
|
196
|
+
local query="$1" repo_hash="${2:-}" limit="${3:-5}"
|
|
197
|
+
|
|
198
|
+
if _has_embeddings 2>/dev/null; then
|
|
199
|
+
# Future: _search_embeddings "$query" "$repo_hash" "$limit"
|
|
200
|
+
:
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Fall back to ranked keyword search (better than SQL LIKE or grep)
|
|
204
|
+
local mem_dir
|
|
205
|
+
mem_dir=""
|
|
206
|
+
if type repo_memory_dir &>/dev/null 2>&1; then
|
|
207
|
+
mem_dir="$(repo_memory_dir)"
|
|
208
|
+
fi
|
|
209
|
+
memory_ranked_search "$query" "$mem_dir" "$limit"
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Inject relevant memories into agent prompts (goal-based)
|
|
213
|
+
memory_inject_goal_context() {
|
|
214
|
+
local goal="$1" repo_hash="${2:-}" max_tokens="${3:-2000}"
|
|
215
|
+
|
|
216
|
+
local memories
|
|
217
|
+
memories=$(memory_semantic_search "$goal" "$repo_hash" 5 2>/dev/null || echo "[]")
|
|
218
|
+
|
|
219
|
+
if [[ "$memories" == "[]" || -z "$memories" ]]; then
|
|
220
|
+
return
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
echo "## Relevant Past Context"
|
|
224
|
+
echo ""
|
|
225
|
+
echo "$memories" | jq -r '.[] | "- [\(.source_type)] \(.content_text | .[0:200])"' 2>/dev/null || true
|
|
226
|
+
echo ""
|
|
227
|
+
}
|
|
228
|
+
|
|
55
229
|
# Get a deterministic hash for the current repo
|
|
56
230
|
repo_hash() {
|
|
57
231
|
local origin
|
|
@@ -207,7 +381,7 @@ memory_capture_failure() {
|
|
|
207
381
|
"$failures_file" 2>/dev/null || echo "-1")
|
|
208
382
|
|
|
209
383
|
(
|
|
210
|
-
if command -v flock
|
|
384
|
+
if command -v flock >/dev/null 2>&1; then
|
|
211
385
|
flock -w 10 200 2>/dev/null || { warn "Memory lock timeout"; return 1; }
|
|
212
386
|
fi
|
|
213
387
|
local tmp_file
|
|
@@ -237,6 +411,15 @@ memory_capture_failure() {
|
|
|
237
411
|
fi
|
|
238
412
|
) 200>"${failures_file}.lock"
|
|
239
413
|
|
|
414
|
+
# Dual-write to DB
|
|
415
|
+
if type db_record_failure >/dev/null 2>&1; then
|
|
416
|
+
local rhash
|
|
417
|
+
rhash="$(repo_hash)"
|
|
418
|
+
db_record_failure "$rhash" "unknown" "$pattern" "" "" "" "$stage" 2>/dev/null || true
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
memory_store_for_embedding "failure" "$pattern" "$(repo_hash)" 2>/dev/null || true
|
|
422
|
+
|
|
240
423
|
emit_event "memory.failure" "stage=${stage}" "pattern=${pattern:0:80}"
|
|
241
424
|
}
|
|
242
425
|
|
|
@@ -274,7 +457,7 @@ memory_record_fix_outcome() {
|
|
|
274
457
|
[[ "$fix_resolved" == "true" ]] && resolved_inc=1
|
|
275
458
|
|
|
276
459
|
(
|
|
277
|
-
if command -v flock
|
|
460
|
+
if command -v flock >/dev/null 2>&1; then
|
|
278
461
|
flock -w 10 200 2>/dev/null || { warn "Memory lock timeout"; return 1; }
|
|
279
462
|
fi
|
|
280
463
|
local tmp_file
|
|
@@ -583,7 +766,7 @@ Return JSON only, no markdown fences, no explanation."
|
|
|
583
766
|
fi
|
|
584
767
|
|
|
585
768
|
# Validate category against shared taxonomy (compat.sh) or built-in list
|
|
586
|
-
if type sw_valid_error_category
|
|
769
|
+
if type sw_valid_error_category >/dev/null 2>&1; then
|
|
587
770
|
if ! sw_valid_error_category "$category"; then
|
|
588
771
|
category="unknown"
|
|
589
772
|
fi
|
|
@@ -725,6 +908,14 @@ memory_capture_pattern() {
|
|
|
725
908
|
}
|
|
726
909
|
}' "$patterns_file" > "$tmp_file" && mv "$tmp_file" "$patterns_file"
|
|
727
910
|
|
|
911
|
+
# Dual-write to DB
|
|
912
|
+
if type db_save_pattern >/dev/null 2>&1; then
|
|
913
|
+
local rhash proj_desc
|
|
914
|
+
rhash="$(repo_hash)"
|
|
915
|
+
proj_desc="type=$proj_type,framework=$framework,test_runner=$test_runner,package_manager=$pkg_mgr,language=$language"
|
|
916
|
+
db_save_pattern "$rhash" "project" "project" "$proj_desc" "" 2>/dev/null || true
|
|
917
|
+
fi
|
|
918
|
+
memory_store_for_embedding "pattern" "project: $proj_type/$framework, $pkg_mgr, $language" "$(repo_hash)" 2>/dev/null || true
|
|
728
919
|
emit_event "memory.pattern" "type=project" "proj_type=${proj_type}" "framework=${framework}"
|
|
729
920
|
success "Captured project patterns (${proj_type}/${framework:-none})"
|
|
730
921
|
;;
|
|
@@ -740,6 +931,14 @@ memory_capture_pattern() {
|
|
|
740
931
|
else . + {known_issues: [$issue]}
|
|
741
932
|
end | .known_issues = (.known_issues | .[-50:])' \
|
|
742
933
|
"$patterns_file" > "$tmp_file" && mv "$tmp_file" "$patterns_file"
|
|
934
|
+
# Dual-write to DB
|
|
935
|
+
if type db_save_pattern >/dev/null 2>&1; then
|
|
936
|
+
local rhash issue_key
|
|
937
|
+
rhash="$(repo_hash)"
|
|
938
|
+
issue_key=$(echo -n "$pattern_data" | shasum -a 256 | cut -c1-16)
|
|
939
|
+
db_save_pattern "$rhash" "known_issue" "$issue_key" "$pattern_data" "" 2>/dev/null || true
|
|
940
|
+
fi
|
|
941
|
+
memory_store_for_embedding "pattern" "known_issue: $pattern_data" "$(repo_hash)" 2>/dev/null || true
|
|
743
942
|
emit_event "memory.pattern" "type=known_issue"
|
|
744
943
|
fi
|
|
745
944
|
;;
|
|
@@ -758,7 +957,7 @@ memory_inject_context() {
|
|
|
758
957
|
local stage_id="${1:-}"
|
|
759
958
|
|
|
760
959
|
# Try intelligence-ranked search first
|
|
761
|
-
if type intelligence_search_memory
|
|
960
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
762
961
|
local config="${REPO_DIR:-.}/.claude/daemon-config.json"
|
|
763
962
|
local intel_enabled="false"
|
|
764
963
|
if [[ -f "$config" ]]; then
|
|
@@ -928,7 +1127,17 @@ memory_inject_context() {
|
|
|
928
1127
|
;;
|
|
929
1128
|
|
|
930
1129
|
*)
|
|
931
|
-
# Generic context
|
|
1130
|
+
# Generic context — use ranked semantic search when intelligence unavailable
|
|
1131
|
+
if ! type intelligence_search_memory &>/dev/null 2>&1; then
|
|
1132
|
+
local ranked_json
|
|
1133
|
+
ranked_json=$(memory_ranked_search "${stage_id} stage context" "$mem_dir" 5 2>/dev/null || echo "[]")
|
|
1134
|
+
if [[ -n "$ranked_json" && "$ranked_json" != "[]" ]]; then
|
|
1135
|
+
echo "## Ranked Relevant Memory"
|
|
1136
|
+
echo "$ranked_json" | jq -r '.[]? | "- [\(.source_type)] \(.content_text[0:200])"' 2>/dev/null || true
|
|
1137
|
+
echo ""
|
|
1138
|
+
fi
|
|
1139
|
+
fi
|
|
1140
|
+
|
|
932
1141
|
echo "## Repository Patterns"
|
|
933
1142
|
if [[ -f "$mem_dir/patterns.json" ]]; then
|
|
934
1143
|
jq -r 'to_entries | map(select(.key != "known_issues")) | from_entries' \
|
|
@@ -1155,6 +1364,15 @@ memory_capture_decision() {
|
|
|
1155
1364
|
}] | .decisions = (.decisions | .[-100:])' \
|
|
1156
1365
|
"$decisions_file" > "$tmp_file" && mv "$tmp_file" "$decisions_file"
|
|
1157
1366
|
|
|
1367
|
+
# Dual-write to DB
|
|
1368
|
+
if type db_save_decision >/dev/null 2>&1; then
|
|
1369
|
+
local rhash
|
|
1370
|
+
rhash="$(repo_hash)"
|
|
1371
|
+
db_save_decision "$rhash" "$dec_type" "${detail:-}" "$summary" "" 2>/dev/null || true
|
|
1372
|
+
fi
|
|
1373
|
+
|
|
1374
|
+
memory_store_for_embedding "decision" "${dec_type}: ${summary} - ${detail:-}" "$(repo_hash)" 2>/dev/null || true
|
|
1375
|
+
|
|
1158
1376
|
emit_event "memory.decision" "type=${dec_type}" "summary=${summary:0:80}"
|
|
1159
1377
|
success "Recorded decision: ${summary}"
|
|
1160
1378
|
}
|
|
@@ -1276,10 +1494,17 @@ memory_show() {
|
|
|
1276
1494
|
}
|
|
1277
1495
|
|
|
1278
1496
|
memory_search() {
|
|
1497
|
+
if [[ "${1:-}" == "--semantic" ]]; then
|
|
1498
|
+
shift
|
|
1499
|
+
memory_semantic_search "$*" "" 10
|
|
1500
|
+
exit 0
|
|
1501
|
+
fi
|
|
1502
|
+
|
|
1279
1503
|
local keyword="${1:-}"
|
|
1280
1504
|
|
|
1281
1505
|
if [[ -z "$keyword" ]]; then
|
|
1282
1506
|
error "Usage: shipwright memory search <keyword>"
|
|
1507
|
+
echo -e " ${DIM}Or: shipwright memory search --semantic <query>${RESET}"
|
|
1283
1508
|
return 1
|
|
1284
1509
|
fi
|
|
1285
1510
|
|
|
@@ -1296,10 +1521,10 @@ memory_search() {
|
|
|
1296
1521
|
local found=0
|
|
1297
1522
|
|
|
1298
1523
|
# ── Semantic search via intelligence (if available) ──
|
|
1299
|
-
if type intelligence_search_memory
|
|
1524
|
+
if type intelligence_search_memory >/dev/null 2>&1; then
|
|
1300
1525
|
local semantic_results
|
|
1301
1526
|
semantic_results=$(intelligence_search_memory "$keyword" "$mem_dir" 5 2>/dev/null || echo "")
|
|
1302
|
-
if [[ -n "$semantic_results" ]] && echo "$semantic_results" | jq -e '.results | length > 0'
|
|
1527
|
+
if [[ -n "$semantic_results" ]] && echo "$semantic_results" | jq -e '.results | length > 0' >/dev/null 2>&1; then
|
|
1303
1528
|
echo -e " ${BOLD}${CYAN}Semantic Results (AI-ranked):${RESET}"
|
|
1304
1529
|
local result_count
|
|
1305
1530
|
result_count=$(echo "$semantic_results" | jq '.results | length')
|
|
@@ -1557,6 +1782,7 @@ show_help() {
|
|
|
1557
1782
|
echo -e " ${CYAN}show${RESET} Display memory for current repo"
|
|
1558
1783
|
echo -e " ${CYAN}show${RESET} --global Display cross-repo learnings"
|
|
1559
1784
|
echo -e " ${CYAN}search${RESET} <keyword> Search memory for keyword"
|
|
1785
|
+
echo -e " ${CYAN}search${RESET} --semantic <query> Semantic search via memory_embeddings"
|
|
1560
1786
|
echo -e " ${CYAN}forget${RESET} --all Clear memory for current repo"
|
|
1561
1787
|
echo -e " ${CYAN}export${RESET} Export memory as JSON"
|
|
1562
1788
|
echo -e " ${CYAN}import${RESET} <file> Import memory from JSON"
|
|
@@ -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="
|
|
10
|
+
VERSION="3.0.0"
|
|
11
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
12
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
13
|
|
|
@@ -35,16 +35,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
35
35
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
36
36
|
}
|
|
37
37
|
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
38
|
format_duration() {
|
|
49
39
|
local secs="$1"
|
|
50
40
|
if [[ "$secs" -ge 3600 ]]; then
|
|
@@ -268,7 +258,7 @@ show_resource_usage() {
|
|
|
268
258
|
|
|
269
259
|
echo -e "${BOLD}System Resources${RESET}"
|
|
270
260
|
|
|
271
|
-
if command -v top
|
|
261
|
+
if command -v top >/dev/null 2>&1 || command -v ps >/dev/null 2>&1; then
|
|
272
262
|
# Get system memory and CPU stats
|
|
273
263
|
local mem_pct=65
|
|
274
264
|
local cpu_pct=42
|
|
@@ -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="
|
|
10
|
+
VERSION="3.0.0"
|
|
11
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
12
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
13
|
|
|
@@ -35,21 +35,17 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
35
35
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
36
36
|
}
|
|
37
37
|
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
38
|
# ─── File Paths ────────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
# Unified: prefer optimization dir (written by self-optimize), fallback to legacy
|
|
40
|
+
OPTIMIZATION_DIR="${HOME}/.shipwright/optimization"
|
|
41
|
+
MODEL_ROUTING_OPTIMIZATION="${OPTIMIZATION_DIR}/model-routing.json"
|
|
42
|
+
MODEL_ROUTING_LEGACY="${HOME}/.shipwright/model-routing.json"
|
|
43
|
+
MODEL_USAGE_LOG="${OPTIMIZATION_DIR}/model-usage.jsonl"
|
|
51
44
|
AB_RESULTS_FILE="${HOME}/.shipwright/ab-results.jsonl"
|
|
52
45
|
|
|
46
|
+
# Resolve which config file to use (set by _resolve_routing_config)
|
|
47
|
+
MODEL_ROUTING_CONFIG=""
|
|
48
|
+
|
|
53
49
|
# ─── Model Costs (per million tokens) ───────────────────────────────────────
|
|
54
50
|
HAIKU_INPUT_COST="0.80"
|
|
55
51
|
HAIKU_OUTPUT_COST="4.00"
|
|
@@ -70,9 +66,25 @@ OPUS_STAGES="plan|design|build|compound_quality"
|
|
|
70
66
|
COMPLEXITY_LOW=30 # Below this: use sonnet
|
|
71
67
|
COMPLEXITY_HIGH=80 # Above this: use opus
|
|
72
68
|
|
|
69
|
+
# ─── Resolve Routing Config Path ────────────────────────────────────────────
|
|
70
|
+
# Priority: optimization (self-optimize writes) > legacy > create in optimization
|
|
71
|
+
_resolve_routing_config() {
|
|
72
|
+
if [[ -f "$MODEL_ROUTING_OPTIMIZATION" ]]; then
|
|
73
|
+
MODEL_ROUTING_CONFIG="$MODEL_ROUTING_OPTIMIZATION"
|
|
74
|
+
return
|
|
75
|
+
fi
|
|
76
|
+
if [[ -f "$MODEL_ROUTING_LEGACY" ]]; then
|
|
77
|
+
MODEL_ROUTING_CONFIG="$MODEL_ROUTING_LEGACY"
|
|
78
|
+
return
|
|
79
|
+
fi
|
|
80
|
+
# Neither exists — use optimization as canonical location
|
|
81
|
+
MODEL_ROUTING_CONFIG="$MODEL_ROUTING_OPTIMIZATION"
|
|
82
|
+
}
|
|
83
|
+
|
|
73
84
|
# ─── Ensure Config File Exists ──────────────────────────────────────────────
|
|
74
85
|
ensure_config() {
|
|
75
|
-
|
|
86
|
+
_resolve_routing_config
|
|
87
|
+
mkdir -p "$(dirname "$MODEL_ROUTING_CONFIG")"
|
|
76
88
|
|
|
77
89
|
if [[ ! -f "$MODEL_ROUTING_CONFIG" ]]; then
|
|
78
90
|
cat > "$MODEL_ROUTING_CONFIG" <<'CONFIG'
|
|
@@ -127,26 +139,53 @@ route_model() {
|
|
|
127
139
|
fi
|
|
128
140
|
|
|
129
141
|
local model=""
|
|
142
|
+
_resolve_routing_config
|
|
143
|
+
|
|
144
|
+
# Strategy 1: Optimization file (self-optimize format) — .routes.stage.model or .routes.stage.recommended
|
|
145
|
+
if [[ -n "$MODEL_ROUTING_CONFIG" && -f "$MODEL_ROUTING_CONFIG" ]] && command -v jq >/dev/null 2>&1; then
|
|
146
|
+
local from_routes
|
|
147
|
+
from_routes=$(jq -r --arg s "$stage" '.routes[$s].model // .routes[$s].recommended // .[$s].recommended // .[$s].model // empty' "$MODEL_ROUTING_CONFIG" 2>/dev/null || true)
|
|
148
|
+
if [[ -n "$from_routes" && "$from_routes" =~ ^(haiku|sonnet|opus)$ ]]; then
|
|
149
|
+
model="$from_routes"
|
|
150
|
+
fi
|
|
151
|
+
# Fallback: legacy default_routing format
|
|
152
|
+
if [[ -z "$model" ]]; then
|
|
153
|
+
local from_default
|
|
154
|
+
from_default=$(jq -r --arg s "$stage" '.default_routing[$s] // empty' "$MODEL_ROUTING_CONFIG" 2>/dev/null || true)
|
|
155
|
+
if [[ -n "$from_default" && "$from_default" =~ ^(haiku|sonnet|opus)$ ]]; then
|
|
156
|
+
model="$from_default"
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
130
160
|
|
|
131
|
-
#
|
|
132
|
-
if [[
|
|
133
|
-
|
|
134
|
-
elif [[ "$complexity" -gt "$COMPLEXITY_HIGH" ]]; then
|
|
135
|
-
model="opus"
|
|
136
|
-
else
|
|
137
|
-
# Stage-based routing for medium complexity
|
|
138
|
-
if [[ "$stage" =~ $HAIKU_STAGES ]]; then
|
|
139
|
-
model="haiku"
|
|
140
|
-
elif [[ "$stage" =~ $SONNET_STAGES ]]; then
|
|
161
|
+
# Strategy 2: Built-in defaults (complexity + stage rules)
|
|
162
|
+
if [[ -z "$model" ]]; then
|
|
163
|
+
if [[ "$complexity" -lt "$COMPLEXITY_LOW" ]]; then
|
|
141
164
|
model="sonnet"
|
|
142
|
-
elif [[ "$
|
|
165
|
+
elif [[ "$complexity" -gt "$COMPLEXITY_HIGH" ]]; then
|
|
143
166
|
model="opus"
|
|
144
167
|
else
|
|
145
|
-
|
|
146
|
-
|
|
168
|
+
if [[ "$stage" =~ $HAIKU_STAGES ]]; then
|
|
169
|
+
model="haiku"
|
|
170
|
+
elif [[ "$stage" =~ $SONNET_STAGES ]]; then
|
|
171
|
+
model="sonnet"
|
|
172
|
+
elif [[ "$stage" =~ $OPUS_STAGES ]]; then
|
|
173
|
+
model="opus"
|
|
174
|
+
else
|
|
175
|
+
model="sonnet"
|
|
176
|
+
fi
|
|
147
177
|
fi
|
|
148
178
|
fi
|
|
149
179
|
|
|
180
|
+
# Complexity override: upgrade/downgrade based on complexity even when config says otherwise
|
|
181
|
+
if [[ "$complexity" -lt "$COMPLEXITY_LOW" && "$model" == "opus" ]]; then
|
|
182
|
+
model="sonnet"
|
|
183
|
+
elif [[ "$complexity" -gt "$COMPLEXITY_HIGH" && "$model" == "haiku" ]]; then
|
|
184
|
+
model="opus"
|
|
185
|
+
elif [[ "$complexity" -gt "$COMPLEXITY_HIGH" ]]; then
|
|
186
|
+
model="opus"
|
|
187
|
+
fi
|
|
188
|
+
|
|
150
189
|
echo "$model"
|
|
151
190
|
}
|
|
152
191
|
|
|
@@ -177,7 +216,7 @@ show_config() {
|
|
|
177
216
|
info "Model Routing Configuration"
|
|
178
217
|
echo ""
|
|
179
218
|
|
|
180
|
-
if command -v jq
|
|
219
|
+
if command -v jq >/dev/null 2>&1; then
|
|
181
220
|
jq . "$MODEL_ROUTING_CONFIG" 2>/dev/null || cat "$MODEL_ROUTING_CONFIG"
|
|
182
221
|
else
|
|
183
222
|
cat "$MODEL_ROUTING_CONFIG"
|
|
@@ -196,7 +235,7 @@ set_config() {
|
|
|
196
235
|
|
|
197
236
|
ensure_config
|
|
198
237
|
|
|
199
|
-
if ! command -v jq
|
|
238
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
200
239
|
error "jq is required for config updates"
|
|
201
240
|
return 1
|
|
202
241
|
fi
|
|
@@ -298,7 +337,7 @@ record_usage() {
|
|
|
298
337
|
local input_tokens="${3:-0}"
|
|
299
338
|
local output_tokens="${4:-0}"
|
|
300
339
|
|
|
301
|
-
mkdir -p "$
|
|
340
|
+
mkdir -p "$(dirname "$MODEL_USAGE_LOG")"
|
|
302
341
|
|
|
303
342
|
local cost
|
|
304
343
|
cost=$(awk "BEGIN {}" ) # Calculate actual cost
|
|
@@ -330,7 +369,7 @@ configure_ab_test() {
|
|
|
330
369
|
|
|
331
370
|
ensure_config
|
|
332
371
|
|
|
333
|
-
if ! command -v jq
|
|
372
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
334
373
|
error "jq is required for A/B test configuration"
|
|
335
374
|
return 1
|
|
336
375
|
fi
|
|
@@ -370,7 +409,7 @@ show_report() {
|
|
|
370
409
|
return 0
|
|
371
410
|
fi
|
|
372
411
|
|
|
373
|
-
if ! command -v jq
|
|
412
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
374
413
|
error "jq is required to view reports"
|
|
375
414
|
return 1
|
|
376
415
|
fi
|
|
@@ -432,7 +471,7 @@ show_ab_results() {
|
|
|
432
471
|
return 0
|
|
433
472
|
fi
|
|
434
473
|
|
|
435
|
-
if ! command -v jq
|
|
474
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
436
475
|
error "jq is required to view A/B test results"
|
|
437
476
|
return 1
|
|
438
477
|
fi
|
|
@@ -520,7 +559,7 @@ main() {
|
|
|
520
559
|
elif [[ "${1:-}" == "disable" ]]; then
|
|
521
560
|
# Disable A/B testing
|
|
522
561
|
ensure_config
|
|
523
|
-
if command -v jq
|
|
562
|
+
if command -v jq >/dev/null 2>&1; then
|
|
524
563
|
local tmp_config
|
|
525
564
|
tmp_config=$(mktemp)
|
|
526
565
|
trap "rm -f '$tmp_config'" RETURN
|