Skip to content

feat: Dorian Claim Warrants for Claude Code (v1.3.0)#24

Merged
ajaysurya1221 merged 5 commits into
mainfrom
feature/claude-code-claim-warrants
Jun 27, 2026
Merged

feat: Dorian Claim Warrants for Claude Code (v1.3.0)#24
ajaysurya1221 merged 5 commits into
mainfrom
feature/claude-code-claim-warrants

Conversation

@ajaysurya1221

@ajaysurya1221 ajaysurya1221 commented Jun 27, 2026

Copy link
Copy Markdown
Owner

What this is

A one-command Claude Code integration: dorian claude-code install-claim-warrants scaffolds a project-local skill (/dorian-claim-warrants) that drafts Dorian claim warrants for the checkable facts a coding agent says it changed — config values, signatures/defaults, constants, file/symbol references — which dorian verify then proves deterministically. The model only drafts; no model is added to the verification path, the core stays zero-dependency, and the warrant format / checker grammar / exit codes / fold policy / security posture are unchanged.

This productizes the validated best-use-case wedge ("claim warrants for what your coding agent said changed") and resolves the adoption blocker (hand-writing good claims.json).

What changed

CLI / scaffolder

  • src/dorian/claude_code.py — mirrors init.py (build_plan/apply, an explicit package-data manifest, _ensure_within + per-pid atomic writes). Writes files only; idempotent; never overwrites without --force; repo-contained.
  • Nested claude-code install-claim-warrants subcommand: --with-hook, --no-hook, --settings-only, --dry-run, --force, --print-next-steps, --target.

Skill / templates (ship as wheel package data via importlib.resources)

  • SKILL.md (invoked as /dorian-claim-warrants), bundle README, examples (good/bad claims, final-message walkthrough), change-note + claims.json skeletons, checker-selection + safety-boundary references, review-first + trusted-local settings examples.

Hook

  • Opt-in, reminder-only Stop hook (stdlib only): returns additionalContext, never decision:block (loop-safe), no-ops on stop_hook_active/non-Stop/no-git/no-relevant-change/existing-artifacts/skip-markers, fails open, writes nothing, runs only read-only git status. Not enabled by scaffolding.

Docs

  • docs/DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md (guide) and docs/CLAIM_WARRANTS_VS_AGENT_RECEIPTS.md (how it differs from / complements the Agent Receipts action-audit protocol). README section + command-surface entry; CLAUDE_CODE_DORIAN_WORKFLOW.md retitled to "claim warrants"; positioning + CHANGELOG.

Release

  • Bump 1.2.0 → 1.3.0 (pyproject, __init__, uv.lock, action@v1.3.0 pins, benchmark stamp). Dogfood warrant seals 11/11 load-bearing facts under --strength-gate=fail --binding-gate=warn.

Evidence

  • uv run pytest (full suite incl. slow) — green; ruff check + ruff format --check — clean.
  • 46 new tests (scaffolder, hook, packaging). tests/test_packaging.py builds a real wheel, installs it into a clean venv, and scaffolds the skill from package data.
  • End-to-end test: scaffold → verify --strength-gate=fail → drift → revalidate REVOKED (exit 4).
  • Independent fresh-context judge: ACCEPT (no must-fix); naming/no-go-branding, hook safety, draft-vs-prove discipline, zero-dep core, and wheel-ships-templates all independently re-verified.

Boundary (unchanged)

Not a sandbox (C4 pytest:/C5 shell: execute code — trusted repos only), not an LLM judge, not a whole-summary verifier, not "Agent Receipts". The two axes stay distinct: binding = when re-checked, strength = whether falsifiable; weak = low confidence, not a false claim.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new claude-code install-claim-warrants workflow to scaffold a Claude Code skill in a project.
    • Introduced an optional reminder-only Stop hook for Claude Code.
    • Added a new release entry and updated versioned documentation and examples to the latest release.
  • Documentation

    • Expanded guidance for the new workflow, verification steps, trust boundaries, and installation examples.
    • Updated terminology across docs to use “claim warrants” consistently.

ajay-dev-2112 and others added 4 commits June 28, 2026 00:53
`dorian claude-code install-claim-warrants` scaffolds a project-local Claude Code
skill at `.claude/skills/dorian-claim-warrants/` (invoked as /dorian-claim-warrants)
that DRAFTS Dorian claim warrants for the checkable facts in a coding agent's change
summary — config values, signatures/defaults, constants, file/symbol references —
which `dorian verify` then proves deterministically. The model only drafts; no model
is added to the verification path.

