shipwright-cli 1.9.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.claude/hooks/post-tool-use.sh +12 -5
  2. package/README.md +114 -36
  3. package/completions/_shipwright +212 -32
  4. package/completions/shipwright.bash +97 -25
  5. package/docs/strategy/01-market-research.md +619 -0
  6. package/docs/strategy/02-mission-and-brand.md +587 -0
  7. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  8. package/docs/strategy/QUICK-START.txt +289 -0
  9. package/docs/strategy/README.md +172 -0
  10. package/package.json +4 -2
  11. package/scripts/sw +217 -2
  12. package/scripts/sw-activity.sh +500 -0
  13. package/scripts/sw-adaptive.sh +925 -0
  14. package/scripts/sw-adversarial.sh +1 -1
  15. package/scripts/sw-architecture-enforcer.sh +1 -1
  16. package/scripts/sw-auth.sh +613 -0
  17. package/scripts/sw-autonomous.sh +664 -0
  18. package/scripts/sw-changelog.sh +704 -0
  19. package/scripts/sw-checkpoint.sh +79 -1
  20. package/scripts/sw-ci.sh +602 -0
  21. package/scripts/sw-cleanup.sh +192 -7
  22. package/scripts/sw-code-review.sh +637 -0
  23. package/scripts/sw-connect.sh +1 -1
  24. package/scripts/sw-context.sh +605 -0
  25. package/scripts/sw-cost.sh +1 -1
  26. package/scripts/sw-daemon.sh +812 -138
  27. package/scripts/sw-dashboard.sh +1 -1
  28. package/scripts/sw-db.sh +540 -0
  29. package/scripts/sw-decompose.sh +539 -0
  30. package/scripts/sw-deps.sh +551 -0
  31. package/scripts/sw-developer-simulation.sh +1 -1
  32. package/scripts/sw-discovery.sh +412 -0
  33. package/scripts/sw-docs-agent.sh +539 -0
  34. package/scripts/sw-docs.sh +1 -1
  35. package/scripts/sw-doctor.sh +59 -1
  36. package/scripts/sw-dora.sh +615 -0
  37. package/scripts/sw-durable.sh +710 -0
  38. package/scripts/sw-e2e-orchestrator.sh +535 -0
  39. package/scripts/sw-eventbus.sh +393 -0
  40. package/scripts/sw-feedback.sh +471 -0
  41. package/scripts/sw-fix.sh +1 -1
  42. package/scripts/sw-fleet-discover.sh +567 -0
  43. package/scripts/sw-fleet-viz.sh +404 -0
  44. package/scripts/sw-fleet.sh +8 -1
  45. package/scripts/sw-github-app.sh +596 -0
  46. package/scripts/sw-github-checks.sh +1 -1
  47. package/scripts/sw-github-deploy.sh +1 -1
  48. package/scripts/sw-github-graphql.sh +1 -1
  49. package/scripts/sw-guild.sh +569 -0
  50. package/scripts/sw-heartbeat.sh +1 -1
  51. package/scripts/sw-hygiene.sh +559 -0
  52. package/scripts/sw-incident.sh +617 -0
  53. package/scripts/sw-init.sh +88 -1
  54. package/scripts/sw-instrument.sh +699 -0
  55. package/scripts/sw-intelligence.sh +1 -1
  56. package/scripts/sw-jira.sh +1 -1
  57. package/scripts/sw-launchd.sh +366 -31
  58. package/scripts/sw-linear.sh +1 -1
  59. package/scripts/sw-logs.sh +1 -1
  60. package/scripts/sw-loop.sh +507 -51
  61. package/scripts/sw-memory.sh +198 -3
  62. package/scripts/sw-mission-control.sh +487 -0
  63. package/scripts/sw-model-router.sh +545 -0
  64. package/scripts/sw-otel.sh +596 -0
  65. package/scripts/sw-oversight.sh +689 -0
  66. package/scripts/sw-pipeline-composer.sh +8 -8
  67. package/scripts/sw-pipeline-vitals.sh +1096 -0
  68. package/scripts/sw-pipeline.sh +2451 -180
  69. package/scripts/sw-pm.sh +693 -0
  70. package/scripts/sw-pr-lifecycle.sh +522 -0
  71. package/scripts/sw-predictive.sh +1 -1
  72. package/scripts/sw-prep.sh +1 -1
  73. package/scripts/sw-ps.sh +4 -3
  74. package/scripts/sw-public-dashboard.sh +798 -0
  75. package/scripts/sw-quality.sh +595 -0
  76. package/scripts/sw-reaper.sh +5 -3
  77. package/scripts/sw-recruit.sh +573 -0
  78. package/scripts/sw-regression.sh +642 -0
  79. package/scripts/sw-release-manager.sh +736 -0
  80. package/scripts/sw-release.sh +706 -0
  81. package/scripts/sw-remote.sh +1 -1
  82. package/scripts/sw-replay.sh +520 -0
  83. package/scripts/sw-retro.sh +691 -0
  84. package/scripts/sw-scale.sh +444 -0
  85. package/scripts/sw-security-audit.sh +505 -0
  86. package/scripts/sw-self-optimize.sh +109 -8
  87. package/scripts/sw-session.sh +31 -9
  88. package/scripts/sw-setup.sh +1 -1
  89. package/scripts/sw-standup.sh +712 -0
  90. package/scripts/sw-status.sh +192 -1
  91. package/scripts/sw-strategic.sh +658 -0
  92. package/scripts/sw-stream.sh +450 -0
  93. package/scripts/sw-swarm.sh +583 -0
  94. package/scripts/sw-team-stages.sh +511 -0
  95. package/scripts/sw-templates.sh +1 -1
  96. package/scripts/sw-testgen.sh +515 -0
  97. package/scripts/sw-tmux-pipeline.sh +554 -0
  98. package/scripts/sw-tmux.sh +1 -1
  99. package/scripts/sw-trace.sh +485 -0
  100. package/scripts/sw-tracker-github.sh +188 -0
  101. package/scripts/sw-tracker-jira.sh +172 -0
  102. package/scripts/sw-tracker-linear.sh +251 -0
  103. package/scripts/sw-tracker.sh +117 -2
  104. package/scripts/sw-triage.sh +603 -0
  105. package/scripts/sw-upgrade.sh +1 -1
  106. package/scripts/sw-ux.sh +677 -0
  107. package/scripts/sw-webhook.sh +627 -0
  108. package/scripts/sw-widgets.sh +530 -0
  109. package/scripts/sw-worktree.sh +1 -1
  110. package/templates/pipelines/autonomous.json +8 -1
  111. package/templates/pipelines/cost-aware.json +21 -0
  112. package/templates/pipelines/deployed.json +40 -6
  113. package/templates/pipelines/enterprise.json +16 -2
  114. package/templates/pipelines/fast.json +19 -0
  115. package/templates/pipelines/full.json +16 -2
  116. package/templates/pipelines/hotfix.json +19 -0
  117. package/templates/pipelines/standard.json +19 -0
