Phase A of the quality-gate rollout — local + pre-push gates that stop
slop before it leaves the laptop. Preventative controls, per the
architect playbook.
- .husky/pre-commit — adds scripts/secret-scan.sh as the first check
before the existing Biome format/lint. Blocks the commit when a
likely secret is found in staged additions. Existing Biome behaviour
is preserved.
- .husky/pre-push (new) — runs `pnpm check` (lint + typecheck + test)
before the branch leaves the laptop. Mirrors the `check` job in
.github/workflows/ci.yml so PR-blocking issues surface locally first.
Bypassable for genuine emergencies with `git push --no-verify`.
- scripts/secret-scan.sh (new) — regex scan over staged additions for
the common accidents: AWS keys, Anthropic/OpenAI/GitHub/Slack/Google/
Stripe tokens, JWTs, PEM private key blocks, hardcoded password
literals. Validated against synthetic leaks of each type. No external
tool dependency — pure bash + grep, runs in <100ms typical.
- scripts/pr-evidence.sh (new) — drafts a PR_BODY.md from the diff vs
origin/main: file lists (added/modified/deleted), commit summaries,
and the QA checklist. Flags screenshots as REQUIRED when the diff
touches packages/web/src/{components,routes} or packages/desktop.
Wired as `pnpm pr:evidence` so it runs from anywhere in the
workspace.
- .gitignore — adds PR_BODY.md so drafts don't get committed by
accident.
Pre-commit stays fast (<2s typical). Heavy checks (typecheck, full
test run) live in pre-push.
117 lines
4.0 KiB
Bash
Executable File
117 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Draft a PR body for the current branch based on the diff vs origin/main.
|
||
#
|
||
# Writes to PR_BODY.md in the repo root (gitignored — see end of script).
|
||
# Pre-fills the structure required by .github/pull_request_template.md
|
||
# and flags whether screenshots are required based on touched paths.
|
||
#
|
||
# Usage:
|
||
# ./scripts/pr-evidence.sh # writes ./PR_BODY.md
|
||
# ./scripts/pr-evidence.sh > /tmp/body.md # write to stdout
|
||
|
||
set -euo pipefail
|
||
|
||
OUTPUT="${1:-PR_BODY.md}"
|
||
|
||
# Find the base branch (default origin/main) the current branch diverged from.
|
||
BASE_REF="${BASE_REF:-origin/main}"
|
||
git fetch origin main --quiet 2>/dev/null || true
|
||
|
||
MERGE_BASE=$(git merge-base HEAD "$BASE_REF" 2>/dev/null || echo "$BASE_REF")
|
||
CHANGED=$(git diff --name-only "$MERGE_BASE"...HEAD)
|
||
ADDED=$(git diff --name-status --diff-filter=A "$MERGE_BASE"...HEAD | awk '{print $2}')
|
||
MODIFIED=$(git diff --name-status --diff-filter=M "$MERGE_BASE"...HEAD | awk '{print $2}')
|
||
DELETED=$(git diff --name-status --diff-filter=D "$MERGE_BASE"...HEAD | awk '{print $2}')
|
||
|
||
# Heuristic: any touched path under packages/web/src/{components,routes} or
|
||
# packages/desktop counts as a UI change and requires screenshots.
|
||
UI_CHANGED=0
|
||
if echo "$CHANGED" | grep -qE '^(packages/web/src/(components|routes)|packages/desktop)/'; then
|
||
UI_CHANGED=1
|
||
fi
|
||
|
||
# Commits since base — useful for the "What" section.
|
||
COMMITS=$(git log --pretty=format:'- %s' "$MERGE_BASE"..HEAD)
|
||
|
||
# Tests touched?
|
||
TESTS_TOUCHED=$(echo "$CHANGED" | grep -E '(\.test\.|/test/|/e2e/)' || true)
|
||
|
||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||
|
||
draft() {
|
||
cat <<EOF
|
||
<!--
|
||
Auto-drafted by scripts/pr-evidence.sh from the diff vs ${BASE_REF}.
|
||
Fill in the prose sections; the file lists and checklist are pre-populated.
|
||
-->
|
||
|
||
## Why
|
||
|
||
<!-- The problem this solves, in 1–3 sentences. What pain or gap does this close? -->
|
||
|
||
## What
|
||
|
||
$(if [ -n "$COMMITS" ]; then printf 'Commits on this branch:\n%s\n' "$COMMITS"; else echo '<!-- describe the change -->'; fi)
|
||
|
||
$(if [ -n "$ADDED" ]; then printf '\n**Added:**\n'; printf '%s\n' "$ADDED" | sed 's/^/- /'; fi)
|
||
$(if [ -n "$MODIFIED" ]; then printf '\n**Modified:**\n'; printf '%s\n' "$MODIFIED" | sed 's/^/- /'; fi)
|
||
$(if [ -n "$DELETED" ]; then printf '\n**Deleted:**\n'; printf '%s\n' "$DELETED" | sed 's/^/- /'; fi)
|
||
|
||
## Screenshots
|
||
|
||
EOF
|
||
|
||
if [ $UI_CHANGED -eq 1 ]; then
|
||
cat <<EOF
|
||
**Required** — this PR touches packages/web/src/{components,routes} or packages/desktop.
|
||
Commit screenshots under \`docs/screenshots/<feature-slug>/\` and reference here:
|
||
|
||
\`\`\`markdown
|
||

|
||
\`\`\`
|
||
|
||
See \`.claude/rules/workflows.md\` → "Open a PR" for capture + commit guidance.
|
||
|
||
EOF
|
||
else
|
||
cat <<EOF
|
||
<!-- No packages/web/src/{components,routes} or packages/desktop paths touched —
|
||
screenshots not strictly required. Delete this section if truly docs-only. -->
|
||
|
||
EOF
|
||
fi
|
||
|
||
cat <<EOF
|
||
## QA checklist
|
||
|
||
- [ ] \`pnpm typecheck\` clean locally
|
||
- [ ] \`pnpm lint\` clean locally
|
||
- [ ] \`pnpm test\` green locally
|
||
$(if [ -n "$TESTS_TOUCHED" ]; then echo '- [x] Tests touched on this branch:'; printf '%s\n' "$TESTS_TOUCHED" | sed 's/^/ - /'; else echo '- [ ] Tests added for new behaviour (or note why none are needed)'; fi)
|
||
- [ ] Manual verification: <!-- which Honcho instance, which workspace/peer, what you clicked, what you saw -->
|
||
$(if echo "$CHANGED" | grep -qE '^packages/desktop/'; then echo '- [ ] \`pnpm --filter @openconcho/desktop cargo-check\` passes'; fi)
|
||
- [x] Worked in a git worktree (current branch: \`${BRANCH}\`)
|
||
|
||
## Out-of-scope
|
||
|
||
<!-- What was intentionally left out and why. -->
|
||
|
||
## Notes
|
||
|
||
<!-- Caveats, follow-ups, anything reviewers should know. -->
|
||
EOF
|
||
}
|
||
|
||
if [ "$OUTPUT" = "-" ] || [ -t 1 ]; then
|
||
# When piped or first arg is "-", write to stdout.
|
||
if [ "${1:-}" = "-" ]; then
|
||
draft
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
draft > "$OUTPUT"
|
||
|
||
echo "✓ Drafted PR body → ${OUTPUT}"
|
||
echo " Open it, fill in Why / Manual verification / Out-of-scope / Notes, then use as the PR body."
|