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,81 @@
1
+ ---
2
+ name: devrites-interview
3
+ description: One-question-at-a-time interview to extract what the user wants — each question carries a best-guess + structured options; stop at ~95% confidence. Use when the user says "interview me", "grill me", "I'm not sure what I want", or `/rite-spec` / `/rite-define` flags the ask as underspecified. Not for casual clarification or ideation (use `rite-pressure-test`).
4
+ user-invocable: false
5
+ ---
6
+
7
+ # devrites-interview — extract intent
8
+
9
+ Close the gap between what the user said and what they want, at the cheapest moment —
10
+ before a plan, spec, or code exists.
11
+
12
+ ## Protocol
13
+ - **One question per turn.** Multiple questions get one answered and the rest ignored.
14
+ - **Attach your best guess** to every question, with the reason:
15
+ > "I'm assuming export is CSV only (covers the stated use case). Right, or also XLSX?"
16
+ This turns an open question into a cheap correction and exposes your model so the user
17
+ can fix the premise.
18
+ - **Highest-value question first** — order by how much the answer changes the build. A
19
+ question that moves the data model or acceptance criteria beats a cosmetic one.
20
+ - **Impact-priority + bounded blocking.** Order unknowns **scope > security/privacy > UX >
21
+ technical**, and cap **blocking** questions at **≤3** per pass — ask the few that actually gate
22
+ the spec; **default-and-record** the rest in `assumptions.md` (best-guess + why). A reversible
23
+ detail never earns a blocking question.
24
+ - **Structured options** when the space is enumerable — present them as the standard ranked
25
+ **option set** (`rules/afk-hitl.md` → "Option set"): recommended **first**, labelled
26
+ `(Recommended)`, each with a dimension-tagged rationale (`logic · infra · business ·
27
+ architecture`, + `security`/`UX`/`risk` when in scope), plus the escape hatch. Render via
28
+ `AskUserQuestion` when the harness has it:
29
+ ```
30
+ 1. <recommended> (Recommended) — logic: … · infra: … · business: … · architecture: …
31
+ 2. <alternative> — <rationale + the trade-off it accepts>
32
+ 3. Something else — I'll describe it
33
+ ```
34
+
35
+ ## Stop condition
36
+ Stop when **any** holds — don't interrogate past the point of value:
37
+ - **Confidence** — you can predict the next answer (~95%); remaining unknowns are
38
+ reversible details that don't change the spec.
39
+ - **Convergence** — the last 2–3 answers only rubber-stamped your guesses and didn't
40
+ move the spec.
41
+ - **Soft cap** — after ~8 material questions, proceed with your best-guess answers logged
42
+ in `assumptions.md` rather than asking more (hard-stop sooner if the ask is small).
43
+
44
+ Depth on the few that matter, not breadth for its own sake. If answers stop converging —
45
+ you keep circling one area without progress — **reframe once** (below) instead of asking
46
+ another question.
47
+
48
+ ## Don't ask
49
+ - Things the codebase answers (read it first).
50
+ - Reversible implementation details (decide, log as an assumption).
51
+ - Everything at once "to be thorough."
52
+
53
+ ## When the ask is vague — map the decision tree first
54
+ For a one-line or fuzzy ask (`"design a contact page"`), don't fire isolated questions.
55
+ First sketch the **decision tree** — the branches the answer splits into — and resolve
56
+ each branch **depth-first** with the protocol above. Domain branches per area:
57
+ `rite-spec/reference/interview-patterns.md`.
58
+
59
+ ## Reframe (once, when stuck)
60
+ If the interview isn't converging, spend **one** turn challenging the premise rather than
61
+ refining it — *"is a form even the right answer here, or a mailto / booking link?"* A good
62
+ reframe collapses several open branches. Use it sparingly, then resume the protocol.
63
+
64
+ ## /clarify mode — coverage scan of an existing spec
65
+ When invoked to clarify a written spec (not extract intent from scratch), scan it against the fixed
66
+ taxonomy and mark each **Clear / Partial / Missing**: Functional scope · Data model · Interaction
67
+ (API / UI states) · Non-functional (auth / latency / scale / compliance) · Edge cases (empty /
68
+ boundary / invalid / concurrent / failure). Then ask **≤5 prioritized questions** (impact order
69
+ above), targeting Missing before Partial, one per turn with a best-guess attached. **Integrate each
70
+ answer into the right spec section** as it lands — not just a Q&A log — and append a dated
71
+ **`## Clarifications`** block to `spec.md` (Q + resolution) for durable provenance. Re-run the scan
72
+ after answers; stop when every area is Clear or explicitly deferred, then re-score the affected
73
+ `checklists/<domain>.md`.
74
+
75
+ ## Output
76
+ A short summary the caller can use: objective in one sentence, confirmed decisions,
77
+ still-open (non-blocking) items, recommended next step. If a workspace is active, write
78
+ Q&A to `questions.md`, confirmed calls to `decisions.md`, standing guesses to
79
+ `assumptions.md`. If not, just return the summary — don't create a workspace.
80
+
81
+ Domain question ladders: `rite-spec/reference/interview-patterns.md`.
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: devrites-lib
3
+ description: Internal shared-script library for DevRites — not a user command and not model-invocable. It exists only to ship DevRites' cross-cutting helper scripts (notably the read-only orientation preamble that every workspace-operating rite-* skill runs at step 0) on both the bash-installer and plugin channels. No workflow; do not invoke.
4
+ user-invocable: false
5
+ disable-model-invocation: true
6
+ ---
7
+
8
+ # devrites-lib — internal shared scripts (not a command)
9
+
10
+ This is **not** a skill you run. It is a library directory housing DevRites'
11
+ cross-cutting helper scripts under `scripts/`, placed inside `skills/` so they
12
+ install on **both** distribution channels (the bash installer copies the pack's
13
+ `skills/` tree into the project's `.claude/`; the plugin ships the `skills/`
14
+ tree). Skills resolve these scripts with the standard three-layout snippet:
15
+ the installed `.claude/` path first, then the plugin cache via
16
+ `${CLAUDE_SKILL_DIR}` (best-effort — Claude Code doesn't expose a stable script path to skill-invoked bash on the plugin channel, so the preamble degrades to reading `state.md` there), then the repo source tree for DevRites
17
+ self-development.
18
+
19
+ ## Scripts
20
+
21
+ All resolve with the standard three-layout snippet (installed `.claude/` path
22
+ first, then `${CLAUDE_SKILL_DIR}`, then the repo `pack/` source).
23
+
24
+ **Read-only — orient / gate (never mutate the workspace):**
25
+
26
+ - `scripts/preamble.sh` — orientation digest for the active `.devrites/` feature:
27
+ prints `state.md`, the artifacts present, the run mode (HITL/AFK), and the
28
+ open-question tally by gate. Run first (step 0) by every workspace-operating
29
+ `rite-*` skill so the model orients deterministically instead of re-deriving
30
+ state from raw Markdown.
31
+ - `scripts/progress.sh` — progress footer; the mirror of `preamble.sh` (which runs
32
+ first). Run **last** (output step) by every lifecycle `rite-*` skill to render — from
33
+ `state.md`, with zero model drift — the `── rite-<phase> ──` header rule, the **slice
34
+ meter** (`Slice 3/5 ██████░░░░ <last-built> ✓`, or `Slices 5/5 ██████████ ✅ ALL
35
+ BUILT` at completion), and the **flow ribbon** (`spec ✓ define ✓ build ◉ … ship ○`).
36
+ The meter answers "how many slices left"; the `✅ ALL BUILT` marker answers "is the
37
+ build done". The skill prints its own what-was-done / next-step / hygiene lines beneath
38
+ it. Read-only; silent (exit 0) when there is no active workspace. Not for the workspace-less
39
+ utilities (`/rite-prototype`, `/rite-zoom-out`, `/rite-pressure-test`, `/rite-handoff`,
40
+ the `/rite` menu) — they have no phase/slice state to render.
41
+ - `scripts/readiness.sh` — build-readiness gate. Exits non-zero on `/rite-build`'s
42
+ step-0 stop conditions so they hold by exit code, not by prose the model must
43
+ remember: `2` no `Plan approved` (→ `/rite-define`), `3` `awaiting_human`
44
+ (→ `/rite-resolve`), `4` `blocked` (→ `/rite-plan`), `5` no workspace, `0` ready.
45
+ - `scripts/evidence-fresh.sh` — evidence-freshness gate for `/rite-seal`. Exits `3`
46
+ when any file in `touched-files.md` is newer than `evidence.md` /
47
+ `browser-evidence.md` (stale proof = NO-GO until re-proven), `0` when fresh.
48
+ - `scripts/check-acceptance.sh` — executable acceptance gate. Compiles `spec.md`'s
49
+ `[ACn]`-tagged criteria and exits `1` unless every one is checked (proven) in `seal.md`;
50
+ used by `/rite-seal` and by the outcome grader.
51
+
52
+ **State mutators — write `state.md` / `questions.md` under one contract:**
53
+
54
+ - `scripts/tick-afk.sh` — decrement the AFK slice budget; exits `3` at 0 (forced HITL stop).
55
+ - `scripts/resolve.sh` — backs the `/rite-resolve` contract (answer / drop / batch).
56
+ - `scripts/close-out.sh` — archive the workspace + clear `ACTIVE` on `/rite-ship`.
57
+
58
+ ### Canonical footer snippet
59
+
60
+ Every lifecycle `rite-*` skill prints this as the **first lines of its output**, then its
61
+ own fact lines below. Same three-layout resolver as the step-0 preamble:
62
+
63
+ ```bash
64
+ PR=.claude/skills/devrites-lib/scripts/progress.sh
65
+ [ -f "$PR" ] || PR="${CLAUDE_SKILL_DIR:-}/../devrites-lib/scripts/progress.sh"
66
+ [ -f "$PR" ] || PR=pack/.claude/skills/devrites-lib/scripts/progress.sh
67
+ [ -f "$PR" ] && bash "$PR" || true
68
+ ```
69
+
70
+ **Unified entrypoint (tool-agnostic):**
71
+
72
+ - `scripts/devrites.sh` — one CLI dispatching to all of the above (`orient` / `ready` /
73
+ `evidence-fresh` / `acceptance` / `tick-afk` / `resolve` / `close` / `active` / `list` /
74
+ `use`), so any agent or human can drive `.devrites/` without the skill prose. The MCP
75
+ wrapper `mcp/devrites-mcp.mjs` exposes the read/gate ops as MCP tools. See
76
+ [`docs/cli-mcp.md`](../../../../docs/cli-mcp.md).
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # analyze.sh — cross-artifact consistency + coverage gate for /rite-vet, run read-only
3
+ # over spec.md / tasks.md (+ coverage.md) BEFORE any code. One pass proving the artifacts
4
+ # are mutually consistent and the plan fully covers the spec, so a gap is caught when it
5
+ # is a one-line plan edit, not a reslice mid-build. Deterministic where it can be; the
6
+ # model walks the rest.
7
+ #
8
+ # Checks (each finding: "[severity] <locus> — issue"):
9
+ # Coverage — every [ACn] in spec.md maps to >=1 slice (tasks.md Satisfies:) CRITICAL
10
+ # Dangling-ref — a slice Satisfies: an AC id that does not exist in spec.md CRITICAL
11
+ # Orphan-slice — a slice satisfies no acceptance criterion warn
12
+ #
13
+ # Usage: analyze.sh [slug] (slug defaults to .devrites/ACTIVE)
14
+ # Exit: 0 clean · 1 CRITICAL found (blocks /rite-build) · 2 no workspace
15
+ set -u
16
+
17
+ slug="${1:-}"
18
+ [ -n "$slug" ] || slug="$(cat .devrites/ACTIVE 2>/dev/null || true)"
19
+ [ -n "$slug" ] || { echo "analyze: no active workspace." >&2; exit 2; }
20
+ d=".devrites/work/$slug"
21
+ spec="$d/spec.md"; tasks="$d/tasks.md"
22
+ [ -f "$spec" ] && [ -f "$tasks" ] || { echo "analyze: need spec.md + tasks.md in $d." >&2; exit 2; }
23
+
24
+ spec_acs="$(grep -oE '\[AC[0-9]+\]' "$spec" 2>/dev/null | tr -d '[]' | sort -u)"
25
+ task_acs="$(grep -oE '\bAC[0-9]+\b' "$tasks" 2>/dev/null | sort -u)"
26
+
27
+ crit=0
28
+ echo "# Cross-artifact analysis: $slug"
29
+ echo
30
+
31
+ # Coverage: spec ACs with no slice reference.
32
+ echo "## Coverage"
33
+ if [ -z "$spec_acs" ]; then
34
+ echo "- [warn] spec.md — no [ACn] acceptance ids found; tag criteria as '[AC1] ...' for a machine-checkable gate."
35
+ else
36
+ while IFS= read -r ac; do
37
+ [ -n "$ac" ] || continue
38
+ if printf '%s\n' "$task_acs" | grep -qxF "$ac"; then
39
+ echo "- [ok] $ac — covered"
40
+ else
41
+ echo "- [CRITICAL] $ac (spec.md) — no slice Satisfies it (uncovered)"; crit=$((crit+1))
42
+ fi
43
+ done <<EOF
44
+ $spec_acs
45
+ EOF
46
+ fi
47
+
48
+ # Dangling refs: tasks Satisfies an AC the spec doesn't define.
49
+ echo
50
+ echo "## Consistency"
51
+ if [ -n "$task_acs" ]; then
52
+ while IFS= read -r ac; do
53
+ [ -n "$ac" ] || continue
54
+ if ! printf '%s\n' "$spec_acs" | grep -qxF "$ac"; then
55
+ echo "- [CRITICAL] $ac (tasks.md) — referenced by a slice but not defined in spec.md (dangling)"; crit=$((crit+1))
56
+ fi
57
+ done <<EOF
58
+ $task_acs
59
+ EOF
60
+ fi
61
+
62
+ # Orphan slices: a slice with no Satisfies line.
63
+ orphans="$(awk '
64
+ /^##[[:space:]]*Slice/ { if (name!="" && !sat) print name; name=$0; sub(/^##[[:space:]]*/,"",name); sat=0 }
65
+ /^[[:space:]]*Satisfies:/ { sat=1 }
66
+ END { if (name!="" && !sat) print name }
67
+ ' "$tasks" 2>/dev/null)"
68
+ if [ -n "$orphans" ]; then
69
+ echo "$orphans" | while IFS= read -r s; do [ -n "$s" ] && echo "- [warn] slice '$s' — satisfies no acceptance criterion (add Satisfies: or justify)"; done
70
+ fi
71
+
72
+ echo
73
+ if [ "$crit" -gt 0 ]; then
74
+ echo "## Verdict: BLOCKED — $crit CRITICAL finding(s). Resolve before /rite-build."
75
+ exit 1
76
+ fi
77
+ echo "## Verdict: clear — spec/tasks consistent and fully mapped."
78
+ exit 0
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ # Executable acceptance criteria — compile spec.md's acceptance criteria into
3
+ # machine-checkable items and verify each is proven (checked) in seal.md. Turns
4
+ # "walk acceptance one by one" (rite-seal/reference/final-evidence.md) into a
5
+ # deterministic check instead of prose the model walks by hand.
6
+ #
7
+ # Convention (optional but recommended): tag each criterion with a stable id —
8
+ # spec.md "## Acceptance criteria" → - [ ] [AC1] <criterion>
9
+ # seal.md "## Acceptance Criteria" → - [x] [AC1] <criterion> — evidence: <…>
10
+ # A criterion is "done" iff its [ACn] id is CHECKED ([x]) in seal.md. This is what
11
+ # makes the criterion executable: a stable handle the prove/seal phases grade against.
12
+ # Without ids it falls back to a count/unchecked heuristic and recommends adding them.
13
+ #
14
+ # Usage: check-acceptance.sh <workspace-dir>
15
+ # Exit: 0 every spec criterion proven in seal · 1 gap (uncovered/unproven) ·
16
+ # 2 usage · 5 missing spec.md or seal.md
17
+
18
+ set -euo pipefail
19
+
20
+ ws="${1:-}"
21
+ if [ -z "$ws" ] || [ ! -d "$ws" ]; then
22
+ printf 'usage: check-acceptance.sh <workspace-dir>\n' >&2
23
+ exit 2
24
+ fi
25
+ spec="$ws/spec.md"; seal="$ws/seal.md"
26
+ [ -f "$spec" ] || { printf 'check-acceptance: no spec.md in %s\n' "$ws" >&2; exit 5; }
27
+ [ -f "$seal" ] || { printf 'check-acceptance: no seal.md in %s — feature not sealed\n' "$ws" >&2; exit 5; }
28
+
29
+ # Print the body lines under an "## Acceptance Criteria" heading (case-insensitive
30
+ # on "criteria"), up to the next "## " heading. BSD/GNU awk safe (no IGNORECASE).
31
+ ac_section() { awk '/^##[[:space:]]+[Aa]cceptance [Cc]riteria/{s=1;next} /^##[[:space:]]/{s=0} s' "$1"; }
32
+
33
+ spec_body="$(ac_section "$spec")"
34
+ seal_body="$(ac_section "$seal")"
35
+
36
+ if [ -z "$spec_body" ]; then
37
+ printf 'check-acceptance: spec.md has no "## Acceptance criteria" section — nothing to grade\n' >&2
38
+ exit 1
39
+ fi
40
+
41
+ spec_ids="$(printf '%s\n' "$spec_body" | grep -oE '\[AC[0-9]+\]' | sort -u || true)"
42
+
43
+ if [ -n "$spec_ids" ]; then
44
+ # ID mode — each spec [ACn] must appear on a checked ([x]) line in seal.md.
45
+ seal_checked="$(printf '%s\n' "$seal_body" | grep -E '^[[:space:]]*-[[:space:]]*\[[xX]\]' | grep -oE '\[AC[0-9]+\]' | sort -u || true)"
46
+ missing=""
47
+ for id in $spec_ids; do
48
+ printf '%s\n' "$seal_checked" | grep -qxF "$id" || missing="$missing $id"
49
+ done
50
+ total=$(printf '%s\n' "$spec_ids" | grep -c . || true)
51
+ if [ -n "$missing" ]; then
52
+ printf 'check-acceptance: %d/%d criteria proven; UNPROVEN/UNCOVERED:%s (must be checked [x] in seal.md)\n' \
53
+ "$(( total - $(printf '%s' "$missing" | wc -w) ))" "$total" "$missing" >&2
54
+ exit 1
55
+ fi
56
+ printf 'check-acceptance: OK — all %d acceptance criteria (%s) proven + checked in seal.md\n' "$total" "$(printf '%s' "$spec_ids" | tr '\n' ' ')"
57
+ exit 0
58
+ fi
59
+
60
+ # Fallback (no ids) — count spec criteria vs seal checked/unchecked items.
61
+ spec_n=$(printf '%s\n' "$spec_body" | grep -cE '^[[:space:]]*-[[:space:]]' || true)
62
+ seal_total=$(printf '%s\n' "$seal_body" | grep -cE '^[[:space:]]*-[[:space:]]*\[[ xX]\]' || true)
63
+ seal_unchecked=$(printf '%s\n' "$seal_body" | grep -cE '^[[:space:]]*-[[:space:]]*\[[[:space:]]\]' || true)
64
+
65
+ if [ "$seal_total" -lt "$spec_n" ]; then
66
+ printf 'check-acceptance: seal.md lists %d acceptance items but spec.md has %d criteria — %d dropped. (Tag criteria [AC1].. to grade by id.)\n' \
67
+ "$seal_total" "$spec_n" "$(( spec_n - seal_total ))" >&2
68
+ exit 1
69
+ fi
70
+ if [ "$seal_unchecked" -gt 0 ]; then
71
+ printf 'check-acceptance: %d acceptance criterion(s) unchecked in seal.md (unproven). (Tag criteria [AC1].. to grade by id.)\n' "$seal_unchecked" >&2
72
+ exit 1
73
+ fi
74
+ printf 'check-acceptance: OK — %d acceptance items all checked in seal.md (no ids; add [AC1].. for id-level grading)\n' "$seal_total"
75
+ exit 0
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ # Close a shipped feature: archive its workspace and clear the ACTIVE cursor.
3
+ #
4
+ # Moves .devrites/work/<slug>/ -> .devrites/archive/<slug>/ (preserving every .md
5
+ # audit file) and clears .devrites/ACTIVE so the next /rite-spec starts clean. The
6
+ # audit trail is never deleted — only relocated.
7
+ #
8
+ # Usage: close-out.sh <slug> [devrites-dir]
9
+ # devrites-dir defaults to .devrites
10
+ #
11
+ # Exit codes:
12
+ # 0 archived (+ ACTIVE cleared if it pointed at <slug>)
13
+ # 4 bad args / workspace missing
14
+ # 5 archive destination already exists (refuse to clobber)
15
+ set -euo pipefail
16
+
17
+ slug="${1:-}"
18
+ dv="${2:-.devrites}"
19
+
20
+ if [ -z "$slug" ]; then
21
+ echo "close-out: usage: close-out.sh <slug> [devrites-dir]" >&2
22
+ exit 4
23
+ fi
24
+
25
+ work="$dv/work/$slug"
26
+ arch="$dv/archive/$slug"
27
+
28
+ if [ ! -d "$work" ]; then
29
+ echo "close-out: no workspace at $work" >&2
30
+ exit 4
31
+ fi
32
+ if [ -e "$arch" ]; then
33
+ echo "close-out: archive already exists at $arch — refusing to clobber" >&2
34
+ exit 5
35
+ fi
36
+
37
+ mkdir -p "$dv/archive"
38
+ mv "$work" "$arch"
39
+
40
+ # Clear ACTIVE only if it still points at the slug we just archived.
41
+ if [ -f "$dv/ACTIVE" ] && [ "$(tr -d '[:space:]' < "$dv/ACTIVE")" = "$slug" ]; then
42
+ : > "$dv/ACTIVE"
43
+ echo "close-out: archived $slug -> $arch and cleared ACTIVE"
44
+ else
45
+ echo "close-out: archived $slug -> $arch (ACTIVE pointed elsewhere — left as-is)"
46
+ fi
47
+ exit 0
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env python3
2
+ """DevRites conventions ledger — store + deterministic corroboration scorer.
3
+
4
+ The ledger is a project-scoped, LOCAL record (`.devrites/conventions.md`, gitignored)
5
+ of conventions a *sealed slice proved* about this codebase: build/test commands, naming /
6
+ layering / error-model idioms, recurring gotchas, where things live. `/rite-seal` promotes
7
+ to it on a GO; orient reads it so the wright stops re-deriving the same idioms every slice.
8
+
9
+ Two things live here (issue B1 modules M1 + M2):
10
+
11
+ M1 — corroboration scorer (`band`): a pure, deterministic function from corroboration
12
+ and contradiction counts to a 0.3–0.9 confidence band. The number is EARNED by how
13
+ many independent sealed slices proved the convention — never guessed by a model.
14
+
15
+ M2 — ledger store (`read` / `promote` / `contradict`): read/update `.devrites/conventions.md`,
16
+ a human-readable Markdown file with per-entry provenance.
17
+
18
+ Confidence never raises a convention's *authority*: a high-band entry is still untrusted
19
+ data, and a fresh observation of the live code always overrides it (enforced at orient,
20
+ issue B2 — not here).
21
+
22
+ Usage:
23
+ conventions.py band --corroborations C --contradictions K
24
+ conventions.py read [--root DIR]
25
+ conventions.py promote --key K --statement S --kind KIND --slug SLUG --evidence E [--date ISO] [--root DIR]
26
+ conventions.py contradict --key K --slug SLUG --evidence E [--date ISO] [--root DIR]
27
+
28
+ Exit codes: 0 ok; 2 usage error; 3 not-found (contradict on an unknown key).
29
+ """
30
+ import argparse
31
+ import datetime
32
+ import os
33
+ import sys
34
+
35
+ LEDGER_RELPATH = os.path.join(".devrites", "conventions.md")
36
+
37
+ HEADER = """# DevRites conventions ledger
38
+
39
+ Conventions proven by sealed slices on this project. **Local, gitignored, advisory.**
40
+ The confidence band is earned: it reflects how many independent sealed slices corroborated
41
+ the convention — it is not a model's guess. A high band is still untrusted data; a fresh
42
+ observation of the live code always wins over a stale entry here.
43
+ """
44
+
45
+
46
+ # --- M1: deterministic corroboration scorer -------------------------------
47
+
48
+ def band(corroborations, contradictions):
49
+ """Pure, total, deterministic. Return a 0.3–0.9 band, or None if retired.
50
+
51
+ First proof seeds mid-band; each further independent corroboration raises it
52
+ toward the 0.9 cap; each contradiction lowers it; net non-positive retires it.
53
+ """
54
+ c = int(corroborations)
55
+ k = int(contradictions)
56
+ if c - k <= 0:
57
+ return None # retired — contradictions caught up with the evidence
58
+ raw = 0.4 + 0.1 * c - 0.2 * k
59
+ return max(0.3, min(0.9, round(raw, 1)))
60
+
61
+
62
+ # --- M2: ledger store -----------------------------------------------------
63
+
64
+ def _ledger_path(root):
65
+ return os.path.join(root, LEDGER_RELPATH)
66
+
67
+
68
+ def _parse(text):
69
+ """Parse the ledger into {key: entry}. entry = dict with fields + proofs list."""
70
+ entries = {}
71
+ key = None
72
+ for line in text.splitlines():
73
+ if line.startswith("## "):
74
+ key = line[3:].strip()
75
+ entries[key] = {
76
+ "statement": "", "kind": "", "corroborations": 0,
77
+ "contradictions": 0, "status": "active", "proofs": [],
78
+ }
79
+ continue
80
+ if key is None:
81
+ continue
82
+ s = line.strip()
83
+ if s.startswith("- proof:"):
84
+ entries[key]["proofs"].append(s[len("- proof:"):].strip())
85
+ elif s.startswith("- ") and ":" in s:
86
+ fld, val = s[2:].split(":", 1)
87
+ fld, val = fld.strip(), val.strip()
88
+ if fld in ("corroborations", "contradictions"):
89
+ try:
90
+ entries[key][fld] = int(val)
91
+ except ValueError:
92
+ pass
93
+ elif fld in ("statement", "kind", "status"):
94
+ entries[key][fld] = val
95
+ return entries
96
+
97
+
98
+ def _serialize(entries):
99
+ out = [HEADER]
100
+ for key in sorted(entries):
101
+ e = entries[key]
102
+ b = band(e["corroborations"], e["contradictions"])
103
+ status = "retired" if b is None else "active"
104
+ e["status"] = status
105
+ band_str = "retired" if b is None else "%.2f" % b
106
+ out.append("## %s" % key)
107
+ out.append("- statement: %s" % e["statement"])
108
+ out.append("- kind: %s" % e["kind"])
109
+ out.append("- band: %s" % band_str)
110
+ out.append("- corroborations: %d" % e["corroborations"])
111
+ out.append("- contradictions: %d" % e["contradictions"])
112
+ out.append("- status: %s" % status)
113
+ for p in e["proofs"]:
114
+ out.append("- proof: %s" % p)
115
+ out.append("")
116
+ return "\n".join(out).rstrip() + "\n"
117
+
118
+
119
+ def _load(root):
120
+ path = _ledger_path(root)
121
+ if not os.path.isfile(path):
122
+ return {}
123
+ with open(path, "r", encoding="utf-8") as fh:
124
+ return _parse(fh.read())
125
+
126
+
127
+ def _save(root, entries):
128
+ path = _ledger_path(root)
129
+ os.makedirs(os.path.dirname(path), exist_ok=True)
130
+ with open(path, "w", encoding="utf-8") as fh:
131
+ fh.write(_serialize(entries))
132
+
133
+
134
+ def _today(date):
135
+ return date or datetime.date.today().isoformat()
136
+
137
+
138
+ def cmd_read(root):
139
+ path = _ledger_path(root)
140
+ if not os.path.isfile(path):
141
+ return 0
142
+ with open(path, "r", encoding="utf-8") as fh:
143
+ sys.stdout.write(fh.read())
144
+ return 0
145
+
146
+
147
+ def cmd_orient(root, min_band):
148
+ """Print a compact digest of ACTIVE conventions for the wright's ORIENT step.
149
+
150
+ These are priors, not law: high-band entries are followed unless the slice contract
151
+ says otherwise, and a fresh observation of the live code always overrides a stale one.
152
+ Retired entries are omitted. Empty output (exit 0) means no priors to apply.
153
+ """
154
+ entries = _load(root)
155
+ rows = []
156
+ for key, e in entries.items():
157
+ b = band(e["corroborations"], e["contradictions"])
158
+ if b is None or b < min_band:
159
+ continue
160
+ rows.append((b, key, e))
161
+ if not rows:
162
+ return 0
163
+ rows.sort(key=lambda r: (-r[0], r[1]))
164
+ print("# Conventions ledger — proven priors (live code always overrides)")
165
+ for b, key, e in rows:
166
+ kind = (" (%s)" % e["kind"]) if e["kind"] else ""
167
+ print("- [%.2f] %s%s: %s" % (b, key, kind, e["statement"]))
168
+ return 0
169
+
170
+
171
+ def cmd_promote(root, key, statement, kind, slug, evidence, date):
172
+ entries = _load(root)
173
+ e = entries.get(key)
174
+ if e is None:
175
+ e = {"statement": statement, "kind": kind, "corroborations": 0,
176
+ "contradictions": 0, "status": "active", "proofs": []}
177
+ entries[key] = e
178
+ else:
179
+ # keep the latest non-empty statement/kind
180
+ if statement:
181
+ e["statement"] = statement
182
+ if kind:
183
+ e["kind"] = kind
184
+ # Corroboration counts DISTINCT sealed slices — re-sealing the same slice is idempotent.
185
+ already = any(p.split(" ", 1)[0] == slug for p in e["proofs"])
186
+ if not already:
187
+ e["corroborations"] += 1
188
+ e["proofs"].append("%s %s — %s" % (slug, _today(date), evidence))
189
+ _save(root, entries)
190
+ b = band(e["corroborations"], e["contradictions"])
191
+ print("promoted '%s' → band %s (%d corroboration(s))"
192
+ % (key, "retired" if b is None else "%.2f" % b, e["corroborations"]))
193
+ return 0
194
+
195
+
196
+ def cmd_contradict(root, key, slug, evidence, date, drift_file):
197
+ entries = _load(root)
198
+ e = entries.get(key)
199
+ if e is None:
200
+ print("no such convention: '%s'" % key, file=sys.stderr)
201
+ return 3
202
+ e["contradictions"] += 1
203
+ e["proofs"].append("%s %s — CONTRADICTED: %s" % (slug, _today(date), evidence))
204
+ _save(root, entries)
205
+ b = band(e["corroborations"], e["contradictions"])
206
+ state = "retired" if b is None else "band %.2f" % b
207
+ when = _today(date)
208
+ # The orchestrator (rite-build) is the single writer of .devrites bookkeeping; it passes
209
+ # --drift-file so the contradiction is logged to the feature's drift.md atomically here.
210
+ if drift_file:
211
+ os.makedirs(os.path.dirname(os.path.abspath(drift_file)), exist_ok=True)
212
+ with open(drift_file, "a", encoding="utf-8") as fh:
213
+ fh.write(
214
+ "\n## convention-drift: %s\n"
215
+ "- slice: %s\n- contradicted_by: %s\n- now: %s\n- at: %s\n"
216
+ % (key, slug, evidence, state, when))
217
+ print("DRIFT: convention '%s' contradicted by %s — now %s" % (key, slug, state))
218
+ return 0
219
+
220
+
221
+ def main(argv):
222
+ p = argparse.ArgumentParser(prog="conventions.py", add_help=True)
223
+ sub = p.add_subparsers(dest="cmd")
224
+
225
+ pb = sub.add_parser("band")
226
+ pb.add_argument("--corroborations", type=int, required=True)
227
+ pb.add_argument("--contradictions", type=int, default=0)
228
+
229
+ pr = sub.add_parser("read")
230
+ pr.add_argument("--root", default=".")
231
+
232
+ po = sub.add_parser("orient")
233
+ po.add_argument("--root", default=".")
234
+ po.add_argument("--min-band", type=float, default=0.0)
235
+
236
+ pp = sub.add_parser("promote")
237
+ pp.add_argument("--key", required=True)
238
+ pp.add_argument("--statement", required=True)
239
+ pp.add_argument("--kind", required=True)
240
+ pp.add_argument("--slug", required=True)
241
+ pp.add_argument("--evidence", required=True)
242
+ pp.add_argument("--date", default=None)
243
+ pp.add_argument("--root", default=".")
244
+
245
+ pc = sub.add_parser("contradict")
246
+ pc.add_argument("--key", required=True)
247
+ pc.add_argument("--slug", required=True)
248
+ pc.add_argument("--evidence", required=True)
249
+ pc.add_argument("--date", default=None)
250
+ pc.add_argument("--root", default=".")
251
+ pc.add_argument("--drift-file", default=None)
252
+
253
+ args = p.parse_args(argv[1:])
254
+ if args.cmd == "band":
255
+ b = band(args.corroborations, args.contradictions)
256
+ print("retired" if b is None else "%.2f" % b)
257
+ return 0
258
+ if args.cmd == "read":
259
+ return cmd_read(args.root)
260
+ if args.cmd == "orient":
261
+ return cmd_orient(args.root, args.min_band)
262
+ if args.cmd == "promote":
263
+ return cmd_promote(args.root, args.key, args.statement, args.kind,
264
+ args.slug, args.evidence, args.date)
265
+ if args.cmd == "contradict":
266
+ return cmd_contradict(args.root, args.key, args.slug, args.evidence, args.date,
267
+ args.drift_file)
268
+ p.print_usage(sys.stderr)
269
+ return 2
270
+
271
+
272
+ if __name__ == "__main__":
273
+ sys.exit(main(sys.argv))