@@ -0,0 +1,505 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright security-audit — Comprehensive Security Auditing ║
4
+ # ║ Secret detection · License checking · Vulnerability scanning · SBOM ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
+
9
+ VERSION="2.0.0"
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
+
13
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
14
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
15
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
16
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
17
+ GREEN='\033[38;2;74;222;128m' # success
18
+ YELLOW='\033[38;2;250;204;21m' # warning
19
+ RED='\033[38;2;248;113;113m' # error
20
+ DIM='\033[2m'
21
+ BOLD='\033[1m'
22
+ RESET='\033[0m'
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
+ # ─── Output Helpers ─────────────────────────────────────────────────────────
29
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
30
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
31
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
32
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
33
+
34
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
35
+
36
+ # ─── Audit State ───────────────────────────────────────────────────────────
37
+ FINDINGS=()
38
+ CRITICAL_COUNT=0
39
+ HIGH_COUNT=0
40
+ MEDIUM_COUNT=0
41
+ LOW_COUNT=0
42
+
43
+ # Append finding with priority
44
+ add_finding() {
45
+ local priority="$1" # CRITICAL, HIGH, MEDIUM, LOW
46
+ local category="$2" # secrets, licenses, vulnerabilities, permissions, network, compliance
47
+ local title="$3"
48
+ local description="$4"
49
+ local remediation="$5"
50
+
51
+ local color=""
52
+ case "$priority" in
53
+ CRITICAL) color="$RED"; ((CRITICAL_COUNT++)) ;;
54
+ HIGH) color="$RED"; ((HIGH_COUNT++)) ;;
55
+ MEDIUM) color="$YELLOW"; ((MEDIUM_COUNT++)) ;;
56
+ LOW) color="$BLUE"; ((LOW_COUNT++)) ;;
57
+ esac
58
+
59
+ FINDINGS+=("${priority}|${category}|${title}|${description}|${remediation}")
60
+ }
61
+
62
+ # ─── Secret Detection ───────────────────────────────────────────────────────
63
+
64
+ scan_secrets() {
65
+ info "Scanning for hardcoded secrets..."
66
+
67
+ local patterns=(
68
+ "AKIA[0-9A-Z]{16}" # AWS Access Key ID
69
+ "aws_secret_access_key\s*=\s*['\"]?[^\s'\"]*['\"]?" # AWS Secret
70
+ "password\s*[=:]\s*['\"]?[^\s'\"]*['\"]?" # Generic password
71
+ "api[_-]?key\s*[=:]\s*['\"]?[^\s'\"]*['\"]?" # API key
72
+ "token\s*[=:]\s*['\"]?[^\s'\"]*['\"]?" # Generic token
73
+ "gh_[a-zA-Z0-9_]{36}" # GitHub token
74
+ "-----BEGIN RSA PRIVATE KEY-----" # RSA private key
75
+ "-----BEGIN PRIVATE KEY-----" # Generic private key
76
+ "PRIVATE KEY" # Private key marker
77
+ "AUTH_TOKEN" # Auth token
78
+ "oauth_token\s*[=:]" # OAuth token
79
+ "x-api-key\s*[=:]" # API key header
80
+ )
81
+
82
+ local secret_files=()
83
+ while IFS= read -r file; do
84
+ [[ -z "$file" ]] && continue
85
+ for pattern in "${patterns[@]}"; do
86
+ if grep -qEi "$pattern" "$file" 2>/dev/null; then
87
+ secret_files+=("$file")
88
+ break
89
+ fi
90
+ done
91
+ done < <(find "$REPO_DIR" -type f \( -name "*.sh" -o -name "*.py" -o -name "*.js" -o -name "*.json" -o -name ".env*" -o -name "*.yaml" -o -name "*.yml" \) 2>/dev/null | grep -v ".git\|node_modules\|.worktree" || true)
92
+
93
+ if [[ ${#secret_files[@]} -gt 0 ]]; then
94
+ for file in "${secret_files[@]}"; do
95
+ add_finding "CRITICAL" "secrets" "Potential hardcoded secret in $file" \
96
+ "Found patterns matching secret formats (AWS keys, API keys, tokens, private keys)" \
97
+ "Rotate credentials immediately. Remove secrets from version control. Use environment variables or secret management system instead. Use git-secrets or pre-commit hooks to prevent future leaks."
98
+ done
99
+ fi
100
+
101
+ # Check for .env files in git
102
+ if find "$REPO_DIR" -name ".env" -type f ! -path "*/.git/*" ! -path "*/.worktree/*" 2>/dev/null | grep -q .; then
103
+ add_finding "HIGH" "secrets" ".env file in repository" \
104
+ ".env files containing secrets should never be committed to version control" \
105
+ "Add .env to .gitignore. Use .env.example template instead. Document required variables in README."
106
+ fi
107
+
108
+ local secret_count=${#secret_files[@]}
109
+ [[ $secret_count -eq 0 ]] && success "No obvious hardcoded secrets detected" || warn "Found $secret_count files with potential secrets"
110
+ }
111
+
112
+ # ─── License Compliance ─────────────────────────────────────────────────────
113
+
114
+ scan_licenses() {
115
+ info "Scanning for license compliance..."
116
+
117
+ # Detect package manager
118
+ local has_npm=false has_pip=false has_go=false has_cargo=false
119
+
120
+ [[ -f "$REPO_DIR/package.json" ]] && has_npm=true
121
+ [[ -f "$REPO_DIR/requirements.txt" || -f "$REPO_DIR/setup.py" ]] && has_pip=true
122
+ [[ -f "$REPO_DIR/go.mod" ]] && has_go=true
123
+ [[ -f "$REPO_DIR/Cargo.toml" ]] && has_cargo=true
124
+
125
+ # Check npm licenses
126
+ if $has_npm && command -v npm &>/dev/null; then
127
+ while IFS= read -r line; do
128
+ [[ "$line" =~ GPL|AGPL ]] && [[ ! "$line" =~ MIT|Apache|BSD ]] && \
129
+ add_finding "MEDIUM" "licenses" "GPL/AGPL dependency in npm project" \
130
+ "Found GPL/AGPL licensed package: $line" \
131
+ "Review license compatibility. Consider alternatives with permissive licenses. Document GPL/AGPL usage."
132
+ done < <(npm list --depth=0 2>/dev/null | grep -i "gpl\|agpl" || true)
133
+ fi
134
+
135
+ # Check for LICENSES directory
136
+ if [[ ! -d "$REPO_DIR/LICENSES" ]]; then
137
+ add_finding "LOW" "licenses" "Missing LICENSES directory" \
138
+ "No LICENSES directory found for SPDX/license documentation" \
139
+ "Create LICENSES/ directory. Document all third-party licenses used. Generate with license scanner tools."
140
+ fi
141
+
142
+ # Detect MIT project using GPL
143
+ if [[ -f "$REPO_DIR/LICENSE" ]]; then
144
+ if grep -qi "MIT\|Apache" "$REPO_DIR/LICENSE" 2>/dev/null; then
145
+ # MIT/Apache project — flag GPL dependencies
146
+ while IFS= read -r line; do
147
+ [[ "$line" =~ GPL|AGPL ]] && \
148
+ add_finding "HIGH" "licenses" "GPL/AGPL in permissive project" \
149
+ "MIT/Apache project using GPL/AGPL dependency: $line" \
150
+ "Replace GPL dependencies with permissive alternatives. Update LICENSE file if needed."
151
+ done < <(npm list 2>/dev/null | grep -i "gpl\|agpl" || true)
152
+ fi
153
+ fi
154
+
155
+ success "License compliance check complete"
156
+ }
157
+
158
+ # ─── Vulnerability Scanning ────────────────────────────────────────────────
159
+
160
+ scan_vulnerabilities() {
161
+ info "Scanning for known vulnerabilities..."
162
+
163
+ local vuln_count=0
164
+
165
+ # Check npm vulnerabilities
166
+ if [[ -f "$REPO_DIR/package.json" ]] && command -v npm &>/dev/null; then
167
+ while IFS= read -r line; do
168
+ [[ -z "$line" ]] && continue
169
+ ((vuln_count++))
170
+ add_finding "HIGH" "vulnerabilities" "npm security vulnerability" \
171
+ "Found npm audit issue: $line" \
172
+ "Run 'npm audit fix' to remediate. Update vulnerable dependencies. Re-test after updates."
173
+ done < <(npm audit 2>/dev/null | grep -i "vulnerability\|found" || true)
174
+ fi
175
+
176
+ # Check pip vulnerabilities
177
+ if [[ -f "$REPO_DIR/requirements.txt" ]] && command -v pip &>/dev/null; then
178
+ if command -v safety &>/dev/null; then
179
+ while IFS= read -r line; do
180
+ [[ -z "$line" ]] && continue
181
+ ((vuln_count++))
182
+ add_finding "HIGH" "vulnerabilities" "Python package vulnerability" \
183
+ "Found via safety: $line" \
184
+ "Update vulnerable package. Test compatibility. Run safety check after updates."
185
+ done < <(safety check 2>/dev/null || true)
186
+ fi
187
+ fi
188
+
189
+ [[ $vuln_count -eq 0 ]] && success "No known vulnerabilities detected" || warn "Found $vuln_count vulnerabilities"
190
+ }
191
+
192
+ # ─── SBOM Generation ───────────────────────────────────────────────────────
193
+
194
+ generate_sbom() {
195
+ info "Generating Software Bill of Materials..."
196
+
197
+ local sbom_file="${REPO_DIR}/.claude/pipeline-artifacts/sbom.json"
198
+ mkdir -p "$(dirname "$sbom_file")"
199
+
200
+ local sbom='{"bomFormat":"CycloneDX","specVersion":"1.4","version":1,"components":[]}'
201
+
202
+ # Add npm packages
203
+ if [[ -f "$REPO_DIR/package.json" ]] && command -v npm &>/dev/null; then
204
+ local npm_list
205
+ npm_list=$(npm list --json 2>/dev/null || echo '{"dependencies":{}}')
206
+ while IFS='=' read -r name version; do
207
+ [[ -z "$name" || -z "$version" ]] && continue
208
+ sbom=$(echo "$sbom" | jq --arg n "$name" --arg v "$version" \
209
+ '.components += [{"type":"library","name":$n,"version":$v,"purl":"pkg:npm/\($n)@\($v)"}]')
210
+ done < <(npm list --depth=0 --json 2>/dev/null | jq -r '.dependencies | to_entries[] | "\(.key)=\(.value.version)"' || true)
211
+ fi
212
+
213
+ # Add git commit info
214
+ local commit=""
215
+ [[ -d "$REPO_DIR/.git" ]] && commit=$(cd "$REPO_DIR" && git rev-parse HEAD 2>/dev/null || echo "unknown")
216
+ sbom=$(echo "$sbom" | jq --arg c "$commit" '.metadata.component.commit = $c')
217
+
218
+ # Write SBOM
219
+ echo "$sbom" | jq . > "$sbom_file" 2>/dev/null || true
220
+
221
+ success "SBOM generated: $sbom_file"
222
+ }
223
+
224
+ # ─── Permissions Audit ──────────────────────────────────────────────────────
225
+
226
+ audit_permissions() {
227
+ info "Auditing file permissions..."
228
+
229
+ # Check for world-writable files
230
+ while IFS= read -r file; do
231
+ [[ -z "$file" ]] && continue
232
+ add_finding "MEDIUM" "permissions" "World-writable file: $file" \
233
+ "File has overly permissive permissions (mode ending in 2 or 7)" \
234
+ "Run: chmod o-w \"$file\" to remove world-writable bit"
235
+ done < <(find "$REPO_DIR" -type f -perm -002 ! -path "*/.git/*" ! -path "*/.worktree/*" 2>/dev/null || true)
236
+
237
+ # Check for setuid/setgid binaries
238
+ while IFS= read -r file; do
239
+ [[ -z "$file" ]] && continue
240
+ add_finding "HIGH" "permissions" "setuid/setgid binary: $file" \
241
+ "Binary has setuid or setgid bit set" \
242
+ "Review necessity. Remove if not essential. Audit access controls."
243
+ done < <(find "$REPO_DIR" -type f \( -perm -4000 -o -perm -2000 \) ! -path "*/.git/*" 2>/dev/null || true)
244
+
245
+ # Check for readable private keys
246
+ while IFS= read -r file; do
247
+ [[ -z "$file" ]] && continue
248
+ add_finding "CRITICAL" "permissions" "Readable private key: $file" \
249
+ "Private key file has permissive read permissions" \
250
+ "Run: chmod 600 \"$file\" immediately. Rotate the key. Audit access logs."
251
+ done < <(find "$REPO_DIR" -type f \( -name "*.pem" -o -name "*.key" -o -name "id_rsa" \) ! -path "*/.git/*" 2>/dev/null | while read -r f; do
252
+ [[ $(stat -f%A "$f" 2>/dev/null || stat -c%a "$f" 2>/dev/null) =~ [^0].. ]] && echo "$f"
253
+ done || true)
254
+
255
+ success "Permissions audit complete"
256
+ }
257
+
258
+ # ─── Network Exposure Analysis ─────────────────────────────────────────────
259
+
260
+ analyze_network() {
261
+ info "Analyzing network exposure..."
262
+
263
+ local urls_found=()
264
+
265
+ # Find external network calls
266
+ while IFS= read -r line; do
267
+ [[ -z "$line" ]] && continue
268
+ if [[ "$line" =~ http://|https://|curl|wget ]]; then
269
+ urls_found+=("$line")
270
+ fi
271
+ done < <(grep -r "http\|curl\|wget\|socket\|fetch\|request" "$REPO_DIR/scripts/" "$REPO_DIR/src/" 2>/dev/null | grep -v ".git\|.worktree\|Binary" || true)
272
+
273
+ if [[ ${#urls_found[@]} -gt 0 ]]; then
274
+ info "Found ${#urls_found[@]} network-related operations"
275
+ for line in "${urls_found[@]}"; do
276
+ warn " $line"
277
+ done
278
+
279
+ add_finding "MEDIUM" "network" "External network calls detected" \
280
+ "Script makes external API/network calls. Found ${#urls_found[@]} references." \
281
+ "Audit all network calls. Ensure TLS/HTTPS. Validate certificates. Log network activity."
282
+ fi
283
+
284
+ success "Network exposure analysis complete"
285
+ }
286
+
287
+ # ─── Compliance Report ──────────────────────────────────────────────────────
288
+
289
+ generate_compliance_report() {
290
+ info "Generating compliance report..."
291
+
292
+ local report_file="${REPO_DIR}/.claude/pipeline-artifacts/security-compliance-report.md"
293
+ mkdir -p "$(dirname "$report_file")"
294
+
295
+ cat > "$report_file" <<'EOF'
296
+ # Security Compliance Report
297
+
298
+ ## SOC2 Checklist
299
+
300
+ ### CC (Common Criteria)
301
+ - [ ] CC1: Risk Assessment completed
302
+ - [ ] CC2: Management objectives and responsibilities defined
303
+ - [ ] CC3: Communication of objectives and responsibilities
304
+ - [ ] CC4: Information security culture established
305
+ - [ ] CC5: Roles and responsibilities assigned
306
+ - [ ] CC6: Segregation of duties enforced
307
+ - [ ] CC7: Human resources policies and procedures
308
+ - [ ] CC8: Competence of personnel
309
+ - [ ] CC9: Accountability assigned
310
+
311
+ ### C (Criteria)
312
+ - [ ] C1.1: Authorization and access controls
313
+ - [ ] C1.2: Change management procedures
314
+ - [ ] C2.1: System monitoring
315
+ - [ ] C2.2: Monitoring of systems and applications
316
+ - [ ] C3.1: Logical access controls
317
+ - [ ] C3.2: Physical access controls
318
+ - [ ] C4.1: Risk assessment documentation
319
+ - [ ] C5.1: Incident identification and reporting
320
+ - [ ] C6.1: Vulnerability identification and remediation
321
+ - [ ] C7.1: Availability and performance monitoring
322
+
323
+ ## ISO 27001 Controls
324
+
325
+ ### A.5 - Organizational Controls
326
+ - [ ] A.5.1: Management commitment to security
327
+ - [ ] A.5.2: Security policy established
328
+ - [ ] A.5.3: Allocation of information security responsibilities
329
+
330
+ ### A.6 - Personnel Controls
331
+ - [ ] A.6.1: Confidentiality or non-disclosure agreements
332
+ - [ ] A.6.2: Information security awareness training
333
+ - [ ] A.6.3: Procedures for third-party access
334
+
335
+ ### A.7 - Physical and Environmental Controls
336
+ - [ ] A.7.1: Perimeter security
337
+ - [ ] A.7.2: Entry controls
338
+ - [ ] A.7.3: Workspace security
339
+
340
+ ### A.8 - Technical Controls
341
+ - [ ] A.8.1: Access control policies
342
+ - [ ] A.8.2: Cryptography usage
343
+ - [ ] A.8.3: Malware protection
344
+
345
+ ## GDPR Compliance
346
+
347
+ - [ ] Data inventory completed
348
+ - [ ] Data processing agreements in place
349
+ - [ ] Data subject rights procedures
350
+ - [ ] Data breach notification plan
351
+ - [ ] Privacy by design implemented
352
+ - [ ] Data retention policy defined
353
+
354
+ ## Findings Summary
355
+
356
+ | Priority | Count |
357
+ |----------|-------|
358
+ | CRITICAL | 0 |
359
+ | HIGH | 0 |
360
+ | MEDIUM | 0 |
361
+ | LOW | 0 |
362
+
363
+ EOF
364
+
365
+ success "Compliance report generated: $report_file"
366
+ }
367
+
368
+ # ─── Unified Full Scan ──────────────────────────────────────────────────────
369
+
370
+ run_full_scan() {
371
+ echo ""
372
+ echo -e "${CYAN}${BOLD}╔═════════════════════════════════════════════════════════════╗${RESET}"
373
+ echo -e "${CYAN}${BOLD}║ SHIPWRIGHT SECURITY AUDIT${RESET}"
374
+ echo -e "${CYAN}${BOLD}║ Repo: $(basename "$REPO_DIR")${RESET}"
375
+ echo -e "${CYAN}${BOLD}╚═════════════════════════════════════════════════════════════╝${RESET}"
376
+ echo ""
377
+
378
+ scan_secrets
379
+ echo ""
380
+ scan_licenses
381
+ echo ""
382
+ scan_vulnerabilities
383
+ echo ""
384
+ generate_sbom
385
+ echo ""
386
+ audit_permissions
387
+ echo ""
388
+ analyze_network
389
+ echo ""
390
+ generate_compliance_report
391
+ echo ""
392
+
393
+ # Print findings summary
394
+ print_findings_summary
395
+ }
396
+
397
+ # ─── Print Findings ────────────────────────────────────────────────────────
398
+
399
+ print_findings_summary() {
400
+ echo -e "${CYAN}${BOLD}╔═════════════════════════════════════════════════════════════╗${RESET}"
401
+ echo -e "${CYAN}${BOLD}║ FINDINGS SUMMARY${RESET}"
402
+ echo -e "${CYAN}${BOLD}╚═════════════════════════════════════════════════════════════╝${RESET}"
403
+ echo ""
404
+
405
+ echo -e " ${RED}${BOLD}CRITICAL:${RESET} $CRITICAL_COUNT"
406
+ echo -e " ${RED}${BOLD}HIGH:${RESET} $HIGH_COUNT"
407
+ echo -e " ${YELLOW}${BOLD}MEDIUM:${RESET} $MEDIUM_COUNT"
408
+ echo -e " ${BLUE}${BOLD}LOW:${RESET} $LOW_COUNT"
409
+ echo ""
410
+
411
+ if [[ ${#FINDINGS[@]} -eq 0 ]]; then
412
+ success "No security findings detected!"
413
+ return 0
414
+ fi
415
+
416
+ # Sort and display findings
417
+ for finding in "${FINDINGS[@]}"; do
418
+ IFS='|' read -r priority category title description remediation <<< "$finding"
419
+
420
+ local color=""
421
+ case "$priority" in
422
+ CRITICAL) color="$RED" ;;
423
+ HIGH) color="$RED" ;;
424
+ MEDIUM) color="$YELLOW" ;;
425
+ LOW) color="$BLUE" ;;
426
+ esac
427
+
428
+ echo -e "${color}${BOLD}[$priority]${RESET} $title"
429
+ echo -e " ${DIM}Category: $category${RESET}"
430
+ echo -e " ${DIM}Issue: $description${RESET}"
431
+ echo -e " ${GREEN}Remediation: $remediation${RESET}"
432
+ echo ""
433
+ done
434
+ }
435
+
436
+ # ─── Help ───────────────────────────────────────────────────────────────────
437
+
438
+ show_help() {
439
+ echo -e "${CYAN}${BOLD}shipwright security-audit${RESET} ${DIM}v${VERSION}${RESET} — Comprehensive Security Auditing"
440
+ echo ""
441
+ echo -e "${BOLD}USAGE${RESET}"
442
+ echo -e " ${CYAN}shipwright security-audit${RESET} [command]"
443
+ echo ""
444
+ echo -e "${BOLD}COMMANDS${RESET}"
445
+ echo -e " ${CYAN}scan${RESET} Full security scan (all checks)"
446
+ echo -e " ${CYAN}secrets${RESET} Secret detection scan"
447
+ echo -e " ${CYAN}licenses${RESET} License compliance check"
448
+ echo -e " ${CYAN}vulnerabilities${RESET} Vulnerability scan"
449
+ echo -e " ${CYAN}sbom${RESET} Generate Software Bill of Materials"
450
+ echo -e " ${CYAN}permissions${RESET} File permissions audit"
451
+ echo -e " ${CYAN}network${RESET} Network exposure analysis"
452
+ echo -e " ${CYAN}report${RESET} Generate compliance report"
453
+ echo -e " ${CYAN}help${RESET} Show this help message"
454
+ echo ""
455
+ echo -e "${BOLD}EXAMPLES${RESET}"
456
+ echo -e " ${DIM}shipwright security-audit scan${RESET}"
457
+ echo -e " ${DIM}shipwright security-audit secrets${RESET}"
458
+ echo -e " ${DIM}shipwright security-audit licenses --json${RESET}"
459
+ }
460
+
461
+ # ─── Source Guard ──────────────────────────────────────────────────────────
462
+
463
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
464
+ cmd="${1:-help}"
465
+
466
+ case "$cmd" in
467
+ scan)
468
+ run_full_scan
469
+ ;;
470
+ secrets)
471
+ scan_secrets
472
+ print_findings_summary
473
+ ;;
474
+ licenses)
475
+ scan_licenses
476
+ print_findings_summary
477
+ ;;
478
+ vulnerabilities)
479
+ scan_vulnerabilities
480
+ print_findings_summary
481
+ ;;
482
+ sbom)
483
+ generate_sbom
484
+ ;;
485
+ permissions)
486
+ audit_permissions
487
+ print_findings_summary
488
+ ;;
489
+ network)
490
+ analyze_network
491
+ print_findings_summary
492
+ ;;
493
+ report)
494
+ generate_compliance_report
495
+ ;;
496
+ help|--help|-h)
497
+ show_help
498
+ ;;
499
+ *)
500
+ error "Unknown command: $cmd"
501
+ show_help
502
+ exit 1
503
+ ;;
504
+ esac
505
+ fi
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="1.9.0"
9
+ VERSION="2.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -316,8 +316,29 @@ optimize_tune_templates() {
316
316
  new_weights=$(echo "$new_weights" | jq --arg key "${tmpl}|${lbl}" --argjson w "$new_weight" '.[$key] = $w')
