Skip to content

feat: add Dorian Loop Guard (loop preflight steering + skill)#25

Merged
ajaysurya1221 merged 1 commit into
mainfrom
feature/dorian-loop-guard
Jul 1, 2026
Merged

feat: add Dorian Loop Guard (loop preflight steering + skill)#25
ajaysurya1221 merged 1 commit into
mainfrom
feature/dorian-loop-guard

Conversation

@ajaysurya1221

@ajaysurya1221 ajaysurya1221 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Deterministic, token-free CONTINUE/REPAIR/ESCALATE steering for AI coding loops, built as a thin classifier over revalidate (no model at check time). Adds 'dorian loop preflight|prompt|install', the /dorian-loop-guard Claude Code skill templates, alignment/positioning docs, a README section, and a dogfood warrant (10/10). Purely additive: warrant schema, checker grammar, exit codes, fold policy, and security posture unchanged; core stays zero-dependency; REVOKED stays a steering signal, ERRORED fail-closed evidence.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Added a new dorian loop workflow with preflight, prompt, and install commands.
    • Introduced loop-guard guidance for AI coding loops, including decision outcomes like continue, repair, and escalate.
    • Added installable templates, examples, and workflow support for loop-guard setup.
  • Documentation

    • Expanded the README and added detailed guides covering loop behavior, safety boundaries, and usage examples.
  • Bug Fixes

    • Improved verification and repair tracking so loop decisions respond consistently to changing project state.

Deterministic, token-free CONTINUE/REPAIR/ESCALATE steering for AI coding loops, built as a thin classifier over revalidate (no model at check time). Adds 'dorian loop preflight|prompt|install', the /dorian-loop-guard Claude Code skill templates, alignment/positioning docs, a README section, and a dogfood warrant (10/10). Purely additive: warrant schema, checker grammar, exit codes, fold policy, and security posture unchanged; core stays zero-dependency; REVOKED stays a steering signal, ERRORED fail-closed evidence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the dorian loop subcommand family (preflight, prompt, install) implementing a deterministic, token-free AI coding loop steering layer built on dorian revalidate. Introduces src/dorian/loop.py with decision logic, packet dataclasses, and renderers; extends cli.py/commands.py with full CLI wiring; adds Claude Code skill templates; and ships documentation, tests, and a self-describing warrant sidecar.

Changes

Dorian Loop Guard

Layer / File(s) Summary
loop.py: decision engine, dataclasses, and renderers
src/dorian/loop.py
Introduces BrokenClaim, LoopDecision dataclasses; preflight(...) entrypoint calling revalidate, enriching broken records via _collect_broken, applying precedence-based _decide, assigning per-claim next steps, and returning a full packet; read_repair_attempts state-file parser; render_json/text/md/prompt renderers; LOOP_MANIFEST scaffolding constants and TRUST_BOUNDARY.
CLI wiring: build_plan extension, parser, and command handlers
src/dorian/claude_code.py, src/dorian/cli.py, src/dorian/commands.py
build_plan gains an optional manifest= parameter for reuse by the loop installer; _add_loop_steer_flags/_add_loop_parser register the loop command with preflight, prompt, and install subcommands; cmd_loop dispatcher and dedicated handler functions (_run_loop_preflight, _cmd_loop_preflight, _cmd_loop_prompt, _cmd_loop_install) implement all three subcommand behaviors including exit-code mapping and install summary printing.
Skill templates
src/dorian/templates/claude_code/dorian-loop-guard/...
Ships the full .claude/skills/dorian-loop-guard/ bundle: SKILL.md, README.md, reference/loop-decisions.md, reference/safety-boundary.md, and example templates (LOOP.md, STATE.md, dorian-loop.yml, loop-run-log.md).
Tests
tests/test_loop_preflight.py, tests/test_loop_prompt.py, tests/test_loop_install.py, tests/test_loop_packaging.py, tests/test_loop_guard_dogfood.py, tests/test_packaging.py
Comprehensive coverage: decision/policy/cap/scope/schema validation in test_loop_preflight.py, prompt rendering in test_loop_prompt.py, scaffold idempotency/overwrite/dry-run in test_loop_install.py, manifest template readability and grouping constraints in test_loop_packaging.py, end-to-end verify+preflight dogfood with real artifacts, and wheel-install packaging verification.
Documentation and warrant artifacts
docs/DORIAN_LOOP_GUARD.md, docs/LOOP_ENGINEERING_ALIGNMENT.md, docs/POSITIONING_LOOP_GUARD_2026_06_28.md, docs/CLAUDE_CODE_DORIAN_WORKFLOW.md, README.md, docs/changes/dorian-loop-guard.*, CHANGELOG.md
Adds full Loop Guard documentation (decision table, policies, safety boundary, GitHub Actions integration, positioning), updates existing workflow/README docs, and ships a self-describing change note with a sealed warrant sidecar.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ajaysurya1221/dorian#6: The new loop.py::preflight calls into revalidate using checker_source CLI plumbing introduced in this earlier V1 strengthening work.
  • ajaysurya1221/dorian#24: Introduced src/dorian/claude_code.py with the original build_plan function, which this PR extends with the manifest= parameter to support the loop-guard installer.

