mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-08 02:14:37 +00:00
docs: add GitHub wiki + scripts/wiki.sh helper with secret-scan
Public docs now live at https://github.com/awizemann/scarf/wiki (separate git repo cloned to .wiki-worktree/, mirroring the .gh-pages-worktree/ pattern). Internal dev notes stay in scarf/docs/. scripts/wiki.sh wraps pull/commit/push with a two-pass secret-scan: hard patterns (token regexes + private-key headers + a user-maintained scripts/wiki-blocklist.txt) abort with non-zero exit; soft assignment patterns (api_key=…, password=…, token=…) warn and require --force-terms. CLAUDE.md gains a Wiki section listing the update triggers (new feature, new service, architecture change, Hermes version bump, full release, keyboard/sidebar change) and the workflow. CONTRIBUTING.md points external contributors at the wiki Edit button or a direct clone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Xcode
|
||||
build/
|
||||
.gh-pages-worktree/
|
||||
.wiki-worktree/
|
||||
DerivedData/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
@@ -52,3 +53,6 @@ scarf/standards/backups/
|
||||
# history. RELEASE_NOTES.md stays tracked (committed with the version bump).
|
||||
releases/v*/*.zip
|
||||
releases/v*/appcast-entry.xml
|
||||
|
||||
# Wiki helper: personal patterns (hostnames, IPs) blocked from the wiki push.
|
||||
scripts/wiki-blocklist.txt
|
||||
|
||||
@@ -59,6 +59,29 @@ The script bumps version, archives Universal (arm64 + x86_64) + ARM64-only varia
|
||||
|
||||
**Prerequisites (one-time, already set up on Alan's machine):** Developer ID Application cert in login Keychain (team `3Q6X2L86C4`), notarytool keychain profile `scarf-notary`, Sparkle EdDSA private key in Keychain item `https://sparkle-project.org`, `gh-pages` branch + GitHub Pages enabled. See the header of [scripts/release.sh](scripts/release.sh) and the Releases section in [README.md](README.md) for details.
|
||||
|
||||
## Wiki
|
||||
|
||||
Public documentation lives in the GitHub wiki at https://github.com/awizemann/scarf/wiki. The wiki is a separate git repo cloned to `.wiki-worktree/` in the repo root (gitignored, sibling to `.gh-pages-worktree/`). Internal dev notes stay in `scarf/docs/`; the wiki is for public-facing reference.
|
||||
|
||||
**Update the wiki when:**
|
||||
- A new feature module is added under `scarf/scarf/scarf/Features/` → extend the relevant User Guide page.
|
||||
- A new core service is added under `Core/Services/` → extend `Core-Services.md`.
|
||||
- Architecture changes (AppCoordinator, transport, MVVM-F rule, sandbox) → `Architecture-Overview.md` + the specific sub-page.
|
||||
- Hermes version bumps in this file → `Hermes-Version-Compatibility.md`.
|
||||
- `scripts/release.sh` completes a full (non-draft) release → bump latest-version on `Home.md` + append to `Release-Notes-Index.md`.
|
||||
- Keyboard shortcut or sidebar section changes → `Keyboard-Shortcuts.md` / `Sidebar-and-Navigation.md`.
|
||||
|
||||
**Skip for:** bug fixes with no user-observable change, pure refactors, typos, test-only changes, internal cleanups.
|
||||
|
||||
```bash
|
||||
./scripts/wiki.sh pull # always first
|
||||
# edit .wiki-worktree/*.md with normal tools
|
||||
./scripts/wiki.sh commit "docs: describe X" # runs secret-scan
|
||||
./scripts/wiki.sh push # runs secret-scan again, then push
|
||||
```
|
||||
|
||||
**Never** commit API keys, tokens, `.env` files, private keys, or real hostnames/IPs to the wiki. The script's two-pass secret-scan blocks common token patterns and a user-maintained blocklist at `scripts/wiki-blocklist.txt` (gitignored). Do not bypass without explicit approval. Full workflow on the wiki itself at `.wiki-worktree/Wiki-Maintenance.md`.
|
||||
|
||||
## Hermes Version
|
||||
|
||||
Targets Hermes v0.9.0 (v2026.4.13). Log lines may carry an optional `[session_id]` tag between the level and logger name — `HermesLogService.parseLine` treats the session tag as an optional capture group, so older untagged lines still parse.
|
||||
|
||||
@@ -33,6 +33,10 @@ Rules:
|
||||
- The app only reads from `~/.hermes/state.db` (never writes). Memory files are the exception.
|
||||
- Swift 6 strict concurrency: `@MainActor` default isolation, `nonisolated` for service methods.
|
||||
|
||||
## Documentation
|
||||
|
||||
Public docs live in the [GitHub wiki](https://github.com/awizemann/scarf/wiki). Small fixes (typos, clarifications) can be made via the "Edit" button on any wiki page — you need push access to the main repo. For larger changes, clone the wiki locally (`git clone git@github.com:awizemann/scarf.wiki.git`) or open an issue describing the proposed change.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Open an issue with:
|
||||
|
||||
Executable
+254
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Scarf wiki helper — wraps the local .wiki-worktree/ clone with a secret-scan.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/wiki.sh status # git status inside .wiki-worktree/
|
||||
# ./scripts/wiki.sh pull # fetch + fast-forward; aborts if dirty
|
||||
# ./scripts/wiki.sh new <Page-Name> # create Page-Name.md with stub template
|
||||
# ./scripts/wiki.sh stub-check # list pages still containing the TODO stub
|
||||
# ./scripts/wiki.sh commit "<msg>" # secret-scan, then git add -A && git commit
|
||||
# ./scripts/wiki.sh push # secret-scan again, then git push
|
||||
# ./scripts/wiki.sh touch <Page-Name> # bump "Last updated" line to today
|
||||
# ./scripts/wiki.sh --help # this help
|
||||
#
|
||||
# The secret-scan has two tiers:
|
||||
# - Hard patterns (tokens, keys, private-key headers): block with non-zero exit.
|
||||
# - Soft keywords (password, api_key, secret, bearer, authorization:, .env):
|
||||
# warn and require --force-terms on commit/push to proceed.
|
||||
#
|
||||
# A user-maintained blocklist lives at scripts/wiki-blocklist.txt (gitignored).
|
||||
# One pattern per line — blank lines and lines starting with # are ignored.
|
||||
# Matches are treated as HARD blocks. Use this for personal IPs, hostnames, etc.
|
||||
#
|
||||
# Bootstrap (one-time):
|
||||
# 1. In GitHub repo Settings → Features → Wikis, enable Wikis and restrict
|
||||
# editing to collaborators.
|
||||
# 2. Visit https://github.com/awizemann/scarf/wiki and save any first page
|
||||
# via the UI (this is what creates the underlying .wiki.git repo).
|
||||
# 3. From repo root:
|
||||
# git clone git@github.com:awizemann/scarf.wiki.git .wiki-worktree
|
||||
#
|
||||
# Recovery: if .wiki-worktree/ is deleted, re-run step 3. Remote is authoritative.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---------- config ----------
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
WIKI_DIR="$REPO_ROOT/.wiki-worktree"
|
||||
WIKI_REMOTE="git@github.com:awizemann/scarf.wiki.git"
|
||||
BLOCKLIST="$REPO_ROOT/scripts/wiki-blocklist.txt"
|
||||
STUB_MARKER='> **TODO: document.**'
|
||||
MAINTENANCE_PAGE="Wiki-Maintenance.md"
|
||||
|
||||
# ---------- helpers ----------
|
||||
log() { printf '\033[1;34m==> %s\033[0m\n' "$*"; }
|
||||
warn() { printf '\033[1;33m[WARN] %s\033[0m\n' "$*" >&2; }
|
||||
die() { printf '\033[1;31m[ERR] %s\033[0m\n' "$*" >&2; exit 1; }
|
||||
|
||||
need_worktree() {
|
||||
[[ -d "$WIKI_DIR/.git" ]] || die "no wiki clone at $WIKI_DIR
|
||||
Run: git clone $WIKI_REMOTE .wiki-worktree
|
||||
(See --help for bootstrap steps.)"
|
||||
}
|
||||
|
||||
in_wiki() { (cd "$WIKI_DIR" && "$@"); }
|
||||
|
||||
today() { date +%Y-%m-%d; }
|
||||
|
||||
# ---------- secret-scan ----------
|
||||
#
|
||||
# scan_hard: exits non-zero if any hard pattern or user-blocklist pattern matches.
|
||||
# scan_soft: warns (returns 1) if any soft keyword matches, else 0. The caller
|
||||
# decides whether to bail based on --force-terms.
|
||||
#
|
||||
# All scans ignore the .git directory of the wiki clone.
|
||||
|
||||
hard_regex='(sk-[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{30,}|ghs_[A-Za-z0-9]{30,}|ghu_[A-Za-z0-9]{30,}|gho_[A-Za-z0-9]{30,}|ghr_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|-----BEGIN [A-Z ]*PRIVATE KEY-----|BEGIN OPENSSH PRIVATE KEY)'
|
||||
# Soft tier targets KEY=value / KEY: value patterns where the key name suggests
|
||||
# a secret. Catches accidental .env paste; ignores legitimate mentions of the
|
||||
# words "password" / "secret" in prose.
|
||||
soft_regex='([Pp]assword|[Aa]pi[_-]?[Kk]ey|[Ss]ecret[_-][Kk]ey|[Tt]oken|[Aa]uth[_-]?[Tt]oken|[Bb]earer)[[:space:]]*[=:][[:space:]]*['"'"'"]?[A-Za-z0-9_+./-]{8,}'
|
||||
|
||||
scan_hard() {
|
||||
local hits
|
||||
hits="$(cd "$WIKI_DIR" && grep -rInE --exclude-dir=.git "$hard_regex" . 2>/dev/null || true)"
|
||||
if [[ -n "$hits" ]]; then
|
||||
printf '%s\n' "$hits" >&2
|
||||
die "hard-pattern secret match — aborting. Remove the above and retry."
|
||||
fi
|
||||
|
||||
if [[ -f "$BLOCKLIST" ]]; then
|
||||
local pat
|
||||
while IFS= read -r pat; do
|
||||
[[ -z "$pat" || "$pat" =~ ^# ]] && continue
|
||||
local blocklist_hits
|
||||
blocklist_hits="$(cd "$WIKI_DIR" && grep -rInF --exclude-dir=.git -- "$pat" . 2>/dev/null || true)"
|
||||
if [[ -n "$blocklist_hits" ]]; then
|
||||
printf '%s\n' "$blocklist_hits" >&2
|
||||
die "user blocklist match on pattern \"$pat\" — aborting."
|
||||
fi
|
||||
done < "$BLOCKLIST"
|
||||
fi
|
||||
}
|
||||
|
||||
scan_soft() {
|
||||
local hits
|
||||
# Exempt the maintenance page (it documents the forbidden terms on purpose).
|
||||
hits="$(cd "$WIKI_DIR" && grep -rInE --exclude-dir=.git --exclude="$MAINTENANCE_PAGE" "$soft_regex" . 2>/dev/null || true)"
|
||||
if [[ -n "$hits" ]]; then
|
||||
printf '%s\n' "$hits" >&2
|
||||
warn "soft-keyword matches above. Review carefully. Pass --force-terms to proceed."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------- commands ----------
|
||||
cmd_status() {
|
||||
need_worktree
|
||||
in_wiki git status
|
||||
}
|
||||
|
||||
cmd_pull() {
|
||||
need_worktree
|
||||
if [[ -n "$(in_wiki git status --porcelain)" ]]; then
|
||||
die "wiki has uncommitted changes — commit or stash before pulling."
|
||||
fi
|
||||
log "Pulling wiki"
|
||||
in_wiki git fetch origin
|
||||
in_wiki git merge --ff-only origin/master
|
||||
log "Up to date."
|
||||
}
|
||||
|
||||
cmd_new() {
|
||||
need_worktree
|
||||
local name="${1:-}"
|
||||
[[ -n "$name" ]] || die "usage: wiki.sh new <Page-Name>"
|
||||
# Normalize spaces → dashes; strip .md if the user added it.
|
||||
name="${name%.md}"
|
||||
name="${name// /-}"
|
||||
local path="$WIKI_DIR/${name}.md"
|
||||
if [[ -e "$path" ]]; then
|
||||
die "already exists: $path"
|
||||
fi
|
||||
local title="${name//-/ }"
|
||||
cat > "$path" <<EOF
|
||||
# ${title}
|
||||
|
||||
${STUB_MARKER} This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
|
||||
---
|
||||
_Last updated: $(today) — stub_
|
||||
EOF
|
||||
log "Created $path"
|
||||
}
|
||||
|
||||
cmd_stub_check() {
|
||||
need_worktree
|
||||
# Wiki-Maintenance.md documents the stub template in a code fence, so it
|
||||
# would always match the marker — exempt it.
|
||||
local matches count
|
||||
matches="$(cd "$WIKI_DIR" && grep -rlnF --exclude-dir=.git --exclude="$MAINTENANCE_PAGE" -- "$STUB_MARKER" . 2>/dev/null || true)"
|
||||
if [[ -z "$matches" ]]; then
|
||||
log "No stub pages remain."
|
||||
return 0
|
||||
fi
|
||||
count="$(printf '%s\n' "$matches" | wc -l | tr -d ' ')"
|
||||
log "$count stub page(s):"
|
||||
printf '%s\n' "$matches" | sed 's|^\./||' | sort
|
||||
}
|
||||
|
||||
cmd_touch() {
|
||||
need_worktree
|
||||
local name="${1:-}"
|
||||
[[ -n "$name" ]] || die "usage: wiki.sh touch <Page-Name>"
|
||||
name="${name%.md}"
|
||||
name="${name// /-}"
|
||||
local path="$WIKI_DIR/${name}.md"
|
||||
[[ -f "$path" ]] || die "no such page: $path"
|
||||
# Replace the YYYY-MM-DD portion of the Last updated line, keep whatever trails it.
|
||||
# Uses sed -i '' for BSD/macOS compatibility.
|
||||
sed -i '' -E "s/(_Last updated: )[0-9]{4}-[0-9]{2}-[0-9]{2}/\1$(today)/" "$path"
|
||||
log "Touched ${name}.md → $(today)"
|
||||
}
|
||||
|
||||
cmd_commit() {
|
||||
need_worktree
|
||||
local msg="" force=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force-terms) force=1 ;;
|
||||
-*) die "unknown flag: $arg" ;;
|
||||
*) [[ -z "$msg" ]] && msg="$arg" || die "unexpected arg: $arg" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$msg" ]] || die 'usage: wiki.sh commit "<message>" [--force-terms]'
|
||||
log "Secret-scan (hard patterns + blocklist)"
|
||||
scan_hard
|
||||
log "Secret-scan (soft keywords)"
|
||||
if ! scan_soft; then
|
||||
if [[ "$force" -eq 1 ]]; then
|
||||
warn "proceeding past soft-keyword matches (--force-terms)"
|
||||
else
|
||||
die "soft-keyword matches — re-run with --force-terms to proceed."
|
||||
fi
|
||||
fi
|
||||
in_wiki git add -A
|
||||
if [[ -z "$(in_wiki git status --porcelain)" ]]; then
|
||||
warn "nothing to commit"
|
||||
return 0
|
||||
fi
|
||||
in_wiki git commit -m "$msg"
|
||||
log "Committed: $msg"
|
||||
}
|
||||
|
||||
cmd_push() {
|
||||
need_worktree
|
||||
local force=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force-terms) force=1 ;;
|
||||
*) die "unknown arg: $arg" ;;
|
||||
esac
|
||||
done
|
||||
log "Secret-scan before push (hard patterns + blocklist)"
|
||||
scan_hard
|
||||
log "Secret-scan before push (soft keywords)"
|
||||
if ! scan_soft; then
|
||||
if [[ "$force" -eq 1 ]]; then
|
||||
warn "proceeding past soft-keyword matches (--force-terms)"
|
||||
else
|
||||
die "soft-keyword matches — re-run with --force-terms to proceed."
|
||||
fi
|
||||
fi
|
||||
# Nothing to push?
|
||||
local ahead
|
||||
ahead="$(in_wiki git rev-list --count @{u}..HEAD 2>/dev/null || echo 0)"
|
||||
if [[ "$ahead" -eq 0 ]]; then
|
||||
warn "nothing to push (0 commits ahead of origin)"
|
||||
return 0
|
||||
fi
|
||||
log "Pushing $ahead commit(s) to origin"
|
||||
in_wiki git push origin HEAD
|
||||
log "Pushed. Verify at https://github.com/awizemann/scarf/wiki"
|
||||
}
|
||||
|
||||
cmd_help() {
|
||||
sed -n '2,32p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
# ---------- dispatch ----------
|
||||
sub="${1:-}"
|
||||
shift || true
|
||||
case "$sub" in
|
||||
status) cmd_status "$@" ;;
|
||||
pull) cmd_pull "$@" ;;
|
||||
new) cmd_new "$@" ;;
|
||||
stub-check) cmd_stub_check "$@" ;;
|
||||
touch) cmd_touch "$@" ;;
|
||||
commit) cmd_commit "$@" ;;
|
||||
push) cmd_push "$@" ;;
|
||||
-h|--help|help|"") cmd_help ;;
|
||||
*) die "unknown subcommand: $sub (run --help)" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user