317
317
  done <<< "$combos"
318
318
 
319
+ # Build consumer-friendly format with per-template aggregates
320
+ local consumer_weights
321
+ consumer_weights=$(echo "$new_weights" | jq '
322
+ . as $raw |
323
+ # Extract unique template names
324
+ [keys[] | split("|")[0]] | unique | map(. as $tmpl |
325
+ {
326
+ key: $tmpl,
327
+ value: {
328
+ success_rate: ([$raw | to_entries[] | select(.key | startswith($tmpl + "|")) | .value] | if length > 0 then (add / length) else 0 end),
329
+ avg_duration_min: 0,
330
+ sample_size: ([$raw | to_entries[] | select(.key | startswith($tmpl + "|"))] | length),
331
+ raw_weights: ([$raw | to_entries[] | select(.key | startswith($tmpl + "|"))] | from_entries)
332
+ }
333
+ }
334
+ ) | from_entries |
335
+ {weights: ., updated_at: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))}
336
+ ' 2>/dev/null || echo "$new_weights")
337
+
319
338
  # Atomic write
320
- echo "$new_weights" > "$tmp_weights" && mv "$tmp_weights" "$TEMPLATE_WEIGHTS_FILE"
339
+ local tmp_cw
340
+ tmp_cw=$(mktemp "${TEMPLATE_WEIGHTS_FILE}.tmp.XXXXXX")
341
+ echo "$consumer_weights" > "$tmp_cw" && mv "$tmp_cw" "$TEMPLATE_WEIGHTS_FILE" || rm -f "$tmp_cw"
321
342
  fi