- New module src/dorian/claude_code.py mirrors init.py (build_plan/apply, an explicit
  package-data manifest, _ensure_within + per-pid atomic writes). Writes files only;
  idempotent; never overwrites without --force; repo-contained.
- Nested `claude-code install-claim-warrants` subcommand with --with-hook, --no-hook,
  --settings-only, --dry-run, --force, --print-next-steps, --target.
- Packaged templates (ship as wheel package data via importlib.resources): SKILL.md,
  bundle README, examples (good/bad claims, final-message walkthrough), change-note +
  claims.json skeletons, checker-selection + safety-boundary references, review-first
  and trusted-local settings examples.
- Opt-in, reminder-only Stop hook (stdlib only): returns additionalContext, never
  decision:block (loop-safe), no-ops on stop_hook_active/non-Stop/no-git/no-relevant-
  change/existing-artifacts/skip-markers, fails open, writes nothing, runs only
  read-only git status. Not enabled by scaffolding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- test_claude_code_scaffold.py: manifest/package-data integrity, dry-run writes
  nothing, default vs --with-hook vs --settings-only, idempotency, --force overwrites
  only scaffolded files, subdirectory --target, non-git warning, JSON output, skill
  frontmatter + required safety phrases, valid example/skeleton JSON, no-go branding
  absent, and an end-to-end scaffold -> verify (--strength-gate=fail) -> drift ->
  revalidate REVOKED (exit 4).
- test_claim_warrants_hook.py: loads the shipped hook template and covers the pure
  helpers, the should_remind gate (non-Stop, stop_hook_active, skip markers, no-git,
  no-relevant-change, existing artifacts, no-change message), transcript parsing,
  real-git changed_paths, and the subprocess entry point (emits additionalContext +
  writes nothing; silent on non-Stop / invalid JSON / env skip markers).
- test_packaging.py: a slow test that builds the wheel, installs it into a clean venv,
  and scaffolds the skill from package data — proving the templates ship and resolve.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md: the integration guide (what it is/is
  not, install, invoke, review-first + trusted-local flows, the opt-in Stop hook,
  checker selection, good vs bad claims, trust boundary, troubleshooting, commit +
  later revalidation).
- CLAIM_WARRANTS_VS_AGENT_RECEIPTS.md: how Dorian claim warrants (deterministic
  claim-truth that revokes on drift) differ from and complement Agent Receipts /
  Obsigna (an Ed25519-signed, hash-chained W3C-VC audit trail of agent actions).
  "receipt" is an explanatory metaphor, never the brand.
- README: a one-command "Claude Code claim warrants" section + a command-surface entry.
- CLAUDE_CODE_DORIAN_WORKFLOW.md: retitled to "claim warrants"; points at the new skill.
- POSITIONING_2026_06_27.md: v1.3.0 naming update + "claim warrants" tagline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bump 1.2.0 -> 1.3.0 (pyproject, __init__, uv.lock) and the action@v1.3.0 pins
  (README, action/README, SECURITY_AND_SAFE_RUNNERS, CLAUDE_CODE_DORIAN_WORKFLOW).
- BENCHMARK_CURRENT.md re-stamped to 1.3.0: the claim-warrants feature is a
  CLI/packaging/docs addition touching no checker/binding/fold code, so the figures
  stand unchanged (suites last executed at v1.2.0).
- CHANGELOG [1.3.0] documents the additive, non-breaking feature (core stays
  zero-dependency; no model in the verification path).
- Dogfood: docs/changes/claude-code-claim-warrants.{md,claims.json,md.warrant} seals
  11/11 load-bearing facts (CLI surface, packaged templates, hook stop_hook_active +
  additionalContext, the not-a-sandbox / model-drafts-Dorian-proves / Agent-Receipts
  docs, zero deps, strength-gate=fail) under --strength-gate=fail --binding-gate=warn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ajaysurya1221, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 50 minutes and 48 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c6cecef-efde-41b3-b857-f95ab5798e53

📥 Commits

Reviewing files that changed from the base of the PR and between 1d326c5 and 1bf8780.

📒 Files selected for processing (1)
  • docs/releases/v1.3.0.md
📝 Walkthrough

Walkthrough

Version 1.3.0 adds a dorian claude-code install-claim-warrants CLI subcommand that scaffolds a project-local Claude Code skill into .claude/, ships packaged skill/settings/hook templates, implements an opt-in reminder-only Stop hook, adds supporting documentation and a "claim warrants" terminology update, and includes full test coverage plus a self-describing claim warrant artifact.

