shipwright-cli 1.7.1 → 1.9.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 +90 -0
- package/.claude/agents/devops-engineer.md +142 -0
- package/.claude/agents/pipeline-agent.md +80 -0
- package/.claude/agents/shell-script-specialist.md +150 -0
- package/.claude/agents/test-specialist.md +196 -0
- package/.claude/hooks/post-tool-use.sh +38 -0
- package/.claude/hooks/pre-tool-use.sh +25 -0
- package/.claude/hooks/session-started.sh +37 -0
- package/README.md +212 -814
- package/claude-code/CLAUDE.md.shipwright +54 -0
- package/claude-code/hooks/notify-idle.sh +2 -2
- package/claude-code/hooks/session-start.sh +24 -0
- package/claude-code/hooks/task-completed.sh +6 -2
- package/claude-code/settings.json.template +12 -0
- package/dashboard/public/app.js +4422 -0
- package/dashboard/public/index.html +816 -0
- package/dashboard/public/styles.css +4755 -0
- package/dashboard/server.ts +4315 -0
- package/docs/KNOWN-ISSUES.md +18 -10
- package/docs/TIPS.md +38 -26
- package/docs/patterns/README.md +33 -23
- package/package.json +9 -5
- package/scripts/adapters/iterm2-adapter.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +52 -23
- package/scripts/adapters/wezterm-adapter.sh +26 -14
- package/scripts/lib/compat.sh +200 -0
- package/scripts/lib/helpers.sh +72 -0
- package/scripts/postinstall.mjs +72 -13
- package/scripts/{cct → sw} +109 -21
- package/scripts/sw-adversarial.sh +274 -0
- package/scripts/sw-architecture-enforcer.sh +330 -0
- package/scripts/sw-checkpoint.sh +390 -0
- package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
- package/scripts/sw-connect.sh +619 -0
- package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
- package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
- package/scripts/sw-dashboard.sh +477 -0
- package/scripts/sw-developer-simulation.sh +252 -0
- package/scripts/sw-docs.sh +635 -0
- package/scripts/sw-doctor.sh +907 -0
- package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
- package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
- package/scripts/sw-github-checks.sh +521 -0
- package/scripts/sw-github-deploy.sh +533 -0
- package/scripts/sw-github-graphql.sh +972 -0
- package/scripts/sw-heartbeat.sh +293 -0
- package/scripts/{cct-init.sh → sw-init.sh} +144 -11
- package/scripts/sw-intelligence.sh +1196 -0
- package/scripts/sw-jira.sh +643 -0
- package/scripts/sw-launchd.sh +364 -0
- package/scripts/sw-linear.sh +648 -0
- package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
- package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
- package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
- package/scripts/sw-patrol-meta.sh +417 -0
- package/scripts/sw-pipeline-composer.sh +455 -0
- package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
- package/scripts/sw-predictive.sh +820 -0
- package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
- package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
- package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
- package/scripts/sw-remote.sh +687 -0
- package/scripts/sw-self-optimize.sh +947 -0
- package/scripts/sw-session.sh +519 -0
- package/scripts/sw-setup.sh +234 -0
- package/scripts/sw-status.sh +605 -0
- package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
- package/scripts/sw-tmux.sh +591 -0
- package/scripts/sw-tracker-jira.sh +277 -0
- package/scripts/sw-tracker-linear.sh +292 -0
- package/scripts/sw-tracker.sh +409 -0
- package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
- package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
- package/templates/pipelines/autonomous.json +27 -5
- package/templates/pipelines/full.json +12 -0
- package/templates/pipelines/standard.json +12 -0
- package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
- package/tmux/templates/accessibility.json +34 -0
- package/tmux/templates/api-design.json +35 -0
- package/tmux/templates/architecture.json +1 -0
- package/tmux/templates/bug-fix.json +9 -0
- package/tmux/templates/code-review.json +1 -0
- package/tmux/templates/compliance.json +36 -0
- package/tmux/templates/data-pipeline.json +36 -0
- package/tmux/templates/debt-paydown.json +34 -0
- package/tmux/templates/devops.json +1 -0
- package/tmux/templates/documentation.json +1 -0
- package/tmux/templates/exploration.json +1 -0
- package/tmux/templates/feature-dev.json +1 -0
- package/tmux/templates/full-stack.json +8 -0
- package/tmux/templates/i18n.json +34 -0
- package/tmux/templates/incident-response.json +36 -0
- package/tmux/templates/migration.json +1 -0
- package/tmux/templates/observability.json +35 -0
- package/tmux/templates/onboarding.json +33 -0
- package/tmux/templates/performance.json +35 -0
- package/tmux/templates/refactor.json +1 -0
- package/tmux/templates/release.json +35 -0
- package/tmux/templates/security-audit.json +8 -0
- package/tmux/templates/spike.json +34 -0
- package/tmux/templates/testing.json +1 -0
- package/tmux/tmux.conf +98 -9
- package/scripts/cct-doctor.sh +0 -414
- package/scripts/cct-session.sh +0 -284
- package/scripts/cct-status.sh +0 -169
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
# ║ Captures learnings · Injects context · Searches memory · Tracks metrics║
|
|
5
5
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
6
|
set -euo pipefail
|
|
7
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
7
8
|
|
|
8
|
-
VERSION="1.
|
|
9
|
+
VERSION="1.9.0"
|
|
9
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
12
|
|
|
@@ -20,6 +21,14 @@ DIM='\033[2m'
|
|
|
20
21
|
BOLD='\033[1m'
|
|
21
22
|
RESET='\033[0m'
|
|
22
23
|
|
|
24
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
25
|
+
# shellcheck source=lib/compat.sh
|
|
26
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
27
|
+
|
|
28
|
+
# ─── Intelligence Engine (optional) ──────────────────────────────────────────
|
|
29
|
+
# shellcheck source=sw-intelligence.sh
|
|
30
|
+
[[ -f "$SCRIPT_DIR/sw-intelligence.sh" ]] && source "$SCRIPT_DIR/sw-intelligence.sh"
|
|
31
|
+
|
|
23
32
|
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
24
33
|
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
25
34
|
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
@@ -30,7 +39,7 @@ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
|
30
39
|
now_epoch() { date +%s; }
|
|
31
40
|
|
|
32
41
|
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
33
|
-
EVENTS_FILE="${HOME}/.
|
|
42
|
+
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
34
43
|
|
|
35
44
|
emit_event() {
|
|
36
45
|
local event_type="$1"
|
|
@@ -46,7 +55,7 @@ emit_event() {
|
|
|
46
55
|
json_fields="${json_fields},\"${key}\":\"${val}\""
|
|
47
56
|
fi
|
|
48
57
|
done
|
|
49
|
-
mkdir -p "${HOME}/.
|
|
58
|
+
mkdir -p "${HOME}/.shipwright"
|
|
50
59
|
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -207,32 +216,100 @@ memory_capture_failure() {
|
|
|
207
216
|
'[.failures[]] | to_entries | map(select(.value.pattern == $pat)) | .[0].key // -1' \
|
|
208
217
|
"$failures_file" 2>/dev/null || echo "-1")
|
|
209
218
|
|
|
210
|
-
|
|
211
|
-
|
|
219
|
+
(
|
|
220
|
+
if command -v flock &>/dev/null; then
|
|
221
|
+
flock -w 10 200 2>/dev/null || { warn "Memory lock timeout"; }
|
|
222
|
+
fi
|
|
223
|
+
local tmp_file
|
|
224
|
+
tmp_file=$(mktemp "${failures_file}.tmp.XXXXXX")
|
|
225
|
+
|
|
226
|
+
if [[ "$existing_idx" != "-1" && "$existing_idx" != "null" ]]; then
|
|
227
|
+
# Update existing entry
|
|
228
|
+
jq --argjson idx "$existing_idx" \
|
|
229
|
+
--arg ts "$(now_iso)" \
|
|
230
|
+
'.failures[$idx].seen_count += 1 | .failures[$idx].last_seen = $ts' \
|
|
231
|
+
"$failures_file" > "$tmp_file" && mv "$tmp_file" "$failures_file" || rm -f "$tmp_file"
|
|
232
|
+
else
|
|
233
|
+
# Add new failure entry
|
|
234
|
+
jq --arg stage "$stage" \
|
|
235
|
+
--arg pattern "$pattern" \
|
|
236
|
+
--arg ts "$(now_iso)" \
|
|
237
|
+
'.failures += [{
|
|
238
|
+
stage: $stage,
|
|
239
|
+
pattern: $pattern,
|
|
240
|
+
root_cause: "",
|
|
241
|
+
fix: "",
|
|
242
|
+
seen_count: 1,
|
|
243
|
+
last_seen: $ts
|
|
244
|
+
}] | .failures = (.failures | .[-100:])' \
|
|
245
|
+
"$failures_file" > "$tmp_file" && mv "$tmp_file" "$failures_file" || rm -f "$tmp_file"
|
|
246
|
+
fi
|
|
247
|
+
) 200>"${failures_file}.lock"
|
|
212
248
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
249
|
+
emit_event "memory.failure" "stage=${stage}" "pattern=${pattern:0:80}"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# memory_record_fix_outcome <failure_hash_or_pattern> <fix_applied:bool> <fix_resolved:bool>
|
|
253
|
+
# Tracks whether suggested fixes actually worked. Builds effectiveness data
|
|
254
|
+
# so future memory injection can prioritize high-success-rate fixes.
|
|
255
|
+
memory_record_fix_outcome() {
|
|
256
|
+
local pattern_match="${1:-}"
|
|
257
|
+
local fix_applied="${2:-false}"
|
|
258
|
+
local fix_resolved="${3:-false}"
|
|
259
|
+
|
|
260
|
+
[[ -z "$pattern_match" ]] && return 1
|
|
261
|
+
|
|
262
|
+
ensure_memory_dir
|
|
263
|
+
local mem_dir
|
|
264
|
+
mem_dir="$(repo_memory_dir)"
|
|
265
|
+
local failures_file="$mem_dir/failures.json"
|
|
266
|
+
|
|
267
|
+
[[ ! -f "$failures_file" ]] && return 1
|
|
268
|
+
|
|
269
|
+
# Find matching failure by pattern substring
|
|
270
|
+
local match_idx
|
|
271
|
+
match_idx=$(jq --arg pat "$pattern_match" \
|
|
272
|
+
'[.failures[]] | to_entries | map(select(.value.pattern | contains($pat))) | .[0].key // -1' \
|
|
273
|
+
"$failures_file" 2>/dev/null || echo "-1")
|
|
274
|
+
|
|
275
|
+
if [[ "$match_idx" == "-1" || "$match_idx" == "null" ]]; then
|
|
276
|
+
warn "No matching failure found for: ${pattern_match:0:60}"
|
|
277
|
+
return 1
|
|
233
278
|
fi
|
|
234
279
|
|
|
235
|
-
|
|
280
|
+
# Update fix outcome tracking fields
|
|
281
|
+
local applied_inc=0 resolved_inc=0
|
|
282
|
+
[[ "$fix_applied" == "true" ]] && applied_inc=1
|
|
283
|
+
[[ "$fix_resolved" == "true" ]] && resolved_inc=1
|
|
284
|
+
|
|
285
|
+
(
|
|
286
|
+
if command -v flock &>/dev/null; then
|
|
287
|
+
flock -w 10 200 2>/dev/null || { warn "Memory lock timeout"; }
|
|
288
|
+
fi
|
|
289
|
+
local tmp_file
|
|
290
|
+
tmp_file=$(mktemp "${failures_file}.tmp.XXXXXX")
|
|
291
|
+
|
|
292
|
+
jq --argjson idx "$match_idx" \
|
|
293
|
+
--argjson app "$applied_inc" \
|
|
294
|
+
--argjson res "$resolved_inc" \
|
|
295
|
+
--arg ts "$(now_iso)" \
|
|
296
|
+
'.failures[$idx].times_fix_suggested = ((.failures[$idx].times_fix_suggested // 0) + 1) |
|
|
297
|
+
.failures[$idx].times_fix_applied = ((.failures[$idx].times_fix_applied // 0) + $app) |
|
|
298
|
+
.failures[$idx].times_fix_resolved = ((.failures[$idx].times_fix_resolved // 0) + $res) |
|
|
299
|
+
.failures[$idx].fix_effectiveness_rate = (
|
|
300
|
+
if ((.failures[$idx].times_fix_applied // 0) + $app) > 0 then
|
|
301
|
+
(((.failures[$idx].times_fix_resolved // 0) + $res) * 100 /
|
|
302
|
+
((.failures[$idx].times_fix_applied // 0) + $app))
|
|
303
|
+
else 0 end
|
|
304
|
+
) |
|
|
305
|
+
.failures[$idx].last_outcome_at = $ts' \
|
|
306
|
+
"$failures_file" > "$tmp_file" && mv "$tmp_file" "$failures_file" || rm -f "$tmp_file"
|
|
307
|
+
) 200>"${failures_file}.lock"
|
|
308
|
+
|
|
309
|
+
emit_event "memory.fix_outcome" \
|
|
310
|
+
"pattern=${pattern_match:0:60}" \
|
|
311
|
+
"applied=${fix_applied}" \
|
|
312
|
+
"resolved=${fix_resolved}"
|
|
236
313
|
}
|
|
237
314
|
|
|
238
315
|
# memory_analyze_failure <log_file> <stage>
|
|
@@ -281,16 +358,41 @@ memory_analyze_failure() {
|
|
|
281
358
|
|
|
282
359
|
info "Analyzing failure in ${CYAN}${stage}${RESET} stage..."
|
|
283
360
|
|
|
361
|
+
# Gather past successful analyses for the same stage/category as examples
|
|
362
|
+
local past_examples=""
|
|
363
|
+
if [[ -f "$failures_file" ]]; then
|
|
364
|
+
past_examples=$(jq -r --arg stg "$stage" \
|
|
365
|
+
'[.failures[] | select(.stage == $stg and .root_cause != "" and .fix != "")] |
|
|
366
|
+
sort_by(-.fix_effectiveness_rate // 0) | .[:2][] |
|
|
367
|
+
"- Pattern: \(.pattern[:80])\n Root cause: \(.root_cause)\n Fix: \(.fix)"' \
|
|
368
|
+
"$failures_file" 2>/dev/null || true)
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
# Build valid categories list (from compat.sh if available, else hardcoded)
|
|
372
|
+
local valid_cats="test_failure, build_error, lint_error, timeout, dependency, flaky, config"
|
|
373
|
+
if [[ -n "${SW_ERROR_CATEGORIES:-}" ]]; then
|
|
374
|
+
valid_cats=$(echo "$SW_ERROR_CATEGORIES" | tr ' ' ', ')
|
|
375
|
+
fi
|
|
376
|
+
|
|
284
377
|
# Build the analysis prompt
|
|
285
378
|
local prompt
|
|
286
379
|
prompt="Analyze this pipeline failure. The stage was: ${stage}.
|
|
287
380
|
The error pattern is: ${last_pattern}
|
|
288
381
|
|
|
289
382
|
Log output (last 200 lines):
|
|
290
|
-
${log_tail}
|
|
383
|
+
${log_tail}"
|
|
384
|
+
|
|
385
|
+
if [[ -n "$past_examples" ]]; then
|
|
386
|
+
prompt="${prompt}
|
|
387
|
+
|
|
388
|
+
Here are examples of how similar failures were diagnosed in this repo:
|
|
389
|
+
${past_examples}"
|
|
390
|
+
fi
|
|
391
|
+
|
|
392
|
+
prompt="${prompt}
|
|
291
393
|
|
|
292
394
|
Return ONLY a JSON object with exactly these fields:
|
|
293
|
-
{\"root_cause\": \"one-line root cause\", \"fix\": \"one-line fix suggestion\", \"category\": \"one of:
|
|
395
|
+
{\"root_cause\": \"one-line root cause\", \"fix\": \"one-line fix suggestion\", \"category\": \"one of: ${valid_cats}\"}
|
|
294
396
|
|
|
295
397
|
Return JSON only, no markdown fences, no explanation."
|
|
296
398
|
|
|
@@ -315,11 +417,17 @@ Return JSON only, no markdown fences, no explanation."
|
|
|
315
417
|
return 1
|
|
316
418
|
fi
|
|
317
419
|
|
|
318
|
-
# Validate category against
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
420
|
+
# Validate category against shared taxonomy (compat.sh) or built-in list
|
|
421
|
+
if type sw_valid_error_category &>/dev/null 2>&1; then
|
|
422
|
+
if ! sw_valid_error_category "$category"; then
|
|
423
|
+
category="unknown"
|
|
424
|
+
fi
|
|
425
|
+
else
|
|
426
|
+
case "$category" in
|
|
427
|
+
test_failure|build_error|lint_error|timeout|dependency|flaky|config) ;;
|
|
428
|
+
*) category="unknown" ;;
|
|
429
|
+
esac
|
|
430
|
+
fi
|
|
323
431
|
|
|
324
432
|
# Update the most recent failure entry with root_cause, fix, category
|
|
325
433
|
local tmp_file
|
|
@@ -478,9 +586,27 @@ memory_capture_pattern() {
|
|
|
478
586
|
|
|
479
587
|
# memory_inject_context <stage_id>
|
|
480
588
|
# Returns a text block of relevant memory for a given pipeline stage.
|
|
589
|
+
# When intelligence engine is available, uses AI-ranked search for better relevance.
|
|
481
590
|
memory_inject_context() {
|
|
482
591
|
local stage_id="${1:-}"
|
|
483
592
|
|
|
593
|
+
# Try intelligence-ranked search first
|
|
594
|
+
if type intelligence_search_memory &>/dev/null 2>&1; then
|
|
595
|
+
local config="${REPO_DIR:-.}/.claude/daemon-config.json"
|
|
596
|
+
local intel_enabled="false"
|
|
597
|
+
if [[ -f "$config" ]]; then
|
|
598
|
+
intel_enabled=$(jq -r '.intelligence.enabled // false' "$config" 2>/dev/null || echo "false")
|
|
599
|
+
fi
|
|
600
|
+
if [[ "$intel_enabled" == "true" ]]; then
|
|
601
|
+
local ranked_result
|
|
602
|
+
ranked_result=$(intelligence_search_memory "$stage_id stage context" "$(repo_memory_dir)" 5 2>/dev/null || echo "")
|
|
603
|
+
if [[ -n "$ranked_result" ]] && [[ "$ranked_result" != *'"error"'* ]]; then
|
|
604
|
+
echo "$ranked_result"
|
|
605
|
+
return 0
|
|
606
|
+
fi
|
|
607
|
+
fi
|
|
608
|
+
fi
|
|
609
|
+
|
|
484
610
|
ensure_memory_dir
|
|
485
611
|
local mem_dir
|
|
486
612
|
mem_dir="$(repo_memory_dir)"
|
|
@@ -537,12 +663,28 @@ memory_inject_context() {
|
|
|
537
663
|
;;
|
|
538
664
|
|
|
539
665
|
build)
|
|
540
|
-
# Failure patterns to avoid +
|
|
666
|
+
# Failure patterns to avoid — ranked by relevance (recency + effectiveness + frequency)
|
|
541
667
|
echo "## Failure Patterns to Avoid"
|
|
542
668
|
if [[ -f "$mem_dir/failures.json" ]]; then
|
|
543
|
-
jq -r '
|
|
669
|
+
jq -r 'now as $now |
|
|
670
|
+
.failures | map(. +
|
|
671
|
+
{ relevance_score:
|
|
672
|
+
((.seen_count // 1) * 1) +
|
|
673
|
+
(if .fix_effectiveness_rate then (.fix_effectiveness_rate / 10) else 0 end) +
|
|
674
|
+
(if .last_seen then
|
|
675
|
+
(($now - ((.last_seen | sub("\\.[0-9]+Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) // 0)) |
|
|
676
|
+
if . < 86400 then 5
|
|
677
|
+
elif . < 604800 then 3
|
|
678
|
+
elif . < 2592000 then 1
|
|
679
|
+
else 0 end)
|
|
680
|
+
else 0 end)
|
|
681
|
+
}
|
|
682
|
+
) | sort_by(-.relevance_score) | .[:10][] |
|
|
544
683
|
"- [\(.stage)] \(.pattern) (seen \(.seen_count)x)" +
|
|
545
|
-
if .fix != "" then
|
|
684
|
+
if .fix != "" then
|
|
685
|
+
"\n Fix: \(.fix)" +
|
|
686
|
+
if .fix_effectiveness_rate then " (effectiveness: \(.fix_effectiveness_rate)%)" else "" end
|
|
687
|
+
else "" end' \
|
|
546
688
|
"$mem_dir/failures.json" 2>/dev/null || echo "- No failures recorded."
|
|
547
689
|
fi
|
|
548
690
|
|
|
@@ -550,7 +692,8 @@ memory_inject_context() {
|
|
|
550
692
|
echo "## Known Fixes"
|
|
551
693
|
if [[ -f "$mem_dir/failures.json" ]]; then
|
|
552
694
|
jq -r '.failures[] | select(.root_cause != "" and .fix != "" and .stage == "build") |
|
|
553
|
-
"- [\(.category // "unknown")] \(.root_cause)\n Fix: \(.fix)"
|
|
695
|
+
"- [\(.category // "unknown")] \(.root_cause)\n Fix: \(.fix)" +
|
|
696
|
+
if .fix_effectiveness_rate then " (effectiveness: \(.fix_effectiveness_rate)%)" else "" end' \
|
|
554
697
|
"$mem_dir/failures.json" 2>/dev/null || echo "- No analyzed fixes yet."
|
|
555
698
|
else
|
|
556
699
|
echo "- No analyzed fixes yet."
|
|
@@ -618,12 +761,35 @@ memory_inject_context() {
|
|
|
618
761
|
;;
|
|
619
762
|
|
|
620
763
|
*)
|
|
621
|
-
# Generic context for any
|
|
764
|
+
# Generic context for any stage — inject top-K most relevant across all categories
|
|
622
765
|
echo "## Repository Patterns"
|
|
623
766
|
if [[ -f "$mem_dir/patterns.json" ]]; then
|
|
624
767
|
jq -r 'to_entries | map(select(.key != "known_issues")) | from_entries' \
|
|
625
768
|
"$mem_dir/patterns.json" 2>/dev/null || true
|
|
626
769
|
fi
|
|
770
|
+
|
|
771
|
+
# Inject top failures regardless of category (ranked by relevance)
|
|
772
|
+
echo ""
|
|
773
|
+
echo "## Relevant Failure Patterns"
|
|
774
|
+
if [[ -f "$mem_dir/failures.json" ]]; then
|
|
775
|
+
jq -r --arg stg "$stage_id" \
|
|
776
|
+
'.failures |
|
|
777
|
+
map(. + { stage_match: (if .stage == $stg then 10 else 0 end) }) |
|
|
778
|
+
sort_by(-(.seen_count + .stage_match + (.fix_effectiveness_rate // 0) / 10)) |
|
|
779
|
+
.[:5][] |
|
|
780
|
+
"- [\(.stage)] \(.pattern[:80]) (seen \(.seen_count)x)" +
|
|
781
|
+
if .fix != "" then "\n Fix: \(.fix)" else "" end' \
|
|
782
|
+
"$mem_dir/failures.json" 2>/dev/null || echo "- None recorded."
|
|
783
|
+
fi
|
|
784
|
+
|
|
785
|
+
# Inject recent decisions
|
|
786
|
+
echo ""
|
|
787
|
+
echo "## Recent Decisions"
|
|
788
|
+
if [[ -f "$mem_dir/decisions.json" ]]; then
|
|
789
|
+
jq -r '.decisions[-3:][] |
|
|
790
|
+
"- [\(.type // "decision")] \(.summary // "no description")"' \
|
|
791
|
+
"$mem_dir/decisions.json" 2>/dev/null || echo "- None recorded."
|
|
792
|
+
fi
|
|
627
793
|
;;
|
|
628
794
|
esac
|
|
629
795
|
|
|
@@ -631,6 +797,91 @@ memory_inject_context() {
|
|
|
631
797
|
emit_event "memory.inject" "stage=${stage_id}"
|
|
632
798
|
}
|
|
633
799
|
|
|
800
|
+
# memory_get_actionable_failures [threshold]
|
|
801
|
+
# Returns JSON array of failure patterns with seen_count >= threshold.
|
|
802
|
+
# Used by daemon patrol to detect recurring failures worth fixing.
|
|
803
|
+
memory_get_actionable_failures() {
|
|
804
|
+
local threshold="${1:-3}"
|
|
805
|
+
|
|
806
|
+
ensure_memory_dir
|
|
807
|
+
local mem_dir
|
|
808
|
+
mem_dir="$(repo_memory_dir)"
|
|
809
|
+
local failures_file="$mem_dir/failures.json"
|
|
810
|
+
|
|
811
|
+
if [[ ! -f "$failures_file" ]]; then
|
|
812
|
+
echo "[]"
|
|
813
|
+
return 0
|
|
814
|
+
fi
|
|
815
|
+
|
|
816
|
+
jq --argjson t "$threshold" \
|
|
817
|
+
'[.failures[] | select(.seen_count >= $t)] | sort_by(-.seen_count)' \
|
|
818
|
+
"$failures_file" 2>/dev/null || echo "[]"
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
# memory_get_dora_baseline [window_days] [offset_days]
|
|
822
|
+
# Calculates DORA metrics for a time window from events.jsonl.
|
|
823
|
+
# Returns JSON: {deploy_freq, cycle_time, cfr, mttr, total, grades: {df, ct, cfr, mttr}}
|
|
824
|
+
memory_get_dora_baseline() {
|
|
825
|
+
local window_days="${1:-7}"
|
|
826
|
+
local offset_days="${2:-0}"
|
|
827
|
+
|
|
828
|
+
local events_file="${HOME}/.shipwright/events.jsonl"
|
|
829
|
+
if [[ ! -f "$events_file" ]]; then
|
|
830
|
+
echo '{"deploy_freq":0,"cycle_time":0,"cfr":0,"mttr":0,"total":0}'
|
|
831
|
+
return 0
|
|
832
|
+
fi
|
|
833
|
+
|
|
834
|
+
local now_e
|
|
835
|
+
now_e=$(now_epoch)
|
|
836
|
+
local window_end=$((now_e - offset_days * 86400))
|
|
837
|
+
local window_start=$((window_end - window_days * 86400))
|
|
838
|
+
|
|
839
|
+
# Extract pipeline events for the window
|
|
840
|
+
local metrics
|
|
841
|
+
metrics=$(jq -s --argjson start "$window_start" --argjson end "$window_end" '
|
|
842
|
+
[.[] | select(.ts_epoch >= $start and .ts_epoch < $end)] as $events |
|
|
843
|
+
[$events[] | select(.type == "pipeline.completed")] as $completed |
|
|
844
|
+
($completed | length) as $total |
|
|
845
|
+
[$completed[] | select(.result == "success")] as $successes |
|
|
846
|
+
[$completed[] | select(.result == "failure")] as $failures |
|
|
847
|
+
($successes | length) as $success_count |
|
|
848
|
+
($failures | length) as $failure_count |
|
|
849
|
+
|
|
850
|
+
# Deploy frequency (per week)
|
|
851
|
+
(if $total > 0 then ($success_count * 7 / '"$window_days"') else 0 end) as $deploy_freq |
|
|
852
|
+
|
|
853
|
+
# Cycle time median
|
|
854
|
+
([$successes[] | .duration_s] | sort |
|
|
855
|
+
if length > 0 then .[length/2 | floor] else 0 end) as $cycle_time |
|
|
856
|
+
|
|
857
|
+
# Change failure rate
|
|
858
|
+
(if $total > 0 then ($failure_count / $total * 100) else 0 end) as $cfr |
|
|
859
|
+
|
|
860
|
+
# MTTR
|
|
861
|
+
($completed | sort_by(.ts_epoch // 0) |
|
|
862
|
+
[range(length) as $i |
|
|
863
|
+
if .[$i].result == "failure" then
|
|
864
|
+
[.[$i+1:][] | select(.result == "success")][0] as $next |
|
|
865
|
+
if $next and $next.ts_epoch and .[$i].ts_epoch then
|
|
866
|
+
($next.ts_epoch - .[$i].ts_epoch)
|
|
867
|
+
else null end
|
|
868
|
+
else null end
|
|
869
|
+
] | map(select(. != null)) |
|
|
870
|
+
if length > 0 then (add / length | floor) else 0 end
|
|
871
|
+
) as $mttr |
|
|
872
|
+
|
|
873
|
+
{
|
|
874
|
+
deploy_freq: ($deploy_freq * 10 | floor / 10),
|
|
875
|
+
cycle_time: $cycle_time,
|
|
876
|
+
cfr: ($cfr * 10 | floor / 10),
|
|
877
|
+
mttr: $mttr,
|
|
878
|
+
total: $total
|
|
879
|
+
}
|
|
880
|
+
' "$events_file" 2>/dev/null || echo '{"deploy_freq":0,"cycle_time":0,"cfr":0,"mttr":0,"total":0}')
|
|
881
|
+
|
|
882
|
+
echo "$metrics"
|
|
883
|
+
}
|
|
884
|
+
|
|
634
885
|
# memory_update_metrics <metric_name> <value>
|
|
635
886
|
# Track performance baselines and flag regressions.
|
|
636
887
|
memory_update_metrics() {
|
|
@@ -838,6 +1089,34 @@ memory_search() {
|
|
|
838
1089
|
|
|
839
1090
|
local found=0
|
|
840
1091
|
|
|
1092
|
+
# ── Semantic search via intelligence (if available) ──
|
|
1093
|
+
if type intelligence_search_memory &>/dev/null 2>&1; then
|
|
1094
|
+
local semantic_results
|
|
1095
|
+
semantic_results=$(intelligence_search_memory "$keyword" "$mem_dir" 5 2>/dev/null || echo "")
|
|
1096
|
+
if [[ -n "$semantic_results" ]] && echo "$semantic_results" | jq -e '.results | length > 0' &>/dev/null; then
|
|
1097
|
+
echo -e " ${BOLD}${CYAN}Semantic Results (AI-ranked):${RESET}"
|
|
1098
|
+
local result_count
|
|
1099
|
+
result_count=$(echo "$semantic_results" | jq '.results | length')
|
|
1100
|
+
local i=0
|
|
1101
|
+
while [[ "$i" -lt "$result_count" ]]; do
|
|
1102
|
+
local file rel summary
|
|
1103
|
+
file=$(echo "$semantic_results" | jq -r ".results[$i].file // \"\"")
|
|
1104
|
+
rel=$(echo "$semantic_results" | jq -r ".results[$i].relevance // 0")
|
|
1105
|
+
summary=$(echo "$semantic_results" | jq -r ".results[$i].summary // \"\"")
|
|
1106
|
+
echo -e " ${GREEN}●${RESET} [${rel}%] ${BOLD}${file}${RESET} — ${summary}"
|
|
1107
|
+
i=$((i + 1))
|
|
1108
|
+
done
|
|
1109
|
+
echo ""
|
|
1110
|
+
found=$((found + 1))
|
|
1111
|
+
|
|
1112
|
+
# Also run grep search below for completeness
|
|
1113
|
+
echo -e " ${DIM}Grep results (supplemental):${RESET}"
|
|
1114
|
+
echo ""
|
|
1115
|
+
fi
|
|
1116
|
+
fi
|
|
1117
|
+
|
|
1118
|
+
# ── Grep-based search (fallback / supplemental) ──
|
|
1119
|
+
|
|
841
1120
|
# Search patterns
|
|
842
1121
|
if [[ -f "$mem_dir/patterns.json" ]]; then
|
|
843
1122
|
local pattern_matches
|
|
@@ -1083,6 +1362,7 @@ show_help() {
|
|
|
1083
1362
|
echo -e " ${CYAN}metric${RESET} <name> <value> Update a performance baseline"
|
|
1084
1363
|
echo -e " ${CYAN}decision${RESET} <type> <summary> Record a design decision"
|
|
1085
1364
|
echo -e " ${CYAN}analyze-failure${RESET} <log> <stage> Analyze failure root cause via AI"
|
|
1365
|
+
echo -e " ${CYAN}fix-outcome${RESET} <pattern> <applied> <resolved> Record fix effectiveness"
|
|
1086
1366
|
echo ""
|
|
1087
1367
|
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
1088
1368
|
echo -e " ${DIM}shipwright memory show${RESET} # View repo memory"
|
|
@@ -1136,6 +1416,9 @@ case "$SUBCOMMAND" in
|
|
|
1136
1416
|
analyze-failure)
|
|
1137
1417
|
memory_analyze_failure "$@"
|
|
1138
1418
|
;;
|
|
1419
|
+
fix-outcome)
|
|
1420
|
+
memory_record_fix_outcome "$@"
|
|
1421
|
+
;;
|
|
1139
1422
|
help|--help|-h)
|
|
1140
1423
|
show_help
|
|
1141
1424
|
;;
|