322
343
 
323
344
  rm -f "$tmp_stats" "$tmp_weights" 2>/dev/null || true
@@ -468,16 +489,23 @@ optimize_learn_iterations() {
468
489
  med_stats=$(calc_stats "$tmp_med")
469
490
  high_stats=$(calc_stats "$tmp_high")
470
491
 
471
- # Build iteration model
492
+ # Build iteration model with predictions wrapper
472
493
  local tmp_model
473
- tmp_model=$(mktemp)
494
+ tmp_model=$(mktemp "${ITERATION_MODEL_FILE}.tmp.XXXXXX")
474
495
  jq -n \
475
496
  --argjson low "$low_stats" \
476
497
  --argjson medium "$med_stats" \
477
498
  --argjson high "$high_stats" \
478
499
  --arg updated "$(now_iso)" \
479
- '{low: $low, medium: $medium, high: $high, updated_at: $updated}' \
480
- > "$tmp_model" && mv "$tmp_model" "$ITERATION_MODEL_FILE"
500
+ '{
501
+ predictions: {
502
+ low: {max_iterations: (if $low.mean > 0 then (($low.mean + $low.stddev) | floor | if . < 5 then 5 else . end) else 10 end), confidence: (if $low.samples >= 10 then 0.8 elif $low.samples >= 5 then 0.6 else 0.4 end), mean: $low.mean, stddev: $low.stddev, samples: $low.samples},
503
+ medium: {max_iterations: (if $medium.mean > 0 then (($medium.mean + $medium.stddev) | floor | if . < 10 then 10 else . end) else 20 end), confidence: (if $medium.samples >= 10 then 0.8 elif $medium.samples >= 5 then 0.6 else 0.4 end), mean: $medium.mean, stddev: $medium.stddev, samples: $medium.samples},
504
+ high: {max_iterations: (if $high.mean > 0 then (($high.mean + $high.stddev) | floor | if . < 15 then 15 else . end) else 30 end), confidence: (if $high.samples >= 10 then 0.8 elif $high.samples >= 5 then 0.6 else 0.4 end), mean: $high.mean, stddev: $high.stddev, samples: $high.samples}
505
+ },
506
+ updated_at: $updated
507
+ }' \
508
+ > "$tmp_model" && mv "$tmp_model" "$ITERATION_MODEL_FILE" || rm -f "$tmp_model"
481
509
 
