shipwright-cli 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/code-reviewer.md +2 -0
- package/.claude/agents/devops-engineer.md +2 -0
- package/.claude/agents/doc-fleet-agent.md +2 -0
- package/.claude/agents/pipeline-agent.md +2 -0
- package/.claude/agents/shell-script-specialist.md +2 -0
- package/.claude/agents/test-specialist.md +2 -0
- package/.claude/hooks/agent-crash-capture.sh +32 -0
- package/.claude/hooks/post-tool-use.sh +3 -2
- package/.claude/hooks/pre-tool-use.sh +35 -3
- package/README.md +22 -8
- package/claude-code/hooks/config-change.sh +18 -0
- package/claude-code/hooks/instructions-reloaded.sh +7 -0
- package/claude-code/hooks/worktree-create.sh +25 -0
- package/claude-code/hooks/worktree-remove.sh +20 -0
- package/config/code-constitution.json +130 -0
- package/config/defaults.json +25 -2
- package/config/policy.json +1 -1
- package/dashboard/middleware/auth.ts +134 -0
- package/dashboard/middleware/constants.ts +21 -0
- package/dashboard/public/index.html +8 -6
- package/dashboard/public/styles.css +176 -97
- package/dashboard/routes/auth.ts +38 -0
- package/dashboard/server.ts +117 -25
- package/dashboard/services/config.ts +26 -0
- package/dashboard/services/db.ts +118 -0
- package/dashboard/src/canvas/pixel-agent.ts +298 -0
- package/dashboard/src/canvas/pixel-sprites.ts +440 -0
- package/dashboard/src/canvas/shipyard-effects.ts +367 -0
- package/dashboard/src/canvas/shipyard-scene.ts +616 -0
- package/dashboard/src/canvas/submarine-layout.ts +267 -0
- package/dashboard/src/components/header.ts +8 -7
- package/dashboard/src/core/api.ts +5 -0
- package/dashboard/src/core/router.ts +1 -0
- package/dashboard/src/design/submarine-theme.ts +253 -0
- package/dashboard/src/main.ts +2 -0
- package/dashboard/src/types/api.ts +12 -1
- package/dashboard/src/views/activity.ts +2 -1
- package/dashboard/src/views/metrics.ts +69 -1
- package/dashboard/src/views/shipyard.ts +39 -0
- package/dashboard/types/index.ts +166 -0
- package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
- package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
- package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
- package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
- package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
- package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
- package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
- package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
- package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
- package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
- package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
- package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
- package/docs/research/RESEARCH_INDEX.md +439 -0
- package/docs/research/RESEARCH_SOURCES.md +440 -0
- package/docs/research/RESEARCH_SUMMARY.txt +275 -0
- package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
- package/package.json +2 -2
- package/scripts/lib/adaptive-model.sh +427 -0
- package/scripts/lib/adaptive-timeout.sh +316 -0
- package/scripts/lib/audit-trail.sh +309 -0
- package/scripts/lib/auto-recovery.sh +471 -0
- package/scripts/lib/bandit-selector.sh +431 -0
- package/scripts/lib/bootstrap.sh +104 -2
- package/scripts/lib/causal-graph.sh +455 -0
- package/scripts/lib/compat.sh +126 -0
- package/scripts/lib/compound-audit.sh +337 -0
- package/scripts/lib/constitutional.sh +454 -0
- package/scripts/lib/context-budget.sh +359 -0
- package/scripts/lib/convergence.sh +594 -0
- package/scripts/lib/cost-optimizer.sh +634 -0
- package/scripts/lib/daemon-adaptive.sh +14 -2
- package/scripts/lib/daemon-dispatch.sh +106 -17
- package/scripts/lib/daemon-failure.sh +34 -4
- package/scripts/lib/daemon-patrol.sh +25 -4
- package/scripts/lib/daemon-poll-github.sh +361 -0
- package/scripts/lib/daemon-poll-health.sh +299 -0
- package/scripts/lib/daemon-poll.sh +27 -611
- package/scripts/lib/daemon-state.sh +119 -66
- package/scripts/lib/daemon-triage.sh +10 -0
- package/scripts/lib/dod-scorecard.sh +442 -0
- package/scripts/lib/error-actionability.sh +300 -0
- package/scripts/lib/formal-spec.sh +461 -0
- package/scripts/lib/helpers.sh +180 -5
- package/scripts/lib/intent-analysis.sh +409 -0
- package/scripts/lib/loop-convergence.sh +350 -0
- package/scripts/lib/loop-iteration.sh +682 -0
- package/scripts/lib/loop-progress.sh +48 -0
- package/scripts/lib/loop-restart.sh +185 -0
- package/scripts/lib/memory-effectiveness.sh +506 -0
- package/scripts/lib/mutation-executor.sh +352 -0
- package/scripts/lib/outcome-feedback.sh +521 -0
- package/scripts/lib/pipeline-cli.sh +336 -0
- package/scripts/lib/pipeline-commands.sh +1216 -0
- package/scripts/lib/pipeline-detection.sh +101 -3
- package/scripts/lib/pipeline-execution.sh +897 -0
- package/scripts/lib/pipeline-github.sh +28 -3
- package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
- package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
- package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
- package/scripts/lib/pipeline-intelligence.sh +104 -1138
- package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
- package/scripts/lib/pipeline-quality-checks.sh +17 -711
- package/scripts/lib/pipeline-quality-gates.sh +563 -0
- package/scripts/lib/pipeline-stages-build.sh +730 -0
- package/scripts/lib/pipeline-stages-delivery.sh +965 -0
- package/scripts/lib/pipeline-stages-intake.sh +1133 -0
- package/scripts/lib/pipeline-stages-monitor.sh +407 -0
- package/scripts/lib/pipeline-stages-review.sh +1022 -0
- package/scripts/lib/pipeline-stages.sh +161 -2901
- package/scripts/lib/pipeline-state.sh +36 -5
- package/scripts/lib/pipeline-util.sh +487 -0
- package/scripts/lib/policy-learner.sh +438 -0
- package/scripts/lib/process-reward.sh +493 -0
- package/scripts/lib/project-detect.sh +649 -0
- package/scripts/lib/quality-profile.sh +334 -0
- package/scripts/lib/recruit-commands.sh +885 -0
- package/scripts/lib/recruit-learning.sh +739 -0
- package/scripts/lib/recruit-roles.sh +648 -0
- package/scripts/lib/reward-aggregator.sh +458 -0
- package/scripts/lib/rl-optimizer.sh +362 -0
- package/scripts/lib/root-cause.sh +427 -0
- package/scripts/lib/scope-enforcement.sh +445 -0
- package/scripts/lib/session-restart.sh +493 -0
- package/scripts/lib/skill-memory.sh +300 -0
- package/scripts/lib/skill-registry.sh +775 -0
- package/scripts/lib/spec-driven.sh +476 -0
- package/scripts/lib/test-helpers.sh +18 -7
- package/scripts/lib/test-holdout.sh +429 -0
- package/scripts/lib/test-optimizer.sh +511 -0
- package/scripts/shipwright-file-suggest.sh +45 -0
- package/scripts/skills/adversarial-quality.md +61 -0
- package/scripts/skills/api-design.md +44 -0
- package/scripts/skills/architecture-design.md +50 -0
- package/scripts/skills/brainstorming.md +43 -0
- package/scripts/skills/data-pipeline.md +44 -0
- package/scripts/skills/deploy-safety.md +64 -0
- package/scripts/skills/documentation.md +38 -0
- package/scripts/skills/frontend-design.md +45 -0
- package/scripts/skills/generated/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
- package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
- package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
- package/scripts/skills/generated/cli-version-management.md +29 -0
- package/scripts/skills/generated/collection-system-validation.md +99 -0
- package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
- package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
- package/scripts/skills/generated/test-parallelization-detection.md +65 -0
- package/scripts/skills/observability.md +79 -0
- package/scripts/skills/performance.md +48 -0
- package/scripts/skills/pr-quality.md +49 -0
- package/scripts/skills/product-thinking.md +43 -0
- package/scripts/skills/security-audit.md +49 -0
- package/scripts/skills/systematic-debugging.md +40 -0
- package/scripts/skills/testing-strategy.md +47 -0
- package/scripts/skills/two-stage-review.md +52 -0
- package/scripts/skills/validation-thoroughness.md +55 -0
- package/scripts/sw +9 -3
- package/scripts/sw-activity.sh +9 -8
- package/scripts/sw-adaptive.sh +8 -7
- package/scripts/sw-adversarial.sh +2 -1
- package/scripts/sw-architecture-enforcer.sh +3 -1
- package/scripts/sw-auth.sh +12 -2
- package/scripts/sw-autonomous.sh +5 -1
- package/scripts/sw-changelog.sh +4 -1
- package/scripts/sw-checkpoint.sh +2 -1
- package/scripts/sw-ci.sh +15 -6
- package/scripts/sw-cleanup.sh +4 -26
- package/scripts/sw-code-review.sh +45 -20
- package/scripts/sw-connect.sh +2 -1
- package/scripts/sw-context.sh +2 -1
- package/scripts/sw-cost.sh +107 -5
- package/scripts/sw-daemon.sh +71 -11
- package/scripts/sw-dashboard.sh +3 -1
- package/scripts/sw-db.sh +71 -20
- package/scripts/sw-decide.sh +8 -2
- package/scripts/sw-decompose.sh +360 -17
- package/scripts/sw-deps.sh +4 -1
- package/scripts/sw-developer-simulation.sh +4 -1
- package/scripts/sw-discovery.sh +378 -5
- package/scripts/sw-doc-fleet.sh +4 -1
- package/scripts/sw-docs-agent.sh +3 -1
- package/scripts/sw-docs.sh +2 -1
- package/scripts/sw-doctor.sh +453 -2
- package/scripts/sw-dora.sh +4 -1
- package/scripts/sw-durable.sh +12 -7
- package/scripts/sw-e2e-orchestrator.sh +17 -16
- package/scripts/sw-eventbus.sh +13 -4
- package/scripts/sw-evidence.sh +364 -12
- package/scripts/sw-feedback.sh +550 -9
- package/scripts/sw-fix.sh +20 -1
- package/scripts/sw-fleet-discover.sh +6 -2
- package/scripts/sw-fleet-viz.sh +9 -4
- package/scripts/sw-fleet.sh +5 -1
- package/scripts/sw-github-app.sh +18 -4
- package/scripts/sw-github-checks.sh +3 -2
- package/scripts/sw-github-deploy.sh +3 -2
- package/scripts/sw-github-graphql.sh +18 -7
- package/scripts/sw-guild.sh +5 -1
- package/scripts/sw-heartbeat.sh +5 -30
- package/scripts/sw-hello.sh +67 -0
- package/scripts/sw-hygiene.sh +10 -3
- package/scripts/sw-incident.sh +273 -5
- package/scripts/sw-init.sh +18 -2
- package/scripts/sw-instrument.sh +10 -2
- package/scripts/sw-intelligence.sh +44 -7
- package/scripts/sw-jira.sh +5 -1
- package/scripts/sw-launchd.sh +2 -1
- package/scripts/sw-linear.sh +4 -1
- package/scripts/sw-logs.sh +4 -1
- package/scripts/sw-loop.sh +436 -1076
- package/scripts/sw-memory.sh +357 -3
- package/scripts/sw-mission-control.sh +6 -1
- package/scripts/sw-model-router.sh +483 -27
- package/scripts/sw-otel.sh +15 -4
- package/scripts/sw-oversight.sh +14 -5
- package/scripts/sw-patrol-meta.sh +334 -0
- package/scripts/sw-pipeline-composer.sh +7 -1
- package/scripts/sw-pipeline-vitals.sh +12 -6
- package/scripts/sw-pipeline.sh +54 -2653
- package/scripts/sw-pm.sh +16 -8
- package/scripts/sw-pr-lifecycle.sh +2 -1
- package/scripts/sw-predictive.sh +17 -5
- package/scripts/sw-prep.sh +185 -2
- package/scripts/sw-ps.sh +5 -25
- package/scripts/sw-public-dashboard.sh +17 -4
- package/scripts/sw-quality.sh +14 -6
- package/scripts/sw-reaper.sh +8 -25
- package/scripts/sw-recruit.sh +156 -2303
- package/scripts/sw-regression.sh +19 -12
- package/scripts/sw-release-manager.sh +3 -1
- package/scripts/sw-release.sh +4 -1
- package/scripts/sw-remote.sh +3 -1
- package/scripts/sw-replay.sh +7 -1
- package/scripts/sw-retro.sh +158 -1
- package/scripts/sw-review-rerun.sh +3 -1
- package/scripts/sw-scale.sh +14 -5
- package/scripts/sw-security-audit.sh +6 -1
- package/scripts/sw-self-optimize.sh +173 -6
- package/scripts/sw-session.sh +9 -3
- package/scripts/sw-setup.sh +3 -1
- package/scripts/sw-stall-detector.sh +406 -0
- package/scripts/sw-standup.sh +15 -7
- package/scripts/sw-status.sh +3 -1
- package/scripts/sw-strategic.sh +14 -6
- package/scripts/sw-stream.sh +13 -4
- package/scripts/sw-swarm.sh +20 -7
- package/scripts/sw-team-stages.sh +13 -6
- package/scripts/sw-templates.sh +7 -31
- package/scripts/sw-testgen.sh +17 -6
- package/scripts/sw-tmux-pipeline.sh +4 -1
- package/scripts/sw-tmux-role-color.sh +2 -0
- package/scripts/sw-tmux-status.sh +1 -1
- package/scripts/sw-tmux.sh +37 -1
- package/scripts/sw-trace.sh +3 -1
- package/scripts/sw-tracker-github.sh +3 -0
- package/scripts/sw-tracker-jira.sh +3 -0
- package/scripts/sw-tracker-linear.sh +3 -0
- package/scripts/sw-tracker.sh +3 -1
- package/scripts/sw-triage.sh +3 -2
- package/scripts/sw-upgrade.sh +3 -1
- package/scripts/sw-ux.sh +5 -2
- package/scripts/sw-webhook.sh +5 -2
- package/scripts/sw-widgets.sh +9 -4
- package/scripts/sw-worktree.sh +15 -3
- package/scripts/test-skill-injection.sh +1233 -0
- package/templates/pipelines/autonomous.json +27 -3
- package/templates/pipelines/cost-aware.json +34 -8
- package/templates/pipelines/deployed.json +12 -0
- package/templates/pipelines/enterprise.json +12 -0
- package/templates/pipelines/fast.json +6 -0
- package/templates/pipelines/full.json +27 -3
- package/templates/pipelines/hotfix.json +6 -0
- package/templates/pipelines/standard.json +12 -0
- package/templates/pipelines/tdd.json +12 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ dod-scorecard.sh — Machine-Verifiable Definition of Done Scorecard ║
|
|
4
|
+
# ║ ║
|
|
5
|
+
# ║ Computes automated checks for PR quality: ║
|
|
6
|
+
# ║ - PR size limits (configurable, default 500 lines) ║
|
|
7
|
+
# ║ - Test count delta (new tests added) ║
|
|
8
|
+
# ║ - Never-ship rule violations (pattern checks) ║
|
|
9
|
+
# ║ - Planned file coverage (from scope-report.json) ║
|
|
10
|
+
# ║ - Acceptance criteria (test evidence verification) ║
|
|
11
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
12
|
+
|
|
13
|
+
[[ -n "${_DOD_SCORECARD_LOADED:-}" ]] && return 0
|
|
14
|
+
_DOD_SCORECARD_LOADED=1
|
|
15
|
+
|
|
16
|
+
# Defaults (safe under set -u)
|
|
17
|
+
ARTIFACTS_DIR="${ARTIFACTS_DIR:-.claude/pipeline-artifacts}"
|
|
18
|
+
SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
19
|
+
PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}"
|
|
20
|
+
BASE_BRANCH="${BASE_BRANCH:-main}"
|
|
21
|
+
QUALITY_PROFILE="${QUALITY_PROFILE:-.claude/quality-profile.json}"
|
|
22
|
+
|
|
23
|
+
# ─── Helper: Safe JSON read ───────────────────────────────────────────────────
|
|
24
|
+
# Usage: json_get "$json_string" ".path.to.key" "default_value"
|
|
25
|
+
json_get() {
|
|
26
|
+
local json="$1"
|
|
27
|
+
local key="$2"
|
|
28
|
+
local default="${3:-}"
|
|
29
|
+
jq -r "$key // \"$default\"" <<< "$json" 2>/dev/null || echo "$default"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# ─── Helper: Array length (bash 3.2 compatible) ────────────────────────────────
|
|
33
|
+
# Usage: array_len "item1" "item2" "item3"
|
|
34
|
+
# Returns count of non-empty arguments
|
|
35
|
+
array_count_items() {
|
|
36
|
+
local count=0
|
|
37
|
+
for item in "$@"; do
|
|
38
|
+
[[ -n "$item" ]] && count=$((count + 1))
|
|
39
|
+
done
|
|
40
|
+
echo "$count"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ─── Check: PR Size ──────────────────────────────────────────────────────────
|
|
44
|
+
# Returns JSON: {"status": "pass|fail", "value": lines, "limit": limit}
|
|
45
|
+
check_pr_size_score() {
|
|
46
|
+
local base_branch="$1"
|
|
47
|
+
local limit="${2:-500}"
|
|
48
|
+
|
|
49
|
+
local total_lines=0
|
|
50
|
+
|
|
51
|
+
# Try git diff stat first
|
|
52
|
+
if git rev-parse "$base_branch" >/dev/null 2>&1; then
|
|
53
|
+
local diff_stat
|
|
54
|
+
diff_stat=$(git diff --stat "$base_branch...HEAD" 2>/dev/null | tail -1 || true)
|
|
55
|
+
if [[ -n "$diff_stat" ]]; then
|
|
56
|
+
# Format: "N files changed, X insertions(+), Y deletions(-)"
|
|
57
|
+
# Extract insertions count
|
|
58
|
+
total_lines=$(echo "$diff_stat" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
local status="pass"
|
|
63
|
+
[[ $total_lines -gt $limit ]] && status="fail"
|
|
64
|
+
|
|
65
|
+
jq -n \
|
|
66
|
+
--arg status "$status" \
|
|
67
|
+
--argjson value "$total_lines" \
|
|
68
|
+
--argjson limit "$limit" \
|
|
69
|
+
'{status: $status, value: $value, limit: $limit}'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ─── Check: Test Count Delta ──────────────────────────────────────────────────
|
|
73
|
+
# Counts test function patterns (describe, it, test, func_test) in changed files
|
|
74
|
+
# Returns JSON: {"status": "pass|fail", "value": new_tests, "baseline": 0}
|
|
75
|
+
check_test_count_delta() {
|
|
76
|
+
local base_branch="$1"
|
|
77
|
+
local baseline="${2:-0}"
|
|
78
|
+
|
|
79
|
+
local new_test_count=0
|
|
80
|
+
local changed_test_files
|
|
81
|
+
|
|
82
|
+
# Get test files that changed
|
|
83
|
+
if git rev-parse "$base_branch" >/dev/null 2>&1; then
|
|
84
|
+
changed_test_files=$(git diff --name-only "$base_branch...HEAD" 2>/dev/null | grep -E '_test\.sh$|\.test\.js$|\.test\.ts$|_spec\.js$' || true)
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Count test patterns in changed files
|
|
88
|
+
if [[ -n "$changed_test_files" ]]; then
|
|
89
|
+
while IFS= read -r file; do
|
|
90
|
+
[[ -z "$file" || ! -f "$file" ]] && continue
|
|
91
|
+
# Count test/describe/it/test patterns
|
|
92
|
+
local count
|
|
93
|
+
count=$(grep -cE '^\s*(describe|it|test|assert_pass|assert_fail)\s*\(' "$file" 2>/dev/null || echo "0")
|
|
94
|
+
new_test_count=$((new_test_count + count))
|
|
95
|
+
done <<< "$changed_test_files"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
local status="pass"
|
|
99
|
+
[[ $new_test_count -eq 0 && $baseline -eq 0 ]] && status="pass"
|
|
100
|
+
# Note: we don't fail on zero new tests — some PRs legitimately add no tests
|
|
101
|
+
|
|
102
|
+
jq -n \
|
|
103
|
+
--arg status "$status" \
|
|
104
|
+
--argjson value "$new_test_count" \
|
|
105
|
+
--argjson baseline "$baseline" \
|
|
106
|
+
'{status: $status, value: $value, baseline: $baseline}'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# ─── Check: Never-Ship Violations ────────────────────────────────────────────
|
|
110
|
+
# Scans diff for patterns listed in quality profile's never_ship rules
|
|
111
|
+
# Returns JSON: {"status": "pass|fail", "violations": [{"rule": "...", "lines": [...]}]}
|
|
112
|
+
check_never_ship_violations() {
|
|
113
|
+
local base_branch="$1"
|
|
114
|
+
local quality_profile="${2:-.claude/quality-profile.json}"
|
|
115
|
+
|
|
116
|
+
local violations="[]"
|
|
117
|
+
local status="pass"
|
|
118
|
+
|
|
119
|
+
# Load never_ship rules from profile
|
|
120
|
+
if [[ -f "$quality_profile" ]]; then
|
|
121
|
+
local never_ship_rules
|
|
122
|
+
never_ship_rules=$(jq -r '.quality.never_ship[]? // empty' "$quality_profile" 2>/dev/null)
|
|
123
|
+
|
|
124
|
+
if [[ -n "$never_ship_rules" ]]; then
|
|
125
|
+
local diff_content
|
|
126
|
+
if git rev-parse "$base_branch" >/dev/null 2>&1; then
|
|
127
|
+
diff_content=$(git diff "$base_branch...HEAD" 2>/dev/null || true)
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [[ -n "$diff_content" ]]; then
|
|
131
|
+
local temp_violations="[]"
|
|
132
|
+
while IFS= read -r rule; do
|
|
133
|
+
[[ -z "$rule" ]] && continue
|
|
134
|
+
# Check if rule pattern appears in diff (case-insensitive)
|
|
135
|
+
if echo "$diff_content" | grep -qi "$rule"; then
|
|
136
|
+
# Collect lines matching the rule
|
|
137
|
+
local matching_lines
|
|
138
|
+
matching_lines=$(echo "$diff_content" | grep -in "$rule" | head -3 || true)
|
|
139
|
+
|
|
140
|
+
# Build violation entry
|
|
141
|
+
temp_violations=$(jq -n \
|
|
142
|
+
--arg rule "$rule" \
|
|
143
|
+
--arg lines "$matching_lines" \
|
|
144
|
+
--argjson prev "$temp_violations" \
|
|
145
|
+
'$prev + [{"rule": $rule, "lines": ($lines | split("\n") | map(select(length > 0)))}]')
|
|
146
|
+
|
|
147
|
+
status="fail"
|
|
148
|
+
fi
|
|
149
|
+
done <<< "$never_ship_rules"
|
|
150
|
+
violations="$temp_violations"
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
jq -n \
|
|
156
|
+
--arg status "$status" \
|
|
157
|
+
--argjson violations "$violations" \
|
|
158
|
+
'{status: $status, violations: $violations}'
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# ─── Check: Planned Files Coverage ───────────────────────────────────────────
|
|
162
|
+
# Compares files in scope-report.json against actual changes
|
|
163
|
+
# Returns JSON: {"status": "pass|fail", "planned": N, "touched": N, "unplanned": N}
|
|
164
|
+
check_planned_files_coverage() {
|
|
165
|
+
local scope_report="${1:-$ARTIFACTS_DIR/scope-report.json}"
|
|
166
|
+
|
|
167
|
+
local planned=0 touched=0 unplanned=0 status="pass"
|
|
168
|
+
|
|
169
|
+
if [[ -f "$scope_report" ]]; then
|
|
170
|
+
planned=$(jq -r '.planned_files | length' "$scope_report" 2>/dev/null || echo "0")
|
|
171
|
+
touched=$(jq -r '.planned_files | map(select(.touched == true)) | length' "$scope_report" 2>/dev/null || echo "0")
|
|
172
|
+
unplanned=$(jq -r '.unplanned_files | length' "$scope_report" 2>/dev/null || echo "0")
|
|
173
|
+
|
|
174
|
+
# Check quality profile for whether unplanned files block
|
|
175
|
+
local unplanned_blocks="false"
|
|
176
|
+
if [[ -f "$QUALITY_PROFILE" ]]; then
|
|
177
|
+
unplanned_blocks=$(jq -r '.scope.unplanned_files_block // false' "$QUALITY_PROFILE" 2>/dev/null)
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Status: fail if unplanned files exist and they're configured to block
|
|
181
|
+
if [[ $unplanned -gt 0 && "$unplanned_blocks" == "true" ]]; then
|
|
182
|
+
status="fail"
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
jq -n \
|
|
187
|
+
--arg status "$status" \
|
|
188
|
+
--argjson planned "$planned" \
|
|
189
|
+
--argjson touched "$touched" \
|
|
190
|
+
--argjson unplanned "$unplanned" \
|
|
191
|
+
'{status: $status, planned: $planned, touched: $touched, unplanned: $unplanned}'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# ─── Check: Acceptance Criteria ──────────────────────────────────────────────
|
|
195
|
+
# Reads acceptance-criteria.json and searches test output for evidence
|
|
196
|
+
# Returns JSON: [{"id": "ac-1", "status": "pass|fail", "evidence": "..."}]
|
|
197
|
+
check_acceptance_criteria() {
|
|
198
|
+
local acceptance_file="${1:-$ARTIFACTS_DIR/acceptance-criteria.json}"
|
|
199
|
+
local test_output="${2:-$ARTIFACTS_DIR/test-results.log}"
|
|
200
|
+
|
|
201
|
+
local criteria="[]"
|
|
202
|
+
|
|
203
|
+
if [[ -f "$acceptance_file" ]]; then
|
|
204
|
+
local test_log=""
|
|
205
|
+
[[ -f "$test_output" ]] && test_log=$(cat "$test_output")
|
|
206
|
+
|
|
207
|
+
# Extract criteria from acceptance file
|
|
208
|
+
local count=0
|
|
209
|
+
while IFS= read -r criterion; do
|
|
210
|
+
[[ -z "$criterion" ]] && continue
|
|
211
|
+
count=$((count + 1))
|
|
212
|
+
|
|
213
|
+
local id="ac-$count"
|
|
214
|
+
local status="pass"
|
|
215
|
+
local evidence=""
|
|
216
|
+
|
|
217
|
+
# Extract search keywords from criterion
|
|
218
|
+
# Acceptance criteria typically contain keywords to search for in test output
|
|
219
|
+
local keywords
|
|
220
|
+
keywords=$(echo "$criterion" | grep -oE '\b(GET|POST|PUT|DELETE|assert|should|expect|test)\b' | head -3 || true)
|
|
221
|
+
|
|
222
|
+
if [[ -n "$test_log" && -n "$keywords" ]]; then
|
|
223
|
+
# Check if any keyword appears in test output
|
|
224
|
+
local found_count=0
|
|
225
|
+
while IFS= read -r keyword; do
|
|
226
|
+
[[ -z "$keyword" ]] && continue
|
|
227
|
+
if echo "$test_log" | grep -qi "$keyword"; then
|
|
228
|
+
found_count=$((found_count + 1))
|
|
229
|
+
fi
|
|
230
|
+
done <<< "$keywords"
|
|
231
|
+
|
|
232
|
+
if [[ $found_count -gt 0 ]]; then
|
|
233
|
+
evidence="Evidence found: keyword(s) present in test output"
|
|
234
|
+
else
|
|
235
|
+
status="fail"
|
|
236
|
+
evidence="No evidence found in test output for criterion"
|
|
237
|
+
fi
|
|
238
|
+
elif [[ -z "$test_log" ]]; then
|
|
239
|
+
evidence="No test output available for verification"
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# Build criterion entry
|
|
243
|
+
criteria=$(jq -n \
|
|
244
|
+
--arg id "$id" \
|
|
245
|
+
--arg status "$status" \
|
|
246
|
+
--arg evidence "$evidence" \
|
|
247
|
+
--arg criterion "$criterion" \
|
|
248
|
+
--argjson prev "$criteria" \
|
|
249
|
+
'$prev + [{"id": $id, "status": $status, "evidence": $evidence, "criterion": $criterion}]')
|
|
250
|
+
done < <(jq -r '.acceptance_criteria[]? // empty' "$acceptance_file" 2>/dev/null | while read -r line; do echo "$line"; done)
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
echo "$criteria"
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# ─── Compute: Overall DoD Scorecard ──────────────────────────────────────────
|
|
257
|
+
# Runs all checks and produces dod-scorecard.json
|
|
258
|
+
# Usage: compute_dod_scorecard "$base_branch" "$artifacts_dir" "$quality_profile"
|
|
259
|
+
compute_dod_scorecard() {
|
|
260
|
+
local base_branch="${1:-main}"
|
|
261
|
+
local artifacts_dir="${2:-.claude/pipeline-artifacts}"
|
|
262
|
+
local quality_profile="${3:-.claude/quality-profile.json}"
|
|
263
|
+
|
|
264
|
+
# Ensure artifacts directory exists
|
|
265
|
+
mkdir -p "$artifacts_dir"
|
|
266
|
+
|
|
267
|
+
# Run all checks
|
|
268
|
+
local pr_size_check acceptance_criteria_list
|
|
269
|
+
local never_ship_check planned_files_check test_delta_check
|
|
270
|
+
|
|
271
|
+
pr_size_check=$(check_pr_size_score "$base_branch" "$(jq -r '.quality.max_pr_lines // 500' "$quality_profile" 2>/dev/null || echo 500)")
|
|
272
|
+
test_delta_check=$(check_test_count_delta "$base_branch" "0")
|
|
273
|
+
never_ship_check=$(check_never_ship_violations "$base_branch" "$quality_profile")
|
|
274
|
+
planned_files_check=$(check_planned_files_coverage "$artifacts_dir/scope-report.json")
|
|
275
|
+
acceptance_criteria_list=$(check_acceptance_criteria "$artifacts_dir/acceptance-criteria.json" "$artifacts_dir/test-results.log")
|
|
276
|
+
|
|
277
|
+
# Determine overall status and blocking failures
|
|
278
|
+
local overall_status="pass"
|
|
279
|
+
local blocking_failures="[]"
|
|
280
|
+
|
|
281
|
+
# Extract status from checks
|
|
282
|
+
local pr_size_status test_status never_status planned_status
|
|
283
|
+
pr_size_status=$(echo "$pr_size_check" | jq -r '.status')
|
|
284
|
+
test_status=$(echo "$test_delta_check" | jq -r '.status')
|
|
285
|
+
never_status=$(echo "$never_ship_check" | jq -r '.status')
|
|
286
|
+
planned_status=$(echo "$planned_files_check" | jq -r '.status')
|
|
287
|
+
|
|
288
|
+
[[ "$pr_size_status" == "fail" ]] && overall_status="fail"
|
|
289
|
+
[[ "$never_status" == "fail" ]] && overall_status="fail"
|
|
290
|
+
|
|
291
|
+
# Check acceptance criteria for failures
|
|
292
|
+
local ac_failures=0
|
|
293
|
+
ac_failures=$(echo "$acceptance_criteria_list" | jq '[.[] | select(.status == "fail")] | length' 2>/dev/null || echo "0")
|
|
294
|
+
[[ $ac_failures -gt 0 ]] && overall_status="fail"
|
|
295
|
+
|
|
296
|
+
# Build blocking_failures array
|
|
297
|
+
if [[ "$pr_size_status" == "fail" ]]; then
|
|
298
|
+
blocking_failures=$(jq -n --argjson prev "$blocking_failures" '$prev + ["pr_size"]')
|
|
299
|
+
fi
|
|
300
|
+
if [[ "$never_status" == "fail" ]]; then
|
|
301
|
+
blocking_failures=$(jq -n --argjson prev "$blocking_failures" '$prev + ["never_ship"]')
|
|
302
|
+
fi
|
|
303
|
+
if [[ $ac_failures -gt 0 ]]; then
|
|
304
|
+
blocking_failures=$(jq -n --argjson prev "$blocking_failures" '$prev + ["acceptance_criteria"]')
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# Build complete scorecard JSON
|
|
308
|
+
local scorecard_json
|
|
309
|
+
scorecard_json=$(jq -n \
|
|
310
|
+
--argjson pr_size "$pr_size_check" \
|
|
311
|
+
--argjson test_count_delta "$test_delta_check" \
|
|
312
|
+
--argjson never_ship_violations "$never_ship_check" \
|
|
313
|
+
--argjson planned_files_coverage "$planned_files_check" \
|
|
314
|
+
--argjson acceptance_criteria "$acceptance_criteria_list" \
|
|
315
|
+
--arg overall "$overall_status" \
|
|
316
|
+
--argjson blocking_failures "$blocking_failures" \
|
|
317
|
+
--arg computed_at "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
|
|
318
|
+
'{
|
|
319
|
+
scorecard: {
|
|
320
|
+
pr_size: $pr_size,
|
|
321
|
+
test_count_delta: $test_count_delta,
|
|
322
|
+
never_ship_violations: $never_ship_violations,
|
|
323
|
+
planned_files_coverage: $planned_files_coverage,
|
|
324
|
+
acceptance_criteria: $acceptance_criteria
|
|
325
|
+
},
|
|
326
|
+
overall: $overall,
|
|
327
|
+
blocking_failures: $blocking_failures,
|
|
328
|
+
computed_at: $computed_at
|
|
329
|
+
}')
|
|
330
|
+
|
|
331
|
+
# Write to file
|
|
332
|
+
echo "$scorecard_json" > "$artifacts_dir/dod-scorecard.json"
|
|
333
|
+
echo "$scorecard_json"
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
# ─── Format: Scorecard for Display ────────────────────────────────────────────
|
|
337
|
+
# Returns human-readable markdown format of the scorecard
|
|
338
|
+
format_scorecard() {
|
|
339
|
+
local scorecard_json="$1"
|
|
340
|
+
|
|
341
|
+
local output="# Definition of Done Scorecard\n\n"
|
|
342
|
+
|
|
343
|
+
# PR Size check
|
|
344
|
+
local pr_size_status pr_size_value pr_size_limit
|
|
345
|
+
pr_size_status=$(echo "$scorecard_json" | jq -r '.scorecard.pr_size.status')
|
|
346
|
+
pr_size_value=$(echo "$scorecard_json" | jq -r '.scorecard.pr_size.value')
|
|
347
|
+
pr_size_limit=$(echo "$scorecard_json" | jq -r '.scorecard.pr_size.limit')
|
|
348
|
+
|
|
349
|
+
local pr_size_emoji="✓"
|
|
350
|
+
[[ "$pr_size_status" == "fail" ]] && pr_size_emoji="✗"
|
|
351
|
+
local pr_size_status_upper
|
|
352
|
+
pr_size_status_upper=$(echo "$pr_size_status" | tr a-z A-Z)
|
|
353
|
+
output+="## PR Size\n$pr_size_emoji $pr_size_status_upper: ${pr_size_value} lines (max: ${pr_size_limit})\n\n"
|
|
354
|
+
|
|
355
|
+
# Test Count Delta
|
|
356
|
+
local test_status test_value test_baseline
|
|
357
|
+
test_status=$(echo "$scorecard_json" | jq -r '.scorecard.test_count_delta.status')
|
|
358
|
+
test_value=$(echo "$scorecard_json" | jq -r '.scorecard.test_count_delta.value')
|
|
359
|
+
test_baseline=$(echo "$scorecard_json" | jq -r '.scorecard.test_count_delta.baseline')
|
|
360
|
+
|
|
361
|
+
local test_emoji="✓"
|
|
362
|
+
[[ "$test_status" == "fail" ]] && test_emoji="✗"
|
|
363
|
+
local test_status_upper
|
|
364
|
+
test_status_upper=$(echo "$test_status" | tr a-z A-Z)
|
|
365
|
+
output+="## Test Coverage\n$test_emoji $test_status_upper: ${test_value} new tests (baseline: ${test_baseline})\n\n"
|
|
366
|
+
|
|
367
|
+
# Never-Ship Violations
|
|
368
|
+
local never_status never_violations_count
|
|
369
|
+
never_status=$(echo "$scorecard_json" | jq -r '.scorecard.never_ship_violations.status')
|
|
370
|
+
never_violations_count=$(echo "$scorecard_json" | jq -r '.scorecard.never_ship_violations.violations | length')
|
|
371
|
+
|
|
372
|
+
local never_emoji="✓"
|
|
373
|
+
[[ "$never_status" == "fail" ]] && never_emoji="✗"
|
|
374
|
+
local never_status_upper
|
|
375
|
+
never_status_upper=$(echo "$never_status" | tr a-z A-Z)
|
|
376
|
+
output+="## Never-Ship Rules\n$never_emoji $never_status_upper: ${never_violations_count} violation(s)\n"
|
|
377
|
+
|
|
378
|
+
if [[ $never_violations_count -gt 0 ]]; then
|
|
379
|
+
output+="$(echo "$scorecard_json" | jq -r '.scorecard.never_ship_violations.violations[] | " - \(.rule)"' | head -5)\n\n"
|
|
380
|
+
else
|
|
381
|
+
output+="\n"
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
# Planned Files Coverage
|
|
385
|
+
local planned_status planned_count touched_count unplanned_count
|
|
386
|
+
planned_status=$(echo "$scorecard_json" | jq -r '.scorecard.planned_files_coverage.status')
|
|
387
|
+
planned_count=$(echo "$scorecard_json" | jq -r '.scorecard.planned_files_coverage.planned')
|
|
388
|
+
touched_count=$(echo "$scorecard_json" | jq -r '.scorecard.planned_files_coverage.touched')
|
|
389
|
+
unplanned_count=$(echo "$scorecard_json" | jq -r '.scorecard.planned_files_coverage.unplanned')
|
|
390
|
+
|
|
391
|
+
local planned_emoji="✓"
|
|
392
|
+
[[ "$planned_status" == "fail" ]] && planned_emoji="✗"
|
|
393
|
+
local planned_status_upper
|
|
394
|
+
planned_status_upper=$(echo "$planned_status" | tr a-z A-Z)
|
|
395
|
+
output+="## Scope Coverage\n$planned_emoji $planned_status_upper: ${touched_count}/${planned_count} planned files touched, ${unplanned_count} unplanned\n\n"
|
|
396
|
+
|
|
397
|
+
# Acceptance Criteria
|
|
398
|
+
local ac_count ac_passed ac_failed
|
|
399
|
+
ac_count=$(echo "$scorecard_json" | jq '.scorecard.acceptance_criteria | length')
|
|
400
|
+
ac_passed=$(echo "$scorecard_json" | jq '[.scorecard.acceptance_criteria[] | select(.status == "pass")] | length')
|
|
401
|
+
ac_failed=$((ac_count - ac_passed))
|
|
402
|
+
|
|
403
|
+
output+="## Acceptance Criteria\n✓ ${ac_passed}/${ac_count} passed"
|
|
404
|
+
if [[ $ac_failed -gt 0 ]]; then
|
|
405
|
+
output+=" (${ac_failed} failed)\n"
|
|
406
|
+
else
|
|
407
|
+
output+="\n"
|
|
408
|
+
fi
|
|
409
|
+
output+="\n"
|
|
410
|
+
|
|
411
|
+
# Overall status
|
|
412
|
+
local overall_status overall_emoji
|
|
413
|
+
overall_status=$(echo "$scorecard_json" | jq -r '.overall')
|
|
414
|
+
[[ "$overall_status" == "pass" ]] && overall_emoji="✓" || overall_emoji="✗"
|
|
415
|
+
local overall_status_upper
|
|
416
|
+
overall_status_upper=$(echo "$overall_status" | tr a-z A-Z)
|
|
417
|
+
|
|
418
|
+
output+="## Overall Result\n${overall_emoji} **${overall_status_upper}**\n"
|
|
419
|
+
|
|
420
|
+
echo -e "$output"
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
# ─── Gate: Scorecard Pass/Fail ───────────────────────────────────────────────
|
|
424
|
+
# Returns 0 if overall == pass, 1 if overall == fail
|
|
425
|
+
scorecard_passed() {
|
|
426
|
+
local scorecard_json="$1"
|
|
427
|
+
local overall_status
|
|
428
|
+
overall_status=$(echo "$scorecard_json" | jq -r '.overall // "fail"')
|
|
429
|
+
|
|
430
|
+
if [[ "$overall_status" == "pass" ]]; then
|
|
431
|
+
return 0
|
|
432
|
+
else
|
|
433
|
+
return 1
|
|
434
|
+
fi
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
# ─── Blocking Failures Extractor ────────────────────────────────────────────
|
|
438
|
+
# Returns list of blocking failure categories
|
|
439
|
+
get_blocking_failures() {
|
|
440
|
+
local scorecard_json="$1"
|
|
441
|
+
echo "$scorecard_json" | jq -r '.blocking_failures[]? // empty'
|
|
442
|
+
}
|