shipwright-cli 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/code-reviewer.md +2 -0
- package/.claude/agents/devops-engineer.md +2 -0
- package/.claude/agents/doc-fleet-agent.md +2 -0
- package/.claude/agents/pipeline-agent.md +2 -0
- package/.claude/agents/shell-script-specialist.md +2 -0
- package/.claude/agents/test-specialist.md +2 -0
- package/.claude/hooks/agent-crash-capture.sh +32 -0
- package/.claude/hooks/post-tool-use.sh +3 -2
- package/.claude/hooks/pre-tool-use.sh +35 -3
- package/README.md +4 -4
- package/claude-code/hooks/config-change.sh +18 -0
- package/claude-code/hooks/instructions-reloaded.sh +7 -0
- package/claude-code/hooks/worktree-create.sh +25 -0
- package/claude-code/hooks/worktree-remove.sh +20 -0
- package/config/code-constitution.json +130 -0
- package/dashboard/middleware/auth.ts +134 -0
- package/dashboard/middleware/constants.ts +21 -0
- package/dashboard/public/index.html +2 -6
- package/dashboard/public/styles.css +100 -97
- package/dashboard/routes/auth.ts +38 -0
- package/dashboard/server.ts +66 -25
- package/dashboard/services/config.ts +26 -0
- package/dashboard/services/db.ts +118 -0
- package/dashboard/src/canvas/pixel-agent.ts +298 -0
- package/dashboard/src/canvas/pixel-sprites.ts +440 -0
- package/dashboard/src/canvas/shipyard-effects.ts +367 -0
- package/dashboard/src/canvas/shipyard-scene.ts +616 -0
- package/dashboard/src/canvas/submarine-layout.ts +267 -0
- package/dashboard/src/components/header.ts +8 -7
- package/dashboard/src/core/router.ts +1 -0
- package/dashboard/src/design/submarine-theme.ts +253 -0
- package/dashboard/src/main.ts +2 -0
- package/dashboard/src/types/api.ts +2 -1
- package/dashboard/src/views/activity.ts +2 -1
- package/dashboard/src/views/shipyard.ts +39 -0
- package/dashboard/types/index.ts +166 -0
- package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
- package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
- package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
- package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
- package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
- package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
- package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
- package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
- package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
- package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
- package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
- package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
- package/docs/research/RESEARCH_INDEX.md +439 -0
- package/docs/research/RESEARCH_SOURCES.md +440 -0
- package/docs/research/RESEARCH_SUMMARY.txt +275 -0
- package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
- package/package.json +2 -2
- package/scripts/lib/adaptive-model.sh +427 -0
- package/scripts/lib/adaptive-timeout.sh +316 -0
- package/scripts/lib/audit-trail.sh +309 -0
- package/scripts/lib/auto-recovery.sh +471 -0
- package/scripts/lib/bandit-selector.sh +431 -0
- package/scripts/lib/bootstrap.sh +104 -2
- package/scripts/lib/causal-graph.sh +455 -0
- package/scripts/lib/compat.sh +126 -0
- package/scripts/lib/compound-audit.sh +337 -0
- package/scripts/lib/constitutional.sh +454 -0
- package/scripts/lib/context-budget.sh +359 -0
- package/scripts/lib/convergence.sh +594 -0
- package/scripts/lib/cost-optimizer.sh +634 -0
- package/scripts/lib/daemon-adaptive.sh +10 -0
- package/scripts/lib/daemon-dispatch.sh +106 -17
- package/scripts/lib/daemon-failure.sh +34 -4
- package/scripts/lib/daemon-patrol.sh +23 -2
- package/scripts/lib/daemon-poll-github.sh +361 -0
- package/scripts/lib/daemon-poll-health.sh +299 -0
- package/scripts/lib/daemon-poll.sh +27 -611
- package/scripts/lib/daemon-state.sh +112 -66
- package/scripts/lib/daemon-triage.sh +10 -0
- package/scripts/lib/dod-scorecard.sh +442 -0
- package/scripts/lib/error-actionability.sh +300 -0
- package/scripts/lib/formal-spec.sh +461 -0
- package/scripts/lib/helpers.sh +177 -4
- package/scripts/lib/intent-analysis.sh +409 -0
- package/scripts/lib/loop-convergence.sh +350 -0
- package/scripts/lib/loop-iteration.sh +682 -0
- package/scripts/lib/loop-progress.sh +48 -0
- package/scripts/lib/loop-restart.sh +185 -0
- package/scripts/lib/memory-effectiveness.sh +506 -0
- package/scripts/lib/mutation-executor.sh +352 -0
- package/scripts/lib/outcome-feedback.sh +521 -0
- package/scripts/lib/pipeline-cli.sh +336 -0
- package/scripts/lib/pipeline-commands.sh +1216 -0
- package/scripts/lib/pipeline-detection.sh +100 -2
- package/scripts/lib/pipeline-execution.sh +897 -0
- package/scripts/lib/pipeline-github.sh +28 -3
- package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
- package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
- package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
- package/scripts/lib/pipeline-intelligence.sh +100 -1136
- package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
- package/scripts/lib/pipeline-quality-checks.sh +17 -715
- package/scripts/lib/pipeline-quality-gates.sh +563 -0
- package/scripts/lib/pipeline-stages-build.sh +730 -0
- package/scripts/lib/pipeline-stages-delivery.sh +965 -0
- package/scripts/lib/pipeline-stages-intake.sh +1133 -0
- package/scripts/lib/pipeline-stages-monitor.sh +407 -0
- package/scripts/lib/pipeline-stages-review.sh +1022 -0
- package/scripts/lib/pipeline-stages.sh +59 -2929
- package/scripts/lib/pipeline-state.sh +36 -5
- package/scripts/lib/pipeline-util.sh +487 -0
- package/scripts/lib/policy-learner.sh +438 -0
- package/scripts/lib/process-reward.sh +493 -0
- package/scripts/lib/project-detect.sh +649 -0
- package/scripts/lib/quality-profile.sh +334 -0
- package/scripts/lib/recruit-commands.sh +885 -0
- package/scripts/lib/recruit-learning.sh +739 -0
- package/scripts/lib/recruit-roles.sh +648 -0
- package/scripts/lib/reward-aggregator.sh +458 -0
- package/scripts/lib/rl-optimizer.sh +362 -0
- package/scripts/lib/root-cause.sh +427 -0
- package/scripts/lib/scope-enforcement.sh +445 -0
- package/scripts/lib/session-restart.sh +493 -0
- package/scripts/lib/skill-memory.sh +300 -0
- package/scripts/lib/skill-registry.sh +775 -0
- package/scripts/lib/spec-driven.sh +476 -0
- package/scripts/lib/test-helpers.sh +18 -7
- package/scripts/lib/test-holdout.sh +429 -0
- package/scripts/lib/test-optimizer.sh +511 -0
- package/scripts/shipwright-file-suggest.sh +45 -0
- package/scripts/skills/adversarial-quality.md +61 -0
- package/scripts/skills/api-design.md +44 -0
- package/scripts/skills/architecture-design.md +50 -0
- package/scripts/skills/brainstorming.md +43 -0
- package/scripts/skills/data-pipeline.md +44 -0
- package/scripts/skills/deploy-safety.md +64 -0
- package/scripts/skills/documentation.md +38 -0
- package/scripts/skills/frontend-design.md +45 -0
- package/scripts/skills/generated/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
- package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
- package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
- package/scripts/skills/generated/cli-version-management.md +29 -0
- package/scripts/skills/generated/collection-system-validation.md +99 -0
- package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
- package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
- package/scripts/skills/generated/test-parallelization-detection.md +65 -0
- package/scripts/skills/observability.md +79 -0
- package/scripts/skills/performance.md +48 -0
- package/scripts/skills/pr-quality.md +49 -0
- package/scripts/skills/product-thinking.md +43 -0
- package/scripts/skills/security-audit.md +49 -0
- package/scripts/skills/systematic-debugging.md +40 -0
- package/scripts/skills/testing-strategy.md +47 -0
- package/scripts/skills/two-stage-review.md +52 -0
- package/scripts/skills/validation-thoroughness.md +55 -0
- package/scripts/sw +9 -3
- package/scripts/sw-activity.sh +9 -2
- package/scripts/sw-adaptive.sh +2 -1
- package/scripts/sw-adversarial.sh +2 -1
- package/scripts/sw-architecture-enforcer.sh +3 -1
- package/scripts/sw-auth.sh +12 -2
- package/scripts/sw-autonomous.sh +5 -1
- package/scripts/sw-changelog.sh +4 -1
- package/scripts/sw-checkpoint.sh +2 -1
- package/scripts/sw-ci.sh +5 -1
- package/scripts/sw-cleanup.sh +4 -26
- package/scripts/sw-code-review.sh +10 -4
- package/scripts/sw-connect.sh +2 -1
- package/scripts/sw-context.sh +2 -1
- package/scripts/sw-cost.sh +48 -3
- package/scripts/sw-daemon.sh +66 -9
- package/scripts/sw-dashboard.sh +3 -1
- package/scripts/sw-db.sh +59 -16
- package/scripts/sw-decide.sh +8 -2
- package/scripts/sw-decompose.sh +360 -17
- package/scripts/sw-deps.sh +4 -1
- package/scripts/sw-developer-simulation.sh +4 -1
- package/scripts/sw-discovery.sh +325 -2
- package/scripts/sw-doc-fleet.sh +4 -1
- package/scripts/sw-docs-agent.sh +3 -1
- package/scripts/sw-docs.sh +2 -1
- package/scripts/sw-doctor.sh +453 -2
- package/scripts/sw-dora.sh +4 -1
- package/scripts/sw-durable.sh +4 -3
- package/scripts/sw-e2e-orchestrator.sh +17 -16
- package/scripts/sw-eventbus.sh +7 -1
- package/scripts/sw-evidence.sh +364 -12
- package/scripts/sw-feedback.sh +550 -9
- package/scripts/sw-fix.sh +20 -1
- package/scripts/sw-fleet-discover.sh +6 -2
- package/scripts/sw-fleet-viz.sh +4 -1
- package/scripts/sw-fleet.sh +5 -1
- package/scripts/sw-github-app.sh +16 -3
- package/scripts/sw-github-checks.sh +3 -2
- package/scripts/sw-github-deploy.sh +3 -2
- package/scripts/sw-github-graphql.sh +18 -7
- package/scripts/sw-guild.sh +5 -1
- package/scripts/sw-heartbeat.sh +5 -30
- package/scripts/sw-hello.sh +67 -0
- package/scripts/sw-hygiene.sh +6 -1
- package/scripts/sw-incident.sh +265 -1
- package/scripts/sw-init.sh +18 -2
- package/scripts/sw-instrument.sh +10 -2
- package/scripts/sw-intelligence.sh +42 -6
- package/scripts/sw-jira.sh +5 -1
- package/scripts/sw-launchd.sh +2 -1
- package/scripts/sw-linear.sh +4 -1
- package/scripts/sw-logs.sh +4 -1
- package/scripts/sw-loop.sh +432 -1128
- package/scripts/sw-memory.sh +356 -2
- package/scripts/sw-mission-control.sh +6 -1
- package/scripts/sw-model-router.sh +481 -26
- package/scripts/sw-otel.sh +13 -4
- package/scripts/sw-oversight.sh +14 -5
- package/scripts/sw-patrol-meta.sh +334 -0
- package/scripts/sw-pipeline-composer.sh +5 -1
- package/scripts/sw-pipeline-vitals.sh +2 -1
- package/scripts/sw-pipeline.sh +53 -2664
- package/scripts/sw-pm.sh +12 -5
- package/scripts/sw-pr-lifecycle.sh +2 -1
- package/scripts/sw-predictive.sh +7 -1
- package/scripts/sw-prep.sh +185 -2
- package/scripts/sw-ps.sh +5 -25
- package/scripts/sw-public-dashboard.sh +15 -3
- package/scripts/sw-quality.sh +2 -1
- package/scripts/sw-reaper.sh +8 -25
- package/scripts/sw-recruit.sh +156 -2303
- package/scripts/sw-regression.sh +19 -12
- package/scripts/sw-release-manager.sh +3 -1
- package/scripts/sw-release.sh +4 -1
- package/scripts/sw-remote.sh +3 -1
- package/scripts/sw-replay.sh +7 -1
- package/scripts/sw-retro.sh +158 -1
- package/scripts/sw-review-rerun.sh +3 -1
- package/scripts/sw-scale.sh +10 -3
- package/scripts/sw-security-audit.sh +6 -1
- package/scripts/sw-self-optimize.sh +6 -3
- package/scripts/sw-session.sh +9 -3
- package/scripts/sw-setup.sh +3 -1
- package/scripts/sw-stall-detector.sh +406 -0
- package/scripts/sw-standup.sh +15 -7
- package/scripts/sw-status.sh +3 -1
- package/scripts/sw-strategic.sh +4 -1
- package/scripts/sw-stream.sh +7 -1
- package/scripts/sw-swarm.sh +18 -6
- package/scripts/sw-team-stages.sh +13 -6
- package/scripts/sw-templates.sh +5 -29
- package/scripts/sw-testgen.sh +7 -1
- package/scripts/sw-tmux-pipeline.sh +4 -1
- package/scripts/sw-tmux-role-color.sh +2 -0
- package/scripts/sw-tmux-status.sh +1 -1
- package/scripts/sw-tmux.sh +3 -1
- package/scripts/sw-trace.sh +3 -1
- package/scripts/sw-tracker-github.sh +3 -0
- package/scripts/sw-tracker-jira.sh +3 -0
- package/scripts/sw-tracker-linear.sh +3 -0
- package/scripts/sw-tracker.sh +3 -1
- package/scripts/sw-triage.sh +2 -1
- package/scripts/sw-upgrade.sh +3 -1
- package/scripts/sw-ux.sh +5 -2
- package/scripts/sw-webhook.sh +3 -1
- package/scripts/sw-widgets.sh +3 -1
- package/scripts/sw-worktree.sh +15 -3
- package/scripts/test-skill-injection.sh +1233 -0
- package/templates/pipelines/autonomous.json +27 -3
- package/templates/pipelines/cost-aware.json +34 -8
- package/templates/pipelines/deployed.json +12 -0
- package/templates/pipelines/enterprise.json +12 -0
- package/templates/pipelines/fast.json +6 -0
- package/templates/pipelines/full.json +27 -3
- package/templates/pipelines/hotfix.json +6 -0
- package/templates/pipelines/standard.json +12 -0
- package/templates/pipelines/tdd.json +12 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ test-optimizer — Test execution optimization: parallel, affected-first, ║
|
|
4
|
+
# ║ fast-fail, with historical data and learning ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
#
|
|
7
|
+
# Functions:
|
|
8
|
+
# testopt_init Initialize test discovery and history loading
|
|
9
|
+
# testopt_select_affected Select tests affected by changed files
|
|
10
|
+
# testopt_prioritize Order tests by likelihood to fail
|
|
11
|
+
# testopt_run_with_fast_fail Execute with stop-on-first-fail
|
|
12
|
+
# testopt_run_parallel Execute independent tests in parallel
|
|
13
|
+
# testopt_record_history Record results for learning
|
|
14
|
+
# testopt_report Print optimization stats
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# source scripts/lib/test-optimizer.sh
|
|
18
|
+
# testopt_init <project_root>
|
|
19
|
+
# testopt_record_history "test_file" "pass/fail" "duration" "changed_files"
|
|
20
|
+
# testopt_report
|
|
21
|
+
#
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
# Module guard
|
|
25
|
+
[[ -n "${_TEST_OPTIMIZER_LOADED:-}" ]] && return 0; _TEST_OPTIMIZER_LOADED=1
|
|
26
|
+
|
|
27
|
+
# ─── Defaults ──────────────────────────────────────────────────────────────
|
|
28
|
+
SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
|
|
29
|
+
|
|
30
|
+
# State: discovered test files, historical data, changed files
|
|
31
|
+
declare -a DISCOVERED_TESTS=()
|
|
32
|
+
declare -a AFFECTED_TESTS=()
|
|
33
|
+
declare -a TEST_HISTORY=()
|
|
34
|
+
declare -a CHANGED_FILES=()
|
|
35
|
+
|
|
36
|
+
TESTOPT_HISTORY_FILE="${HOME}/.shipwright/optimization/test-history.jsonl"
|
|
37
|
+
TESTOPT_PROJECT_ROOT=""
|
|
38
|
+
TESTOPT_STATS_TESTS_RUN=0
|
|
39
|
+
TESTOPT_STATS_TESTS_SKIPPED=0
|
|
40
|
+
TESTOPT_STATS_TIME_SAVED=0
|
|
41
|
+
TESTOPT_STATS_FAIL_EARLY="false"
|
|
42
|
+
|
|
43
|
+
# Ensure helpers
|
|
44
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
45
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
46
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
47
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
48
|
+
[[ "$(type -t emit_event 2>/dev/null)" == "function" ]] || emit_event() { true; }
|
|
49
|
+
|
|
50
|
+
# ─── Test Discovery ────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
# Discover all test files in a project
|
|
53
|
+
# testopt_discover_tests <project_root>
|
|
54
|
+
testopt_discover_tests() {
|
|
55
|
+
local project_root="${1:-.}"
|
|
56
|
+
[[ ! -d "$project_root" ]] && { error "Project root not found: $project_root"; return 1; }
|
|
57
|
+
|
|
58
|
+
DISCOVERED_TESTS=()
|
|
59
|
+
|
|
60
|
+
# Pattern 1: *-test.sh
|
|
61
|
+
while IFS= read -r test_file; do
|
|
62
|
+
[[ -f "$test_file" ]] && DISCOVERED_TESTS+=("$test_file")
|
|
63
|
+
done < <(find "$project_root" -name "*-test.sh" -type f 2>/dev/null || true)
|
|
64
|
+
|
|
65
|
+
# Pattern 2: *_test.sh
|
|
66
|
+
while IFS= read -r test_file; do
|
|
67
|
+
[[ -f "$test_file" ]] && DISCOVERED_TESTS+=("$test_file")
|
|
68
|
+
done < <(find "$project_root" -name "*_test.sh" -type f 2>/dev/null || true)
|
|
69
|
+
|
|
70
|
+
# Pattern 3: test_*.sh
|
|
71
|
+
while IFS= read -r test_file; do
|
|
72
|
+
[[ -f "$test_file" ]] && DISCOVERED_TESTS+=("$test_file")
|
|
73
|
+
done < <(find "$project_root" -name "test_*.sh" -type f 2>/dev/null || true)
|
|
74
|
+
|
|
75
|
+
# Deduplicate
|
|
76
|
+
local IFS=$'\n'
|
|
77
|
+
DISCOVERED_TESTS=($(sort -u <<<"${DISCOVERED_TESTS[*]}" 2>/dev/null || true))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ─── History Loading ──────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
# Load historical test data
|
|
83
|
+
testopt_load_history() {
|
|
84
|
+
TEST_HISTORY=()
|
|
85
|
+
if [[ ! -f "$TESTOPT_HISTORY_FILE" ]]; then
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
while IFS= read -r line; do
|
|
90
|
+
[[ -z "$line" ]] && continue
|
|
91
|
+
TEST_HISTORY+=("$line")
|
|
92
|
+
done < "$TESTOPT_HISTORY_FILE"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Query history for a test file: returns duration, or 0 if not found
|
|
96
|
+
testopt_get_historical_duration() {
|
|
97
|
+
local test_file="$1"
|
|
98
|
+
for entry in "${TEST_HISTORY[@]:-}"; do
|
|
99
|
+
local file
|
|
100
|
+
file=$(echo "$entry" | jq -r '.test_file // empty' 2>/dev/null || true)
|
|
101
|
+
if [[ "$file" == "$test_file" ]]; then
|
|
102
|
+
echo "$entry" | jq -r '.duration_s // 0' 2>/dev/null || echo 0
|
|
103
|
+
return 0
|
|
104
|
+
fi
|
|
105
|
+
done
|
|
106
|
+
echo 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Query history for fail rate (0.0-1.0)
|
|
110
|
+
testopt_get_fail_rate() {
|
|
111
|
+
local test_file="$1"
|
|
112
|
+
local pass_count=0
|
|
113
|
+
local fail_count=0
|
|
114
|
+
|
|
115
|
+
for entry in "${TEST_HISTORY[@]:-}"; do
|
|
116
|
+
local file result
|
|
117
|
+
file=$(echo "$entry" | jq -r '.test_file // empty' 2>/dev/null || true)
|
|
118
|
+
result=$(echo "$entry" | jq -r '.result // empty' 2>/dev/null || true)
|
|
119
|
+
if [[ "$file" == "$test_file" ]]; then
|
|
120
|
+
if [[ "$result" == "pass" ]]; then
|
|
121
|
+
pass_count=$((pass_count + 1))
|
|
122
|
+
elif [[ "$result" == "fail" ]]; then
|
|
123
|
+
fail_count=$((fail_count + 1))
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
local total=$((pass_count + fail_count))
|
|
129
|
+
if [[ "$total" -eq 0 ]]; then
|
|
130
|
+
echo "0.0"
|
|
131
|
+
else
|
|
132
|
+
# Return fail_count/total as float (bash approximation)
|
|
133
|
+
echo "$fail_count" | awk -v total="$total" '{ printf "%.2f", $1 / total }'
|
|
134
|
+
fi
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# ─── Affected Test Selection ───────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
# Detect which files changed between revisions
|
|
140
|
+
# testopt_get_changed_files [<from_ref> <to_ref>]
|
|
141
|
+
testopt_get_changed_files() {
|
|
142
|
+
local from_ref="${1:-HEAD~1}"
|
|
143
|
+
local to_ref="${2:-HEAD}"
|
|
144
|
+
|
|
145
|
+
CHANGED_FILES=()
|
|
146
|
+
|
|
147
|
+
# Try git diff first
|
|
148
|
+
if command -v git >/dev/null 2>&1; then
|
|
149
|
+
while IFS= read -r file; do
|
|
150
|
+
[[ -n "$file" ]] && CHANGED_FILES+=("$file")
|
|
151
|
+
done < <(git diff --name-only "$from_ref" "$to_ref" 2>/dev/null || true)
|
|
152
|
+
fi
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Map changed files to affected test files
|
|
156
|
+
# Returns: test files that import/source changed files or are in same directory
|
|
157
|
+
testopt_select_affected() {
|
|
158
|
+
local -a changed_files=("$@")
|
|
159
|
+
AFFECTED_TESTS=()
|
|
160
|
+
|
|
161
|
+
if [[ ${#changed_files[@]} -eq 0 ]]; then
|
|
162
|
+
# No changes detected, return all tests
|
|
163
|
+
AFFECTED_TESTS=("${DISCOVERED_TESTS[@]}")
|
|
164
|
+
return 0
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# Extract directories from changed files
|
|
168
|
+
declare -a changed_dirs=()
|
|
169
|
+
for file in "${changed_files[@]}"; do
|
|
170
|
+
local dir
|
|
171
|
+
dir=$(dirname "$file")
|
|
172
|
+
changed_dirs+=("$dir")
|
|
173
|
+
done
|
|
174
|
+
|
|
175
|
+
# For each discovered test, check if it's affected
|
|
176
|
+
for test_file in "${DISCOVERED_TESTS[@]}"; do
|
|
177
|
+
local test_dir
|
|
178
|
+
test_dir=$(dirname "$test_file")
|
|
179
|
+
|
|
180
|
+
local is_affected=0
|
|
181
|
+
|
|
182
|
+
# Check 1: Test in same directory as changed file
|
|
183
|
+
for dir in "${changed_dirs[@]}"; do
|
|
184
|
+
if [[ "$test_dir" == "$dir" ]]; then
|
|
185
|
+
is_affected=1
|
|
186
|
+
break
|
|
187
|
+
fi
|
|
188
|
+
done
|
|
189
|
+
|
|
190
|
+
# Check 2: Test sources/imports changed file
|
|
191
|
+
if [[ "$is_affected" -eq 0 ]]; then
|
|
192
|
+
for changed_file in "${changed_files[@]}"; do
|
|
193
|
+
# Check if test file sources the changed file
|
|
194
|
+
if grep -qF "source.*$changed_file\|source.*./$(basename "$changed_file")" "$test_file" 2>/dev/null || true; then
|
|
195
|
+
is_affected=1
|
|
196
|
+
break
|
|
197
|
+
fi
|
|
198
|
+
# Check by pattern (lib imports)
|
|
199
|
+
local changed_base
|
|
200
|
+
changed_base=$(basename "$changed_file")
|
|
201
|
+
if grep -qF "source.*$changed_base" "$test_file" 2>/dev/null || true; then
|
|
202
|
+
is_affected=1
|
|
203
|
+
break
|
|
204
|
+
fi
|
|
205
|
+
done
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
if [[ "$is_affected" -eq 1 ]]; then
|
|
209
|
+
AFFECTED_TESTS+=("$test_file")
|
|
210
|
+
fi
|
|
211
|
+
done
|
|
212
|
+
|
|
213
|
+
# Fallback: if no affected tests found, use all tests
|
|
214
|
+
if [[ ${#AFFECTED_TESTS[@]} -eq 0 ]]; then
|
|
215
|
+
AFFECTED_TESTS=("${DISCOVERED_TESTS[@]}")
|
|
216
|
+
fi
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# ─── Test Prioritization ──────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
# Prioritize tests by: fail rate, historical duration, then name
|
|
222
|
+
# Returns: space-separated test list (stdout)
|
|
223
|
+
testopt_prioritize() {
|
|
224
|
+
local -a tests_to_sort=("$@")
|
|
225
|
+
|
|
226
|
+
if [[ ${#tests_to_sort[@]} -eq 0 ]]; then
|
|
227
|
+
tests_to_sort=("${AFFECTED_TESTS[@]}")
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# Build temp file with scoring
|
|
231
|
+
local tmp_score_file
|
|
232
|
+
tmp_score_file=$(mktemp)
|
|
233
|
+
trap "rm -f '$tmp_score_file'" RETURN
|
|
234
|
+
|
|
235
|
+
for test_file in "${tests_to_sort[@]}"; do
|
|
236
|
+
local fail_rate duration
|
|
237
|
+
fail_rate=$(testopt_get_fail_rate "$test_file")
|
|
238
|
+
duration=$(testopt_get_historical_duration "$test_file")
|
|
239
|
+
|
|
240
|
+
# Score: fail_rate (0-100) * 100 + duration (so high-fail tests run first, then fast ones)
|
|
241
|
+
local fail_score
|
|
242
|
+
fail_score=$(echo "$fail_rate" | awk '{ printf "%.0f", $1 * 100 }')
|
|
243
|
+
local score=$((fail_score * 100 - duration))
|
|
244
|
+
|
|
245
|
+
echo "$score $test_file" >> "$tmp_score_file"
|
|
246
|
+
done
|
|
247
|
+
|
|
248
|
+
# Sort by score descending, output just test files
|
|
249
|
+
sort -rn "$tmp_score_file" 2>/dev/null | awk '{ print $2 }' || echo "${tests_to_sort[@]}"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# ─── Test Execution ────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
# Run tests with fast-fail: stop on first failure
|
|
255
|
+
# testopt_run_with_fast_fail [--continue-on-fail] <test1> [test2] ...
|
|
256
|
+
# Returns: 0 on all pass, 1 on first fail
|
|
257
|
+
testopt_run_with_fast_fail() {
|
|
258
|
+
local continue_on_fail=false
|
|
259
|
+
[[ "$1" == "--continue-on-fail" ]] && { continue_on_fail=true; shift; }
|
|
260
|
+
|
|
261
|
+
local -a tests=("$@")
|
|
262
|
+
[[ ${#tests[@]} -eq 0 ]] && tests=("${AFFECTED_TESTS[@]}")
|
|
263
|
+
|
|
264
|
+
local failed_test=""
|
|
265
|
+
local all_passed=true
|
|
266
|
+
local tmp_results
|
|
267
|
+
tmp_results=$(mktemp)
|
|
268
|
+
trap "rm -f '$tmp_results'" RETURN
|
|
269
|
+
|
|
270
|
+
info "Running ${#tests[@]} test(s) with fast-fail..."
|
|
271
|
+
|
|
272
|
+
for test_file in "${tests[@]}"; do
|
|
273
|
+
[[ ! -f "$test_file" ]] && continue
|
|
274
|
+
|
|
275
|
+
local start_ts exit_code=0
|
|
276
|
+
start_ts=$(date +%s)
|
|
277
|
+
|
|
278
|
+
# Run the test
|
|
279
|
+
bash "$test_file" > /dev/null 2>&1 || exit_code=$?
|
|
280
|
+
|
|
281
|
+
local duration=$(($(date +%s) - start_ts))
|
|
282
|
+
|
|
283
|
+
if [[ "$exit_code" -ne 0 ]]; then
|
|
284
|
+
all_passed=false
|
|
285
|
+
failed_test="$test_file"
|
|
286
|
+
local result="fail"
|
|
287
|
+
TESTOPT_STATS_FAIL_EARLY=true
|
|
288
|
+
TESTOPT_STATS_TESTS_RUN=$((TESTOPT_STATS_TESTS_RUN + 1))
|
|
289
|
+
|
|
290
|
+
# Record this failure
|
|
291
|
+
{
|
|
292
|
+
echo "{"
|
|
293
|
+
echo " \"test_file\": \"$test_file\","
|
|
294
|
+
echo " \"result\": \"$result\","
|
|
295
|
+
echo " \"duration_s\": $duration,"
|
|
296
|
+
echo " \"ts\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
|
|
297
|
+
echo "}"
|
|
298
|
+
} >> "$tmp_results"
|
|
299
|
+
|
|
300
|
+
error "Test failed: $test_file (${duration}s)"
|
|
301
|
+
emit_event "testopt.fail_fast" "test=$test_file" "duration=$duration"
|
|
302
|
+
|
|
303
|
+
if [[ "$continue_on_fail" == false ]]; then
|
|
304
|
+
break
|
|
305
|
+
fi
|
|
306
|
+
else
|
|
307
|
+
TESTOPT_STATS_TESTS_RUN=$((TESTOPT_STATS_TESTS_RUN + 1))
|
|
308
|
+
{
|
|
309
|
+
echo "{"
|
|
310
|
+
echo " \"test_file\": \"$test_file\","
|
|
311
|
+
echo " \"result\": \"pass\","
|
|
312
|
+
echo " \"duration_s\": $duration,"
|
|
313
|
+
echo " \"ts\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
|
|
314
|
+
echo "}"
|
|
315
|
+
} >> "$tmp_results" 2>/dev/null || true
|
|
316
|
+
success "Test passed: $test_file (${duration}s)"
|
|
317
|
+
fi
|
|
318
|
+
done
|
|
319
|
+
|
|
320
|
+
# Return failed test name in stdout for caller to process
|
|
321
|
+
[[ -n "$failed_test" ]] && echo "$failed_test"
|
|
322
|
+
|
|
323
|
+
[[ "$all_passed" == "true" ]] && return 0 || return 1
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# Run tests in parallel (grouped by directory)
|
|
327
|
+
# testopt_run_parallel [--max-workers=N] <test1> [test2] ...
|
|
328
|
+
# Returns: 0 on all pass, 1 on any fail
|
|
329
|
+
testopt_run_parallel() {
|
|
330
|
+
local max_workers=4
|
|
331
|
+
[[ "$1" == --max-workers=* ]] && { max_workers="${1#--max-workers=}"; shift; }
|
|
332
|
+
|
|
333
|
+
local -a tests=("$@")
|
|
334
|
+
[[ ${#tests[@]} -eq 0 ]] && tests=("${AFFECTED_TESTS[@]}")
|
|
335
|
+
|
|
336
|
+
info "Running ${#tests[@]} test(s) in parallel (max ${max_workers} workers)..."
|
|
337
|
+
|
|
338
|
+
# Group tests by directory for better cache locality
|
|
339
|
+
declare -a test_groups=()
|
|
340
|
+
declare -a current_group=()
|
|
341
|
+
local current_dir=""
|
|
342
|
+
|
|
343
|
+
for test_file in "${tests[@]}"; do
|
|
344
|
+
local test_dir
|
|
345
|
+
test_dir=$(dirname "$test_file")
|
|
346
|
+
if [[ "$test_dir" != "$current_dir" ]] && [[ ${#current_group[@]} -gt 0 ]]; then
|
|
347
|
+
test_groups+=("${current_group[*]}")
|
|
348
|
+
current_group=()
|
|
349
|
+
fi
|
|
350
|
+
current_dir="$test_dir"
|
|
351
|
+
current_group+=("$test_file")
|
|
352
|
+
done
|
|
353
|
+
[[ ${#current_group[@]} -gt 0 ]] && test_groups+=("${current_group[*]}")
|
|
354
|
+
|
|
355
|
+
# Run groups in parallel
|
|
356
|
+
local tmp_results
|
|
357
|
+
tmp_results=$(mktemp)
|
|
358
|
+
trap "rm -f '$tmp_results'" RETURN
|
|
359
|
+
|
|
360
|
+
local all_passed=true
|
|
361
|
+
local job_count=0
|
|
362
|
+
|
|
363
|
+
for group in "${test_groups[@]:-}"; do
|
|
364
|
+
# Wait for a worker slot
|
|
365
|
+
while [[ $(jobs -r | wc -l) -ge "$max_workers" ]]; do
|
|
366
|
+
sleep 0.1
|
|
367
|
+
done
|
|
368
|
+
|
|
369
|
+
# Run this group in background
|
|
370
|
+
{
|
|
371
|
+
for test_file in $group; do
|
|
372
|
+
[[ ! -f "$test_file" ]] && continue
|
|
373
|
+
local start_ts exit_code=0
|
|
374
|
+
start_ts=$(date +%s)
|
|
375
|
+
bash "$test_file" > /dev/null 2>&1 || exit_code=$?
|
|
376
|
+
local duration=$(($(date +%s) - start_ts))
|
|
377
|
+
|
|
378
|
+
if [[ "$exit_code" -ne 0 ]]; then
|
|
379
|
+
all_passed=false
|
|
380
|
+
echo "$test_file FAIL $duration" >> "$tmp_results"
|
|
381
|
+
else
|
|
382
|
+
echo "$test_file PASS $duration" >> "$tmp_results"
|
|
383
|
+
fi
|
|
384
|
+
done
|
|
385
|
+
} &
|
|
386
|
+
|
|
387
|
+
job_count=$((job_count + 1))
|
|
388
|
+
done
|
|
389
|
+
|
|
390
|
+
# Wait for all background jobs
|
|
391
|
+
wait
|
|
392
|
+
TESTOPT_STATS_TESTS_RUN=$((TESTOPT_STATS_TESTS_RUN + ${#tests[@]}))
|
|
393
|
+
|
|
394
|
+
# Report results
|
|
395
|
+
if [[ -f "$tmp_results" ]]; then
|
|
396
|
+
while IFS= read -r line; do
|
|
397
|
+
local test_file status duration
|
|
398
|
+
test_file=$(echo "$line" | awk '{ print $1 }')
|
|
399
|
+
status=$(echo "$line" | awk '{ print $2 }')
|
|
400
|
+
duration=$(echo "$line" | awk '{ print $3 }')
|
|
401
|
+
|
|
402
|
+
if [[ "$status" == "PASS" ]]; then
|
|
403
|
+
success "Test passed: $test_file (${duration}s)"
|
|
404
|
+
else
|
|
405
|
+
error "Test failed: $test_file (${duration}s)"
|
|
406
|
+
fi
|
|
407
|
+
done < "$tmp_results"
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
[[ "$all_passed" == true ]] && return 0 || return 1
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
# ─── History Recording ─────────────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
# Record a test execution result for future prioritization
|
|
416
|
+
# testopt_record_history <test_file> <result> <duration> [changed_files...]
|
|
417
|
+
testopt_record_history() {
|
|
418
|
+
local test_file="$1"
|
|
419
|
+
local result="${2:-unknown}" # pass or fail
|
|
420
|
+
local duration="${3:-0}"
|
|
421
|
+
shift 3 || true
|
|
422
|
+
local changed_files=("$@")
|
|
423
|
+
|
|
424
|
+
[[ -z "$test_file" ]] && return 1
|
|
425
|
+
|
|
426
|
+
# Ensure history directory exists
|
|
427
|
+
mkdir -p "$(dirname "$TESTOPT_HISTORY_FILE")"
|
|
428
|
+
|
|
429
|
+
# Atomic write: temp file + move
|
|
430
|
+
local tmp_history
|
|
431
|
+
tmp_history=$(mktemp)
|
|
432
|
+
trap "rm -f '$tmp_history'" RETURN
|
|
433
|
+
|
|
434
|
+
# Build JSON entry (single line JSONL format)
|
|
435
|
+
local changed_files_json="[]"
|
|
436
|
+
if [[ ${#changed_files[@]} -gt 0 ]]; then
|
|
437
|
+
changed_files_json="[$(printf '"%s",' "${changed_files[@]}" | sed 's/,$//')]"
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
local json_line
|
|
441
|
+
if command -v jq >/dev/null 2>&1; then
|
|
442
|
+
json_line=$(jq -c -n \
|
|
443
|
+
--arg test_file "$test_file" \
|
|
444
|
+
--arg result "$result" \
|
|
445
|
+
--argjson duration "$duration" \
|
|
446
|
+
--argjson changed_files "$changed_files_json" \
|
|
447
|
+
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
448
|
+
'{test_file: $test_file, result: $result, duration_s: $duration, changed_files: $changed_files, ts: $ts}' 2>/dev/null)
|
|
449
|
+
else
|
|
450
|
+
json_line="{\"test_file\": \"$test_file\", \"result\": \"$result\", \"duration_s\": $duration, \"changed_files\": [], \"ts\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
|
|
451
|
+
fi
|
|
452
|
+
|
|
453
|
+
echo "$json_line" >> "$tmp_history"
|
|
454
|
+
|
|
455
|
+
# Append to history (use >> to append, not overwrite)
|
|
456
|
+
cat "$tmp_history" >> "$TESTOPT_HISTORY_FILE" 2>/dev/null || true
|
|
457
|
+
|
|
458
|
+
emit_event "testopt.recorded" "test=$test_file" "result=$result" "duration=$duration"
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
# ─── Initialization ────────────────────────────────────────────────────────
|
|
462
|
+
|
|
463
|
+
# Initialize test optimizer for a pipeline run
|
|
464
|
+
# testopt_init <project_root>
|
|
465
|
+
testopt_init() {
|
|
466
|
+
local project_root="${1:-.}"
|
|
467
|
+
TESTOPT_PROJECT_ROOT="$project_root"
|
|
468
|
+
|
|
469
|
+
info "Initializing test optimizer..."
|
|
470
|
+
|
|
471
|
+
# Discover tests
|
|
472
|
+
testopt_discover_tests "$project_root"
|
|
473
|
+
[[ ${#DISCOVERED_TESTS[@]} -eq 0 ]] && { warn "No test files discovered"; return 0; }
|
|
474
|
+
info "Discovered ${#DISCOVERED_TESTS[@]} test file(s)"
|
|
475
|
+
|
|
476
|
+
# Load historical data
|
|
477
|
+
testopt_load_history
|
|
478
|
+
[[ ${#TEST_HISTORY[@]} -eq 0 ]] && { info "No historical test data found"; } || { info "Loaded ${#TEST_HISTORY[@]} historical record(s)"; }
|
|
479
|
+
|
|
480
|
+
# Get changed files (assume standard git workflow)
|
|
481
|
+
testopt_get_changed_files "HEAD~1" "HEAD" 2>/dev/null || testopt_get_changed_files
|
|
482
|
+
|
|
483
|
+
if [[ ${#CHANGED_FILES[@]} -gt 0 ]]; then
|
|
484
|
+
info "Detected ${#CHANGED_FILES[@]} changed file(s)"
|
|
485
|
+
testopt_select_affected
|
|
486
|
+
info "Selected ${#AFFECTED_TESTS[@]} affected test(s)"
|
|
487
|
+
else
|
|
488
|
+
AFFECTED_TESTS=("${DISCOVERED_TESTS[@]}")
|
|
489
|
+
fi
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
# ─── Reporting ────────────────────────────────────────────────────────────
|
|
493
|
+
|
|
494
|
+
# Print test optimization statistics
|
|
495
|
+
testopt_report() {
|
|
496
|
+
local test_saved=0
|
|
497
|
+
[[ "$TESTOPT_STATS_FAIL_EARLY" == true ]] && test_saved=$((${#DISCOVERED_TESTS[@]} - TESTOPT_STATS_TESTS_RUN))
|
|
498
|
+
|
|
499
|
+
echo ""
|
|
500
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
501
|
+
echo "Test Execution Optimization Report"
|
|
502
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
503
|
+
echo ""
|
|
504
|
+
echo " Discovered tests: ${#DISCOVERED_TESTS[@]}"
|
|
505
|
+
echo " Affected tests: ${#AFFECTED_TESTS[@]}"
|
|
506
|
+
echo " Tests run: $TESTOPT_STATS_TESTS_RUN"
|
|
507
|
+
echo " Tests skipped: $TESTOPT_STATS_TESTS_SKIPPED"
|
|
508
|
+
[[ "$TESTOPT_STATS_FAIL_EARLY" == true ]] && echo " Fast-fail: Yes (stopped at first failure, saved $test_saved tests)"
|
|
509
|
+
echo ""
|
|
510
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
511
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Custom file suggestion for Claude Code @ autocomplete
|
|
3
|
+
# Surfaces Shipwright-specific files for quick access
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".")
|
|
7
|
+
|
|
8
|
+
# Core config files
|
|
9
|
+
for f in \
|
|
10
|
+
".claude/pipeline-state.md" \
|
|
11
|
+
".claude/daemon-config.json" \
|
|
12
|
+
".claude/fleet-config.json" \
|
|
13
|
+
".claude/loop-state.md" \
|
|
14
|
+
".claude/managed-mcp.json" \
|
|
15
|
+
".claude/settings.json" \
|
|
16
|
+
".claude/CLAUDE.md" \
|
|
17
|
+
"CLAUDE.md" \
|
|
18
|
+
"CHANGELOG.md"; do
|
|
19
|
+
[[ -f "$PROJECT_ROOT/$f" ]] && echo "$f"
|
|
20
|
+
done
|
|
21
|
+
|
|
22
|
+
# Agent definitions
|
|
23
|
+
for f in "$PROJECT_ROOT"/.claude/agents/*.md; do
|
|
24
|
+
[[ -f "$f" ]] && echo ".claude/agents/$(basename "$f")"
|
|
25
|
+
done
|
|
26
|
+
|
|
27
|
+
# Schemas
|
|
28
|
+
for f in "$PROJECT_ROOT"/schemas/*.json; do
|
|
29
|
+
[[ -f "$f" ]] && echo "schemas/$(basename "$f")"
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
# Pipeline artifacts (most recent)
|
|
33
|
+
if [[ -d "$PROJECT_ROOT/.claude/pipeline-artifacts" ]]; then
|
|
34
|
+
for f in plan.md design.md composed-pipeline.json; do
|
|
35
|
+
[[ -f "$PROJECT_ROOT/.claude/pipeline-artifacts/$f" ]] && echo ".claude/pipeline-artifacts/$f"
|
|
36
|
+
done
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Loop logs (latest iteration)
|
|
40
|
+
if [[ -d "$PROJECT_ROOT/.claude/loop-logs" ]]; then
|
|
41
|
+
# shellcheck disable=SC2012
|
|
42
|
+
ls -t "$PROJECT_ROOT/.claude/loop-logs"/iteration-*.log 2>/dev/null | head -3 | while read -r f; do
|
|
43
|
+
echo ".claude/loop-logs/$(basename "$f")"
|
|
44
|
+
done
|
|
45
|
+
fi
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
## Adversarial Quality: Systematic Edge Case Discovery
|
|
2
|
+
|
|
3
|
+
Think like an attacker and a chaos engineer. Find the ways this code will break.
|
|
4
|
+
|
|
5
|
+
### Failure Mode Analysis
|
|
6
|
+
For each component changed, ask:
|
|
7
|
+
1. What happens when the input is empty? Null? Maximum size?
|
|
8
|
+
2. What happens when an external dependency is down?
|
|
9
|
+
3. What happens under concurrent access?
|
|
10
|
+
4. What happens when disk is full? Memory is low? Network is flaky?
|
|
11
|
+
5. What happens when the clock skews or timezone changes?
|
|
12
|
+
|
|
13
|
+
### Edge Case Categories
|
|
14
|
+
|
|
15
|
+
**Data Edge Cases:**
|
|
16
|
+
- Empty collections, single-element collections, max-size collections
|
|
17
|
+
- Unicode, emoji, RTL text, null bytes in strings
|
|
18
|
+
- Numeric overflow, underflow, NaN, Infinity, negative zero
|
|
19
|
+
- Date boundaries: midnight, DST transitions, leap seconds, year 2038
|
|
20
|
+
|
|
21
|
+
**Timing Edge Cases:**
|
|
22
|
+
- Race conditions between concurrent operations
|
|
23
|
+
- Operations that span a retry/timeout boundary
|
|
24
|
+
- Stale cache reads during updates
|
|
25
|
+
- Clock skew between distributed components
|
|
26
|
+
|
|
27
|
+
**State Edge Cases:**
|
|
28
|
+
- Partially completed operations (crash mid-write)
|
|
29
|
+
- Re-entrant calls (function called while already executing)
|
|
30
|
+
- State corruption from previous failed operations
|
|
31
|
+
- Idempotency violations (same request processed twice)
|
|
32
|
+
|
|
33
|
+
### Negative Testing Prompts
|
|
34
|
+
- What if a user deliberately sends malformed input?
|
|
35
|
+
- What if the network drops mid-request?
|
|
36
|
+
- What if the database returns stale data?
|
|
37
|
+
- What if two users modify the same resource simultaneously?
|
|
38
|
+
- What if the system runs for 30 days without restart?
|
|
39
|
+
|
|
40
|
+
### Adversarial Thinking
|
|
41
|
+
- How could a malicious user exploit this change?
|
|
42
|
+
- What error messages leak internal implementation details?
|
|
43
|
+
- Are there timing side-channels in security-sensitive operations?
|
|
44
|
+
- Can rate limits be bypassed by parameter manipulation?
|
|
45
|
+
|
|
46
|
+
### Definition of Done for Quality
|
|
47
|
+
- All happy paths tested
|
|
48
|
+
- All identified edge cases tested or documented as known limitations
|
|
49
|
+
- Error paths return meaningful messages (not stack traces)
|
|
50
|
+
- Resource cleanup happens even on failure (finally/defer patterns)
|
|
51
|
+
|
|
52
|
+
### Required Output (Mandatory)
|
|
53
|
+
|
|
54
|
+
Your output MUST include these sections when this skill is active:
|
|
55
|
+
|
|
56
|
+
1. **Failure Modes Found**: For each component, list what happens when it fails (5+ specific scenarios)
|
|
57
|
+
2. **Negative Test Cases**: Specific test cases covering empty input, null, maximum size, concurrent access, resource exhaustion
|
|
58
|
+
3. **Edge Cases Tested**: Data edge cases (Unicode, numeric overflow), timing edge cases (race conditions), state edge cases (partial failure recovery)
|
|
59
|
+
4. **Definition of Done for Quality**: Confirmation that all happy paths are tested, edge cases are covered or documented as known limitations, error messages are clear
|
|
60
|
+
|
|
61
|
+
If any section is not applicable, explicitly state why it's skipped.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
## API Design Expertise
|
|
2
|
+
|
|
3
|
+
Apply these API design patterns:
|
|
4
|
+
|
|
5
|
+
### RESTful Conventions
|
|
6
|
+
- Use nouns for resources, HTTP verbs for actions (GET /users, POST /users, DELETE /users/:id)
|
|
7
|
+
- Return appropriate status codes: 200 OK, 201 Created, 400 Bad Request, 404 Not Found, 422 Unprocessable
|
|
8
|
+
- Use consistent error response format: `{ "error": { "code": "...", "message": "..." } }`
|
|
9
|
+
- Version APIs when breaking changes are needed (/v1/users, /v2/users)
|
|
10
|
+
|
|
11
|
+
### Request/Response Design
|
|
12
|
+
- Accept and return JSON (Content-Type: application/json)
|
|
13
|
+
- Use camelCase for JSON field names
|
|
14
|
+
- Include pagination for list endpoints (limit, offset or cursor)
|
|
15
|
+
- Support filtering and sorting via query parameters
|
|
16
|
+
|
|
17
|
+
### Input Validation
|
|
18
|
+
- Validate ALL input at the API boundary — never trust client data
|
|
19
|
+
- Return specific validation errors with field names
|
|
20
|
+
- Sanitize strings against injection (SQL, XSS, command injection)
|
|
21
|
+
- Set reasonable size limits on request bodies
|
|
22
|
+
|
|
23
|
+
### Error Handling
|
|
24
|
+
- Never expose stack traces or internal errors to clients
|
|
25
|
+
- Log full error details server-side
|
|
26
|
+
- Use consistent error codes that clients can programmatically handle
|
|
27
|
+
- Include request-id in responses for debugging
|
|
28
|
+
|
|
29
|
+
### Authentication & Authorization
|
|
30
|
+
- Verify auth on EVERY endpoint (don't rely on frontend-only checks)
|
|
31
|
+
- Use principle of least privilege for authorization
|
|
32
|
+
- Validate tokens/sessions on each request
|
|
33
|
+
- Rate limit sensitive endpoints (login, password reset)
|
|
34
|
+
|
|
35
|
+
### Required Output (Mandatory)
|
|
36
|
+
|
|
37
|
+
Your output MUST include these sections when this skill is active:
|
|
38
|
+
|
|
39
|
+
1. **Endpoint Specification**: For each endpoint: HTTP method, path, request body schema, response schema, success/error status codes
|
|
40
|
+
2. **Error Codes**: Complete list of all possible error responses with status code and error message format
|
|
41
|
+
3. **Rate Limiting**: If applicable, specify rate limit strategy (requests per minute, burst limits, throttle behavior)
|
|
42
|
+
4. **Versioning**: API version number and deprecation policy if breaking changes are possible
|
|
43
|
+
|
|
44
|
+
If any section is not applicable, explicitly state why it's skipped.
|