Poem

🐇 Hop! Before the loop runs free,
I check each claim deterministically.
CONTINUE, REPAIR, or ESCALATE—
No model needed to steer straight.
Token-free truth, warrant-sealed tight,
The loop guard keeps the code loop right! 🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 matches the main change: adding Dorian Loop Guard with loop preflight steering and skill scaffolding.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/dorian-loop-guard

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.

@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: 8

🧹 Nitpick comments (1)
tests/test_packaging.py (1)

173-175: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the full --with-state bundle here.

This wheel-level test only checks LOOP.md, but the state group also includes STATE.md and loop-run-log.md in tests/test_loop_install.py, Lines 74-76. If either package-data entry drops from the wheel, the source-tree install tests still pass and this packaging test will miss it.

Suggested diff
     assert (repo / ".claude/skills/dorian-loop-guard/SKILL.md").is_file()
     assert (repo / "LOOP.md").is_file()
+    assert (repo / "STATE.md").is_file()
+    assert (repo / "loop-run-log.md").is_file()
     assert (repo / ".github/workflows/dorian-loop.yml").is_file()
🤖 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_packaging.py` around lines 173 - 175, The packaging test currently
verifies only part of the `--with-state` bundle, so it can miss missing wheel
assets. Update `test_packaging` to assert the full state bundle by checking for
`STATE.md` and `loop-run-log.md` alongside `LOOP.md`, matching the bundle
covered in `test_loop_install` and the related state install checks. Use the
existing `repo` path assertions in this test so the wheel-level coverage fails
if any package-data entry for the state group drops from the wheel.
🤖 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 `@src/dorian/cli.py`:
- Around line 352-354: `--repair-attempts` currently uses a falsy default so `0`
is treated the same as “not provided,” which breaks precedence in the CLI
preflight path. Update the argument handling in `src/dorian/cli.py` and
`_run_loop_preflight()` in `src/dorian/commands.py` so omission is represented
by `None`, then only fall back to `state_file` when `args.repair_attempts` is
`None`. Keep `args.repair_attempts` as the explicit value when passed, including
`0`, so the loop uses the user-supplied override instead of stale persisted
state.

In `@src/dorian/commands.py`:
- Around line 1221-1232: Guard the saved-packet path in `commands.py` before
calling `loop.render_prompt(packet)`: after `json.loads(data)` succeeds,
validate that the parsed packet contains the fields `render_prompt` expects (for
example `decision` and `broken_claims`) and treat missing/invalid structure as a
usage error. If validation fails, print a clear `dorian loop prompt: invalid
--from-json packet` message to stderr and return `EXIT_USAGE` instead of letting
`loop.render_prompt()` raise a traceback.

In `@src/dorian/loop.py`:
- Around line 239-257: The unreadable-claim fallback in loop.py currently
creates a BrokenClaim with load_bearing set but still marks it as non-sensitive
and in scope, which can let assist return repair too easily. Update the
BrokenClaim construction in this claim None branch to fail closed by marking the
record escalation-worthy, or add an explicit unreadable-metadata decision cause
that prevents a normal repair path; use the BrokenClaim fields and the
surrounding claim None handling to locate the fix.
- Around line 591-597: The render_prompt function currently indexes the header
map directly with d["decision"], so arbitrary --from-json input can raise
KeyError instead of a usage-style error. Update render_prompt in the loop module
to validate the saved packet before lookup by checking that d is a dict and that
decision is one of the expected values used in the header mapping, and have
commands.py handle invalid packets by returning a clear error path rather than
letting the exception traceback.
- Around line 51-62: The sensitive path filter in SENSITIVE_GLOBS misses
root-level directories because the current patterns only match nested paths.
Update SENSITIVE_GLOBS in loop.py to include root equivalents for auth,
security, migrations, and secrets alongside the existing "*/auth/*",
"*/security/*", "*/migrations/*", and "*/secrets/*" entries so root files like
auth/login.py and secrets/prod.env are also excluded.

In `@src/dorian/templates/claude_code/dorian-loop-guard/SKILL.md`:
- Around line 43-46: The `--with-state` docs are incomplete because they
reference `--state-file STATE.md.json` but the `state` bundle in the SKILL docs
only scaffolds `LOOP.md`, `STATE.md`, and `loop-run-log.md`. Update the relevant
guidance in `SKILL.md` so the `--with-state`/`--state-file` flow clearly
includes creating or emitting the JSON state file expected by
`loop.read_repair_attempts()`, and make sure the `decision`, `reason`,
`broken_claims[]`, and `loop_instruction` packet fields are documented as part
of that state path.

In `@src/dorian/templates/claude_code/dorian-loop-guard/templates/LOOP.md`:
- Around line 17-24: The verification example in LOOP.md uses a state file name
that does not match the scaffolded artifacts, so update the loop preflight
command in the LOOP.md template to reference the actual state document used by
the bundled workflow. Keep the guidance aligned with the symbols and files
already mentioned in the template, especially dorian loop install --with-state,
LOOP.md, and STATE.md, so the example is consistent with the generated state-cap
tracking setup.

In `@src/dorian/templates/claude_code/dorian-loop-guard/templates/STATE.md`:
- Around line 31-38: The state bundle is missing the machine-readable
`STATE.json` seed, so preflight can’t reliably persist `repair_attempts` through
`dorian loop preflight --state-file STATE.json`. Add a `STATE.json` template
alongside the existing `STATE.md` guidance and include it in the `state` install
group so fresh installs ship the counter file by default. Keep the existing loop
guard flow in `src/dorian/loop.py` aligned with this seeded state file.

---

Nitpick comments:
In `@tests/test_packaging.py`:
- Around line 173-175: The packaging test currently verifies only part of the
`--with-state` bundle, so it can miss missing wheel assets. Update
`test_packaging` to assert the full state bundle by checking for `STATE.md` and
`loop-run-log.md` alongside `LOOP.md`, matching the bundle covered in
`test_loop_install` and the related state install checks. Use the existing
`repo` path assertions in this test so the wheel-level coverage fails if any
package-data entry for the state group drops from the wheel.
🪄 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: 7534dbd0-feba-4e6b-934d-56792c27005a

📥 Commits

Reviewing files that changed from the base of the PR and between 4e47a3d and 48c9e5e.

📒 Files selected for processing (27)
  • CHANGELOG.md
  • README.md
  • docs/CLAUDE_CODE_DORIAN_WORKFLOW.md
  • docs/DORIAN_LOOP_GUARD.md
  • docs/LOOP_ENGINEERING_ALIGNMENT.md
  • docs/POSITIONING_LOOP_GUARD_2026_06_28.md
  • docs/changes/dorian-loop-guard.claims.json
  • docs/changes/dorian-loop-guard.md
  • docs/changes/dorian-loop-guard.md.warrant
  • src/dorian/claude_code.py
  • src/dorian/cli.py
  • src/dorian/commands.py
  • src/dorian/loop.py
  • src/dorian/templates/claude_code/dorian-loop-guard/README.md
  • src/dorian/templates/claude_code/dorian-loop-guard/SKILL.md
  • src/dorian/templates/claude_code/dorian-loop-guard/reference/loop-decisions.md
  • src/dorian/templates/claude_code/dorian-loop-guard/reference/safety-boundary.md
  • src/dorian/templates/claude_code/dorian-loop-guard/templates/LOOP.md
  • src/dorian/templates/claude_code/dorian-loop-guard/templates/STATE.md
  • src/dorian/templates/claude_code/dorian-loop-guard/templates/dorian-loop.yml
  • src/dorian/templates/claude_code/dorian-loop-guard/templates/loop-run-log.md
  • tests/test_loop_guard_dogfood.py
  • tests/test_loop_install.py
  • tests/test_loop_packaging.py
  • tests/test_loop_preflight.py
  • tests/test_loop_prompt.py
  • tests/test_packaging.py

Comment thread src/dorian/cli.py
Comment on lines +352 to +354
"--repair-attempts",
type=int,
default=0,

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 | 🟠 Major | ⚡ Quick win

Make --repair-attempts 0 distinguishable from “flag omitted”.

With default=0, argparse cannot tell whether the user explicitly passed --repair-attempts 0 or omitted the flag. That breaks the precedence promised in src/dorian/commands.py (--repair-attempts should win over --state-file), because _run_loop_preflight() currently falls back to the state file on any falsy value. A loop that resets its counter to zero can therefore still inherit a stale persisted count and escalate incorrectly.

💡 Proposed fix
     parser.add_argument(
         "--repair-attempts",
         type=int,
-        default=0,
+        default=None,
         help="prior repair attempts for the current broken set (the loop threads this in;"
         " reaching --max-repairs escalates as an infinite-fix-loop guard)",
     )
# src/dorian/commands.py
repair_attempts = 0 if args.repair_attempts is None else args.repair_attempts
if args.state_file and args.repair_attempts is None:
    repair_attempts, note = loop.read_repair_attempts(Path(args.state_file))
🤖 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/cli.py` around lines 352 - 354, `--repair-attempts` currently uses
a falsy default so `0` is treated the same as “not provided,” which breaks
precedence in the CLI preflight path. Update the argument handling in
`src/dorian/cli.py` and `_run_loop_preflight()` in `src/dorian/commands.py` so
omission is represented by `None`, then only fall back to `state_file` when
`args.repair_attempts` is `None`. Keep `args.repair_attempts` as the explicit
value when passed, including `0`, so the loop uses the user-supplied override
instead of stale persisted state.

