feat: Dorian Claim Warrants for Claude Code (v1.3.0)#24
Conversation
`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>
|
Warning Review limit reached
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughVersion 1.3.0 adds a Changesclaude-code install-claim-warrants feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
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 valueSlight 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 valueConsider validating
transcript_pathbefore opening.
payload.get("transcript_path")is used directly withopen(). 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
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (32)
CHANGELOG.mdREADME.mdaction/README.mddocs/BENCHMARK_CURRENT.mddocs/CLAIM_WARRANTS_VS_AGENT_RECEIPTS.mddocs/CLAUDE_CODE_DORIAN_WORKFLOW.mddocs/DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.mddocs/POSITIONING_2026_06_27.mddocs/SECURITY_AND_SAFE_RUNNERS.mddocs/changes/claude-code-claim-warrants.claims.jsondocs/changes/claude-code-claim-warrants.mddocs/changes/claude-code-claim-warrants.md.warrantpyproject.tomlsrc/dorian/__init__.pysrc/dorian/claude_code.pysrc/dorian/cli.pysrc/dorian/commands.pysrc/dorian/templates/claude_code/dorian-claim-warrants/README.mdsrc/dorian/templates/claude_code/dorian-claim-warrants/SKILL.mdsrc/dorian/templates/claude_code/dorian-claim-warrants/examples/bad-claims.mdsrc/dorian/templates/claude_code/dorian-claim-warrants/examples/final-message-example.mdsrc/dorian/templates/claude_code/dorian-claim-warrants/examples/good-claims.jsonsrc/dorian/templates/claude_code/dorian-claim-warrants/reference/checker-selection.mdsrc/dorian/templates/claude_code/dorian-claim-warrants/reference/safety-boundary.mdsrc/dorian/templates/claude_code/dorian-claim-warrants/templates/change-note.mdsrc/dorian/templates/claude_code/dorian-claim-warrants/templates/claims.jsonsrc/dorian/templates/claude_code/hooks/dorian_claim_warrants_stop.pysrc/dorian/templates/claude_code/settings/settings.dorian-claim-warrants.review-first.example.jsonsrc/dorian/templates/claude_code/settings/settings.dorian-claim-warrants.trusted-local.example.jsontests/test_claim_warrants_hook.pytests/test_claude_code_scaffold.pytests/test_packaging.py
| > **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)). | ||
|
|
There was a problem hiding this comment.
🎯 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.
| > **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.
| 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) |
There was a problem hiding this comment.
🗄️ 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.
| 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.
| 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) |
There was a problem hiding this comment.
🎯 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.
| 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.
| 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, |
There was a problem hiding this comment.
🩺 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.
| 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.
| 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 |
There was a problem hiding this comment.
🎯 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.
| 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.
| # 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 |
There was a problem hiding this comment.
🗄️ 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.
What this is
A one-command Claude Code integration:
dorian claude-code install-claim-warrantsscaffolds 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 — whichdorian verifythen 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— mirrorsinit.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.claude-code install-claim-warrantssubcommand:--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
additionalContext, neverdecision:block(loop-safe), no-ops onstop_hook_active/non-Stop/no-git/no-relevant-change/existing-artifacts/skip-markers, fails open, writes nothing, runs only read-onlygit status. Not enabled by scaffolding.Docs
docs/DORIAN_CLAIM_WARRANTS_CLAUDE_CODE_SKILL.md(guide) anddocs/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.mdretitled to "claim warrants"; positioning + CHANGELOG.Release
__init__, uv.lock,action@v1.3.0pins, 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.tests/test_packaging.pybuilds a real wheel, installs it into a clean venv, and scaffolds the skill from package data.verify --strength-gate=fail→ drift →revalidateREVOKED (exit 4).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
claude-code install-claim-warrantsworkflow to scaffold a Claude Code skill in a project.Documentation