Changes

claude-code install-claim-warrants feature

Layer / File(s) Summary
Scaffold module: dataclasses, plan, apply
src/dorian/claude_code.py
Defines GROUPS, DEFAULT_GROUPS, destination constants, ScaffoldFile/ScaffoldPlan/ApplyResult dataclasses, find_repo_root, template loading via importlib.resources, path-safety guard, atomic write using temp+os.replace, build_plan, and apply with force/dry-run semantics.
CLI parser and command dispatch
src/dorian/cli.py, src/dorian/commands.py
Adds the claude-code install-claim-warrants argparse subcommand with all option flags; wires cmd_claude_code dispatch, _install_groups mutual-exclusion, _cmd_install_claim_warrants (JSON and human-readable output), and next-steps/trust-boundary printers.
Reminder-only Stop hook
src/dorian/templates/claude_code/hooks/dorian_claim_warrants_stop.py
Implements the opt-in Claude Code Stop hook: suppression marker/suffix/phrase constants, load_input, skip gating, read-only git status via changed_paths, has_relevant_change, already_has_warrant_artifacts, last_assistant_message (direct field + transcript backscan), should_remind decision gate, reminder_output, and fail-open main.
Packaged skill, settings, and example templates
src/dorian/templates/claude_code/dorian-claim-warrants/..., src/dorian/templates/claude_code/settings/...
Adds all scaffolded template files: SKILL.md with frontmatter and step-by-step instructions, README.md, change-note.md and claims.json templates, bad-claims.md, good-claims.json, final-message-example.md, checker-selection.md, safety-boundary.md, and two settings permission examples.
Tests: hook, scaffold CLI, and packaging
tests/test_claim_warrants_hook.py, tests/test_claude_code_scaffold.py, tests/test_packaging.py
Adds unit tests for all hook helpers and subprocess entrypoint behavior; CLI behavior tests for all install modes (dry-run, force, settings-only, json, print-next-steps); manifest integrity, content safety-phrase, and JSON validity checks; end-to-end seal/revalidate/REVOKED cycle; and packaged entrypoint smoke test.
Docs, warrant artifacts, version bump, and changelog
CHANGELOG.md, README.md, action/README.md, pyproject.toml, src/dorian/__init__.py, docs/..., docs/changes/claude-code-claim-warrants.*
Bumps version to 1.3.0 in pyproject.toml and __init__.py; updates action version references from v1.2.0 to v1.3.0 across all READMEs and security docs; adds new docs (DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md, CLAIM_WARRANTS_VS_AGENT_RECEIPTS.md); updates POSITIONING_2026_06_27.md branding to "claim warrants"; adds self-describing claude-code-claim-warrants.md, .claims.json, and .md.warrant artifacts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ajaysurya1221/dorian#8: Adds CLI subcommands in src/dorian/cli.py and src/dorian/commands.py at the same argparse/dispatch layer that this PR extends with the claude-code command group.

Poem

🐰 A warrant for each claim I've made,
No receipt — a checkable grade!
One command to scaffold the skill,
The hook just nudges, never kills.
dorian verify seals the deal — hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a Claude Code integration for Dorian claim warrants in v1.3.0.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/claude-code-claim-warrants

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
src/dorian/templates/claude_code/dorian-claim-warrants/SKILL.md (1)

38-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Slight style improvement: reduce adverb repetition.

"Include only statements a deterministic checker can falsify" — the word "only" appears twice in close proximity ("Include only" and "only statements"). Consider rephrasing for smoother reading:

- Include only statements a deterministic checker can falsify:
+ Include statements a deterministic checker can falsify:

