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,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))
|