Comment thread src/dorian/commands.py
Comment on lines +1221 to +1232
if args.from_json: # render a saved packet instead of re-running preflight
try:
data = (
sys.stdin.read()
if args.from_json == "-"
else Path(args.from_json).read_text(encoding="utf-8")
)
packet = json.loads(data)
except (OSError, ValueError) as exc:
print(f"dorian loop prompt: cannot read --from-json: {exc}", file=sys.stderr)
return EXIT_USAGE
print(loop.render_prompt(packet), end="")

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

Guard malformed --from-json packets before rendering.

json.loads() failures are handled, but a saved packet missing keys like decision or broken_claims will still raise inside loop.render_prompt() and terminate the command with a traceback instead of EXIT_USAGE.

Suggested fix
     if args.from_json:  # render a saved packet instead of re-running preflight
         try:
             data = (
                 sys.stdin.read()
                 if args.from_json == "-"
                 else Path(args.from_json).read_text(encoding="utf-8")
             )
             packet = json.loads(data)
-        except (OSError, ValueError) as exc:
+            rendered = loop.render_prompt(packet)
+        except (OSError, ValueError, KeyError, TypeError) as exc:
             print(f"dorian loop prompt: cannot read --from-json: {exc}", file=sys.stderr)
             return EXIT_USAGE