Or alternatively: "Only include statements a deterministic checker can falsify:"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dorian/templates/claude_code/dorian-claim-warrants/SKILL.md` at line 38,
The sentence in SKILL.md repeats “only” too closely and reads awkwardly.
Rephrase the instruction in the affected markdown text to remove the repetition
while preserving the meaning, using the existing “Include only statements a
deterministic checker can falsify” wording as the anchor and adjusting it to a
smoother variant such as “Only include statements a deterministic checker can
falsify:”
src/dorian/templates/claude_code/hooks/dorian_claim_warrants_stop.py (1)

161-162: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Consider validating transcript_path before opening.

payload.get("transcript_path") is used directly with open(). While this path is provided by the Claude Code runtime (trusted source), defense in depth would validate it resolves within the project directory. However, the hook is read-only (errors='replace') and fail-open (any exception returns ""), so the practical risk is minimal.

If you want to add validation:

         path = payload.get("transcript_path")
         if not isinstance(path, str) or not path:
             return ""
+        # Defense in depth: ensure transcript_path is within project bounds
+        project_dir = env.get("CLAUDE_PROJECT_DIR", ".")
+        resolved = os.path.normpath(os.path.join(project_dir, path))
+        if not resolved.startswith(os.path.normpath(project_dir)):
+            return ""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dorian/templates/claude_code/hooks/dorian_claim_warrants_stop.py` around
lines 161 - 162, Validate transcript_path before it reaches the open() call in
dorian_claim_warrants_stop.py by resolving it and confirming it stays within the
project directory; if it is outside, treat it as invalid and return an empty
string like the existing fail-open path. Keep the change local to the transcript
loading flow around the path handling and the with open(...) block so the hook
remains read-only and resilient.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 311-321: The README command example is broken because an inline
code span in the quoted text crosses a newline, so the tail of the command
renders as plain text. Update the markdown around the `dorian verify` example to
use a fenced code block or keep the full command on one line, and make sure the
blockquote formatting still preserves the example cleanly.

In `@src/dorian/claude_code.py`:
- Around line 225-233: The apply() loop in claude_code.py trusts the cached
f.exists value from build_plan(), which can let newly created files be
overwritten without force. Re-check the current filesystem state for each plan
file just before _atomic_write in apply(), using the existing target path and
_ensure_within guard, and only skip or write based on the live existence check
so the no-clobber behavior is preserved.

In `@src/dorian/commands.py`:
- Around line 1052-1072: The hook_installed flag is being derived from
plan.files, which reflects the planned selection rather than the actual
post-command result. Update the install flow in commands.py so hook_installed is
computed from the final state in result (for example, whether the hook file was
actually created or overwritten), and use that same value in both the JSON
output and _print_install_summary to avoid reporting hook registration during
dry-run cases like --dry-run --with-hook.

In `@tests/test_claim_warrants_hook.py`:
- Around line 237-251: The repository contents check in the Stop hook test is
only comparing basenames, which can miss path-level changes when different files
share the same name. Update the assertion in the test around _run_hook and
repo.rglob("*") to compare full repo-relative paths instead of p.name, so the
before/after snapshot detects create/delete changes even when basenames collide.
- Around line 212-220: The _run_hook helper only filters out DORIAN_* variables,
so non-DORIAN skip markers can still leak into subprocesses and silence
reminder-path tests. Update the env construction in _run_hook to explicitly
strip the hook skip markers honored by the hook, including
NO_DORIAN_CLAIM_WARRANTS and NO_DORIAN_RECEIPTS, alongside the existing DORIAN_*
cleanup.

In `@tests/test_claude_code_scaffold.py`:
- Around line 318-325: The revalidate flow in the end-to-end test only asserts
the exit code, so it can miss a bug where the warrant is not persisted as
revoked. Update the test around main(..., "revalidate", "--since", "HEAD~1") to
also re-read docs/changes/bump.md.warrant after the call and assert its state
now folds to REVOKED, using the existing repo setup and warrant file path in
test_claude_code_scaffold.py.

---

Nitpick comments:
In `@src/dorian/templates/claude_code/dorian-claim-warrants/SKILL.md`:
- Line 38: The sentence in SKILL.md repeats “only” too closely and reads
awkwardly. Rephrase the instruction in the affected markdown text to remove the
repetition while preserving the meaning, using the existing “Include only
statements a deterministic checker can falsify” wording as the anchor and
adjusting it to a smoother variant such as “Only include statements a
deterministic checker can falsify:”

In `@src/dorian/templates/claude_code/hooks/dorian_claim_warrants_stop.py`:
- Around line 161-162: Validate transcript_path before it reaches the open()
call in dorian_claim_warrants_stop.py by resolving it and confirming it stays
within the project directory; if it is outside, treat it as invalid and return
an empty string like the existing fail-open path. Keep the change local to the
transcript loading flow around the path handling and the with open(...) block so
the hook remains read-only and resilient.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fe4209c7-3336-4052-a1f8-8bb54cef13d2

📥 Commits

