create-qa-architect 5.12.0 → 5.13.2
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/.github/dependabot.yml +10 -30
- package/.github/workflows/claude-md-validation.yml +5 -7
- package/.github/workflows/dependabot-auto-merge.yml +1 -0
- package/.github/workflows/quality.yml +26 -12
- package/.github/workflows/release.yml +2 -1
- package/.github/workflows/stale-prs.yml +42 -0
- package/.github/workflows/weekly-gitleaks-verification.yml +6 -4
- package/LICENSE +3 -3
- package/README.md +19 -20
- package/config/quality-config.schema.json +1 -1
- package/docs/CI-COST-ANALYSIS.md +8 -8
- package/docs/DEPLOYMENT.md +1 -1
- package/docs/DEVELOPMENT-WORKFLOW.md +2 -2
- package/docs/TURBOREPO-SUPPORT.md +3 -3
- package/docs/dev_guide/CONVENTIONS.md +132 -0
- package/eslint.config.cjs +25 -0
- package/lib/blob-storage.js +57 -0
- package/lib/commands/analyze-ci.js +267 -27
- package/lib/commands/deps.js +5 -5
- package/lib/commands/license-commands.js +2 -2
- package/lib/commands/maturity-check.js +20 -2
- package/lib/dependency-monitoring-basic.js +4 -4
- package/lib/dependency-monitoring-premium.js +5 -5
- package/lib/license-validator.js +1 -1
- package/lib/licensing.js +3 -3
- package/lib/smart-strategy-generator.js +1 -1
- package/lib/validation/documentation.js +2 -0
- package/lib/workflow-config.js +106 -61
- package/package.json +51 -21
- package/scripts/deploy-consumers.sh +369 -0
- package/scripts/pattern-check.sh +607 -0
- package/scripts/run-semgrep.sh +244 -0
- package/scripts/smart-test-strategy.sh +1 -1
- package/setup.js +62 -32
- package/templates/CLAUDE_WORKFLOW_POLICY.md +3 -3
- package/templates/scripts/smart-test-strategy.sh +1 -1
- package/.github/workflows/auto-release.yml +0 -39
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# deploy-consumers.sh — Auto-discover and update ALL consumer repos with staged rollout
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ./scripts/deploy-consumers.sh # Dry run (validate only)
|
|
8
|
+
# ./scripts/deploy-consumers.sh --canary-only --push # Deploy ONLY to canary, wait for CI
|
|
9
|
+
# ./scripts/deploy-consumers.sh --push # Full rollout: canary first, then rest
|
|
10
|
+
# ./scripts/deploy-consumers.sh --skip-canary --push # Emergency: bypass canary (use with caution)
|
|
11
|
+
#
|
|
12
|
+
# Staged Rollout Process:
|
|
13
|
+
# 1. Deploy to canary repo (buildproven) first
|
|
14
|
+
# 2. Wait for CI to complete on canary (up to 10 minutes)
|
|
15
|
+
# 3. If CI passes, deploy to remaining repos
|
|
16
|
+
# 4. If CI fails, abort rollout (prevents cascading failures)
|
|
17
|
+
#
|
|
18
|
+
# Auto-discovers repos by scanning ~/Projects for .github/workflows/quality.yml
|
|
19
|
+
# files that contain the WORKFLOW_MODE marker from qa-architect.
|
|
20
|
+
|
|
21
|
+
PROJECTS_DIR="$HOME/Projects"
|
|
22
|
+
QA_ARCHITECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
23
|
+
CANARY_REPO="buildproven"
|
|
24
|
+
PUSH=false
|
|
25
|
+
TIER="minimal"
|
|
26
|
+
VERBOSE=false
|
|
27
|
+
CANARY_ONLY=false
|
|
28
|
+
SKIP_CANARY=false
|
|
29
|
+
CI_TIMEOUT=600 # 10 minutes in seconds
|
|
30
|
+
CI_POLL_INTERVAL=15 # Check every 15 seconds
|
|
31
|
+
|
|
32
|
+
# Parse args
|
|
33
|
+
while [[ $# -gt 0 ]]; do
|
|
34
|
+
case "$1" in
|
|
35
|
+
--push) PUSH=true; shift ;;
|
|
36
|
+
--tier) TIER="$2"; shift 2 ;;
|
|
37
|
+
--verbose) VERBOSE=true; shift ;;
|
|
38
|
+
--canary-only) CANARY_ONLY=true; shift ;;
|
|
39
|
+
--skip-canary) SKIP_CANARY=true; shift ;;
|
|
40
|
+
--help|-h)
|
|
41
|
+
cat <<EOF
|
|
42
|
+
Usage: deploy-consumers.sh [OPTIONS]
|
|
43
|
+
|
|
44
|
+
OPTIONS:
|
|
45
|
+
--push Commit and push changes (default: dry run)
|
|
46
|
+
--canary-only Deploy ONLY to canary repo, wait for CI green
|
|
47
|
+
--skip-canary Skip canary validation (emergency use only)
|
|
48
|
+
--tier <tier> Workflow tier to apply (default: minimal)
|
|
49
|
+
--verbose Show detailed output
|
|
50
|
+
--help, -h Show this help
|
|
51
|
+
|
|
52
|
+
STAGED ROLLOUT:
|
|
53
|
+
Default behavior (--push):
|
|
54
|
+
1. Deploy to canary (buildproven)
|
|
55
|
+
2. Wait for CI to pass (up to 10 minutes)
|
|
56
|
+
3. Deploy to remaining repos
|
|
57
|
+
|
|
58
|
+
Canary-only mode (--canary-only --push):
|
|
59
|
+
Deploy to canary only, validate CI passes
|
|
60
|
+
|
|
61
|
+
Skip canary (--skip-canary --push):
|
|
62
|
+
Deploy to all repos simultaneously (emergency only)
|
|
63
|
+
|
|
64
|
+
EXAMPLES:
|
|
65
|
+
# Dry run (no changes)
|
|
66
|
+
./scripts/deploy-consumers.sh
|
|
67
|
+
|
|
68
|
+
# Canary validation
|
|
69
|
+
./scripts/deploy-consumers.sh --canary-only --push
|
|
70
|
+
|
|
71
|
+
# Full rollout (staged)
|
|
72
|
+
./scripts/deploy-consumers.sh --push
|
|
73
|
+
|
|
74
|
+
# Emergency bypass (use with caution)
|
|
75
|
+
./scripts/deploy-consumers.sh --skip-canary --push
|
|
76
|
+
EOF
|
|
77
|
+
exit 0
|
|
78
|
+
;;
|
|
79
|
+
*) echo "Unknown option: $1"; exit 1 ;;
|
|
80
|
+
esac
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
echo "=== QA Architect Consumer Deployment ==="
|
|
84
|
+
echo "Mode: $([ "$PUSH" = true ] && echo "PUSH (will commit + push)" || echo "DRY RUN (validate only)")"
|
|
85
|
+
echo "Tier: $TIER"
|
|
86
|
+
echo "Canary repo: $CANARY_REPO"
|
|
87
|
+
if [ "$CANARY_ONLY" = true ]; then
|
|
88
|
+
echo "⚠️ CANARY-ONLY mode: will deploy to $CANARY_REPO only"
|
|
89
|
+
elif [ "$SKIP_CANARY" = true ]; then
|
|
90
|
+
echo "⚠️ SKIP-CANARY mode: bypassing canary validation (emergency use)"
|
|
91
|
+
else
|
|
92
|
+
echo "Staged rollout: canary first, then remaining repos"
|
|
93
|
+
fi
|
|
94
|
+
echo ""
|
|
95
|
+
|
|
96
|
+
# Discover consumer repos: any repo with quality.yml containing WORKFLOW_MODE marker
|
|
97
|
+
# Excludes qa-architect itself
|
|
98
|
+
CONSUMERS=()
|
|
99
|
+
CANARY_DIR=""
|
|
100
|
+
for workflow in "$PROJECTS_DIR"/*/".github/workflows/quality.yml"; do
|
|
101
|
+
[ -f "$workflow" ] || continue
|
|
102
|
+
repo_dir="$(dirname "$(dirname "$(dirname "$workflow")")")"
|
|
103
|
+
repo_name="$(basename "$repo_dir")"
|
|
104
|
+
|
|
105
|
+
# Skip qa-architect itself (compare basenames to avoid case/symlink issues)
|
|
106
|
+
[ "$(basename "$repo_dir")" = "$(basename "$QA_ARCHITECT_DIR")" ] && continue
|
|
107
|
+
|
|
108
|
+
# Check for WORKFLOW_MODE marker (proves it was generated by qa-architect)
|
|
109
|
+
if grep -q "WORKFLOW_MODE:" "$workflow" 2>/dev/null; then
|
|
110
|
+
if [ "$repo_name" = "$CANARY_REPO" ]; then
|
|
111
|
+
CANARY_DIR="$repo_dir"
|
|
112
|
+
else
|
|
113
|
+
CONSUMERS+=("$repo_dir")
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
done
|
|
117
|
+
|
|
118
|
+
# Ensure canary was found
|
|
119
|
+
if [ -z "$CANARY_DIR" ]; then
|
|
120
|
+
echo "❌ ERROR: Canary repo '$CANARY_REPO' not found or doesn't have WORKFLOW_MODE marker"
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# In canary-only mode, only process canary
|
|
125
|
+
if [ "$CANARY_ONLY" = true ]; then
|
|
126
|
+
echo "Canary-only mode: processing $CANARY_REPO only"
|
|
127
|
+
CONSUMERS=()
|
|
128
|
+
else
|
|
129
|
+
echo "Discovered canary repo: $CANARY_REPO"
|
|
130
|
+
echo "Discovered ${#CONSUMERS[@]} additional consumer repos:"
|
|
131
|
+
for dir in "${CONSUMERS[@]}"; do
|
|
132
|
+
echo " - $(basename "$dir")"
|
|
133
|
+
done
|
|
134
|
+
fi
|
|
135
|
+
echo ""
|
|
136
|
+
|
|
137
|
+
# Function to wait for CI to complete on a repo
|
|
138
|
+
# Args: $1 = repo_dir, $2 = repo_name
|
|
139
|
+
wait_for_ci() {
|
|
140
|
+
local repo_dir="$1"
|
|
141
|
+
local repo_name="$2"
|
|
142
|
+
|
|
143
|
+
echo " Waiting for CI to complete on $repo_name..."
|
|
144
|
+
|
|
145
|
+
# Check if gh CLI is installed
|
|
146
|
+
if ! command -v gh &>/dev/null; then
|
|
147
|
+
echo " ⚠️ WARNING: gh CLI not installed, cannot check CI status"
|
|
148
|
+
echo " Install with: brew install gh"
|
|
149
|
+
echo " Proceeding without CI validation..."
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# Get the repo owner/name for gh CLI
|
|
154
|
+
local remote_url
|
|
155
|
+
remote_url=$(cd "$repo_dir" && git remote get-url origin 2>/dev/null || echo "")
|
|
156
|
+
if [ -z "$remote_url" ]; then
|
|
157
|
+
echo " ⚠️ WARNING: No git remote found, cannot check CI status"
|
|
158
|
+
return 0
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Extract owner/repo from URL (handles both HTTPS and SSH)
|
|
162
|
+
local repo_slug
|
|
163
|
+
repo_slug=$(echo "$remote_url" | sed -E 's|^.*[:/]([^/]+/[^/]+)(\.git)?$|\1|')
|
|
164
|
+
|
|
165
|
+
# Get the current branch
|
|
166
|
+
local branch
|
|
167
|
+
branch=$(cd "$repo_dir" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
|
|
168
|
+
|
|
169
|
+
echo " Checking CI status for $repo_slug on branch $branch..."
|
|
170
|
+
|
|
171
|
+
local elapsed=0
|
|
172
|
+
local status=""
|
|
173
|
+
|
|
174
|
+
while [ $elapsed -lt $CI_TIMEOUT ]; do
|
|
175
|
+
# Get latest workflow run status for quality.yml on this branch
|
|
176
|
+
status=$(gh run list --repo "$repo_slug" --workflow quality.yml --branch "$branch" --limit 1 --json status,conclusion --jq '.[0].status + ":" + (.[0].conclusion // "")' 2>/dev/null || echo "")
|
|
177
|
+
|
|
178
|
+
if [ -z "$status" ]; then
|
|
179
|
+
echo " ⚠️ No CI runs found yet (elapsed: ${elapsed}s)"
|
|
180
|
+
elif [[ "$status" == "completed:success" ]]; then
|
|
181
|
+
echo " ✅ CI PASSED on $repo_name (elapsed: ${elapsed}s)"
|
|
182
|
+
return 0
|
|
183
|
+
elif [[ "$status" == "completed:failure" ]] || [[ "$status" == "completed:cancelled" ]]; then
|
|
184
|
+
echo " ❌ CI FAILED on $repo_name (status: $status)"
|
|
185
|
+
return 1
|
|
186
|
+
else
|
|
187
|
+
echo " ⏳ CI in progress on $repo_name (status: $status, elapsed: ${elapsed}s)"
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
sleep $CI_POLL_INTERVAL
|
|
191
|
+
elapsed=$((elapsed + CI_POLL_INTERVAL))
|
|
192
|
+
done
|
|
193
|
+
|
|
194
|
+
echo " ⏰ TIMEOUT: CI did not complete within ${CI_TIMEOUT}s"
|
|
195
|
+
return 1
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Function to deploy to a single repo
|
|
199
|
+
# Args: $1 = repo_dir, $2 = is_canary (true/false)
|
|
200
|
+
deploy_to_repo() {
|
|
201
|
+
local repo_dir="$1"
|
|
202
|
+
local is_canary="${2:-false}"
|
|
203
|
+
local repo_name="$(basename "$repo_dir")"
|
|
204
|
+
|
|
205
|
+
echo "--- $repo_name $([ "$is_canary" = true ] && echo "(CANARY)" || echo "") ---"
|
|
206
|
+
|
|
207
|
+
# Check if repo has package.json (needed for npx)
|
|
208
|
+
if [ ! -f "$repo_dir/package.json" ]; then
|
|
209
|
+
echo " SKIP: No package.json"
|
|
210
|
+
return 2 # Return code 2 = skipped
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Detect the existing tier from the workflow
|
|
214
|
+
local existing_tier="$TIER"
|
|
215
|
+
if grep -q "WORKFLOW_MODE: standard" "$repo_dir/.github/workflows/quality.yml" 2>/dev/null; then
|
|
216
|
+
existing_tier="standard"
|
|
217
|
+
elif grep -q "WORKFLOW_MODE: comprehensive" "$repo_dir/.github/workflows/quality.yml" 2>/dev/null; then
|
|
218
|
+
existing_tier="comprehensive"
|
|
219
|
+
elif grep -q "WORKFLOW_MODE: minimal" "$repo_dir/.github/workflows/quality.yml" 2>/dev/null; then
|
|
220
|
+
existing_tier="minimal"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# Regenerate workflow (must cd to consumer dir — setup.js uses process.cwd())
|
|
224
|
+
echo " Regenerating workflow (tier: $existing_tier)..."
|
|
225
|
+
if (cd "$repo_dir" && QAA_DEVELOPER=true node "$QA_ARCHITECT_DIR/setup.js" --update "--workflow-${existing_tier}" \
|
|
226
|
+
2>&1 | { [ "$VERBOSE" = true ] && cat || tail -1; }); then
|
|
227
|
+
:
|
|
228
|
+
else
|
|
229
|
+
echo " FAIL: Workflow generation failed"
|
|
230
|
+
return 1
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
local workflow_file="$repo_dir/.github/workflows/quality.yml"
|
|
234
|
+
|
|
235
|
+
# Validate: no node_modules/create-qa-architect references
|
|
236
|
+
if grep -q "node_modules/create-qa-architect" "$workflow_file"; then
|
|
237
|
+
echo " FAIL: Contains node_modules/create-qa-architect references"
|
|
238
|
+
return 1
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# Validate: no section markers leaked
|
|
242
|
+
if grep -qE '\{\{(QA_ARCHITECT_ONLY|FULL_DETECTION|FULL_REPORT)_(BEGIN|END)\}\}' "$workflow_file"; then
|
|
243
|
+
echo " FAIL: Contains section markers"
|
|
244
|
+
return 1
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
# Validate: valid YAML (requires node + js-yaml)
|
|
248
|
+
if ! node -e "require('$QA_ARCHITECT_DIR/node_modules/js-yaml').load(require('fs').readFileSync('$workflow_file','utf8'))" 2>/dev/null; then
|
|
249
|
+
echo " FAIL: Invalid YAML"
|
|
250
|
+
return 1
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Remove stale devDep if present
|
|
254
|
+
if grep -q '"create-qa-architect"' "$repo_dir/package.json" 2>/dev/null; then
|
|
255
|
+
echo " Removing stale create-qa-architect devDependency..."
|
|
256
|
+
if [ "$PUSH" = true ]; then
|
|
257
|
+
(cd "$repo_dir" && npm uninstall create-qa-architect 2>/dev/null || true)
|
|
258
|
+
else
|
|
259
|
+
echo " (dry run - would remove devDep)"
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
echo " PASS: Validated"
|
|
264
|
+
|
|
265
|
+
# Commit and push if --push
|
|
266
|
+
if [ "$PUSH" = true ]; then
|
|
267
|
+
(
|
|
268
|
+
cd "$repo_dir"
|
|
269
|
+
if git diff --quiet .github/workflows/quality.yml package.json 2>/dev/null; then
|
|
270
|
+
echo " No changes to commit"
|
|
271
|
+
else
|
|
272
|
+
git add .github/workflows/quality.yml package.json package-lock.json 2>/dev/null || true
|
|
273
|
+
git commit -m "chore: regenerate qa-architect workflow (${existing_tier} tier)
|
|
274
|
+
|
|
275
|
+
Staged rollout: $([ "$is_canary" = true ] && echo "canary deployment" || echo "validated via canary")
|
|
276
|
+
Fixes node_modules/create-qa-architect fallback paths.
|
|
277
|
+
Consumers use npx create-qa-architect@latest instead of devDep." 2>/dev/null || true
|
|
278
|
+
git push 2>/dev/null && echo " Pushed" || echo " Push failed (check remote)"
|
|
279
|
+
fi
|
|
280
|
+
)
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
echo ""
|
|
284
|
+
return 0
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
# Validation counters
|
|
288
|
+
PASS=0
|
|
289
|
+
FAIL=0
|
|
290
|
+
SKIPPED=0
|
|
291
|
+
|
|
292
|
+
# STAGE 1: Deploy to canary (unless --skip-canary)
|
|
293
|
+
if [ "$SKIP_CANARY" = false ]; then
|
|
294
|
+
echo "=== STAGE 1: Canary Deployment ==="
|
|
295
|
+
echo ""
|
|
296
|
+
|
|
297
|
+
if deploy_to_repo "$CANARY_DIR" true; then
|
|
298
|
+
PASS=$((PASS + 1))
|
|
299
|
+
|
|
300
|
+
# Wait for CI if in push mode
|
|
301
|
+
if [ "$PUSH" = true ]; then
|
|
302
|
+
if ! wait_for_ci "$CANARY_DIR" "$CANARY_REPO"; then
|
|
303
|
+
echo ""
|
|
304
|
+
echo "❌ ROLLOUT ABORTED: Canary CI failed or timed out"
|
|
305
|
+
echo " Fix the issue on $CANARY_REPO before rolling out to other repos"
|
|
306
|
+
exit 1
|
|
307
|
+
fi
|
|
308
|
+
fi
|
|
309
|
+
else
|
|
310
|
+
ret=$?
|
|
311
|
+
if [ $ret -eq 2 ]; then
|
|
312
|
+
SKIPPED=$((SKIPPED + 1))
|
|
313
|
+
else
|
|
314
|
+
FAIL=$((FAIL + 1))
|
|
315
|
+
echo ""
|
|
316
|
+
echo "❌ ROLLOUT ABORTED: Canary deployment failed"
|
|
317
|
+
exit 1
|
|
318
|
+
fi
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
echo ""
|
|
322
|
+
echo "✅ Canary deployment successful"
|
|
323
|
+
echo ""
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# Exit early if canary-only mode
|
|
327
|
+
if [ "$CANARY_ONLY" = true ]; then
|
|
328
|
+
echo "=== Summary (Canary-Only) ==="
|
|
329
|
+
echo " Pass: $PASS"
|
|
330
|
+
echo " Fail: $FAIL"
|
|
331
|
+
echo " Skipped: $SKIPPED"
|
|
332
|
+
echo ""
|
|
333
|
+
echo "✅ Canary deployment complete. Review CI results before full rollout."
|
|
334
|
+
exit 0
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# STAGE 2: Deploy to remaining repos
|
|
338
|
+
if [ ${#CONSUMERS[@]} -gt 0 ]; then
|
|
339
|
+
echo "=== STAGE 2: Remaining Repos Deployment ==="
|
|
340
|
+
echo ""
|
|
341
|
+
|
|
342
|
+
for repo_dir in "${CONSUMERS[@]}"; do
|
|
343
|
+
if deploy_to_repo "$repo_dir" false; then
|
|
344
|
+
PASS=$((PASS + 1))
|
|
345
|
+
else
|
|
346
|
+
ret=$?
|
|
347
|
+
if [ $ret -eq 2 ]; then
|
|
348
|
+
SKIPPED=$((SKIPPED + 1))
|
|
349
|
+
else
|
|
350
|
+
FAIL=$((FAIL + 1))
|
|
351
|
+
fi
|
|
352
|
+
fi
|
|
353
|
+
done
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
echo "=== Summary ==="
|
|
357
|
+
echo " Pass: $PASS"
|
|
358
|
+
echo " Fail: $FAIL"
|
|
359
|
+
echo " Skipped: $SKIPPED"
|
|
360
|
+
echo " Total: $((${#CONSUMERS[@]} + 1))" # +1 for canary
|
|
361
|
+
|
|
362
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
363
|
+
echo ""
|
|
364
|
+
echo "⚠️ Some repos failed validation. Review output above."
|
|
365
|
+
exit 1
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
echo ""
|
|
369
|
+
echo "✅ Deployment complete!"
|