-        print(loop.render_prompt(packet), end="")
+        print(rendered, end="")
         return EXIT_OK
📝 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
if args.from_json: # render a saved packet instead of re-running preflight
try:
data = (
sys.stdin.read()
if args.from_json == "-"
else Path(args.from_json).read_text(encoding="utf-8")
)
packet = json.loads(data)
except (OSError, ValueError) as exc:
print(f"dorian loop prompt: cannot read --from-json: {exc}", file=sys.stderr)
return EXIT_USAGE
print(loop.render_prompt(packet), end="")
if args.from_json: # render a saved packet instead of re-running preflight
try:
data = (
sys.stdin.read()
if args.from_json == "-"
else Path(args.from_json).read_text(encoding="utf-8")
)
packet = json.loads(data)
rendered = loop.render_prompt(packet)
except (OSError, ValueError, KeyError, TypeError) as exc:
print(f"dorian loop prompt: cannot read --from-json: {exc}", file=sys.stderr)
return EXIT_USAGE
print(rendered, end="")
🤖 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 1221 - 1232, Guard the saved-packet path
in `commands.py` before calling `loop.render_prompt(packet)`: after
`json.loads(data)` succeeds, validate that the parsed packet contains the fields
`render_prompt` expects (for example `decision` and `broken_claims`) and treat
missing/invalid structure as a usage error. If validation fails, print a clear
`dorian loop prompt: invalid --from-json packet` message to stderr and return
`EXIT_USAGE` instead of letting `loop.render_prompt()` raise a traceback.