Reviewing files that changed from the base of the PR and between 629d52a and 1d326c5.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (32)
  • CHANGELOG.md
  • README.md
  • action/README.md
  • docs/BENCHMARK_CURRENT.md
  • docs/CLAIM_WARRANTS_VS_AGENT_RECEIPTS.md
  • docs/CLAUDE_CODE_DORIAN_WORKFLOW.md
  • docs/DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md
  • docs/POSITIONING_2026_06_27.md
  • docs/SECURITY_AND_SAFE_RUNNERS.md
  • docs/changes/claude-code-claim-warrants.claims.json
  • docs/changes/claude-code-claim-warrants.md
  • docs/changes/claude-code-claim-warrants.md.warrant
  • pyproject.toml
  • src/dorian/__init__.py
  • src/dorian/claude_code.py
  • src/dorian/cli.py
  • src/dorian/commands.py
  • src/dorian/templates/claude_code/dorian-claim-warrants/README.md
  • src/dorian/templates/claude_code/dorian-claim-warrants/SKILL.md
  • src/dorian/templates/claude_code/dorian-claim-warrants/examples/bad-claims.md
  • src/dorian/templates/claude_code/dorian-claim-warrants/examples/final-message-example.md
  • src/dorian/templates/claude_code/dorian-claim-warrants/examples/good-claims.json
  • src/dorian/templates/claude_code/dorian-claim-warrants/reference/checker-selection.md
  • src/dorian/templates/claude_code/dorian-claim-warrants/reference/safety-boundary.md
  • src/dorian/templates/claude_code/dorian-claim-warrants/templates/change-note.md
  • src/dorian/templates/claude_code/dorian-claim-warrants/templates/claims.json
  • src/dorian/templates/claude_code/hooks/dorian_claim_warrants_stop.py
  • src/dorian/templates/claude_code/settings/settings.dorian-claim-warrants.review-first.example.json
  • src/dorian/templates/claude_code/settings/settings.dorian-claim-warrants.trusted-local.example.json
  • tests/test_claim_warrants_hook.py
  • tests/test_claude_code_scaffold.py
  • tests/test_packaging.py

Comment thread README.md
Comment on lines +311 to +321
> **One command: `dorian claude-code install-claim-warrants`.** In a trusted repo this scaffolds a
> project-local Claude Code skill — invoke **`/dorian-claim-warrants`** after a change — that drafts the
> change note + `claims.json` for the checkable facts your agent claimed, then prints the verify command:
> `dorian verify docs/changes/<slug>.md --claims docs/changes/<slug>.claims.json --strength-gate=fail
> --binding-gate=warn`. The **model only drafts; `dorian verify` proves** it deterministically and
> token-free. Later, `dorian revalidate --since <base>` REVOKEs a claim the code drifted away from. Add
> `--with-hook` for an opt-in, reminder-only Stop hook. Not a sandbox — trusted repos only. Guide:
> [`docs/DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md`](docs/DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md)
> (and how it differs from Agent Receipts:
> [`docs/CLAIM_WARRANTS_VS_AGENT_RECEIPTS.md`](docs/CLAIM_WARRANTS_VS_AGENT_RECEIPTS.md)).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix broken inline code span across line break.

The command on Line 314-315 uses single backticks (inline code span) but spans a line break inside a blockquote. In standard markdown, inline code cannot contain newlines — the backtick pair ends at the newline, so --binding-gate=warn renders as plain text rather than code. Use a fenced code block or keep the command on one line.

📝 Proposed fix

Replace the inline backticks with a fenced code block inside the blockquote:

 > `dorian verify docs/changes/<slug>.md --claims docs/changes/<slug>.claims.json --strength-gate=fail
 > --binding-gate=warn`. The **model only drafts; `dorian verify` proves** it deterministically and
 > token-free. Later, `dorian revalidate --since <base>` REVOKEs a claim the code drifted away from. Add
 > `--with-hook` for an opt-in, reminder-only Stop hook. Not a sandbox — trusted repos only. Guide:
 > ```bash
 > dorian verify docs/changes/<slug>.md \
 >   --claims docs/changes/<slug>.claims.json \
 >   --strength-gate=fail \
 >   --binding-gate=warn
 > ```
 > The **model only drafts; `dorian verify` proves** it deterministically and
 > token-free. Later, `dorian revalidate --since <base>` REVOKEs a claim the code drifted away from. Add
 > `--with-hook` for an opt-in, reminder-only Stop hook. Not a sandbox — trusted repos only. Guide:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> **One command: `dorian claude-code install-claim-warrants`.** In a trusted repo this scaffolds a