482
510
  rm -f "$tmp_low" "$tmp_med" "$tmp_high" 2>/dev/null || true
483
511
 
@@ -606,10 +634,29 @@ optimize_route_models() {
606
634
  done <<< "$stages"
607
635
  fi
608
636
 
637
+ # Wrap in consumer-friendly format
638
+ local consumer_routing
639
+ consumer_routing=$(echo "$routing" | jq '{
640
+ routes: (. | to_entries | map({
641
+ key: .key,
642
+ value: {
643
+ model: .value.recommended,
644
+ confidence: (if .value.sonnet_samples + .value.opus_samples >= 10 then 0.9
645
+ elif .value.sonnet_samples + .value.opus_samples >= 5 then 0.7
646
+ else 0.5 end),
647
+ sonnet_rate: .value.sonnet_rate,
648
+ opus_rate: .value.opus_rate,
649
+ sonnet_samples: .value.sonnet_samples,
650
+ opus_samples: .value.opus_samples
651
+ }
652
+ }) | from_entries),
653
+ updated_at: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
654
+ }' 2>/dev/null || echo "$routing")
655
+
609
656
  # Atomic write
610
657
  local tmp_routing
611
- tmp_routing=$(mktemp)
612
- echo "$routing" > "$tmp_routing" && mv "$tmp_routing" "$MODEL_ROUTING_FILE"
658
+ tmp_routing=$(mktemp "${MODEL_ROUTING_FILE}.tmp.XXXXXX")
659
+ echo "$consumer_routing" > "$tmp_routing" && mv "$tmp_routing" "$MODEL_ROUTING_FILE" || rm -f "$tmp_routing"
613
660
 
614
661
  rm -f "$tmp_stage_stats" 2>/dev/null || true
615
662
 
@@ -779,6 +826,8 @@ optimize_full_analysis() {
779
826
  optimize_learn_iterations
780
827
  optimize_route_models
781
828
  optimize_evolve_memory
829
+ optimize_report >> "${OPTIMIZATION_DIR}/last-report.txt" 2>/dev/null || true
830
+ optimize_adjust_audit_intensity 2>/dev/null || true
782
831
 
783
832
  echo ""
784
833
  success "Full optimization analysis complete"
@@ -899,6 +948,58 @@ optimize_report() {
899
948
  success "Report complete"
900
949
  }
901
950
 
951
+ # optimize_adjust_audit_intensity
952
+ # Reads quality-scores.jsonl trends and adjusts intelligence feature flags
953
+ # to increase audit intensity when quality is declining.
954
+ optimize_adjust_audit_intensity() {
955
+ local quality_file="${HOME}/.shipwright/optimization/quality-scores.jsonl"
956
+ local daemon_config="${REPO_DIR:-.}/.claude/daemon-config.json"
957
+
958
+ [[ ! -f "$quality_file" ]] && return 0
959
+ [[ ! -f "$daemon_config" ]] && return 0
960
+
961
+ # Get last 10 quality scores
962
+ local recent_scores avg_quality trend
963
+ recent_scores=$(tail -10 "$quality_file" 2>/dev/null || true)
964
+ [[ -z "$recent_scores" ]] && return 0
965
+
966
+ avg_quality=$(echo "$recent_scores" | jq -r '.quality_score // 70' 2>/dev/null \
967
+ | awk '{ sum += $1; count++ } END { if (count > 0) printf "%.0f", sum/count; else print 70 }')
968
+ avg_quality="${avg_quality:-70}"
969
+
970
+ # Detect trend: compare first half vs second half
971
+ local first_half_avg second_half_avg
972
+ first_half_avg=$(echo "$recent_scores" | head -5 | jq -r '.quality_score // 70' 2>/dev/null \
973
+ | awk '{ sum += $1; count++ } END { if (count > 0) printf "%.0f", sum/count; else print 70 }')
974
+ second_half_avg=$(echo "$recent_scores" | tail -5 | jq -r '.quality_score // 70' 2>/dev/null \
975
+ | awk '{ sum += $1; count++ } END { if (count > 0) printf "%.0f", sum/count; else print 70 }')
976
+
977
+ if [[ "${second_half_avg:-70}" -lt "${first_half_avg:-70}" ]]; then
978
+ trend="declining"
979
+ else
980
+ trend="stable_or_improving"
981
+ fi
982
+
983
+ # Declining quality → enable more audits
984
+ if [[ "$trend" == "declining" || "${avg_quality:-70}" -lt 60 ]]; then
985
+ info "Quality trend: ${trend} (avg: ${avg_quality}) — increasing audit intensity"
986
+ local tmp_dc
987
+ tmp_dc=$(mktemp "${daemon_config}.tmp.XXXXXX")
988
+ jq '.intelligence.adversarial_enabled = true | .intelligence.architecture_enabled = true' \
989
+ "$daemon_config" > "$tmp_dc" 2>/dev/null && mv "$tmp_dc" "$daemon_config" || rm -f "$tmp_dc"
990
+ emit_event "optimize.audit_intensity" \
991
+ "avg_quality=$avg_quality" \
992
+ "trend=$trend" \
993
+ "action=increase"
994
+ elif [[ "${avg_quality:-70}" -gt 85 ]]; then
995
+ info "Quality trend: excellent (avg: ${avg_quality}) — maintaining standard audits"
996
+ emit_event "optimize.audit_intensity" \
997
+ "avg_quality=$avg_quality" \
998
+ "trend=$trend" \
999
+ "action=maintain"
1000
+ fi
1001
+ }
1002
+
902
1003
  # ═════════════════════════════════════════════════════════════════════════════
903
1004
  # HELP
904
1005
  # ═════════════════════════════════════════════════════════════════════════════