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.
Files changed (232) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.claude-plugin/plugin.json +43 -0
  3. package/CHANGELOG.md +391 -0
  4. package/LICENSE +56 -0
  5. package/NOTICE.md +18 -0
  6. package/README.md +582 -0
  7. package/SECURITY.md +193 -0
  8. package/bin/devrites.mjs +100 -0
  9. package/docs/architecture.md +272 -0
  10. package/docs/cli-mcp.md +57 -0
  11. package/docs/command-map.md +143 -0
  12. package/docs/flow.md +360 -0
  13. package/docs/release.md +29 -0
  14. package/docs/skills.md +214 -0
  15. package/docs/usage.md +325 -0
  16. package/install.sh +359 -0
  17. package/mcp/devrites-mcp.mjs +103 -0
  18. package/pack/.claude/agents/devrites-code-reviewer.md +50 -0
  19. package/pack/.claude/agents/devrites-doubt-reviewer.md +55 -0
  20. package/pack/.claude/agents/devrites-frontend-reviewer.md +52 -0
  21. package/pack/.claude/agents/devrites-performance-reviewer.md +47 -0
  22. package/pack/.claude/agents/devrites-plan-reviewer.md +79 -0
  23. package/pack/.claude/agents/devrites-security-auditor.md +53 -0
  24. package/pack/.claude/agents/devrites-simplifier-reviewer.md +75 -0
  25. package/pack/.claude/agents/devrites-slice-wright.md +181 -0
  26. package/pack/.claude/agents/devrites-spec-reviewer.md +72 -0
  27. package/pack/.claude/agents/devrites-strategy-reviewer.md +62 -0
  28. package/pack/.claude/agents/devrites-test-analyst.md +47 -0
  29. package/pack/.claude/hooks/devrites-a1-guard.sh +81 -0
  30. package/pack/.claude/hooks/devrites-allow.sh +44 -0
  31. package/pack/.claude/hooks/devrites-cursor.sh +28 -0
  32. package/pack/.claude/hooks/devrites-orient.sh +53 -0
  33. package/pack/.claude/hooks/devrites-redwatch.sh +39 -0
  34. package/pack/.claude/hooks/devrites-refresh-indexes.sh +127 -0
  35. package/pack/.claude/hooks/devrites-reviewer-readonly.sh +28 -0
  36. package/pack/.claude/hooks/devrites-statusline.sh +18 -0
  37. package/pack/.claude/hooks/devrites-stop-gate.sh +45 -0
  38. package/pack/.claude/hooks/devrites-wright-scope.sh +35 -0
  39. package/pack/.claude/hooks/hooks.json +52 -0
  40. package/pack/.claude/rules/README.md +48 -0
  41. package/pack/.claude/rules/afk-hitl.md +245 -0
  42. package/pack/.claude/rules/agents.md +98 -0
  43. package/pack/.claude/rules/anti-patterns.md +48 -0
  44. package/pack/.claude/rules/code-review.md +38 -0
  45. package/pack/.claude/rules/coding-style.md +55 -0
  46. package/pack/.claude/rules/context-hygiene.md +97 -0
  47. package/pack/.claude/rules/core.md +119 -0
  48. package/pack/.claude/rules/development-workflow.md +40 -0
  49. package/pack/.claude/rules/documentation.md +27 -0
  50. package/pack/.claude/rules/error-handling.md +33 -0
  51. package/pack/.claude/rules/git-workflow.md +35 -0
  52. package/pack/.claude/rules/hooks.md +38 -0
  53. package/pack/.claude/rules/patterns.md +45 -0
  54. package/pack/.claude/rules/performance.md +27 -0
  55. package/pack/.claude/rules/prose-style.md +101 -0
  56. package/pack/.claude/rules/security.md +63 -0
  57. package/pack/.claude/rules/testing.md +88 -0
  58. package/pack/.claude/rules/tooling.md +72 -0
  59. package/pack/.claude/settings.json +53 -0
  60. package/pack/.claude/skills/devrites-api-interface/SKILL.md +45 -0
  61. package/pack/.claude/skills/devrites-audit/SKILL.md +73 -0
  62. package/pack/.claude/skills/devrites-browser-proof/SKILL.md +38 -0
  63. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +50 -0
  64. package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +47 -0
  65. package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +17 -0
  66. package/pack/.claude/skills/devrites-debug-recovery/reference/hypotheses.md +17 -0
  67. package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +21 -0
  68. package/pack/.claude/skills/devrites-debug-recovery/reference/regression-test.md +31 -0
  69. package/pack/.claude/skills/devrites-doubt/SKILL.md +75 -0
  70. package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +96 -0
  71. package/pack/.claude/skills/devrites-frontend-craft/reference/craft.md +59 -0
  72. package/pack/.claude/skills/devrites-frontend-craft/reference/design-references.md +116 -0
  73. package/pack/.claude/skills/devrites-frontend-craft/reference/fullstack.md +45 -0
  74. package/pack/.claude/skills/devrites-frontend-craft/reference/quality-standards.md +215 -0
  75. package/pack/.claude/skills/devrites-frontend-craft/reference/reuse-first.md +59 -0
  76. package/pack/.claude/skills/devrites-frontend-craft/reference/shape.md +60 -0
  77. package/pack/.claude/skills/devrites-interview/SKILL.md +81 -0
  78. package/pack/.claude/skills/devrites-lib/SKILL.md +76 -0
  79. package/pack/.claude/skills/devrites-lib/scripts/analyze.sh +78 -0
  80. package/pack/.claude/skills/devrites-lib/scripts/check-acceptance.sh +75 -0
  81. package/pack/.claude/skills/devrites-lib/scripts/close-out.sh +47 -0
  82. package/pack/.claude/skills/devrites-lib/scripts/conventions.py +273 -0
  83. package/pack/.claude/skills/devrites-lib/scripts/coverage.sh +51 -0
  84. package/pack/.claude/skills/devrites-lib/scripts/devrites.sh +69 -0
  85. package/pack/.claude/skills/devrites-lib/scripts/doctor.sh +92 -0
  86. package/pack/.claude/skills/devrites-lib/scripts/evidence-fresh.sh +63 -0
  87. package/pack/.claude/skills/devrites-lib/scripts/footprint.sh +45 -0
  88. package/pack/.claude/skills/devrites-lib/scripts/learnings.sh +74 -0
  89. package/pack/.claude/skills/devrites-lib/scripts/mutation-gate.sh +52 -0
  90. package/pack/.claude/skills/devrites-lib/scripts/package-existence.sh +68 -0
  91. package/pack/.claude/skills/devrites-lib/scripts/preamble.sh +76 -0
  92. package/pack/.claude/skills/devrites-lib/scripts/progress.sh +103 -0
  93. package/pack/.claude/skills/devrites-lib/scripts/readiness.sh +62 -0
  94. package/pack/.claude/skills/devrites-lib/scripts/reconcile.sh +123 -0
  95. package/pack/.claude/skills/devrites-lib/scripts/resolve.sh +279 -0
  96. package/pack/.claude/skills/devrites-lib/scripts/stuck.sh +67 -0
  97. package/pack/.claude/skills/devrites-lib/scripts/test-integrity.sh +87 -0
  98. package/pack/.claude/skills/devrites-lib/scripts/tick-afk.sh +52 -0
  99. package/pack/.claude/skills/devrites-prose-craft/SKILL.md +105 -0
  100. package/pack/.claude/skills/devrites-prose-craft/reference/banned-phrases.md +95 -0
  101. package/pack/.claude/skills/devrites-prose-craft/reference/examples.md +88 -0
  102. package/pack/.claude/skills/devrites-prose-craft/reference/structures.md +134 -0
  103. package/pack/.claude/skills/devrites-refresh-indexes/SKILL.md +54 -0
  104. package/pack/.claude/skills/devrites-source-driven/SKILL.md +36 -0
  105. package/pack/.claude/skills/devrites-ux-shape/SKILL.md +121 -0
  106. package/pack/.claude/skills/devrites-ux-shape/reference/brief-template.md +93 -0
  107. package/pack/.claude/skills/devrites-ux-shape/reference/visual-direction-probe.md +48 -0
  108. package/pack/.claude/skills/rite/SKILL.md +135 -0
  109. package/pack/.claude/skills/rite/reference/menu.md +32 -0
  110. package/pack/.claude/skills/rite-adopt/SKILL.md +83 -0
  111. package/pack/.claude/skills/rite-adopt/reference/adoption.md +58 -0
  112. package/pack/.claude/skills/rite-adopt/reference/anti-patterns.md +19 -0
  113. package/pack/.claude/skills/rite-autocomplete/SKILL.md +96 -0
  114. package/pack/.claude/skills/rite-autocomplete/reference/decision-policy.md +35 -0
  115. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +54 -0
  116. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +59 -0
  117. package/pack/.claude/skills/rite-build/SKILL.md +261 -0
  118. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +145 -0
  119. package/pack/.claude/skills/rite-build/reference/anti-patterns.md +25 -0
  120. package/pack/.claude/skills/rite-build/reference/checkpoint-protocol.md +149 -0
  121. package/pack/.claude/skills/rite-build/reference/evidence-standard.md +32 -0
  122. package/pack/.claude/skills/rite-build/reference/frontend-trigger.md +39 -0
  123. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +38 -0
  124. package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +43 -0
  125. package/pack/.claude/skills/rite-build/reference/tdd.md +26 -0
  126. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +115 -0
  127. package/pack/.claude/skills/rite-define/SKILL.md +157 -0
  128. package/pack/.claude/skills/rite-define/reference/anti-patterns.md +25 -0
  129. package/pack/.claude/skills/rite-define/reference/gates.md +152 -0
  130. package/pack/.claude/skills/rite-define/reference/plan-template.md +65 -0
  131. package/pack/.claude/skills/rite-doctor/SKILL.md +50 -0
  132. package/pack/.claude/skills/rite-frame/SKILL.md +116 -0
  133. package/pack/.claude/skills/rite-frame/reference/failure-modes.md +68 -0
  134. package/pack/.claude/skills/rite-handoff/SKILL.md +95 -0
  135. package/pack/.claude/skills/rite-handoff/reference/handoff-template.md +34 -0
  136. package/pack/.claude/skills/rite-learn/SKILL.md +82 -0
  137. package/pack/.claude/skills/rite-plan/SKILL.md +82 -0
  138. package/pack/.claude/skills/rite-plan/reference/anti-patterns.md +24 -0
  139. package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +33 -0
  140. package/pack/.claude/skills/rite-plan/reference/replan-and-repair.md +42 -0
  141. package/pack/.claude/skills/rite-plan/reference/slicing.md +52 -0
  142. package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +34 -0
  143. package/pack/.claude/skills/rite-polish/SKILL.md +90 -0
  144. package/pack/.claude/skills/rite-polish/reference/anti-ai-slop.md +177 -0
  145. package/pack/.claude/skills/rite-polish/reference/anti-patterns.md +27 -0
  146. package/pack/.claude/skills/rite-polish/reference/backend-polish.md +80 -0
  147. package/pack/.claude/skills/rite-polish/reference/browser-polish-evidence.md +31 -0
  148. package/pack/.claude/skills/rite-polish/reference/code.md +85 -0
  149. package/pack/.claude/skills/rite-polish/reference/design-system-discovery.md +35 -0
  150. package/pack/.claude/skills/rite-polish/reference/harden-checklist.md +109 -0
  151. package/pack/.claude/skills/rite-polish/reference/ui.md +136 -0
  152. package/pack/.claude/skills/rite-pressure-test/SKILL.md +43 -0
  153. package/pack/.claude/skills/rite-prototype/SKILL.md +87 -0
  154. package/pack/.claude/skills/rite-prove/SKILL.md +120 -0
  155. package/pack/.claude/skills/rite-prove/reference/anti-patterns.md +25 -0
  156. package/pack/.claude/skills/rite-prove/reference/browser-proof.md +26 -0
  157. package/pack/.claude/skills/rite-prove/reference/failure-triage.md +25 -0
  158. package/pack/.claude/skills/rite-prove/reference/proof-ladder.md +26 -0
  159. package/pack/.claude/skills/rite-prove/reference/test-command-discovery.md +30 -0
  160. package/pack/.claude/skills/rite-quick/SKILL.md +81 -0
  161. package/pack/.claude/skills/rite-resolve/SKILL.md +113 -0
  162. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +114 -0
  163. package/pack/.claude/skills/rite-review/SKILL.md +170 -0
  164. package/pack/.claude/skills/rite-review/reference/anti-patterns.md +32 -0
  165. package/pack/.claude/skills/rite-review/reference/cognitive-load.md +90 -0
  166. package/pack/.claude/skills/rite-review/reference/feature-scoped-review.md +26 -0
  167. package/pack/.claude/skills/rite-review/reference/five-axis-review.md +46 -0
  168. package/pack/.claude/skills/rite-review/reference/nielsen-heuristics.md +130 -0
  169. package/pack/.claude/skills/rite-review/reference/parallel-dispatch.md +62 -0
  170. package/pack/.claude/skills/rite-review/reference/performance-review.md +28 -0
  171. package/pack/.claude/skills/rite-review/reference/security-review.md +32 -0
  172. package/pack/.claude/skills/rite-seal/SKILL.md +183 -0
  173. package/pack/.claude/skills/rite-seal/reference/anti-patterns.md +27 -0
  174. package/pack/.claude/skills/rite-seal/reference/conventions-ledger.md +63 -0
  175. package/pack/.claude/skills/rite-seal/reference/final-evidence.md +72 -0
  176. package/pack/.claude/skills/rite-seal/reference/go-no-go.md +37 -0
  177. package/pack/.claude/skills/rite-seal/reference/parallel-dispatch.md +69 -0
  178. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +30 -0
  179. package/pack/.claude/skills/rite-seal/reference/seal-template.md +36 -0
  180. package/pack/.claude/skills/rite-ship/SKILL.md +120 -0
  181. package/pack/.claude/skills/rite-ship/reference/anti-patterns.md +25 -0
  182. package/pack/.claude/skills/rite-ship/reference/close-out.md +31 -0
  183. package/pack/.claude/skills/rite-ship/reference/design-memory.md +120 -0
  184. package/pack/.claude/skills/rite-ship/reference/git-ship.md +42 -0
  185. package/pack/.claude/skills/rite-ship/reference/ship-template.md +33 -0
  186. package/pack/.claude/skills/rite-spec/SKILL.md +126 -0
  187. package/pack/.claude/skills/rite-spec/reference/acceptance-criteria.md +31 -0
  188. package/pack/.claude/skills/rite-spec/reference/anti-patterns.md +25 -0
  189. package/pack/.claude/skills/rite-spec/reference/interview-patterns.md +56 -0
  190. package/pack/.claude/skills/rite-spec/reference/investigation.md +64 -0
  191. package/pack/.claude/skills/rite-spec/reference/question-protocol.md +61 -0
  192. package/pack/.claude/skills/rite-spec/reference/references-intake.md +57 -0
  193. package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +73 -0
  194. package/pack/.claude/skills/rite-spec/reference/spec-template.md +124 -0
  195. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +159 -0
  196. package/pack/.claude/skills/rite-status/SKILL.md +101 -0
  197. package/pack/.claude/skills/rite-temper/SKILL.md +119 -0
  198. package/pack/.claude/skills/rite-temper/reference/anti-patterns.md +29 -0
  199. package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +65 -0
  200. package/pack/.claude/skills/rite-temper/reference/scope-modes.md +53 -0
  201. package/pack/.claude/skills/rite-temper/reference/significance.md +46 -0
  202. package/pack/.claude/skills/rite-temper/reference/strategy-template.md +90 -0
  203. package/pack/.claude/skills/rite-vet/SKILL.md +155 -0
  204. package/pack/.claude/skills/rite-vet/reference/anti-patterns.md +29 -0
  205. package/pack/.claude/skills/rite-vet/reference/artifacts.md +135 -0
  206. package/pack/.claude/skills/rite-vet/reference/cross-model.md +41 -0
  207. package/pack/.claude/skills/rite-vet/reference/depth.md +53 -0
  208. package/pack/.claude/skills/rite-vet/reference/eng-lenses.md +48 -0
  209. package/pack/.claude/skills/rite-vet/reference/review-axes.md +167 -0
  210. package/pack/.claude/skills/rite-zoom-out/SKILL.md +75 -0
  211. package/package.json +68 -0
  212. package/scripts/build-release-tarball.sh +74 -0
  213. package/scripts/check-cross-refs.py +121 -0
  214. package/scripts/check-no-global-writes.sh +44 -0
  215. package/scripts/check-rule-uniqueness.sh +73 -0
  216. package/scripts/devrites-detect.sh +175 -0
  217. package/scripts/eval-runner.py +273 -0
  218. package/scripts/grade-feature.sh +104 -0
  219. package/scripts/install-lib.sh +83 -0
  220. package/scripts/pin.sh +166 -0
  221. package/scripts/render-eval-summary.py +48 -0
  222. package/scripts/run-evals.sh +149 -0
  223. package/scripts/run-outcome-evals.sh +49 -0
  224. package/scripts/scan-pack-security.py +209 -0
  225. package/scripts/scan-supply-chain-iocs.py +127 -0
  226. package/scripts/supply-chain-iocs.json +11 -0
  227. package/scripts/sync-version.sh +56 -0
  228. package/scripts/validate-frontmatter.py +149 -0
  229. package/scripts/validate-workflow-security.py +86 -0
  230. package/scripts/validate.sh +234 -0
  231. package/uninstall.sh +137 -0
  232. 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
+ }