> project-local Claude Code skill — invoke **`/dorian-claim-warrants`** after a change — that drafts the
> change note + `claims.json` for the checkable facts your agent claimed, then prints the verify command:
> `dorian verify docs/changes/<slug>.md --claims docs/changes/<slug>.claims.json --strength-gate=fail
> --binding-gate=warn`. The **model only drafts; `dorian verify` proves** it deterministically and
> token-free. Later, `dorian revalidate --since <base>` REVOKEs a claim the code drifted away from. Add
> `--with-hook` for an opt-in, reminder-only Stop hook. Not a sandbox — trusted repos only. Guide:
> [`docs/DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md`](docs/DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md)
> (and how it differs from Agent Receipts:
> [`docs/CLAIM_WARRANTS_VS_AGENT_RECEIPTS.md`](docs/CLAIM_WARRANTS_VS_AGENT_RECEIPTS.md)).
> **One command: `dorian claude-code install-claim-warrants`.** In a trusted repo this scaffolds a
> project-local Claude Code skill — invoke **`/dorian-claim-warrants`** after a change — that drafts the
> change note + `claims.json` for the checkable facts your agent claimed, then prints the verify command:
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 311 - 321, The README command example is broken
because an inline code span in the quoted text crosses a newline, so the tail of
the command renders as plain text. Update the markdown around the `dorian
verify` example to use a fenced code block or keep the full command on one line,
and make sure the blockquote formatting still preserves the example cleanly.

