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.
Files changed (162) hide show
  1. package/README.md +95 -28
  2. package/completions/_shipwright +1 -1
  3. package/completions/shipwright.bash +3 -8
  4. package/completions/shipwright.fish +1 -1
  5. package/config/defaults.json +111 -0
  6. package/config/event-schema.json +81 -0
  7. package/config/policy.json +155 -2
  8. package/config/policy.schema.json +162 -1
  9. package/dashboard/coverage/coverage-summary.json +14 -0
  10. package/dashboard/public/index.html +1 -1
  11. package/dashboard/server.ts +306 -17
  12. package/dashboard/src/components/charts/bar.test.ts +79 -0
  13. package/dashboard/src/components/charts/donut.test.ts +68 -0
  14. package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
  15. package/dashboard/src/components/charts/sparkline.test.ts +125 -0
  16. package/dashboard/src/core/api.test.ts +309 -0
  17. package/dashboard/src/core/helpers.test.ts +301 -0
  18. package/dashboard/src/core/router.test.ts +307 -0
  19. package/dashboard/src/core/router.ts +7 -0
  20. package/dashboard/src/core/sse.test.ts +144 -0
  21. package/dashboard/src/views/metrics.test.ts +186 -0
  22. package/dashboard/src/views/overview.test.ts +173 -0
  23. package/dashboard/src/views/pipelines.test.ts +183 -0
  24. package/dashboard/src/views/team.test.ts +253 -0
  25. package/dashboard/vitest.config.ts +14 -5
  26. package/docs/TIPS.md +1 -1
  27. package/docs/patterns/README.md +1 -1
  28. package/package.json +15 -5
  29. package/scripts/adapters/docker-deploy.sh +1 -1
  30. package/scripts/adapters/tmux-adapter.sh +11 -1
  31. package/scripts/adapters/wezterm-adapter.sh +1 -1
  32. package/scripts/check-version-consistency.sh +1 -1
  33. package/scripts/lib/architecture.sh +126 -0
  34. package/scripts/lib/bootstrap.sh +75 -0
  35. package/scripts/lib/compat.sh +89 -6
  36. package/scripts/lib/config.sh +91 -0
  37. package/scripts/lib/daemon-adaptive.sh +3 -3
  38. package/scripts/lib/daemon-dispatch.sh +39 -16
  39. package/scripts/lib/daemon-health.sh +1 -1
  40. package/scripts/lib/daemon-patrol.sh +24 -12
  41. package/scripts/lib/daemon-poll.sh +37 -25
  42. package/scripts/lib/daemon-state.sh +115 -23
  43. package/scripts/lib/daemon-triage.sh +30 -8
  44. package/scripts/lib/fleet-failover.sh +63 -0
  45. package/scripts/lib/helpers.sh +30 -6
  46. package/scripts/lib/pipeline-detection.sh +2 -2
  47. package/scripts/lib/pipeline-github.sh +9 -9
  48. package/scripts/lib/pipeline-intelligence.sh +85 -35
  49. package/scripts/lib/pipeline-quality-checks.sh +16 -16
  50. package/scripts/lib/pipeline-quality.sh +1 -1
  51. package/scripts/lib/pipeline-stages.sh +242 -28
  52. package/scripts/lib/pipeline-state.sh +40 -4
  53. package/scripts/lib/test-helpers.sh +247 -0
  54. package/scripts/postinstall.mjs +3 -11
  55. package/scripts/sw +10 -4
  56. package/scripts/sw-activity.sh +1 -11
  57. package/scripts/sw-adaptive.sh +109 -85
  58. package/scripts/sw-adversarial.sh +4 -14
  59. package/scripts/sw-architecture-enforcer.sh +1 -11
  60. package/scripts/sw-auth.sh +8 -17
  61. package/scripts/sw-autonomous.sh +111 -49
  62. package/scripts/sw-changelog.sh +1 -11
  63. package/scripts/sw-checkpoint.sh +144 -20
  64. package/scripts/sw-ci.sh +2 -12
  65. package/scripts/sw-cleanup.sh +13 -17
  66. package/scripts/sw-code-review.sh +16 -36
  67. package/scripts/sw-connect.sh +5 -12
  68. package/scripts/sw-context.sh +9 -26
  69. package/scripts/sw-cost.sh +6 -16
  70. package/scripts/sw-daemon.sh +75 -70
  71. package/scripts/sw-dashboard.sh +57 -17
  72. package/scripts/sw-db.sh +506 -15
  73. package/scripts/sw-decompose.sh +1 -11
  74. package/scripts/sw-deps.sh +15 -25
  75. package/scripts/sw-developer-simulation.sh +1 -11
  76. package/scripts/sw-discovery.sh +112 -30
  77. package/scripts/sw-doc-fleet.sh +7 -17
  78. package/scripts/sw-docs-agent.sh +6 -16
  79. package/scripts/sw-docs.sh +4 -12
  80. package/scripts/sw-doctor.sh +134 -43
  81. package/scripts/sw-dora.sh +11 -19
  82. package/scripts/sw-durable.sh +35 -52
  83. package/scripts/sw-e2e-orchestrator.sh +11 -27
  84. package/scripts/sw-eventbus.sh +115 -115
  85. package/scripts/sw-evidence.sh +748 -0
  86. package/scripts/sw-feedback.sh +3 -13
  87. package/scripts/sw-fix.sh +2 -20
  88. package/scripts/sw-fleet-discover.sh +1 -11
  89. package/scripts/sw-fleet-viz.sh +10 -18
  90. package/scripts/sw-fleet.sh +13 -17
  91. package/scripts/sw-github-app.sh +6 -16
  92. package/scripts/sw-github-checks.sh +1 -11
  93. package/scripts/sw-github-deploy.sh +1 -11
  94. package/scripts/sw-github-graphql.sh +2 -12
  95. package/scripts/sw-guild.sh +1 -11
  96. package/scripts/sw-heartbeat.sh +49 -12
  97. package/scripts/sw-hygiene.sh +45 -43
  98. package/scripts/sw-incident.sh +284 -67
  99. package/scripts/sw-init.sh +35 -37
  100. package/scripts/sw-instrument.sh +1 -11
  101. package/scripts/sw-intelligence.sh +362 -51
  102. package/scripts/sw-jira.sh +5 -14
  103. package/scripts/sw-launchd.sh +2 -12
  104. package/scripts/sw-linear.sh +8 -17
  105. package/scripts/sw-logs.sh +4 -12
  106. package/scripts/sw-loop.sh +641 -90
  107. package/scripts/sw-memory.sh +243 -17
  108. package/scripts/sw-mission-control.sh +2 -12
  109. package/scripts/sw-model-router.sh +73 -34
  110. package/scripts/sw-otel.sh +11 -21
  111. package/scripts/sw-oversight.sh +1 -11
  112. package/scripts/sw-patrol-meta.sh +5 -11
  113. package/scripts/sw-pipeline-composer.sh +7 -17
  114. package/scripts/sw-pipeline-vitals.sh +1 -11
  115. package/scripts/sw-pipeline.sh +478 -122
  116. package/scripts/sw-pm.sh +2 -12
  117. package/scripts/sw-pr-lifecycle.sh +203 -29
  118. package/scripts/sw-predictive.sh +16 -22
  119. package/scripts/sw-prep.sh +6 -16
  120. package/scripts/sw-ps.sh +1 -11
  121. package/scripts/sw-public-dashboard.sh +2 -12
  122. package/scripts/sw-quality.sh +77 -10
  123. package/scripts/sw-reaper.sh +1 -11
  124. package/scripts/sw-recruit.sh +15 -25
  125. package/scripts/sw-regression.sh +11 -21
  126. package/scripts/sw-release-manager.sh +19 -28
  127. package/scripts/sw-release.sh +8 -16
  128. package/scripts/sw-remote.sh +1 -11
  129. package/scripts/sw-replay.sh +48 -44
  130. package/scripts/sw-retro.sh +70 -92
  131. package/scripts/sw-review-rerun.sh +220 -0
  132. package/scripts/sw-scale.sh +109 -32
  133. package/scripts/sw-security-audit.sh +12 -22
  134. package/scripts/sw-self-optimize.sh +239 -23
  135. package/scripts/sw-session.sh +3 -13
  136. package/scripts/sw-setup.sh +8 -18
  137. package/scripts/sw-standup.sh +5 -15
  138. package/scripts/sw-status.sh +32 -23
  139. package/scripts/sw-strategic.sh +129 -13
  140. package/scripts/sw-stream.sh +1 -11
  141. package/scripts/sw-swarm.sh +76 -36
  142. package/scripts/sw-team-stages.sh +10 -20
  143. package/scripts/sw-templates.sh +4 -14
  144. package/scripts/sw-testgen.sh +3 -13
  145. package/scripts/sw-tmux-pipeline.sh +1 -19
  146. package/scripts/sw-tmux-role-color.sh +0 -10
  147. package/scripts/sw-tmux-status.sh +3 -11
  148. package/scripts/sw-tmux.sh +2 -20
  149. package/scripts/sw-trace.sh +1 -19
  150. package/scripts/sw-tracker-github.sh +0 -10
  151. package/scripts/sw-tracker-jira.sh +1 -11
  152. package/scripts/sw-tracker-linear.sh +1 -11
  153. package/scripts/sw-tracker.sh +7 -24
  154. package/scripts/sw-triage.sh +24 -34
  155. package/scripts/sw-upgrade.sh +5 -23
  156. package/scripts/sw-ux.sh +1 -19
  157. package/scripts/sw-webhook.sh +18 -32
  158. package/scripts/sw-widgets.sh +3 -21
  159. package/scripts/sw-worktree.sh +11 -27
  160. package/scripts/update-homebrew-sha.sh +67 -0
  161. package/templates/pipelines/tdd.json +72 -0
  162. package/scripts/sw-pipeline.sh.mock +0 -7
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.3.1"
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
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
39
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
40
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
41
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
42
- RED="${RED:-\033[38;2;248;113;113m}"
43
- DIM="${DIM:-\033[2m}"
44
- BOLD="${BOLD:-\033[1m}"
45
- RESET="${RESET:-\033[0m}"
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 &>/dev/null; then
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 &>/dev/null; then
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 &>/dev/null 2>&1; then
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 &>/dev/null 2>&1; then
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 for any stage inject top-K most relevant across all categories
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 &>/dev/null 2>&1; then
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' &>/dev/null; then
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="2.3.1"
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 &>/dev/null || command -v ps &>/dev/null; then
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="2.3.1"
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
- MODEL_ROUTING_CONFIG="${HOME}/.shipwright/model-routing.json"
50
- MODEL_USAGE_LOG="${HOME}/.shipwright/model-usage.jsonl"
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
- mkdir -p "${HOME}/.shipwright"
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
- # Complexity-based override (applies to all stages)
132
- if [[ "$complexity" -lt "$COMPLEXITY_LOW" ]]; then
133
- model="sonnet"
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 [[ "$stage" =~ $OPUS_STAGES ]]; then
165
+ elif [[ "$complexity" -gt "$COMPLEXITY_HIGH" ]]; then
143
166
  model="opus"
144
167
  else
145
- # Default to sonnet for unknown stages
146
- model="sonnet"
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 &>/dev/null; then
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 &>/dev/null; then
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 "${HOME}/.shipwright"
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 &>/dev/null; then
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 &>/dev/null; then
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 &>/dev/null; then
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 &>/dev/null; then
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