devrites 1.19.0
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/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +43 -0
- package/CHANGELOG.md +391 -0
- package/LICENSE +56 -0
- package/NOTICE.md +18 -0
- package/README.md +582 -0
- package/SECURITY.md +193 -0
- package/bin/devrites.mjs +100 -0
- package/docs/architecture.md +272 -0
- package/docs/cli-mcp.md +57 -0
- package/docs/command-map.md +143 -0
- package/docs/flow.md +360 -0
- package/docs/release.md +29 -0
- package/docs/skills.md +214 -0
- package/docs/usage.md +325 -0
- package/install.sh +359 -0
- package/mcp/devrites-mcp.mjs +103 -0
- package/pack/.claude/agents/devrites-code-reviewer.md +50 -0
- package/pack/.claude/agents/devrites-doubt-reviewer.md +55 -0
- package/pack/.claude/agents/devrites-frontend-reviewer.md +52 -0
- package/pack/.claude/agents/devrites-performance-reviewer.md +47 -0
- package/pack/.claude/agents/devrites-plan-reviewer.md +79 -0
- package/pack/.claude/agents/devrites-security-auditor.md +53 -0
- package/pack/.claude/agents/devrites-simplifier-reviewer.md +75 -0
- package/pack/.claude/agents/devrites-slice-wright.md +181 -0
- package/pack/.claude/agents/devrites-spec-reviewer.md +72 -0
- package/pack/.claude/agents/devrites-strategy-reviewer.md +62 -0
- package/pack/.claude/agents/devrites-test-analyst.md +47 -0
- package/pack/.claude/hooks/devrites-a1-guard.sh +81 -0
- package/pack/.claude/hooks/devrites-allow.sh +44 -0
- package/pack/.claude/hooks/devrites-cursor.sh +28 -0
- package/pack/.claude/hooks/devrites-orient.sh +53 -0
- package/pack/.claude/hooks/devrites-redwatch.sh +39 -0
- package/pack/.claude/hooks/devrites-refresh-indexes.sh +127 -0
- package/pack/.claude/hooks/devrites-reviewer-readonly.sh +28 -0
- package/pack/.claude/hooks/devrites-statusline.sh +18 -0
- package/pack/.claude/hooks/devrites-stop-gate.sh +45 -0
- package/pack/.claude/hooks/devrites-wright-scope.sh +35 -0
- package/pack/.claude/hooks/hooks.json +52 -0
- package/pack/.claude/rules/README.md +48 -0
- package/pack/.claude/rules/afk-hitl.md +245 -0
- package/pack/.claude/rules/agents.md +98 -0
- package/pack/.claude/rules/anti-patterns.md +48 -0
- package/pack/.claude/rules/code-review.md +38 -0
- package/pack/.claude/rules/coding-style.md +55 -0
- package/pack/.claude/rules/context-hygiene.md +97 -0
- package/pack/.claude/rules/core.md +119 -0
- package/pack/.claude/rules/development-workflow.md +40 -0
- package/pack/.claude/rules/documentation.md +27 -0
- package/pack/.claude/rules/error-handling.md +33 -0
- package/pack/.claude/rules/git-workflow.md +35 -0
- package/pack/.claude/rules/hooks.md +38 -0
- package/pack/.claude/rules/patterns.md +45 -0
- package/pack/.claude/rules/performance.md +27 -0
- package/pack/.claude/rules/prose-style.md +101 -0
- package/pack/.claude/rules/security.md +63 -0
- package/pack/.claude/rules/testing.md +88 -0
- package/pack/.claude/rules/tooling.md +72 -0
- package/pack/.claude/settings.json +53 -0
- package/pack/.claude/skills/devrites-api-interface/SKILL.md +45 -0
- package/pack/.claude/skills/devrites-audit/SKILL.md +73 -0
- package/pack/.claude/skills/devrites-browser-proof/SKILL.md +38 -0
- package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +50 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +47 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +17 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/hypotheses.md +17 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +21 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/regression-test.md +31 -0
- package/pack/.claude/skills/devrites-doubt/SKILL.md +75 -0
- package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +96 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/craft.md +59 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/design-references.md +116 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/fullstack.md +45 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/quality-standards.md +215 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/reuse-first.md +59 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/shape.md +60 -0
- package/pack/.claude/skills/devrites-interview/SKILL.md +81 -0
- package/pack/.claude/skills/devrites-lib/SKILL.md +76 -0
- package/pack/.claude/skills/devrites-lib/scripts/analyze.sh +78 -0
- package/pack/.claude/skills/devrites-lib/scripts/check-acceptance.sh +75 -0
- package/pack/.claude/skills/devrites-lib/scripts/close-out.sh +47 -0
- package/pack/.claude/skills/devrites-lib/scripts/conventions.py +273 -0
- package/pack/.claude/skills/devrites-lib/scripts/coverage.sh +51 -0
- package/pack/.claude/skills/devrites-lib/scripts/devrites.sh +69 -0
- package/pack/.claude/skills/devrites-lib/scripts/doctor.sh +92 -0
- package/pack/.claude/skills/devrites-lib/scripts/evidence-fresh.sh +63 -0
- package/pack/.claude/skills/devrites-lib/scripts/footprint.sh +45 -0
- package/pack/.claude/skills/devrites-lib/scripts/learnings.sh +74 -0
- package/pack/.claude/skills/devrites-lib/scripts/mutation-gate.sh +52 -0
- package/pack/.claude/skills/devrites-lib/scripts/package-existence.sh +68 -0
- package/pack/.claude/skills/devrites-lib/scripts/preamble.sh +76 -0
- package/pack/.claude/skills/devrites-lib/scripts/progress.sh +103 -0
- package/pack/.claude/skills/devrites-lib/scripts/readiness.sh +62 -0
- package/pack/.claude/skills/devrites-lib/scripts/reconcile.sh +123 -0
- package/pack/.claude/skills/devrites-lib/scripts/resolve.sh +279 -0
- package/pack/.claude/skills/devrites-lib/scripts/stuck.sh +67 -0
- package/pack/.claude/skills/devrites-lib/scripts/test-integrity.sh +87 -0
- package/pack/.claude/skills/devrites-lib/scripts/tick-afk.sh +52 -0
- package/pack/.claude/skills/devrites-prose-craft/SKILL.md +105 -0
- package/pack/.claude/skills/devrites-prose-craft/reference/banned-phrases.md +95 -0
- package/pack/.claude/skills/devrites-prose-craft/reference/examples.md +88 -0
- package/pack/.claude/skills/devrites-prose-craft/reference/structures.md +134 -0
- package/pack/.claude/skills/devrites-refresh-indexes/SKILL.md +54 -0
- package/pack/.claude/skills/devrites-source-driven/SKILL.md +36 -0
- package/pack/.claude/skills/devrites-ux-shape/SKILL.md +121 -0
- package/pack/.claude/skills/devrites-ux-shape/reference/brief-template.md +93 -0
- package/pack/.claude/skills/devrites-ux-shape/reference/visual-direction-probe.md +48 -0
- package/pack/.claude/skills/rite/SKILL.md +135 -0
- package/pack/.claude/skills/rite/reference/menu.md +32 -0
- package/pack/.claude/skills/rite-adopt/SKILL.md +83 -0
- package/pack/.claude/skills/rite-adopt/reference/adoption.md +58 -0
- package/pack/.claude/skills/rite-adopt/reference/anti-patterns.md +19 -0
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +96 -0
- package/pack/.claude/skills/rite-autocomplete/reference/decision-policy.md +35 -0
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +54 -0
- package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +59 -0
- package/pack/.claude/skills/rite-build/SKILL.md +261 -0
- package/pack/.claude/skills/rite-build/reference/afk-discipline.md +145 -0
- package/pack/.claude/skills/rite-build/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-build/reference/checkpoint-protocol.md +149 -0
- package/pack/.claude/skills/rite-build/reference/evidence-standard.md +32 -0
- package/pack/.claude/skills/rite-build/reference/frontend-trigger.md +39 -0
- package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +38 -0
- package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +43 -0
- package/pack/.claude/skills/rite-build/reference/tdd.md +26 -0
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +115 -0
- package/pack/.claude/skills/rite-define/SKILL.md +157 -0
- package/pack/.claude/skills/rite-define/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-define/reference/gates.md +152 -0
- package/pack/.claude/skills/rite-define/reference/plan-template.md +65 -0
- package/pack/.claude/skills/rite-doctor/SKILL.md +50 -0
- package/pack/.claude/skills/rite-frame/SKILL.md +116 -0
- package/pack/.claude/skills/rite-frame/reference/failure-modes.md +68 -0
- package/pack/.claude/skills/rite-handoff/SKILL.md +95 -0
- package/pack/.claude/skills/rite-handoff/reference/handoff-template.md +34 -0
- package/pack/.claude/skills/rite-learn/SKILL.md +82 -0
- package/pack/.claude/skills/rite-plan/SKILL.md +82 -0
- package/pack/.claude/skills/rite-plan/reference/anti-patterns.md +24 -0
- package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +33 -0
- package/pack/.claude/skills/rite-plan/reference/replan-and-repair.md +42 -0
- package/pack/.claude/skills/rite-plan/reference/slicing.md +52 -0
- package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +34 -0
- package/pack/.claude/skills/rite-polish/SKILL.md +90 -0
- package/pack/.claude/skills/rite-polish/reference/anti-ai-slop.md +177 -0
- package/pack/.claude/skills/rite-polish/reference/anti-patterns.md +27 -0
- package/pack/.claude/skills/rite-polish/reference/backend-polish.md +80 -0
- package/pack/.claude/skills/rite-polish/reference/browser-polish-evidence.md +31 -0
- package/pack/.claude/skills/rite-polish/reference/code.md +85 -0
- package/pack/.claude/skills/rite-polish/reference/design-system-discovery.md +35 -0
- package/pack/.claude/skills/rite-polish/reference/harden-checklist.md +109 -0
- package/pack/.claude/skills/rite-polish/reference/ui.md +136 -0
- package/pack/.claude/skills/rite-pressure-test/SKILL.md +43 -0
- package/pack/.claude/skills/rite-prototype/SKILL.md +87 -0
- package/pack/.claude/skills/rite-prove/SKILL.md +120 -0
- package/pack/.claude/skills/rite-prove/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-prove/reference/browser-proof.md +26 -0
- package/pack/.claude/skills/rite-prove/reference/failure-triage.md +25 -0
- package/pack/.claude/skills/rite-prove/reference/proof-ladder.md +26 -0
- package/pack/.claude/skills/rite-prove/reference/test-command-discovery.md +30 -0
- package/pack/.claude/skills/rite-quick/SKILL.md +81 -0
- package/pack/.claude/skills/rite-resolve/SKILL.md +113 -0
- package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +114 -0
- package/pack/.claude/skills/rite-review/SKILL.md +170 -0
- package/pack/.claude/skills/rite-review/reference/anti-patterns.md +32 -0
- package/pack/.claude/skills/rite-review/reference/cognitive-load.md +90 -0
- package/pack/.claude/skills/rite-review/reference/feature-scoped-review.md +26 -0
- package/pack/.claude/skills/rite-review/reference/five-axis-review.md +46 -0
- package/pack/.claude/skills/rite-review/reference/nielsen-heuristics.md +130 -0
- package/pack/.claude/skills/rite-review/reference/parallel-dispatch.md +62 -0
- package/pack/.claude/skills/rite-review/reference/performance-review.md +28 -0
- package/pack/.claude/skills/rite-review/reference/security-review.md +32 -0
- package/pack/.claude/skills/rite-seal/SKILL.md +183 -0
- package/pack/.claude/skills/rite-seal/reference/anti-patterns.md +27 -0
- package/pack/.claude/skills/rite-seal/reference/conventions-ledger.md +63 -0
- package/pack/.claude/skills/rite-seal/reference/final-evidence.md +72 -0
- package/pack/.claude/skills/rite-seal/reference/go-no-go.md +37 -0
- package/pack/.claude/skills/rite-seal/reference/parallel-dispatch.md +69 -0
- package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +30 -0
- package/pack/.claude/skills/rite-seal/reference/seal-template.md +36 -0
- package/pack/.claude/skills/rite-ship/SKILL.md +120 -0
- package/pack/.claude/skills/rite-ship/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-ship/reference/close-out.md +31 -0
- package/pack/.claude/skills/rite-ship/reference/design-memory.md +120 -0
- package/pack/.claude/skills/rite-ship/reference/git-ship.md +42 -0
- package/pack/.claude/skills/rite-ship/reference/ship-template.md +33 -0
- package/pack/.claude/skills/rite-spec/SKILL.md +126 -0
- package/pack/.claude/skills/rite-spec/reference/acceptance-criteria.md +31 -0
- package/pack/.claude/skills/rite-spec/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-spec/reference/interview-patterns.md +56 -0
- package/pack/.claude/skills/rite-spec/reference/investigation.md +64 -0
- package/pack/.claude/skills/rite-spec/reference/question-protocol.md +61 -0
- package/pack/.claude/skills/rite-spec/reference/references-intake.md +57 -0
- package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +73 -0
- package/pack/.claude/skills/rite-spec/reference/spec-template.md +124 -0
- package/pack/.claude/skills/rite-spec/reference/state-workspace.md +159 -0
- package/pack/.claude/skills/rite-status/SKILL.md +101 -0
- package/pack/.claude/skills/rite-temper/SKILL.md +119 -0
- package/pack/.claude/skills/rite-temper/reference/anti-patterns.md +29 -0
- package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +65 -0
- package/pack/.claude/skills/rite-temper/reference/scope-modes.md +53 -0
- package/pack/.claude/skills/rite-temper/reference/significance.md +46 -0
- package/pack/.claude/skills/rite-temper/reference/strategy-template.md +90 -0
- package/pack/.claude/skills/rite-vet/SKILL.md +155 -0
- package/pack/.claude/skills/rite-vet/reference/anti-patterns.md +29 -0
- package/pack/.claude/skills/rite-vet/reference/artifacts.md +135 -0
- package/pack/.claude/skills/rite-vet/reference/cross-model.md +41 -0
- package/pack/.claude/skills/rite-vet/reference/depth.md +53 -0
- package/pack/.claude/skills/rite-vet/reference/eng-lenses.md +48 -0
- package/pack/.claude/skills/rite-vet/reference/review-axes.md +167 -0
- package/pack/.claude/skills/rite-zoom-out/SKILL.md +75 -0
- package/package.json +68 -0
- package/scripts/build-release-tarball.sh +74 -0
- package/scripts/check-cross-refs.py +121 -0
- package/scripts/check-no-global-writes.sh +44 -0
- package/scripts/check-rule-uniqueness.sh +73 -0
- package/scripts/devrites-detect.sh +175 -0
- package/scripts/eval-runner.py +273 -0
- package/scripts/grade-feature.sh +104 -0
- package/scripts/install-lib.sh +83 -0
- package/scripts/pin.sh +166 -0
- package/scripts/render-eval-summary.py +48 -0
- package/scripts/run-evals.sh +149 -0
- package/scripts/run-outcome-evals.sh +49 -0
- package/scripts/scan-pack-security.py +209 -0
- package/scripts/scan-supply-chain-iocs.py +127 -0
- package/scripts/supply-chain-iocs.json +11 -0
- package/scripts/sync-version.sh +56 -0
- package/scripts/validate-frontmatter.py +149 -0
- package/scripts/validate-workflow-security.py +86 -0
- package/scripts/validate.sh +234 -0
- package/uninstall.sh +137 -0
- package/update.sh +196 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/devrites-detect.sh — deterministic anti-slop detector.
|
|
3
|
+
#
|
|
4
|
+
# Greps the current git diff (or a passed file list) for ~25 actionable
|
|
5
|
+
# anti-slop patterns from rite-polish/reference/anti-ai-slop.md +
|
|
6
|
+
# devrites-frontend-craft/reference/quality-standards.md. No LLM needed.
|
|
7
|
+
#
|
|
8
|
+
# Exits 0 if no findings, 1 if any (so a pre-push hook can block).
|
|
9
|
+
# Use --advisory to always exit 0 (CI does this).
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# scripts/devrites-detect.sh # diff vs upstream / HEAD~
|
|
13
|
+
# scripts/devrites-detect.sh file1 file2 # check specific files
|
|
14
|
+
# scripts/devrites-detect.sh --advisory # report-only, never fail
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
ADVISORY=0
|
|
19
|
+
FILES=()
|
|
20
|
+
|
|
21
|
+
for arg in "$@"; do
|
|
22
|
+
case "$arg" in
|
|
23
|
+
--advisory) ADVISORY=1 ;;
|
|
24
|
+
*) FILES+=("$arg") ;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
# Resolve which files to scan: explicit args, else diff against the base.
|
|
29
|
+
if [[ ${#FILES[@]} -eq 0 ]]; then
|
|
30
|
+
BASE=""
|
|
31
|
+
for candidate in origin/main origin/master main master HEAD~1; do
|
|
32
|
+
if git rev-parse --verify "$candidate" >/dev/null 2>&1; then
|
|
33
|
+
BASE="$candidate"
|
|
34
|
+
break
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
if [[ -n "$BASE" ]]; then
|
|
38
|
+
mapfile -t FILES < <(git diff --name-only --diff-filter=ACMR "$BASE" 2>/dev/null || true)
|
|
39
|
+
fi
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Filter into two scan lists: code/UI source (most rules) and markdown
|
|
43
|
+
# (em-dash overuse only). Meta / reference docs are excluded from the markdown
|
|
44
|
+
# scan because they discuss the patterns under audit.
|
|
45
|
+
SCAN=()
|
|
46
|
+
SCAN_MD=()
|
|
47
|
+
for f in "${FILES[@]}"; do
|
|
48
|
+
[[ -z "$f" ]] && continue
|
|
49
|
+
[[ ! -f "$f" ]] && continue
|
|
50
|
+
case "$f" in
|
|
51
|
+
*.lock|*.png|*.jpg|*.jpeg|*.gif|*.svg|*.ico|*.woff*|*.ttf|*.zip|*.gz) continue ;;
|
|
52
|
+
*vendor/*|*node_modules/*|*dist/*|*build/*|*.min.*) continue ;;
|
|
53
|
+
esac
|
|
54
|
+
case "$f" in
|
|
55
|
+
*.md)
|
|
56
|
+
case "$f" in
|
|
57
|
+
REVIEW.md|CHANGELOG.md|res.md) continue ;;
|
|
58
|
+
.review-notes/*|*/.review-notes/*) continue ;;
|
|
59
|
+
pack/.claude/rules/*|*/pack/.claude/rules/*) continue ;;
|
|
60
|
+
pack/.claude/skills/*|*/pack/.claude/skills/*) continue ;;
|
|
61
|
+
esac
|
|
62
|
+
SCAN_MD+=("$f")
|
|
63
|
+
;;
|
|
64
|
+
*)
|
|
65
|
+
SCAN+=("$f")
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
if [[ ${#SCAN[@]} -eq 0 && ${#SCAN_MD[@]} -eq 0 ]]; then
|
|
71
|
+
echo "devrites-detect: no files to scan."
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Each rule: pattern (ERE), severity, message.
|
|
76
|
+
# Severity = critical (auth/safety), high (slop tell), low (style nit).
|
|
77
|
+
RULES=(
|
|
78
|
+
# --- UI anti-slop (CSS-ish) ---
|
|
79
|
+
'#000([^0-9a-f]|$)|#fff([^0-9a-f]|$)|#000000|#ffffff' 'high' 'Pure #000 / #fff — use near-black / near-white tokens.'
|
|
80
|
+
'#6366f1|#8b5cf6|#a855f7|#d946ef|#ec4899' 'high' 'Purple/blue gradient hex range — likely default AI palette.'
|
|
81
|
+
'background-clip:[[:space:]]*text|-webkit-background-clip:[[:space:]]*text' 'high' 'Gradient text decoration — likely AI slop.'
|
|
82
|
+
'backdrop-filter:[[:space:]]*blur' 'high' 'Glassmorphism `backdrop-filter: blur(...)` — banned default surface.'
|
|
83
|
+
'border-left:[[:space:]]*[1-6]px[[:space:]]+solid|border-l-[2-8]' 'low' 'Side-stripe accent border — common templating tell; confirm vs design system.'
|
|
84
|
+
'cubic-bezier\([^)]*0\.34[[:space:]]*,[[:space:]]*1\.56' 'high' 'Bounce/elastic easing curve — banned unless design system uses it.'
|
|
85
|
+
'animation-duration:[[:space:]]*[5-9][0-9]{2}ms[[:space:]]*;[[:space:]]*animation-delay' 'low' 'Long animation durations stacked — verify motion budget.'
|
|
86
|
+
'z-index:[[:space:]]*[0-9]{4,}' 'high' 'Raw z-index in the 1000s — use semantic z scale.'
|
|
87
|
+
'font-family:[[:space:]]*"?(DM Sans|Plus Jakarta|Fraunces|Newsreader)' 'high' 'Reflex font choice — use the project type system.'
|
|
88
|
+
'text-transform:[[:space:]]*uppercase[[:space:]]*;[^}]*letter-spacing' 'low' 'All-CAPS with letter-spacing — verify register; banned for body text.'
|
|
89
|
+
# --- Code anti-slop ---
|
|
90
|
+
'\bconsole\.log\(' 'high' 'console.log left in code — remove before polish.'
|
|
91
|
+
'if[[:space:]]*\([[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*&&[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*\.length[[:space:]]*>[[:space:]]*0' 'high' 'Over-defensive `x && x.length > 0` — validate at boundaries.'
|
|
92
|
+
'\bcatch[[:space:]]*\([[:space:]]*\)[[:space:]]*\{[^}]*\}' 'high' 'Blanket catch with no narrow handling — likely swallowing errors.'
|
|
93
|
+
'\bcatch[[:space:]]*\([[:space:]]*[A-Za-z][A-Za-z0-9_]*[[:space:]]*\)[[:space:]]*\{[[:space:]]*\}' 'high' 'Empty catch body — silently swallows errors. Catch narrow; recover or rethrow.'
|
|
94
|
+
'//[[:space:]]*helper function|//[[:space:]]*increment' 'low' 'Tutorial-style comment — restates code; remove.'
|
|
95
|
+
'\bfunction[[:space:]]+(process|handle|do)(Data|Item|Thing|It)?\b' 'high' 'Generic AI naming (`processData` / `handleItem` / `doIt`) — name for intent.'
|
|
96
|
+
'\bfunction[[:space:]]+get[A-Z][a-zA-Z]*\([^)]*\)[[:space:]]*\{[[:space:]]*return[[:space:]]+[A-Z][a-zA-Z]+\.find' 'low' 'Useless wrapper around a single ORM call — inline.'
|
|
97
|
+
'//[[:space:]]*TODO:[[:space:]]*(improve|cleanup|refactor)[[:space:]]+(this|later)' 'low' '"TODO: improve this later" — without owner/issue, this rots.'
|
|
98
|
+
'\btemp\s*=|\bresult\s*=|\bdata\s*=[^=]' 'low' 'Generic local name (temp/result/data) — name for intent.'
|
|
99
|
+
'\binterface[[:space:]]+I[A-Z][a-zA-Z]+\b' 'low' '`IFoo` prefix on interfaces — most modern style guides prefer `Foo`.'
|
|
100
|
+
'/\*\s*eslint-disable\s*\*/' 'high' 'Global eslint-disable — explain or remove.'
|
|
101
|
+
# --- Security ---
|
|
102
|
+
'\bnew Function\(' 'critical' '`new Function(...)` — dynamic code construction.'
|
|
103
|
+
'\beval\s*\(' 'critical' '`eval(...)` — avoid.'
|
|
104
|
+
'\bdangerouslySetInnerHTML\b' 'high' 'dangerouslySetInnerHTML — confirm XSS-safe.'
|
|
105
|
+
'process\.env\.[A-Z_]+(.{0,40}(?:console|res\.send|res\.json))' 'high' 'env var possibly echoed to response/log — re-check for secret leakage.'
|
|
106
|
+
# --- Performance pitfalls ---
|
|
107
|
+
'\.forEach\([^)]+\)\s*\.\s*forEach\(' 'low' 'Nested `forEach` — accidental quadratic loop?'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Markdown-only rules. Run against SCAN_MD, not SCAN.
|
|
111
|
+
# Em-dash overuse: 2+ em-dashes (U+2014) on the same line — the per-line proxy
|
|
112
|
+
# for the "multiple em-dashes per paragraph" rule in anti-ai-slop.md.
|
|
113
|
+
MD_RULES=(
|
|
114
|
+
$'.*\xe2\x80\x94.*\xe2\x80\x94' 'high' 'Em-dash overuse on one line (2+ em-dashes) — a common AI tell; prefer comma, period, or parenthetical.'
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
FOUND=0
|
|
118
|
+
|
|
119
|
+
# Code rules against code files.
|
|
120
|
+
if [[ ${#SCAN[@]} -gt 0 ]]; then
|
|
121
|
+
N=${#RULES[@]}
|
|
122
|
+
i=0
|
|
123
|
+
while (( i < N )); do
|
|
124
|
+
pat="${RULES[$i]}"
|
|
125
|
+
sev="${RULES[$((i+1))]}"
|
|
126
|
+
msg="${RULES[$((i+2))]}"
|
|
127
|
+
i=$((i+3))
|
|
128
|
+
|
|
129
|
+
HITS="$(grep -HnIE --color=never "$pat" "${SCAN[@]}" 2>/dev/null || true)"
|
|
130
|
+
[[ -z "$HITS" ]] && continue
|
|
131
|
+
|
|
132
|
+
while IFS= read -r line; do
|
|
133
|
+
[[ -z "$line" ]] && continue
|
|
134
|
+
FOUND=$((FOUND + 1))
|
|
135
|
+
printf '[%s] %s — %s\n' "$sev" "$line" "$msg"
|
|
136
|
+
done <<<"$HITS"
|
|
137
|
+
done
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# Markdown rules against markdown files.
|
|
141
|
+
if [[ ${#SCAN_MD[@]} -gt 0 ]]; then
|
|
142
|
+
N=${#MD_RULES[@]}
|
|
143
|
+
i=0
|
|
144
|
+
while (( i < N )); do
|
|
145
|
+
pat="${MD_RULES[$i]}"
|
|
146
|
+
sev="${MD_RULES[$((i+1))]}"
|
|
147
|
+
msg="${MD_RULES[$((i+2))]}"
|
|
148
|
+
i=$((i+3))
|
|
149
|
+
|
|
150
|
+
HITS="$(grep -HnIE --color=never "$pat" "${SCAN_MD[@]}" 2>/dev/null || true)"
|
|
151
|
+
[[ -z "$HITS" ]] && continue
|
|
152
|
+
|
|
153
|
+
while IFS= read -r line; do
|
|
154
|
+
[[ -z "$line" ]] && continue
|
|
155
|
+
FOUND=$((FOUND + 1))
|
|
156
|
+
printf '[%s] %s — %s\n' "$sev" "$line" "$msg"
|
|
157
|
+
done <<<"$HITS"
|
|
158
|
+
done
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
TOTAL=$(( ${#SCAN[@]} + ${#SCAN_MD[@]} ))
|
|
162
|
+
|
|
163
|
+
echo
|
|
164
|
+
if [[ $FOUND -eq 0 ]]; then
|
|
165
|
+
echo "devrites-detect: clean — scanned $TOTAL files (${#SCAN[@]} code, ${#SCAN_MD[@]} md)."
|
|
166
|
+
exit 0
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
echo "devrites-detect: $FOUND finding(s) across $TOTAL files (${#SCAN[@]} code, ${#SCAN_MD[@]} md)."
|
|
170
|
+
|
|
171
|
+
if [[ $ADVISORY -eq 1 ]]; then
|
|
172
|
+
exit 0
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
exit 1
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DevRites trigger-eval runner.
|
|
3
|
+
|
|
4
|
+
Wraps the Anthropic SDK to actually execute a trigger eval against a Claude
|
|
5
|
+
model. Off by default in CI (schema-only validation lives in
|
|
6
|
+
`scripts/run-evals.sh`); this runner is invoked explicitly when
|
|
7
|
+
`CLAUDE_API_KEY` is set.
|
|
8
|
+
|
|
9
|
+
Inputs
|
|
10
|
+
------
|
|
11
|
+
A trigger-eval JSON of the shape DevRites ships under `evals/`:
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
"skill": "rite-spec",
|
|
15
|
+
"description": "...",
|
|
16
|
+
"queries": [
|
|
17
|
+
{"text": "...", "expected": "should_trigger"|"should_not_trigger", "rationale": "..."}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Plus the pack at `pack/.claude/skills/` (so the runner can read the candidate
|
|
22
|
+
skills' descriptions and present them to the model).
|
|
23
|
+
|
|
24
|
+
What it does per query
|
|
25
|
+
----------------------
|
|
26
|
+
1. Loads each skill's `name` + `description` from `pack/.claude/skills/*/SKILL.md`.
|
|
27
|
+
2. Sends the query as a `user` message to Claude with a system prompt
|
|
28
|
+
instructing it to pick exactly one skill name from the candidate list, or
|
|
29
|
+
"none" if no skill applies. We do NOT ask the model to invoke a tool — we
|
|
30
|
+
ask it to predict which skill the harness would have triggered. That keeps
|
|
31
|
+
the eval cheap (no agentic loop, one round-trip per query) and faithful to
|
|
32
|
+
what skill discovery actually decides.
|
|
33
|
+
3. Compares the predicted skill name to the expected verdict:
|
|
34
|
+
- `should_trigger` → predicted name must equal the eval's `skill` field.
|
|
35
|
+
- `should_not_trigger` → predicted name must NOT equal the eval's `skill`.
|
|
36
|
+
|
|
37
|
+
Outputs
|
|
38
|
+
-------
|
|
39
|
+
A per-query line and a final accuracy summary. Exit code 1 if accuracy is
|
|
40
|
+
below `--min-accuracy` (default 0.85).
|
|
41
|
+
|
|
42
|
+
Usage
|
|
43
|
+
-----
|
|
44
|
+
CLAUDE_API_KEY=sk-... ./scripts/eval-runner.py evals/rite-spec.json
|
|
45
|
+
CLAUDE_API_KEY=sk-... ./scripts/eval-runner.py --min-accuracy 0.9 evals/*.json
|
|
46
|
+
|
|
47
|
+
The runner is intentionally thin (~200 lines). It is not the eval-viewer in
|
|
48
|
+
Anthropic's `skill-creator` — it just provides the missing "did the model
|
|
49
|
+
pick the right skill" signal so we can answer that question without firing
|
|
50
|
+
up the full Claude Code harness.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import argparse
|
|
56
|
+
import json
|
|
57
|
+
import os
|
|
58
|
+
import re
|
|
59
|
+
import sys
|
|
60
|
+
from dataclasses import dataclass
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
|
|
63
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
64
|
+
SKILLS_DIR = ROOT / "pack" / ".claude" / "skills"
|
|
65
|
+
|
|
66
|
+
SYSTEM_TEMPLATE = """\
|
|
67
|
+
You are the DevRites skill router. Your job is to predict which DevRites
|
|
68
|
+
skill (if any) should fire for a given user message.
|
|
69
|
+
|
|
70
|
+
Each candidate skill is described below. Read the descriptions carefully —
|
|
71
|
+
DevRites' triggers are encoded in them.
|
|
72
|
+
|
|
73
|
+
For the user message at the end, respond with exactly one line of the form:
|
|
74
|
+
|
|
75
|
+
name: <skill-name>
|
|
76
|
+
|
|
77
|
+
…where <skill-name> is the name of the skill that should fire, or the
|
|
78
|
+
literal token `none` if no DevRites skill applies. Do not explain. Do not
|
|
79
|
+
suggest. Do not output anything except that one line.
|
|
80
|
+
|
|
81
|
+
# Candidate skills
|
|
82
|
+
|
|
83
|
+
{skills}
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class Skill:
|
|
89
|
+
name: str
|
|
90
|
+
description: str
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class Query:
|
|
95
|
+
text: str
|
|
96
|
+
expected: str
|
|
97
|
+
rationale: str
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class Outcome:
|
|
102
|
+
query: Query
|
|
103
|
+
predicted: str
|
|
104
|
+
correct: bool
|
|
105
|
+
false_positive: bool # expected should_not_trigger but skill fired
|
|
106
|
+
false_negative: bool # expected should_trigger but skill didn't fire
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def load_skills() -> list[Skill]:
|
|
110
|
+
skills: list[Skill] = []
|
|
111
|
+
for skill_dir in sorted(SKILLS_DIR.iterdir()):
|
|
112
|
+
skill_md = skill_dir / "SKILL.md"
|
|
113
|
+
if not skill_md.is_file():
|
|
114
|
+
continue
|
|
115
|
+
body = skill_md.read_text(encoding="utf-8")
|
|
116
|
+
# Lightweight YAML frontmatter parse — we only need name + description.
|
|
117
|
+
m = re.match(r"^---\n(.*?)\n---", body, re.DOTALL)
|
|
118
|
+
if not m:
|
|
119
|
+
continue
|
|
120
|
+
front = m.group(1)
|
|
121
|
+
name_m = re.search(r"^name:\s*(\S+)", front, re.MULTILINE)
|
|
122
|
+
desc_m = re.search(r"^description:\s*(.+)$", front, re.MULTILINE)
|
|
123
|
+
if not name_m or not desc_m:
|
|
124
|
+
continue
|
|
125
|
+
skills.append(Skill(name=name_m.group(1), description=desc_m.group(1).strip()))
|
|
126
|
+
return skills
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def render_system(skills: list[Skill]) -> str:
|
|
130
|
+
lines = []
|
|
131
|
+
for s in skills:
|
|
132
|
+
lines.append(f"- **{s.name}** — {s.description}")
|
|
133
|
+
return SYSTEM_TEMPLATE.format(skills="\n".join(lines))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def load_eval(path: Path) -> tuple[str, list[Query]]:
|
|
137
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
138
|
+
queries = [
|
|
139
|
+
Query(text=q["text"], expected=q["expected"], rationale=q.get("rationale", ""))
|
|
140
|
+
for q in data["queries"]
|
|
141
|
+
]
|
|
142
|
+
return data["skill"], queries
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def predict(client, model: str, system: str, query_text: str) -> str:
|
|
146
|
+
response = client.messages.create(
|
|
147
|
+
model=model,
|
|
148
|
+
max_tokens=64,
|
|
149
|
+
system=system,
|
|
150
|
+
messages=[{"role": "user", "content": query_text}],
|
|
151
|
+
)
|
|
152
|
+
text = "".join(block.text for block in response.content if getattr(block, "text", None))
|
|
153
|
+
m = re.search(r"^\s*name:\s*([A-Za-z0-9_\-]+)", text, re.MULTILINE)
|
|
154
|
+
if not m:
|
|
155
|
+
return "none"
|
|
156
|
+
name = m.group(1).strip().lower()
|
|
157
|
+
return name
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def score(target_skill: str, predicted: str, expected: str) -> bool:
|
|
161
|
+
predicted = predicted.lower()
|
|
162
|
+
target = target_skill.lower()
|
|
163
|
+
if expected == "should_trigger":
|
|
164
|
+
return predicted == target
|
|
165
|
+
if expected == "should_not_trigger":
|
|
166
|
+
return predicted != target
|
|
167
|
+
raise ValueError(f"unknown expected verdict: {expected!r}")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def run_one(client, model: str, system: str, eval_path: Path) -> tuple[int, int, list[Outcome]]:
|
|
171
|
+
target_skill, queries = load_eval(eval_path)
|
|
172
|
+
outcomes: list[Outcome] = []
|
|
173
|
+
correct = 0
|
|
174
|
+
for q in queries:
|
|
175
|
+
predicted = predict(client, model, system, q.text)
|
|
176
|
+
ok = score(target_skill, predicted, q.expected)
|
|
177
|
+
fp = (q.expected == "should_not_trigger" and not ok)
|
|
178
|
+
fn = (q.expected == "should_trigger" and not ok)
|
|
179
|
+
outcomes.append(Outcome(query=q, predicted=predicted, correct=ok, false_positive=fp, false_negative=fn))
|
|
180
|
+
if ok:
|
|
181
|
+
correct += 1
|
|
182
|
+
return correct, len(queries), outcomes
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def main() -> int:
|
|
186
|
+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
187
|
+
parser.add_argument("eval_files", nargs="+", type=Path, help="Eval JSON files to execute")
|
|
188
|
+
parser.add_argument("--model", default=os.environ.get("DEVRITES_EVAL_MODEL", "claude-haiku-4-5-20251001"))
|
|
189
|
+
parser.add_argument("--min-accuracy", type=float, default=0.90,
|
|
190
|
+
help="Per-eval minimum correct/total (default: 0.90 ≈ 18/20)")
|
|
191
|
+
parser.add_argument("--max-false-positives", type=int, default=2,
|
|
192
|
+
help="Per-eval max should_not_trigger queries that fired (default: 2)")
|
|
193
|
+
parser.add_argument("--verbose", action="store_true", help="Print every per-query line")
|
|
194
|
+
parser.add_argument("--summary-file", type=Path, default=None,
|
|
195
|
+
help="Write a machine-readable summary (one JSON line per eval) to this file")
|
|
196
|
+
args = parser.parse_args()
|
|
197
|
+
|
|
198
|
+
if not os.environ.get("CLAUDE_API_KEY"):
|
|
199
|
+
sys.stderr.write("error: CLAUDE_API_KEY is not set; cannot run live eval.\n")
|
|
200
|
+
return 2
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
import anthropic
|
|
204
|
+
except ImportError:
|
|
205
|
+
sys.stderr.write(
|
|
206
|
+
"error: anthropic SDK not installed. Run `pip install anthropic` first.\n"
|
|
207
|
+
)
|
|
208
|
+
return 2
|
|
209
|
+
|
|
210
|
+
client = anthropic.Anthropic(api_key=os.environ["CLAUDE_API_KEY"])
|
|
211
|
+
skills = load_skills()
|
|
212
|
+
system = render_system(skills)
|
|
213
|
+
|
|
214
|
+
total_correct = 0
|
|
215
|
+
total_queries = 0
|
|
216
|
+
total_fp = 0
|
|
217
|
+
total_fn = 0
|
|
218
|
+
per_file_failed = 0
|
|
219
|
+
summary_lines: list[str] = []
|
|
220
|
+
|
|
221
|
+
for eval_path in args.eval_files:
|
|
222
|
+
if not eval_path.is_file():
|
|
223
|
+
sys.stderr.write(f"skip: {eval_path} not a file\n")
|
|
224
|
+
continue
|
|
225
|
+
print(f"== {eval_path} ==")
|
|
226
|
+
correct, n, outcomes = run_one(client, args.model, system, eval_path)
|
|
227
|
+
fp = sum(1 for o in outcomes if o.false_positive)
|
|
228
|
+
fn = sum(1 for o in outcomes if o.false_negative)
|
|
229
|
+
total_correct += correct
|
|
230
|
+
total_queries += n
|
|
231
|
+
total_fp += fp
|
|
232
|
+
total_fn += fn
|
|
233
|
+
accuracy = correct / n if n else 0.0
|
|
234
|
+
if args.verbose:
|
|
235
|
+
for o in outcomes:
|
|
236
|
+
marker = "✓" if o.correct else "✗"
|
|
237
|
+
tag = "FP" if o.false_positive else ("FN" if o.false_negative else " ")
|
|
238
|
+
print(f" {marker} {tag} [{o.query.expected:20s}] predicted={o.predicted:24s} : {o.query.text[:60]}")
|
|
239
|
+
print(f" accuracy: {correct}/{n} = {accuracy:.0%} (FP={fp}, FN={fn})")
|
|
240
|
+
failed_reasons = []
|
|
241
|
+
if accuracy < args.min_accuracy:
|
|
242
|
+
failed_reasons.append(f"accuracy {accuracy:.0%} < {args.min_accuracy:.0%}")
|
|
243
|
+
if fp > args.max_false_positives:
|
|
244
|
+
failed_reasons.append(f"false-positives {fp} > {args.max_false_positives}")
|
|
245
|
+
if failed_reasons:
|
|
246
|
+
per_file_failed += 1
|
|
247
|
+
print(f" FAIL: {'; '.join(failed_reasons)}")
|
|
248
|
+
if args.summary_file:
|
|
249
|
+
summary_lines.append(json.dumps({
|
|
250
|
+
"file": str(eval_path),
|
|
251
|
+
"skill": load_eval(eval_path)[0],
|
|
252
|
+
"correct": correct,
|
|
253
|
+
"total": n,
|
|
254
|
+
"accuracy": round(accuracy, 4),
|
|
255
|
+
"false_positives": fp,
|
|
256
|
+
"false_negatives": fn,
|
|
257
|
+
"passed": not failed_reasons,
|
|
258
|
+
}))
|
|
259
|
+
|
|
260
|
+
overall = total_correct / total_queries if total_queries else 0.0
|
|
261
|
+
print()
|
|
262
|
+
print(f"Overall: {total_correct}/{total_queries} = {overall:.0%} (FP={total_fp}, FN={total_fn}, model={args.model})")
|
|
263
|
+
if args.summary_file:
|
|
264
|
+
args.summary_file.write_text("\n".join(summary_lines) + "\n", encoding="utf-8")
|
|
265
|
+
print(f"Summary written to {args.summary_file}")
|
|
266
|
+
if per_file_failed:
|
|
267
|
+
print(f"{per_file_failed} eval file(s) below threshold")
|
|
268
|
+
return 1
|
|
269
|
+
return 0
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
sys.exit(main())
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Deterministic OUTCOME grader for a finished DevRites feature workspace.
|
|
3
|
+
#
|
|
4
|
+
# The trigger evals (evals/*.json) test whether the right SKILL *fires*. This
|
|
5
|
+
# grades whether a finished run actually reached a *shippable* state — the
|
|
6
|
+
# product claim DevRites sells ("won't claim done without proof"). It reads only
|
|
7
|
+
# committed Markdown artifacts (no API key, no Claude Code harness), so it runs
|
|
8
|
+
# in CI against a golden fixture and is the kind of deterministic grader
|
|
9
|
+
# Anthropic's eval guidance recommends for coding agents.
|
|
10
|
+
#
|
|
11
|
+
# Live evidence-freshness (mtime: does proof post-date code?) is NOT graded here
|
|
12
|
+
# — git fixtures don't preserve mtimes; that gate is enforced on a live
|
|
13
|
+
# workspace by pack/.claude/skills/devrites-lib/scripts/evidence-fresh.sh.
|
|
14
|
+
#
|
|
15
|
+
# Usage: grade-feature.sh <workspace-dir>
|
|
16
|
+
# e.g. evals/golden/shippable-feature | .devrites/work/<slug> | .devrites/archive/<slug>
|
|
17
|
+
#
|
|
18
|
+
# Invariants (from rite-seal/reference/{seal-template,go-no-go,final-evidence}.md):
|
|
19
|
+
# 1. seal.md present with "Verdict: GO" (not NO-GO)
|
|
20
|
+
# 2. seal.md "## Acceptance Criteria" has no unchecked "- [ ]" item
|
|
21
|
+
# 3. seal.md "## Blockers" is empty / "none"
|
|
22
|
+
# 4. evidence.md present and non-empty
|
|
23
|
+
# 5. review.md present
|
|
24
|
+
# 6. questions.md has no entry with gate: validating + status: open (NO-GO by definition)
|
|
25
|
+
# 7. state.md Phase in {seal, ship, done}; Status not awaiting_human / blocked
|
|
26
|
+
#
|
|
27
|
+
# Exit codes: 0 shippable; 1 one or more invariants failed; 2 bad usage.
|
|
28
|
+
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
|
|
31
|
+
ws="${1:-}"
|
|
32
|
+
if [ -z "$ws" ] || [ ! -d "$ws" ]; then
|
|
33
|
+
printf 'usage: grade-feature.sh <workspace-dir>\n' >&2
|
|
34
|
+
exit 2
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
seal="$ws/seal.md"; ev="$ws/evidence.md"; rev="$ws/review.md"
|
|
38
|
+
q="$ws/questions.md"; st="$ws/state.md"
|
|
39
|
+
problems=()
|
|
40
|
+
|
|
41
|
+
# 1 / 2 / 3 — seal verdict, acceptance, blockers
|
|
42
|
+
if [ ! -f "$seal" ]; then
|
|
43
|
+
problems+=("seal.md missing — feature never sealed")
|
|
44
|
+
else
|
|
45
|
+
grep -qiE '^[[:space:]]*Verdict:[[:space:]]*GO[[:space:]]*$' "$seal" \
|
|
46
|
+
|| problems+=("seal.md Verdict is not GO")
|
|
47
|
+
unchecked=$(awk '/^## /{insec=($0 ~ /^## Acceptance Criteria/)} insec && /^- \[ \]/{c++} END{print c+0}' "$seal")
|
|
48
|
+
[ "${unchecked:-0}" -gt 0 ] && problems+=("seal.md has ${unchecked} unchecked acceptance criterion(s)")
|
|
49
|
+
if awk '
|
|
50
|
+
/^## /{ insec=($0 ~ /^## Blockers/); next }
|
|
51
|
+
insec { l=$0; gsub(/^[[:space:]]+|[[:space:]]+$/,"",l)
|
|
52
|
+
if (l=="") next
|
|
53
|
+
ll=tolower(l); if (ll ~ /^-?[[:space:]]*(none|n\/a)$/) next
|
|
54
|
+
nz=1 }
|
|
55
|
+
END { exit(nz?0:1) }' "$seal"; then
|
|
56
|
+
problems+=("seal.md lists unresolved blockers")
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# 4 — evidence
|
|
61
|
+
{ [ -f "$ev" ] && [ -s "$ev" ]; } || problems+=("evidence.md missing or empty — acceptance unproven")
|
|
62
|
+
|
|
63
|
+
# 5 — review
|
|
64
|
+
[ -f "$rev" ] || problems+=("review.md missing — feature not reviewed")
|
|
65
|
+
|
|
66
|
+
# 6 — open validating gate (merge-blocking by definition)
|
|
67
|
+
if [ -f "$q" ]; then
|
|
68
|
+
if awk '
|
|
69
|
+
/^## q-/{ if(inq && s=="open" && g=="validating") bad=1; inq=1; s=""; g=""; next }
|
|
70
|
+
inq && /^status:/{ v=$0; sub(/^status:[[:space:]]*/,"",v); s=v }
|
|
71
|
+
inq && /^gate:/ { v=$0; sub(/^gate:[[:space:]]*/,"",v); g=v }
|
|
72
|
+
END{ if(inq && s=="open" && g=="validating") bad=1; exit(bad?0:1) }' "$q"; then
|
|
73
|
+
problems+=("open 'gate: validating' question — merge-blocking by definition (NO-GO)")
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# 7 — state phase / status
|
|
78
|
+
if [ -f "$st" ]; then
|
|
79
|
+
sfield() { awk -v k="$1" 'match($0,"^[[:space:]]*-?[[:space:]]*" k ":[[:space:]]*"){r=substr($0,RLENGTH+1); sub(/[[:space:]]*(#|\|).*$/,"",r); sub(/[[:space:]]+$/,"",r); print r; exit}' "$st"; }
|
|
80
|
+
ph="$(sfield Phase)"; stt="$(sfield Status)"
|
|
81
|
+
case "$ph" in seal|ship|done) ;; *) problems+=("state.md Phase='${ph}' (expected seal/ship/done)") ;; esac
|
|
82
|
+
case "$stt" in awaiting_human|blocked) problems+=("state.md Status='${stt}' (not shippable)") ;; esac
|
|
83
|
+
else
|
|
84
|
+
problems+=("state.md missing")
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# 8 — executable acceptance criteria: every spec [ACn] proven + checked in seal
|
|
88
|
+
SELF="$(cd "$(dirname "$0")" && pwd)"
|
|
89
|
+
AC="$SELF/../pack/.claude/skills/devrites-lib/scripts/check-acceptance.sh"
|
|
90
|
+
[ -f "$AC" ] || AC=".claude/skills/devrites-lib/scripts/check-acceptance.sh"
|
|
91
|
+
if [ -f "$AC" ]; then
|
|
92
|
+
if ! acout=$(bash "$AC" "$ws" 2>&1); then
|
|
93
|
+
problems+=("acceptance: $(printf '%s' "$acout" | tail -1 | sed 's/^check-acceptance: //')")
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
slug="$(basename "$ws")"
|
|
98
|
+
if [ "${#problems[@]}" -eq 0 ]; then
|
|
99
|
+
printf 'GO %s — shippable: sealed GO, acceptance proven, no blockers, no open validating gate.\n' "$slug"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
printf 'NO-GO %s — %d blocker(s):\n' "$slug" "${#problems[@]}"
|
|
103
|
+
for p in "${problems[@]}"; do printf ' - %s\n' "$p"; done
|
|
104
|
+
exit 1
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install-lib.sh — shared helpers for install.sh / uninstall.sh.
|
|
3
|
+
# Sourced, not executed. Bash 3.2 compatible (no assoc arrays, no mapfile).
|
|
4
|
+
|
|
5
|
+
# ---- logging -------------------------------------------------------------
|
|
6
|
+
if [ -t 1 ]; then
|
|
7
|
+
DR_B="$(printf '\033[1m')"; DR_R="$(printf '\033[0m')"
|
|
8
|
+
DR_Y="$(printf '\033[33m')"; DR_G="$(printf '\033[32m')"; DR_C="$(printf '\033[36m')"
|
|
9
|
+
else
|
|
10
|
+
DR_B=""; DR_R=""; DR_Y=""; DR_G=""; DR_C=""
|
|
11
|
+
fi
|
|
12
|
+
dr_say() { printf '%s\n' "$*"; }
|
|
13
|
+
dr_info() { printf '%s%s%s\n' "$DR_C" "$*" "$DR_R"; }
|
|
14
|
+
dr_ok() { printf '%s%s%s\n' "$DR_G" "$*" "$DR_R"; }
|
|
15
|
+
dr_warn() { printf '%swarning:%s %s\n' "$DR_Y" "$DR_R" "$*" >&2; }
|
|
16
|
+
dr_err() { printf 'error: %s\n' "$*" >&2; }
|
|
17
|
+
dr_die() { dr_err "$*"; exit 1; }
|
|
18
|
+
|
|
19
|
+
# ---- paths ---------------------------------------------------------------
|
|
20
|
+
# Canonical absolute path of an existing directory.
|
|
21
|
+
dr_abspath_dir() {
|
|
22
|
+
( cd "$1" 2>/dev/null && pwd -P ) || return 1
|
|
23
|
+
}
|
|
24
|
+
# Canonical absolute path of a path whose parent exists (file may not).
|
|
25
|
+
dr_canon() {
|
|
26
|
+
_p="$1"
|
|
27
|
+
case "$_p" in
|
|
28
|
+
/*) : ;;
|
|
29
|
+
*) _p="$PWD/$_p" ;;
|
|
30
|
+
esac
|
|
31
|
+
_d="$( cd "$(dirname "$_p")" 2>/dev/null && pwd -P )" || return 1
|
|
32
|
+
printf '%s/%s\n' "$_d" "$(basename "$_p")"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# ---- manifest ------------------------------------------------------------
|
|
36
|
+
# Manifest = newline list of target-relative paths, plus comment header lines.
|
|
37
|
+
DR_MANIFEST_NAME=".claude/devrites.manifest"
|
|
38
|
+
|
|
39
|
+
# True if a target-relative path is listed in the manifest file.
|
|
40
|
+
dr_manifest_contains() {
|
|
41
|
+
_mf="$1"; _rel="$2"
|
|
42
|
+
[ -f "$_mf" ] || return 1
|
|
43
|
+
# exact line match, ignoring comment/blank lines
|
|
44
|
+
grep -v '^#' "$_mf" 2>/dev/null | grep -v '^[[:space:]]*$' \
|
|
45
|
+
| grep -Fxq "$_rel"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ---- misc ----------------------------------------------------------------
|
|
49
|
+
dr_lc() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
|
|
50
|
+
|
|
51
|
+
# Is $1 (canonical) equal to, or inside, the user's global ~/.claude?
|
|
52
|
+
# Used to refuse global writes. No write verbs here — read-only comparison.
|
|
53
|
+
dr_is_global_claude() {
|
|
54
|
+
_t="$1"
|
|
55
|
+
_home_claude="$HOME/.claude"
|
|
56
|
+
[ "$_t" = "$_home_claude" ] && return 0
|
|
57
|
+
case "$_t/" in
|
|
58
|
+
"$_home_claude"/*) return 0 ;;
|
|
59
|
+
esac
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# ---- alias wrapper template ----------------------------------------------
|
|
64
|
+
# Generate a thin SKILL.md that delegates to a DevRites skill. Used by:
|
|
65
|
+
# - install.sh (--short-aliases=all → /define, /build, /prove, /seal)
|
|
66
|
+
# - scripts/pin.sh (user-pinned ad-hoc shortcuts)
|
|
67
|
+
# Args: $1=alias-name, $2=target-skill-name (e.g. rite-build), $3=output-file
|
|
68
|
+
dr_gen_alias_wrapper() {
|
|
69
|
+
_name="$1"; _to="$2"; _out="$3"
|
|
70
|
+
cat > "$_out" <<EOF
|
|
71
|
+
---
|
|
72
|
+
name: $_name
|
|
73
|
+
description: Alias of DevRites /$_to. Use when the user runs /$_name. Delegates to /$_to.
|
|
74
|
+
argument-hint: "[args]"
|
|
75
|
+
user-invocable: true
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
# /$_name — alias of /$_to
|
|
79
|
+
|
|
80
|
+
This command **delegates to DevRites \`/$_to\`**. State that to the user, then load and
|
|
81
|
+
run \`$_to/SKILL.md\` with the given arguments, following it exactly.
|
|
82
|
+
EOF
|
|
83
|
+
}
|