Comment thread src/dorian/claude_code.py
Comment on lines +225 to +233
for f in plan.files:
target = plan.repo_root / f.path
_ensure_within(plan.repo_root, target)
if f.exists and not force:
skipped.append(f.path)
continue
if not dry_run:
_atomic_write(target, f.content)
(overwritten if f.exists else created).append(f.path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Re-check file existence in apply() before writing.

f.exists is captured during build_plan(), then trusted here. If a file appears between planning and execution, this path will still overwrite it without --force, which breaks the no-clobber guarantee and can lose local edits.

Suggested fix
     for f in plan.files:
         target = plan.repo_root / f.path
         _ensure_within(plan.repo_root, target)
-        if f.exists and not force:
+        target_exists = target.exists()
+        if target_exists and not force:
             skipped.append(f.path)
             continue
         if not dry_run:
             _atomic_write(target, f.content)
-        (overwritten if f.exists else created).append(f.path)
+        (overwritten if target_exists else created).append(f.path)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for f in plan.files:
target = plan.repo_root / f.path
_ensure_within(plan.repo_root, target)
if f.exists and not force:
skipped.append(f.path)
continue
if not dry_run:
_atomic_write(target, f.content)
(overwritten if f.exists else created).append(f.path)
for f in plan.files:
target = plan.repo_root / f.path
_ensure_within(plan.repo_root, target)
target_exists = target.exists()
if target_exists and not force:
skipped.append(f.path)
continue
if not dry_run:
_atomic_write(target, f.content)
(overwritten if target_exists else created).append(f.path)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dorian/claude_code.py` around lines 225 - 233, The apply() loop in
claude_code.py trusts the cached f.exists value from build_plan(), which can let
newly created files be overwritten without force. Re-check the current
filesystem state for each plan file just before _atomic_write in apply(), using
the existing target path and _ensure_within guard, and only skip or write based
on the live existence check so the no-clobber behavior is preserved.

Comment thread src/dorian/commands.py
Comment on lines +1052 to +1072
hook_installed = any(f.group == "hook" for f in plan.files)
if args.json:
print(
json.dumps(
{
"repo": str(plan.repo_root),
"is_git": plan.is_git,
"dry_run": args.dry_run,
"created": list(result.created),
"overwritten": list(result.overwritten),
"skipped": list(result.skipped),
"warnings": list(result.warnings),
"hook_installed": hook_installed,
"next_steps": list(claude_code.NEXT_STEPS),
"trust_boundary": claude_code.TRUST_BOUNDARY,
},
indent=2,
)
)
else:
_print_install_summary(plan, result, dry_run=args.dry_run, hook_installed=hook_installed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Derive hook_installed from the post-command state, not the plan.

This is currently “hook selected”, not “hook installed”. On --dry-run --with-hook, JSON will report hook_installed: true and the text path will print registration guidance even though no hook file was written.

Suggested fix
-    hook_installed = any(f.group == "hook" for f in plan.files)
+    hook_file = next((f for f in plan.files if f.group == "hook"), None)
+    hook_installed = bool(hook_file and (hook_file.exists or not args.dry_run))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hook_installed = any(f.group == "hook" for f in plan.files)
if args.json:
print(
json.dumps(
{
"repo": str(plan.repo_root),
"is_git": plan.is_git,
"dry_run": args.dry_run,
"created": list(result.created),
"overwritten": list(result.overwritten),
"skipped": list(result.skipped),
"warnings": list(result.warnings),
"hook_installed": hook_installed,
"next_steps": list(claude_code.NEXT_STEPS),
"trust_boundary": claude_code.TRUST_BOUNDARY,
},
indent=2,
)
)
else:
_print_install_summary(plan, result, dry_run=args.dry_run, hook_installed=hook_installed)
hook_file = next((f for f in plan.files if f.group == "hook"), None)
hook_installed = bool(hook_file and (hook_file.exists or not args.dry_run))
if args.json:
print(
json.dumps(
{
"repo": str(plan.repo_root),
"is_git": plan.is_git,
"dry_run": args.dry_run,
"created": list(result.created),
"overwritten": list(result.overwritten),
"skipped": list(result.skipped),
"warnings": list(result.warnings),
"hook_installed": hook_installed,
"next_steps": list(claude_code.NEXT_STEPS),
"trust_boundary": claude_code.TRUST_BOUNDARY,
},
indent=2,
)
)
else:
_print_install_summary(plan, result, dry_run=args.dry_run, hook_installed=hook_installed)
🧰 Tools
🪛 ast-grep (0.44.0)

[info] 1054-1068: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"repo": str(plan.repo_root),
"is_git": plan.is_git,
"dry_run": args.dry_run,
"created": list(result.created),
"overwritten": list(result.overwritten),
"skipped": list(result.skipped),
"warnings": list(result.warnings),
"hook_installed": hook_installed,
"next_steps": list(claude_code.NEXT_STEPS),
"trust_boundary": claude_code.TRUST_BOUNDARY,
},
indent=2,
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dorian/commands.py` around lines 1052 - 1072, The hook_installed flag is
being derived from plan.files, which reflects the planned selection rather than
the actual post-command result. Update the install flow in commands.py so
hook_installed is computed from the final state in result (for example, whether
the hook file was actually created or overwritten), and use that same value in
both the JSON output and _print_install_summary to avoid reporting hook
registration during dry-run cases like --dry-run --with-hook.

Comment on lines +212 to +220
def _run_hook(payload: dict, cwd: Path) -> subprocess.CompletedProcess:
return subprocess.run(
[sys.executable, str(HOOK_SRC)],
input=json.dumps(payload),
capture_output=True,
text=True,
cwd=cwd,
env={k: v for k, v in os.environ.items() if not k.startswith("DORIAN_")},
timeout=30,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Strip all hook skip markers from the subprocess environment.

_run_hook() only removes DORIAN_*, but the hook also honors non-DORIAN_ markers like NO_DORIAN_CLAIM_WARRANTS / NO_DORIAN_RECEIPTS. If either is present in the parent environment, the reminder-path tests become spuriously silent.

Suggested fix
 def _run_hook(payload: dict, cwd: Path) -> subprocess.CompletedProcess:
+    blocked = set(hook.SKIP_MARKERS)
     return subprocess.run(
         [sys.executable, str(HOOK_SRC)],
         input=json.dumps(payload),
         capture_output=True,
         text=True,
         cwd=cwd,
-        env={k: v for k, v in os.environ.items() if not k.startswith("DORIAN_")},
+        env={
+            k: v
+            for k, v in os.environ.items()
+            if not k.startswith("DORIAN_") and k not in blocked
+        },
         timeout=30,
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _run_hook(payload: dict, cwd: Path) -> subprocess.CompletedProcess:
return subprocess.run(
[sys.executable, str(HOOK_SRC)],
input=json.dumps(payload),
capture_output=True,
text=True,
cwd=cwd,
env={k: v for k, v in os.environ.items() if not k.startswith("DORIAN_")},
timeout=30,
def _run_hook(payload: dict, cwd: Path) -> subprocess.CompletedProcess:
blocked = set(hook.SKIP_MARKERS)
return subprocess.run(
[sys.executable, str(HOOK_SRC)],
input=json.dumps(payload),
capture_output=True,
text=True,
cwd=cwd,
env={
k: v
for k, v in os.environ.items()
if not k.startswith("DORIAN_") and k not in blocked
},
timeout=30,
🧰 Tools
🪛 ast-grep (0.44.0)

[info] 214-214: use jsonify instead of json.dumps for JSON output
Context: json.dumps(payload)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[error] 212-220: Command coming from incoming request
Context: subprocess.run(
[sys.executable, str(HOOK_SRC)],
input=json.dumps(payload),
capture_output=True,
text=True,
cwd=cwd,
env={k: v for k, v in os.environ.items() if not k.startswith("DORIAN_")},
timeout=30,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_claim_warrants_hook.py` around lines 212 - 220, The _run_hook
helper only filters out DORIAN_* variables, so non-DORIAN skip markers can still
leak into subprocesses and silence reminder-path tests. Update the env
construction in _run_hook to explicitly strip the hook skip markers honored by
the hook, including NO_DORIAN_CLAIM_WARRANTS and NO_DORIAN_RECEIPTS, alongside
the existing DORIAN_* cleanup.

Comment on lines +237 to +251
before = {p.name for p in repo.rglob("*")}
r = _run_hook(
{
"hook_event_name": "Stop",
"stop_hook_active": False,
"cwd": str(repo),
"transcript_path": str(transcript),
},
cwd=repo,
)
assert r.returncode == 0, r.stderr
out = json.loads(r.stdout)
assert out["hookSpecificOutput"]["additionalContext"]
# the hook wrote nothing into the repo
assert {p.name for p in repo.rglob("*")} == before

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Compare repo-relative paths, not just basenames.

Using p.name collapses many .git entries, so this can still pass after a create/delete if the new path reuses an existing basename elsewhere in the repo.

Suggested fix
-    before = {p.name for p in repo.rglob("*")}
+    before = {p.relative_to(repo).as_posix() for p in repo.rglob("*")}
@@
-    assert {p.name for p in repo.rglob("*")} == before
+    assert {p.relative_to(repo).as_posix() for p in repo.rglob("*")} == before
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
before = {p.name for p in repo.rglob("*")}
r = _run_hook(
{
"hook_event_name": "Stop",
"stop_hook_active": False,
"cwd": str(repo),
"transcript_path": str(transcript),
},
cwd=repo,
)
assert r.returncode == 0, r.stderr
out = json.loads(r.stdout)
assert out["hookSpecificOutput"]["additionalContext"]
# the hook wrote nothing into the repo
assert {p.name for p in repo.rglob("*")} == before
before = {p.relative_to(repo).as_posix() for p in repo.rglob("*")}
r = _run_hook(
{
"hook_event_name": "Stop",
"stop_hook_active": False,
"cwd": str(repo),
"transcript_path": str(transcript),
},
cwd=repo,
)
assert r.returncode == 0, r.stderr
out = json.loads(r.stdout)
assert out["hookSpecificOutput"]["additionalContext"]
# the hook wrote nothing into the repo
assert {p.relative_to(repo).as_posix() for p in repo.rglob("*")} == before
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_claim_warrants_hook.py` around lines 237 - 251, The repository
contents check in the Stop hook test is only comparing basenames, which can miss
path-level changes when different files share the same name. Update the
assertion in the test around _run_hook and repo.rglob("*") to compare full
repo-relative paths instead of p.name, so the before/after snapshot detects
create/delete changes even when basenames collide.

Comment on lines +318 to +325
# drift: the version changes; the sealed claim is now false
(repo / "pyproject.toml").write_text(
'[project]\nname = "demo"\nversion = "1.5.0"\n', encoding="utf-8"
)
_git(repo, "add", "-A")
_git(repo, "commit", "-qm", "bump again — breaks the sealed claim")

assert main(["--repo", str(repo), "revalidate", "--since", "HEAD~1"]) == 4 # REVOKED

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Assert the warrant actually folds to REVOKED.

This end-to-end test only checks exit code 4, so a regression that reports revocation without updating docs/changes/bump.md.warrant would still pass. Re-read the warrant after revalidate and assert the persisted state changed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_claude_code_scaffold.py` around lines 318 - 325, The revalidate
flow in the end-to-end test only asserts the exit code, so it can miss a bug
where the warrant is not persisted as revoked. Update the test around main(...,
"revalidate", "--since", "HEAD~1") to also re-read docs/changes/bump.md.warrant
after the call and assert its state now folds to REVOKED, using the existing
repo setup and warrant file path in test_claude_code_scaffold.py.

@ajaysurya1221 ajaysurya1221 merged commit 4e47a3d into main Jun 27, 2026
5 checks passed
@ajaysurya1221 ajaysurya1221 deleted the feature/claude-code-claim-warrants branch June 27, 2026 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants