chore: replace AI skill library with 16 verified mvp-* skills
Deploy to Staging / Build Images (push) Successful in 5m32s
Deploy to Staging / Deploy to Staging (push) Successful in 44s
Deploy to Staging / Verify Staging (push) Successful in 5s
Deploy to Staging / Notify Staging Ready (push) Successful in 4s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Deploy to Staging / Build Images (push) Successful in 5m32s
Deploy to Staging / Deploy to Staging (push) Successful in 44s
Deploy to Staging / Verify Staging (push) Successful in 5s
Deploy to Staging / Notify Staging Ready (push) Successful in 4s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Removes the old planner/decision-critic/role-agents/domain-agents system (step-injector scripts, stale scopes, sprint-era workflow) and replaces it with 16 ground-truth-verified skills under .claude/skills/: change control, debugging playbook, failure archaeology, architecture contract, domain reference, OCR/Gemini pipeline, config and secrets, build and env, run and operate, diagnostics (with tested scripts), validation and QA, docs and writing, launch readiness, deploy-safety campaign, proof and analysis toolkit, and research frontier. RULE 0/1/2, the temporal-contamination rule, and the decision stress-test protocol are carried forward into the new skills; the retired content remains in git history. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e729d425fd
commit
c239bb9347
+77
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
# check-numeric-coercion.sh - Find NUMERIC/DECIMAL columns returned from
|
||||
# repositories without Number()/parseFloat() coercion.
|
||||
#
|
||||
# Why: node-postgres returns NUMERIC/DECIMAL (OID 1700) as *strings*.
|
||||
# backend/src/core/config/database.ts overrides only the DATE parser, so
|
||||
# every repository mapper must coerce manually. Missed coercion is this
|
||||
# project's #1 recurring bug class (issues #239, #241, #244).
|
||||
#
|
||||
# Usage: ./check-numeric-coercion.sh (read-only; run from anywhere in repo)
|
||||
# Exit: 0 = no suspects, 1 = suspects found, 2 = setup error
|
||||
set -u
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
|
||||
[ -z "$ROOT" ] && { echo "ERROR: not inside a git repo" >&2; exit 2; }
|
||||
SRC="$ROOT/backend/src"
|
||||
[ -d "$SRC" ] && cd "$ROOT" || { echo "ERROR: $SRC missing" >&2; exit 2; }
|
||||
|
||||
# 1. Collect numeric/decimal column names from all migration SQL.
|
||||
# Three shapes: column definition lines, ADD COLUMN, ALTER COLUMN ... TYPE.
|
||||
COLS=$(
|
||||
{
|
||||
grep -rhiE '^[[:space:]]*"?[a-z_]+"?[[:space:]]+(numeric|decimal)[[:space:](]' \
|
||||
--include='*.sql' "$SRC" | awk '{gsub(/"/,""); print tolower($1)}'
|
||||
grep -rhioE 'add column (if not exists )?"?[a-z_]+"? +(numeric|decimal)' \
|
||||
--include='*.sql' "$SRC" | awk '{gsub(/"/,""); print tolower($(NF-1))}'
|
||||
grep -rhioE 'alter column "?[a-z_]+"? type +(numeric|decimal)' \
|
||||
--include='*.sql' "$SRC" | awk '{gsub(/"/,""); print tolower($(NF-2))}'
|
||||
} | sort -u
|
||||
)
|
||||
[ -z "$COLS" ] && { echo "ERROR: no numeric columns found (wrong dir?)" >&2; exit 2; }
|
||||
|
||||
echo "Numeric/decimal columns found in migrations:"
|
||||
echo "$COLS" | tr '\n' ' '; echo; echo
|
||||
|
||||
# 2. In every repository file, flag lines that read row.<col> without
|
||||
# Number( or parseFloat( on the same line.
|
||||
SUSPECTS=0
|
||||
for col in $COLS; do
|
||||
HITS=$(find "$SRC" -name '*.repository.ts' -exec \
|
||||
grep -nE "row\.${col}([^a-zA-Z0-9_]|\$)" {} + 2>/dev/null |
|
||||
grep -v 'parseFloat(' | grep -v 'Number(')
|
||||
if [ -n "$HITS" ]; then
|
||||
echo "SUSPECT column '$col' (string from pg, no coercion on line):"
|
||||
echo "$HITS" | sed "s|$ROOT/||; s/^/ /"
|
||||
SUSPECTS=$((SUSPECTS + $(echo "$HITS" | wc -l | tr -d ' ')))
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. Flag raw-row returns (the actual shape of bug #244: "return res.rows[0]"
|
||||
# bypasses the mapper entirely) in features whose migrations define
|
||||
# numeric columns. Scalar returns (.count/.exists/.length) are filtered.
|
||||
for repo in $(find "$SRC/features" -name '*.repository.ts'); do
|
||||
feat=$(echo "$repo" | sed "s|$SRC/features/||; s|/.*||")
|
||||
featcols=$(grep -rhiE "(numeric|decimal)" --include='*.sql' \
|
||||
"$SRC/features/$feat/migrations" 2>/dev/null | head -1)
|
||||
[ -z "$featcols" ] && continue
|
||||
RAW=$(grep -nE 'return (res|result)\.rows' "$repo" |
|
||||
grep -vE 'map[A-Za-z]*\(|\.map\(|\?\.|\.length|\.count|\.exists')
|
||||
if [ -n "$RAW" ]; then
|
||||
echo "SUSPECT raw-row return in numeric-bearing feature '$feat':"
|
||||
echo "$RAW" | sed "s|^| ${repo#"$ROOT"/}:|"
|
||||
SUSPECTS=$((SUSPECTS + $(echo "$RAW" | wc -l | tr -d ' ')))
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
if [ "$SUSPECTS" -eq 0 ]; then
|
||||
echo "PASS: no uncoerced numeric column reads or raw-row returns found."
|
||||
exit 0
|
||||
else
|
||||
echo "FAIL: $SUSPECTS suspect line(s). Each must wrap the value in Number()"
|
||||
echo "or parseFloat() (nullable: 'row.x != null ? parseFloat(row.x) : null')."
|
||||
echo "Limitations: same-line heuristic; coercion done on a different line or"
|
||||
echo "in the service layer will still be flagged - verify before fixing."
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
# check-route-auth.sh - List backend routes with NO auth guard in their
|
||||
# registration block, so unintentionally-public endpoints are visible.
|
||||
#
|
||||
# Why: there is no global auth hook. Every route must opt in via
|
||||
# preHandler: [fastify.authenticate] / requireAdmin / requireTier
|
||||
# (backend/src/core/plugins/*.plugin.ts). A forgotten preHandler ships a
|
||||
# public endpoint silently.
|
||||
#
|
||||
# Usage: ./check-route-auth.sh (read-only; run from anywhere in repo)
|
||||
# Exit: 0 = only known-public routes unguarded, 1 = unexpected unguarded route
|
||||
set -u
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
|
||||
[ -z "$ROOT" ] && { echo "ERROR: not inside a git repo" >&2; exit 2; }
|
||||
cd "$ROOT" || exit 2
|
||||
|
||||
# CANONICAL intentional-public-route list (verified 2026-07-09). This comment
|
||||
# is the ONE home for it; mvp-architecture-contract invariant 2.1 and
|
||||
# mvp-debugging-playbook section 7 point here instead of keeping their own lists.
|
||||
#
|
||||
# Within this script's scan scope (backend/src/features/*/api/*.routes.ts),
|
||||
# allowlisted via KNOWN_PUBLIC below:
|
||||
# /webhooks/stripe, /webhooks/resend/inbound - provider signature auth
|
||||
# /auth/signup, /auth/resend-verification-public - pre-auth flows
|
||||
# (comments in auth.routes.ts: "public, no JWT required")
|
||||
#
|
||||
# OUTSIDE this script's scan scope (registered in core code, also intentionally
|
||||
# public - the script can neither flag nor clear them):
|
||||
# /health, /api/health - backend/src/app.ts health endpoints
|
||||
# /auth/verify - backend/src/app.ts (Traefik forward-auth)
|
||||
# GET /api/config/feature-tiers - backend/src/core/config/config.routes.ts
|
||||
# (no preHandler; config is not sensitive)
|
||||
KNOWN_PUBLIC='/webhooks/stripe /webhooks/resend/inbound /auth/signup /auth/resend-verification-public'
|
||||
|
||||
OUT=$(awk '
|
||||
function flush() {
|
||||
if (inblock && !guarded) printf "%s:%d %s %s\n", f, startline, method, path
|
||||
inblock = 0
|
||||
}
|
||||
FNR == 1 { flush() }
|
||||
/fastify\.(get|post|put|patch|delete)[<(]/ {
|
||||
flush()
|
||||
inblock = 1; guarded = 0; startline = FNR; path = "?"; f = FILENAME
|
||||
match($0, /fastify\.(get|post|put|patch|delete)/)
|
||||
method = toupper(substr($0, RSTART + 8, RLENGTH - 8))
|
||||
}
|
||||
inblock && path == "?" {
|
||||
if (match($0, "\047/[^\047]*\047")) path = substr($0, RSTART + 1, RLENGTH - 2)
|
||||
}
|
||||
# requireAuth is the common local alias: const requireAuth = fastify.authenticate.bind(fastify)
|
||||
inblock && /[ .\[](authenticate|requireAuth|requireAdmin|requireTier)[ ,\]\}\)\(]/ { guarded = 1 }
|
||||
END { flush() }
|
||||
' backend/src/features/*/api/*.routes.ts)
|
||||
|
||||
TOTAL=$(grep -c 'fastify\.\(get\|post\|put\|patch\|delete\)[<(]' \
|
||||
backend/src/features/*/api/*.routes.ts | awk -F: '{s+=$2} END {print s}')
|
||||
|
||||
echo "Route registrations scanned: $TOTAL"
|
||||
echo
|
||||
|
||||
FAIL=0
|
||||
if [ -z "$OUT" ]; then
|
||||
echo "PASS: every route block contains an auth guard."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Routes with NO authenticate/requireAdmin/requireTier in their block:"
|
||||
echo "$OUT" | while read -r line; do
|
||||
p=$(echo "$line" | awk '{print $3}')
|
||||
case " $KNOWN_PUBLIC " in
|
||||
*" $p "*) echo " OK (known-public, allowlisted): $line" ;;
|
||||
*) echo " UNGUARDED: $line" ;;
|
||||
esac
|
||||
done
|
||||
# Re-count outside the pipe subshell (bash 3.2: while-in-pipe loses vars)
|
||||
FAIL=$(echo "$OUT" | awk -v kp=" $KNOWN_PUBLIC " '{ if (index(kp, " " $3 " ") == 0) n++ } END {print n+0}')
|
||||
|
||||
echo
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "PASS: all unguarded routes are on the known-public allowlist."
|
||||
exit 0
|
||||
else
|
||||
echo "FAIL: $FAIL route(s) unguarded and not on the allowlist."
|
||||
echo "Either add a preHandler guard or, if intentionally public, add the"
|
||||
echo "path to KNOWN_PUBLIC in this script with a justification comment."
|
||||
echo "Limitation: block-scope heuristic; a guard mentioned in a comment"
|
||||
echo "inside the block can mask a real gap - verify hits by reading code."
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# local-gate.sh - Run every quality check that works on a dev machine and
|
||||
# print a PASS/FAIL table.
|
||||
#
|
||||
# Why: CI runs ZERO tests and ZERO lint (the only PR gate is that images
|
||||
# build and staging boots healthy). This script IS the pre-push gate.
|
||||
#
|
||||
# Scope (deliberate):
|
||||
# - backend lint, type-check, UNIT tests only (--forceExit; open pg/redis
|
||||
# handles otherwise hang jest). Integration tests are EXCLUDED: they
|
||||
# need a live database and some DROP TABLE CASCADE (owner non-negotiable:
|
||||
# never without a fresh backup). audit-log __tests__ also need a DB and
|
||||
# are excluded via path pattern.
|
||||
# - frontend lint, type-check. Frontend jest is broken outside the
|
||||
# container (2026-07-07), so no frontend tests here.
|
||||
#
|
||||
# Usage: ./local-gate.sh (needs node_modules in backend/ and frontend/:
|
||||
# run "npm install" in each first)
|
||||
# Exit: 0 = all pass, 1 = at least one failure
|
||||
set -u
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
|
||||
[ -z "$ROOT" ] && { echo "ERROR: not inside a git repo" >&2; exit 2; }
|
||||
LOGDIR="${TMPDIR:-/tmp}/mvp-local-gate.$$"
|
||||
mkdir -p "$LOGDIR"
|
||||
|
||||
RESULTS=""
|
||||
OVERALL=0
|
||||
|
||||
run_step() { # run_step <label> <dir> <command...>
|
||||
label="$1"; dir="$2"; shift 2
|
||||
log="$LOGDIR/$(echo "$label" | tr ' /' '__').log"
|
||||
printf '%-28s ' "$label..."
|
||||
if (cd "$ROOT/$dir" && "$@") >"$log" 2>&1; then
|
||||
echo "PASS"
|
||||
RESULTS="$RESULTS$label|PASS
|
||||
"
|
||||
else
|
||||
echo "FAIL (log: $log)"
|
||||
tail -15 "$log" | sed 's/^/ /'
|
||||
RESULTS="$RESULTS$label|FAIL
|
||||
"
|
||||
OVERALL=1
|
||||
fi
|
||||
}
|
||||
|
||||
run_step "backend lint" backend npm run --silent lint
|
||||
run_step "backend type-check" backend npm run --silent type-check
|
||||
run_step "backend unit tests" backend npm test --silent -- \
|
||||
--testPathPattern='(tests/unit|src/core)' \
|
||||
--testPathIgnorePatterns='integration' --forceExit
|
||||
run_step "frontend lint" frontend npm run --silent lint
|
||||
run_step "frontend type-check" frontend npm run --silent type-check
|
||||
|
||||
echo
|
||||
echo "==================== LOCAL GATE ===================="
|
||||
printf '%-28s %s\n' "CHECK" "RESULT"
|
||||
echo "$RESULTS" | awk -F'|' 'NF { printf "%-28s %s\n", $1, $2 }'
|
||||
echo "====================================================="
|
||||
if [ "$OVERALL" -eq 0 ]; then
|
||||
echo "PASS: safe to push. (Integration tests NOT run - container-only.)"
|
||||
else
|
||||
echo "FAIL: fix before pushing. CI will NOT catch these."
|
||||
fi
|
||||
exit "$OVERALL"
|
||||
Reference in New Issue
Block a user