Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ dist-ssr
# Tauri
packages/desktop/src-tauri/target/
packages/desktop/src-tauri/gen/

# Drafts from scripts/pr-evidence.sh
PR_BODY.md
10 changes: 10 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
#!/bin/sh
# Pre-commit gates — must be fast (<2s typical). Anything slow goes in pre-push.
#
# Order:
# 1. Secret scan (must run first; blocks commit if a secret leaks in)
# 2. Biome format/lint on staged TS/JS/JSON/CSS files

# 1. Secret scan over staged additions
./scripts/secret-scan.sh

# 2. Biome on staged files (auto-fixes, re-stages)
STAGED=$(git diff --cached --name-only --diff-filter=ACMR | grep -E "\.(ts|tsx|js|jsx|css|json)$" || true)
[ -z "$STAGED" ] && exit 0
pnpm exec biome check --write --staged
Expand Down
8 changes: 8 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
# Pre-push gate — runs the full quality check before the branch leaves the
# laptop. Mirrors the `check` job in .github/workflows/ci.yml.
#
# Bypass for genuine emergencies with: git push --no-verify

echo "→ Running pre-push checks (lint + typecheck + test)…"
pnpm check
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"check": "turbo run lint typecheck test",
"ci:web": "turbo run lint typecheck test build --filter=@openconcho/web",
"ci:desktop": "turbo run cargo-check --filter=@openconcho/desktop",
"pr:evidence": "./scripts/pr-evidence.sh",
"prepare": "husky"
},
"devDependencies": {
Expand Down
116 changes: 116 additions & 0 deletions scripts/pr-evidence.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/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
![Description](https://raw.githubusercontent.com/BenSheridanEdwards/openconcho/${BRANCH}/docs/screenshots/<feature-slug>/01-<state>.png)
\`\`\`

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."
64 changes: 64 additions & 0 deletions scripts/secret-scan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Secret scan for staged files.
#
# Pre-commit hook calls this against staged additions. Fast (no external
# tool; just regex over the staged diff). Designed to catch the common
# accidents — API keys committed alongside code — not to replace a full
# secret-scanning service.
#
# Exits non-zero with a clear message if a likely secret is found.

set -euo pipefail

# Only scan added/modified content (the `+` lines in the staged diff).
# This avoids false positives from existing committed strings.
STAGED_DIFF=$(git diff --cached --diff-filter=ACMR --unified=0 -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.json' '*.yml' '*.yaml' '*.toml' '*.env*' '*.sh' '*.md' 2>/dev/null || true)

if [ -z "$STAGED_DIFF" ]; then
exit 0
fi

# Only look at added lines (starting with `+`, excluding diff headers `+++`).
ADDED=$(printf '%s\n' "$STAGED_DIFF" | grep -E '^\+[^+]' || true)

if [ -z "$ADDED" ]; then
exit 0
fi

FOUND=0
FINDINGS=""

check_pattern() {
local name="$1"
local pattern="$2"
# Use `-e` to safely pass patterns that begin with `-` (e.g. PEM headers).
if printf '%s\n' "$ADDED" | grep -qE -e "$pattern"; then
FOUND=1
FINDINGS="${FINDINGS} - ${name}\n"
fi
}

check_pattern "AWS access key" 'AKIA[0-9A-Z]{16}'
check_pattern "AWS secret key (high-entropy)" 'aws_secret_access_key[[:space:]]*[:=][[:space:]]*[A-Za-z0-9/+=]{40}'
check_pattern "Anthropic API key" 'sk-ant-[a-zA-Z0-9_-]{32,}'
check_pattern "OpenAI API key" 'sk-[a-zA-Z0-9]{20,}T3BlbkFJ[a-zA-Z0-9]{20,}'
check_pattern "OpenAI project key (newer)" 'sk-proj-[a-zA-Z0-9_-]{40,}'
check_pattern "GitHub personal access token" 'gh[psoru]_[A-Za-z0-9_]{36,}'
check_pattern "GitHub fine-grained PAT" 'github_pat_[A-Za-z0-9_]{82,}'
check_pattern "Slack token" 'xox[abprs]-[A-Za-z0-9-]{10,}'
check_pattern "Google API key" 'AIza[0-9A-Za-z_-]{35}'
check_pattern "Stripe live key" 'sk_live_[A-Za-z0-9]{24,}'
check_pattern "Honcho-style JWT (likely)" 'eyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}'
check_pattern "RSA/EC/DSA/OpenSSH private key block" '-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----'
check_pattern "Generic hardcoded password" '(password|passwd|pwd)[[:space:]]*[:=][[:space:]]*["'\'']\w{8,}["'\'']'

if [ $FOUND -eq 1 ]; then
printf '\n\033[31m✗ Secret scan: potential secrets in staged changes\033[0m\n' >&2
printf '%b' "$FINDINGS" >&2
printf '\n' >&2
printf 'If this is a false positive, bypass with: \033[33mgit commit --no-verify\033[0m\n' >&2
printf 'Otherwise: remove the secret, rotate the credential, and re-stage.\n\n' >&2
exit 1
fi

exit 0
Loading