Comment thread src/dorian/loop.py
Comment on lines +51 to +62
SENSITIVE_GLOBS = (
"*secret*",
"*.pem",
"*.key",
"*.env",
"*.env.*",
".github/workflows/*",
"*Dockerfile*",
"*/secrets/*",
"*/auth/*",
"*/security/*",
"*/migrations/*",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import fnmatch
for path, pattern in [
    ("auth/login.py", "*/auth/*"),
    ("security/policy.py", "*/security/*"),
    ("migrations/001.sql", "*/migrations/*"),
    ("secrets/prod.env", "*/secrets/*"),
]:
    print(path, pattern, fnmatch.fnmatch(path, pattern))
PY

Repository: ajaysurya1221/dorian

Length of output: 300


🏁 Script executed:

#!/bin/bash
sed -n '1,140p' src/dorian/loop.py

Repository: ajaysurya1221/dorian

Length of output: 5460


Cover root-level sensitive directories. */auth/*, */security/*, */migrations/*, and */secrets/* miss root paths like auth/login.py, security/policy.py, migrations/001.sql, and secrets/prod.env, so those can still be repaired autonomously. Add root-level globs alongside the nested ones.

🤖 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/loop.py` around lines 51 - 62, The sensitive path filter in
SENSITIVE_GLOBS misses root-level directories because the current patterns only
match nested paths. Update SENSITIVE_GLOBS in loop.py to include root
equivalents for auth, security, migrations, and secrets alongside the existing
"*/auth/*", "*/security/*", "*/migrations/*", and "*/secrets/*" entries so root
files like auth/login.py and secrets/prod.env are also excluded.

Comment thread src/dorian/loop.py
Comment on lines +239 to +257
if claim is None:
notes.append(f"{cid}: claim unreadable from {artifact}.warrant; using bare record")
out.append(
BrokenClaim(
artifact=artifact,
warrant=wid,
claim_id=cid,
text="",
kind="",
load_bearing=True, # fail-safe: treat unknown as load-bearing
verdict=verdict,
trust_state=states.get(wid, ""),
checker=ctype or "",
evidence=evidence,
paths=(),
sensitive=False,
in_scope=True,
suggested_next_step=step,
)

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

Fail closed when claim metadata is unreadable.

This fallback treats an unreadable claim as load-bearing, but leaves it sensitive=False and in_scope=True, so assist can return repair without any bound paths. Mark it as escalation-worthy, or add an explicit unreadable-metadata decision cause.

🤖 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/loop.py` around lines 239 - 257, The unreadable-claim fallback in
loop.py currently creates a BrokenClaim with load_bearing set but still marks it
as non-sensitive and in scope, which can let assist return repair too easily.
Update the BrokenClaim construction in this claim None branch to fail closed by
marking the record escalation-worthy, or add an explicit unreadable-metadata
decision cause that prevents a normal repair path; use the BrokenClaim fields
and the surrounding claim None handling to locate the fix.

Comment thread src/dorian/loop.py
Comment on lines +591 to +597
def render_prompt(d: dict) -> str:
"""A compact markdown prompt for the next coding-agent iteration."""
header = {
"continue": "Dorian Loop Guard says CONTINUE.",
"repair": "Dorian Loop Guard says REPAIR before continuing.",
"escalate": "Dorian Loop Guard says ESCALATE — stop autonomous edits.",
}[d["decision"]]

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 | 🟡 Minor | ⚡ Quick win

Validate saved prompt packets before indexing.

commands.py passes arbitrary --from-json data into render_prompt; a valid JSON object with a missing or unknown decision raises KeyError and can traceback instead of returning a usage error.

🤖 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/loop.py` around lines 591 - 597, The render_prompt function
currently indexes the header map directly with d["decision"], so arbitrary
--from-json input can raise KeyError instead of a usage-style error. Update
render_prompt in the loop module to validate the saved packet before lookup by
checking that d is a dict and that decision is one of the expected values used
in the header mapping, and have commands.py handle invalid packets by returning
a clear error path rather than letting the exception traceback.

Comment on lines +43 to +46
Useful flags: `--scope 'src/**'` (bound the loop's lane — required by `unattended`),
`--max-repairs N` and `--repair-attempts N` (infinite-fix-loop guard), `--state-file
STATE.md.json` (read the prior attempt count), `--deny-path 'infra/**'` (extra sensitive
paths). The packet's `decision`, `reason`, `broken_claims[]`, and `loop_instruction`

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 | 🟠 Major | ⚡ Quick win

--with-state does not scaffold the file this flag expects.

The docs point users at --state-file STATE.md.json, but the installed "state" bundle only creates LOOP.md, STATE.md, and loop-run-log.md, while loop.read_repair_attempts() accepts JSON only. As written, the advertised repair-attempt threading path is incomplete unless users invent their own sidecar file, so the cap stays inactive by default.

🤖 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-loop-guard/SKILL.md` around lines 43
- 46, The `--with-state` docs are incomplete because they reference
`--state-file STATE.md.json` but the `state` bundle in the SKILL docs only
scaffolds `LOOP.md`, `STATE.md`, and `loop-run-log.md`. Update the relevant
guidance in `SKILL.md` so the `--with-state`/`--state-file` flow clearly
includes creating or emitting the JSON state file expected by
`loop.read_repair_attempts()`, and make sure the `decision`, `reason`,
`broken_claims[]`, and `loop_instruction` packet fields are documented as part
of that state path.

Comment on lines +17 to +24
## Verification commands
```bash
# before each iteration: deterministic steering signal
dorian loop preflight --since <base> --policy unattended --scope 'src/**' \
--max-repairs 3 --state-file STATE.json --format json
# after a change: seal new warrants (model drafts, dorian proves)
dorian verify docs/changes/<slug>.md --claims docs/changes/<slug>.claims.json \
--strength-gate=fail --binding-gate=warn

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

The bundled state-file example does not match the scaffolded files.

This example tells users to pass --state-file STATE.json, but dorian loop install --with-state only scaffolds LOOP.md, STATE.md, and loop-run-log.md, and the reference guide also says the counter lives in STATE.md. As shipped, someone following the bundle verbatim has no compatible JSON state file for cap tracking, so repeated repairs can silently lose the infinite-fix guard.

🤖 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-loop-guard/templates/LOOP.md` around
lines 17 - 24, The verification example in LOOP.md uses a state file name that
does not match the scaffolded artifacts, so update the loop preflight command in
the LOOP.md template to reference the actual state document used by the bundled
workflow. Keep the guidance aligned with the symbols and files already mentioned
in the template, especially dorian loop install --with-state, LOOP.md, and
STATE.md, so the example is consistent with the generated state-cap tracking
setup.

Comment on lines +31 to +38
Machine-read attempt count for `dorian loop preflight --state-file STATE.json`:

```json
{ "repair_attempts": 0 }
```
<!-- Keep the JSON above in STATE.json. Increment repair_attempts after each repair
attempt on the current broken set; reset to 0 once the loop returns to continue.
Reaching --max-repairs makes preflight escalate (infinite-fix-loop guard). -->

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

Ship the STATE.json seed with the state bundle.

This template tells users to run preflight with --state-file STATE.json, and src/dorian/loop.py:510-523 only persists the repair cap through that JSON file. But the shipped --with-state scaffold currently creates LOOP.md, STATE.md, and loop-run-log.md only, so a fresh install has no machine-readable counter unless the user notices this footnote and creates it manually. That silently falls back to repair_attempts=0 and weakens the infinite-fix-loop guard.

Proposed fix
+{
+  "repair_attempts": 0
+}

Add this as a STATE.json template and include it in the state install group alongside STATE.md.

🤖 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-loop-guard/templates/STATE.md` around
lines 31 - 38, The state bundle is missing the machine-readable `STATE.json`
seed, so preflight can’t reliably persist `repair_attempts` through `dorian loop
preflight --state-file STATE.json`. Add a `STATE.json` template alongside the
existing `STATE.md` guidance and include it in the `state` install group so
fresh installs ship the counter file by default. Keep the existing loop guard
flow in `src/dorian/loop.py` aligned with this seeded state file.

@ajaysurya1221 ajaysurya1221 merged commit 0042825 into main Jul 1, 2026
5 checks passed
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