Skip to content

park --resolve / plan risk --resolve: the close-out for parked vagueness (0.20.0, #45 #55 #57 #60) - #81

Merged
OriNachum merged 23 commits into
mainfrom
park-resolve
Jul 17, 2026
Merged

park --resolve / plan risk --resolve: the close-out for parked vagueness (0.20.0, #45 #55 #57 #60)#81
OriNachum merged 23 commits into
mainfrom
park-resolve

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

What ships

The close-out for parked vagueness, on both engines (0.20.0):

  • devague park --resolve <vN> --decision "<text>" [--claim <cN>] — a decided blocking park resolves through a CLI move: the item stays on the record with its resolution text (and optionally the deciding claim via the new resolution_claim_id), drops out of the convergence gate and parked_items, and renders under a ## Resolved vagueness section in exported specs. A bare --resolve without --decision is refused (evidence-bearing close-out); unknown and already-resolved ids are refused fail-closed.
  • devague plan risk --resolve <rN> --decision "<text>" — the plan-side twin for blocking PlanRisks.
  • Frame SCHEMA_VERSION and PLAN_SCHEMA_VERSION bump 2 → 3; older binaries fail closed on v3 artifacts with the existing upgrade hint, v2 artifacts load with defaults.
  • The blocking-vagueness / blocking-risk convergence hints now name the executable resolve move — previously they recommended "re-park it as non-blocking", which only ever appended a second item while the first kept blocking (Add a move to reclassify/resolve a parked vagueness (unblocks /think convergence after a dependency is downgraded) #45's acceptance bar, confirmed live in Add a move to reclassify/resolve a parked vagueness (unblocks /think convergence after a dependency is downgraded) #45's lobes-cli comment).
  • Teaching surfaces (devague learn, docs/llm-guidance.md, the /think skill, docs/spec-contract.md) teach the resolve close-out wherever park is taught.

Closes #45, closes #55, closes #57, closes #60 — four independent reports of the same gap in five weeks, across colleague, lobes-cli, league-of-agents, and devague's own dogfooding. (#60's task-text half was already shipped as plan amend in 0.18.0/#68; only its vagueness half was live.)

How it was built

Full seven-leg dogfood: /scope/think/challenge/spec-to-plan/assign-to-workforce (9 tasks fanned out to subagent worktrees across 3 waves, TDD-gated merges) → this PR. Artifacts: docs/specs/2026-07-17-resolve-parked-vagueness.md, docs/plans/2026-07-17-resolve-parked-vagueness.md (frame/plan state committed under .devague/).

The /challenge pass (rigorous — schema-migration + data-loss signals) caught the two secondary consumers of open vagueness (parked_items in both gates, plan deliverables open items) that the initial spec missed; they're covered here (c18).

Verification

  • 689 tests pass (was 616 at baseline); coverage 98.1% (gate ≥ 95%); flake8 / black / isort / bandit / markdownlint clean.
  • New e2e test drives issue no unpark/resolve move: a parked unknown_blocking blocks convergence forever #57's exact repro through the real CLI on both engines: park blocking → capture decision → resolve → converge passes → export renders the resolution. Zero .devague hand-edits anywhere.
  • Boundary held: the package diff introduces no subprocess/network/LLM usage (git diff main...HEAD -- devague/ grep is empty).

Note for the reviewer

The pre-existing resolve-parked-vagueness branch (commit b5c9e7c, an earlier session) specs a different shape — a new flat resolve verb with --kind re-kinding, widened to scope entries. This session's frame decisions (q1: extend park --resolve, no new verb; q3: --decision required) supersede it; that branch was left untouched and can be deleted if you agree it's superseded.

  • devague (Claude)

OriNachum and others added 21 commits July 17, 2026 13:28
Frame + plan state and exported artifacts, produced via scope -> think ->
challenge -> spec-to-plan. Supersedes the resolve-parked-vagueness branch's
flat-verb spec: q1 decided park --resolve (no new verb), q2 pulled in the
plan-side twin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UyFmS7dRyBj87DpgmDCu9u
Add Vagueness.resolved/resolution (default False/""), Frame.resolve_vagueness
(fails closed on unknown or already-resolved ids), and bump SCHEMA_VERSION to
3. set_status stays untouched — v-ids stay out of confirm/reject (decision
c11). A v2 frame JSON without the new keys loads with defaults via the
existing Vagueness(**v) splat in from_dict (dataclass defaults apply), and
store.load still fails closed above the current version.

Also fixes two pre-existing hardcoded schema_version pins (tests/test_store.py,
tests/test_frame_schema_v2.py) that this legitimate bump made false, so the
full suite stays green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UyFmS7dRyBj87DpgmDCu9u
Mirror t1's frame-side Vagueness.resolved/resolution exactly on the plan
engine: PlanRisk gains resolved: bool = False and resolution: str = "",
Plan.resolve_risk(rid, resolution) raises ValueError on an unknown or
already-resolved id, and PLAN_SCHEMA_VERSION bumps 2 -> 3. PlanRisk(**r)
in from_dict already defaults missing keys via dataclass field defaults,
so a v2 plan JSON without the new keys loads unchanged.

Covers: c13, h9
…solve

_missing_open_uncertainty no longer counts a resolved unknown_blocking
vagueness as a blocker, and _parked_items drops any resolved item (not
just unknown_blocking) from the tracked-open list — a resolved item is
closed, not open. suggest_move's blocking-vagueness branch now emits the
executable devague park --resolve VID --decision "<the decision>" move
in place of the old dead-end "capture+confirm the answer, or re-park it
as non-blocking" hint, which could never actually clear the blocker.

Covers c3, h3, c18, h14. Method-only change to devague/convergence.py and
its tests; no renderer, CLI, plan_convergence.py, or .devague state touched.
Mirrors t5's park --resolve semantics on the risk subcommand: positional
text and --kind become optional at the parser level so --resolve can omit
both, the create path refuses a missing --kind (or text) in the handler,
and --resolve routes through Plan.resolve_risk with the same fail-closed
contract — bare --resolve without --decision persists nothing, unknown and
already-resolved ids are refused with a hint, and the transition echoes on
stdout with --json parity.
Mirrors t3's frame-side change on the plan engine: `_missing_risks` and
`_parked_items` in devague/plan_convergence.py now filter `r.resolved`, so a
resolved `unknown_blocking` PlanRisk no longer blocks `devague plan converge`
and no longer advertises as parked. The blocking-risk hint in `suggest_move`
now names the executable `devague plan risk --resolve RID --decision TEXT`
syntax verbatim instead of the old vague "cover it with a task, or re-record
it as non-blocking" text.

Adds 6 tests to tests/test_plan_convergence.py covering: unresolved risk
still blocks (baseline), resolved risk unblocks convergence, resolved risk
drops out of required_next_moves, the hint's verbatim executable syntax
(direct + end-to-end via evaluate()), and resolved risk exclusion from
parked_items.

Full suite: 640 passed. black/isort/flake8 clean.
Harmonizes the plan-side hint with t3's frame-side shape — the hint now
names the real rN instead of a literal RID placeholder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UyFmS7dRyBj87DpgmDCu9u
Add a user-only resolve surface for parked vagueness that mirrors
question --resolve. park's positional text becomes optional (nargs='?'),
mirroring question.py; --resolve VID, --decision TEXT, and --claim CN
route through Frame.resolve_vagueness. A bare --resolve without
--decision, an unknown/already-resolved vagueness id, positional text
combined with --resolve, and an unknown --claim id are all refused with
a hint and persist nothing (decision c21, issue #57's refuse-not-no-op
answer).

Grants a minimal frame.py extension: Vagueness gains
resolution_claim_id (the deciding claim recorded at resolve time,
distinct from claim_id, the owning claim set at park time) and
resolve_vagueness gains an optional claim_id parameter, validated in
the model the same way add_scope_entry validates seed ids. Dataclass
defaults keep v2/early-v3 artifacts loading with no SCHEMA_VERSION
bump.

Covers c4, h4. Full suite: 651 passed, 98% coverage.
frame_md keeps the flat "Open vagueness" list but appends "— resolved:
TEXT" to a resolved item's bullet; spec_md gains a new "Resolved
vagueness" subsection rendering resolved items of any kind with their
resolution verbatim (previously only follow_up/out_of_scope kinds were
shown at all, and never with resolution text) — a resolved follow_up/
out_of_scope item now moves out of "Open / follow-up" into that
section instead of being fabricated as still open. deliverables_md's
surviving-open-items filter now excludes any resolved frame vagueness
or plan risk, not just unknown_blocking-kind items. An empty
resolution renders nothing extra, matching the existing
never-fabricate convention (_instruction_lines).

Covers c6, h6, c18, h14 (#53-esd t7). Full suite: 651 passed (was
634); black/isort/flake8 clean; new golden fixtures pass real
markdownlint-cli2.
Drives devague.cli.main() end to end through issue 57's exact repro on both
engines: park a blocking unknown, capture the deciding claim, resolve via
park --resolve/plan risk --resolve, assert converge blocks before and passes
after, and export renders the resolved item verbatim. Includes a markdownlint
integration check on the exported spec (skips cleanly without the binary).
Full suite: 687 passed, 98.12% coverage. flake8/black/isort/bandit clean.
…aught

devague learn's move table + operating rules, docs/llm-guidance.md's park
rows, .claude/skills/think/SKILL.md's move table + hard rules, and
docs/spec-contract.md (Vagueness/PlanRisk entities, the resolve move rows,
schema_version 3 for both engines) now all name `park --resolve VID
--decision TEXT` / `plan risk --resolve RID --decision TEXT` — the fix
shipped in t5/t6 for issues 45/55/57/60. A decided blocking park no longer
reads as a permanent dead end in any teaching surface. devague plan learn
already carried the risk --resolve text from t6; new tests in
tests/test_cli_learn.py lock both `devague learn` and `devague plan learn`
naming the resolve moves.
@OriNachum

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add resolve close-out for parked vagueness and plan risks (schema v3)

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add park --resolve and plan risk --resolve to close decided blocking items via CLI.
• Persist resolution state (schema v3) and stop resolved items blocking convergence.
• Render resolved items in exports and update docs/tests to teach/verify the workflow.
Diagram

graph TD
  U(["User"]) --> CLI["CLI resolve moves"] --> M[["Frame/Plan models"]] --> S[("JSON stores")]
  CLI --> G{"Converge gates"}
  CLI --> R["Render & export"] --> O["Specs/Plans MD"]
  S --> G
  S --> R

  subgraph Legend
    direction LR
    _u(["User"]) ~~~ _cli["CLI"] ~~~ _m[["Model"]] ~~~ _s[("Store")] ~~~ _g{"Gate"} ~~~ _r["Renderer"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. New top-level `resolve` command
  • ➕ Clear separation between creating vs resolving parked items
  • ➕ Potentially extensible to other resolvable entities
  • ➖ Adds a new verb to learn and document
  • ➖ Diverges from the existing question --resolve/park mental model this PR leverages
2. Delete resolved items instead of marking resolved
  • ➕ Simpler convergence logic (no filtering by resolved flag)
  • ➕ Less state carried forward
  • ➖ Loses provenance/evidence trail (explicitly a requirement here)
  • ➖ Harder to render resolutions in exported specs/plans
3. Allow in-place reclassification (`--kind` update) instead of resolve+re-park
  • ➕ Directly supports “reclassify” workflows without duplicating entries
  • ➕ Reduces number of items in the record
  • ➖ Mutates historical record (weaker provenance)
  • ➖ Broader CLI surface area and additional validation rules

Recommendation: Keep the PR’s approach: extend existing verbs with --resolve and persist resolution state (fail-closed). It minimizes new CLI surface area, preserves provenance for exported specs, and fixes the convergence dead-end without requiring hand-edits. The alternatives either expand the command surface materially or compromise the evidence trail that motivated the change.

Files changed (35) +2541 / -58

Enhancement (7) +275 / -22
park.pyImplement 'park --resolve' with required decision and optional claim link +79/-6

Implement 'park --resolve' with required decision and optional claim link

• Splits park handling into create vs resolve paths, adds new flags ('--resolve', '--decision', '--claim'), validates fail-closed, and emits JSON/stdout results consistent with existing CLI conventions.

devague/cli/_commands/park.py

plan.pyImplement 'plan risk --resolve' with required decision +59/-4

Implement 'plan risk --resolve' with required decision

• Adds a resolve path to the plan risk subcommand, making create args optional at parse-time but enforced in-handler; persists resolution via 'Plan.resolve_risk' and provides JSON/stdout output with fail-closed validation.

devague/cli/_commands/plan.py

frame.pyAdd Vagueness resolution state and 'Frame.resolve_vagueness'; bump schema v3 +39/-1

Add Vagueness resolution state and 'Frame.resolve_vagueness'; bump schema v3

• Introduces 'resolved', 'resolution', and 'resolution_claim_id' fields on Vagueness, adds lookup + resolve mutators with fail-closed validation, and bumps 'SCHEMA_VERSION' to 3.

devague/frame.py

plan.pyAdd PlanRisk resolution state and 'Plan.resolve_risk'; bump plan schema v3 +34/-1

Add PlanRisk resolution state and 'Plan.resolve_risk'; bump plan schema v3

• Introduces 'resolved' and 'resolution' on PlanRisk, adds lookup + resolve mutators mirroring frame-side error semantics, and bumps 'PLAN_SCHEMA_VERSION' to 3.

devague/plan.py

deliverables_md.pyExclude resolved vagueness/risks from deliverables open-items section +11/-6

Exclude resolved vagueness/risks from deliverables open-items section

• Updates deliverables rendering to treat resolved items as closed and exclude them from the surviving open-items list.

devague/render/deliverables_md.py

frame_md.pyRender resolved vagueness bullets with resolution text +17/-2

Render resolved vagueness bullets with resolution text

• Adds formatting to show '— resolved: <text>' for resolved items (only when resolution is non-empty) while keeping the single Open vagueness section.

devague/render/frame_md.py

spec_md.pyAdd 'Resolved vagueness' section to exported spec markdown +36/-2

Add 'Resolved vagueness' section to exported spec markdown

• Filters resolved items out of open/follow-up lists and introduces a dedicated section rendering resolved vagueness (with URL autolinking) only when resolution text exists.

devague/render/spec_md.py

Bug fix (2) +48 / -9
convergence.pyExclude resolved vagueness from blockers and parked items; update hint +22/-5

Exclude resolved vagueness from blockers and parked items; update hint

• Stops counting resolved blocking vagueness as convergence blockers, removes resolved items from parked-item listings, and updates suggest_move to an executable 'devague park --resolve ...' hint.

devague/convergence.py

plan_convergence.pyExclude resolved risks from blockers and parked items; update hint +26/-4

Exclude resolved risks from blockers and parked items; update hint

• Stops counting resolved blocking risks as plan convergence blockers, removes resolved risks from parked-item listings, and updates suggest_move to executable 'devague plan risk --resolve ...' syntax.

devague/plan_convergence.py

Tests (15) +1036 / -9
resolved_vagueness_frame.mdAdd golden for frame-md rendering of resolved vagueness +13/-0

Add golden for frame-md rendering of resolved vagueness

• Introduces expected markdown output validating resolved vagueness formatting in frame renderings.

tests/goldens/resolved_vagueness_frame.md

resolved_vagueness_spec.mdAdd golden for spec-md 'Resolved vagueness' section +8/-0

Add golden for spec-md 'Resolved vagueness' section

• Introduces expected markdown output validating the new spec export section for resolved vagueness.

tests/goldens/resolved_vagueness_spec.md

test_cli_learn.pyAssert learn output names 'park --resolve' and 'risk --resolve' +24/-0

Assert learn output names 'park --resolve' and 'risk --resolve'

• Adds tests ensuring teaching surfaces include the new resolve commands and flags.

tests/test_cli_learn.py

test_cli_moves.pyAdd CLI tests for 'park --resolve' validation and persistence +123/-0

Add CLI tests for 'park --resolve' validation and persistence

• Covers happy path, JSON parity, required decision enforcement, unknown/already-resolved id refusals, and deciding-claim linking semantics.

tests/test_cli_moves.py

test_cli_plan.pyAdd CLI tests for 'plan risk --resolve' and create-path enforcement +69/-0

Add CLI tests for 'plan risk --resolve' and create-path enforcement

• Ensures risk creation still requires text+kind while resolve requires '--decision', and validates unknown/already-resolved refusals.

tests/test_cli_plan.py

test_convergence.pyTest convergence skips resolved vagueness and emits new hint +49/-1

Test convergence skips resolved vagueness and emits new hint

• Adds coverage for resolved items not blocking, parked_items excluding resolved, and suggest_move emitting executable 'park --resolve' syntax.

tests/test_convergence.py

test_e2e_resolve.pyAdd end-to-end tests for issue #57 lifecycle (frame + plan) +218/-0

Add end-to-end tests for issue #57 lifecycle (frame + plan)

• Drives the real CLI workflow: park blocking → decide → resolve → converge passes → export renders resolution; includes optional markdownlint-cli2 checks when available.

tests/test_e2e_resolve.py

test_frame.pyTest Vagueness resolution fields, resolver behavior, and legacy defaults +134/-0

Test Vagueness resolution fields, resolver behavior, and legacy defaults

• Validates schema version bump, default field values, resolve semantics, deciding-claim linking behavior, and compatibility with v2/v3 legacy persisted shapes.

tests/test_frame.py

test_frame_schema_v2.pyUpdate schema-version pin test for frame schema v3 +4/-1

Update schema-version pin test for frame schema v3

• Adjusts the previously pinned schema version assertion to match the new legitimate bump.

tests/test_frame_schema_v2.py

test_plan.pyTest PlanRisk resolution fields, resolver behavior, and legacy defaults +70/-4

Test PlanRisk resolution fields, resolver behavior, and legacy defaults

• Adds unit coverage for PlanRisk resolution defaults, resolve semantics, schema version bump to v3, and v2 plan compatibility defaults.

tests/test_plan.py

test_plan_convergence.pyTest plan convergence skips resolved risks and emits new hint +60/-1

Test plan convergence skips resolved risks and emits new hint

• Ensures unresolved risks still block, resolved risks no longer block or appear in required moves/parked items, and hints name the executable resolve syntax.

tests/test_plan_convergence.py

test_plan_deliverables.pyTest deliverables excludes resolved vagueness and risks from open items +47/-0

Test deliverables excludes resolved vagueness and risks from open items

• Validates that resolved items are removed from the Open items section while unresolved items remain visible.

tests/test_plan_deliverables.py

test_plan_store.pyTest plan store round-trips resolved risks and loads v2 defaults +34/-1

Test plan store round-trips resolved risks and loads v2 defaults

• Adds persistence tests for resolved risks and updates schema version upgrade assertions to match the new plan schema version.

tests/test_plan_store.py

test_render.pyTest rendering behavior for resolved vagueness and markdownlint cleanliness +151/-0

Test rendering behavior for resolved vagueness and markdownlint cleanliness

• Adds render tests (including goldens) for frame/spec output, URL autolinking in resolutions, and optional markdownlint-cli2 verification when present.

tests/test_render.py

test_store.pyTest frame store round-trips resolved vagueness and loads v2 defaults +32/-1

Test frame store round-trips resolved vagueness and loads v2 defaults

• Adds persistence tests for resolved vagueness and updates schema version upgrade assertions to follow the bumped frame schema version.

tests/test_store.py

Documentation (7) +321 / -16
SKILL.mdTeach 'park --resolve' in the /think skill move table and rules +7/-1

Teach 'park --resolve' in the /think skill move table and rules

• Adds the resolve close-out move to the documented workflow and reinforces that decided blocking parks should be resolved via CLI rather than hand-editing state.

.claude/skills/think/SKILL.md

CHANGELOG.mdDocument 0.20.0: resolve moves, schema bump, and updated hints +32/-0

Document 0.20.0: resolve moves, schema bump, and updated hints

• Adds a 0.20.0 entry describing new resolve commands, schema version changes, and updated learning surfaces.

CHANGELOG.md

learn.pyUpdate learn text to include resolve close-out for park +8/-2

Update learn text to include resolve close-out for park

• Extends command descriptions and operating rules to reference 'park --resolve ... --decision ...' as the supported close-out path.

devague/cli/_commands/learn.py

llm-guidance.mdTeach resolve close-out for parked items and plan risks +13/-3

Teach resolve close-out for parked items and plan risks

• Updates guidance to explicitly instruct resolving decided blocking parks/risks via the new CLI moves instead of leaving them parked or editing state.

docs/llm-guidance.md

2026-07-17-resolve-parked-vagueness.mdAdd build plan markdown for resolve-parked-vagueness +105/-0

Add build plan markdown for resolve-parked-vagueness

• Adds a human-readable plan document enumerating tasks, acceptance criteria, and risks for the work shipped in this PR.

docs/plans/2026-07-17-resolve-parked-vagueness.md

spec-contract.mdDocument schema v3 and new resolve moves in the contract +42/-10

Document schema v3 and new resolve moves in the contract

• Updates entity definitions, move tables, validation rules, and versioning sections for frame/plan schema v3 and the new resolve commands.

docs/spec-contract.md

2026-07-17-resolve-parked-vagueness.mdAdd feature spec markdown describing resolve close-out and constraints +114/-0

Add feature spec markdown describing resolve close-out and constraints

• Adds the spec artifact capturing motivations, requirements, decisions, and boundaries for resolving parked vagueness and plan risks.

docs/specs/2026-07-17-resolve-parked-vagueness.md

Other (4) +861 / -2
current_planPoint current devague plan at resolve-parked-vagueness +1/-1

Point current devague plan at resolve-parked-vagueness

• Updates the tracked plan slug used by the repo’s devague workflow state.

.devague/current_plan

resolve-parked-vagueness.jsonAdd exported frame artifact for resolve-parked-vagueness +490/-0

Add exported frame artifact for resolve-parked-vagueness

• Checks in the frame JSON capturing requirements/decisions and acceptance criteria for the feature work.

.devague/frames/resolve-parked-vagueness.json

resolve-parked-vagueness.jsonAdd exported plan artifact for resolve-parked-vagueness +369/-0

Add exported plan artifact for resolve-parked-vagueness

• Checks in the plan JSON (tasks, dependencies, risks) driving implementation and verification of the resolve feature.

.devague/plans/resolve-parked-vagueness.json

pyproject.tomlBump package version to 0.20.0 +1/-1

Bump package version to 0.20.0

• Updates project version to reflect the new CLI surface and schema changes.

pyproject.toml

@sonarqubecloud

Copy link
Copy Markdown

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 75 rules
✅ Skills: cicd, communicate

Grey Divider


Informational

1. llm claims marked confirmed 📘 Rule violation ≡ Correctness
Description
The newly added frame and plan artifacts include LLM-originated Claim and Task entries marked
status: confirmed, which conflicts with the requirement that LLM-originated items must start in
proposed and not be auto-confirmed. This bypasses the intended human-review gate for
LLM-originated state.
Code

.devague/frames/resolve-parked-vagueness.json[R27-33]

+    {
+      "id": "c2",
+      "kind": "requirement",
+      "text": "the Vagueness dataclass (devague/frame.py:106) gains resolution state \u2014 e.g. a resolved flag plus resolution text \u2014 kept in frame state for the evidence trail rather than deleted; add_vagueness today only appends and set_status (frame.py:226) routes only claim and honesty ids, so v-ids are unaddressable by any move",
+      "origin": "llm",
+      "status": "confirmed",
+      "honesty_conditions": [
Relevance

⭐ Low

Similar suggestions to change origin:llm status confirmed→proposed in artifacts were repeatedly
rejected (PRs #15, #63).

PR-#15
PR-#63

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 738290 specifies that any LLM-originated claims/tasks must start in proposed and
must never be auto-confirmed. The added frame file contains a Claim with origin: llm and
status: confirmed, and the added plan file contains Task entries with origin: llm and `status:
confirmed`, directly demonstrating the non-compliant state in both artifacts.

Rule 738290: LLM-originated claims and tasks must start in proposed status and never be auto-confirmed
.devague/frames/resolve-parked-vagueness.json[27-33]
.devague/plans/resolve-parked-vagueness.json[151-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The committed frame and plan state contain LLM-originated `Claim` and `Task` records that are already marked `confirmed`, violating the requirement that LLM-originated items must begin as `proposed` and not be auto-confirmed.

## Issue Context
PR Compliance ID 738290 requires LLM-originated claims/tasks to start in `proposed` and never be auto-confirmed, preserving a human-review step before confirmation.

## Fix Focus Areas
- .devague/frames/resolve-parked-vagueness.json[27-33]
- .devague/plans/resolve-parked-vagueness.json[151-156]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Committed plan pointer file 🐞 Bug ⚙ Maintainability
Description
The PR modifies and commits .devague/current_plan, but this path is documented as an uncommitted
local pointer and is rewritten by plan_store.save(), so it will keep generating meaningless
diffs/merge conflicts. It can also make devague plan default to an unintended/stale plan slug on
other checkouts.
Code

.devague/current_plan[1]

+resolve-parked-vagueness
Relevance

⭐ Low

Same request to gitignore/untrack .devague/current_plan was rejected in PR #15 review history.

PR-#15

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The repo explicitly documents .devague/current_plan as a local (non-committed) pointer, but it is
tracked and changed in this PR; additionally, it is not ignored by .gitignore and is rewritten by
the plan persistence layer, guaranteeing recurring diffs.

README.md[66-74]
.gitignore[226-233]
devague/plan_store.py[18-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`.devague/current_plan` is a local pointer (not meant to be committed) but this PR changes it and keeps it tracked. Because the CLI writes this file on every plan save, it will constantly churn in git and can point other checkouts/branches at the wrong default plan.

### Issue Context
- README documents `.devague/current_plan` as **not committed**.
- `.gitignore` currently ignores `.devague/current` but **not** `.devague/current_plan`.
- `devague/plan_store.py` always writes `.devague/current_plan`.

### Fix
1. Remove `.devague/current_plan` from the PR / untrack it (`git rm --cached .devague/current_plan`) so it becomes local-only.
2. Add `.devague/current_plan` to `.gitignore` near the existing `.devague/current` ignore.
3. (Optional but aligns with README promise) Update `plan_store.save()` to ensure `.devague/current_plan` is added to the devague-managed `.gitignore` block (similar to how `devague.store.ensure_ignored()` is used for other working-state paths).

### Fix Focus Areas
- .devague/current_plan[1-1]
- .gitignore[226-233]
- devague/plan_store.py[18-47]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@OriNachum

Copy link
Copy Markdown
Contributor Author

Triage of the latest Qodo review (1 bug, 1 rule violation) — both rejected as not-a-defect after verification, consistent with this repo's prior review history:

1. "LLM claims marked confirmed" (rule 738290) — rejected. (origin: llm, status: confirmed) is the post-confirmation state, not an auto-confirm: every such claim/task in the committed artifacts was explicitly confirmed by the human operator during the session (the devague confirm batches recorded in the frame's review flow). State and provenance are orthogonal by design — docs/spec-contract.md's provenance table maps user_confirmed to exactly (status=confirmed) after an explicit user confirm, regardless of origin. Rewriting the committed state to proposed would falsify a real human confirmation. Qodo's own relevance note records this same suggestion rejected on #15 and #63.

2. "Committed plan pointer file" (.devague/current_plan) — rejected. The claimed evidence ("the repo explicitly documents .devague/current_plan as a local non-committed pointer") does not exist: the only occurrence of current_plan outside state is the path constant in devague/plan_store.py. Repo convention is the opposite — the pointer has been committed in every recent feature PR (#63, #72, #76, and the standalone 0e47e9c chore: point current_plan at challenge-skill), and untracking it was already rejected in #15's review history. What devague deliberately keeps uncommitted is .devague/reviews/ and .devague/questions/ (#17), which this PR honors.

No code changes made for either finding.

  • devague (Claude)

@OriNachum
OriNachum merged commit 65fca10 into main Jul 17, 2026
8 checks passed
@OriNachum
OriNachum deleted the park-resolve branch July 17, 2026 12:46
OriNachum added a commit that referenced this pull request Jul 28, 2026
* spec: issue-backlog-sweep — 15-issue sweep frame + exported spec (devague /scope + /think + /challenge)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* plan: issue-backlog-sweep — 19 tasks / 6 waves covering 52 targets (devague /spec-to-plan)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* feat(scope): fan exploration out to smaller-tier read-only subagents (#79, #91, t16)

Steps 1-2 of the /scope method now instruct subagent fan-out for a broad
survey (5+ candidate surfaces), one read-only exploration subagent per
surface or tight cluster, defaulting to a smaller tier (sonnet). 4 or fewer
surfaces still explore inline, and the no-wizard escape for small ideas is
unchanged. The load-bearing rule survives explicitly in both step 2/4 and
the Hard rules section: subagents explore and report only, never running a
devague move — the main agent alone runs capture/scope/question/park so
provenance and the anti-fabrication contract stay in one place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* fix(plan): validate cover targets against the live frame (#90, t8)

`_require_target` only checked the plan's stored coverage-target snapshot,
frozen at `plan new`/last `plan converge` time. But `plan status`/`converge`
re-derive targets from the LIVE source frame, so a frame that legitimately
grows a confirmed claim mid-run left `status` recommending a cover that
`cover`/`task --covers` immediately refused as "unknown coverage target" --
the exact #90 repro.

`_require_target` now falls back to the live frame when a target is absent
from the stored snapshot, and persists the refreshed snapshot on success so
the stored copy catches up without a separate `plan converge`. Decision
(park v4 / plan risk r2): when the source frame has itself regressed below
its own convergence gate, `_live`'s regression error is let through as-is
rather than reworded into "unknown coverage target" -- an unverifiable
target should say why, not blame the wrong thing. A target already known to
the stored snapshot never touches the live frame at all, so it keeps working
through a frame regression exactly as before.

* fix(store): gate schema_version before parsing; tolerant nested loads (t2)

store.load and plan_store.load parsed via from_dict before checking
schema_version, so a genuinely newer-schema file crashed with a raw
TypeError (HardQuestion(**q) / Vagueness(**v) reject unexpected kwargs)
instead of the intended fail-closed IncompatibleSchemaError. Both loaders
now read the raw JSON, check schema_version first, and only then build the
domain object. HardQuestion/Vagueness construction in frame.from_dict is
now tolerant of unknown keys the same way Claim already is, so a future
field lands safely under a version bump instead of crashing same-version
loads that happen to carry it.

Bumps frame.SCHEMA_VERSION 3->4 (reserved for t4's HardQuestion resolution
field) and plan.PLAN_SCHEMA_VERSION 3->4 (reserved for t9's per-target
deferral state) - this task only hardens load order/tolerance, it does not
add either field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* feat(render): identifier-aware markdown escaping for verbatim text (#87, t1)

Add md_safe_text() to render/_md_safety.py: wraps underscore/dunder
identifiers (_read_file, __init__.py) in code spans per the #87 issue
comment's preferred fix (fixes MD037 and MD050 in one move, reads better
than backslash-escaping), and backslash-escapes the remaining control
characters (*, [, ], a stray backtick, a leading #) as the fallback.
Text already inside a code span is left byte-for-byte untouched and
double application is a no-op (c32/h25). No renderer call sites yet —
those land in t3/t9/t13.

* feat(assign-to-workforce): durable gate-2 split artifact (#82, t15)

Add `split-plan --write`, producing docs/plans/<created-date>-<slug>-split.md
alongside the exported plan-md: real per-task summaries, instructions, and
acceptance criteria from `devague plan waves --json`; a Task assignments
(Owner/Model) table the script reads back on re-run so a human's edited
assignment survives regeneration; and the End state section. Re-running
overwrites the same dated path in place. Artifact-only per decision c25 —
no plan schema change, no new devague CLI verb; devague plan waves/show/
deliverables stay read-only. SKILL.md documents the flow.

* feat(interrogate): --resolve clears a claim's blocking hard question (#48, #52, t4)

interrogate <cN> --resolve <qN> [--decision "<text>"] adds Frame.resolve_hard_question
and a resolve mode on the interrogate CLI parser (mirrors park --resolve), fixing the
permanent convergence deadlock where nothing ever set HardQuestion.resolved. The
blocking-question gate also skips rejected claims, and suggest_move now names the
shipped move instead of the old dead-end "capture/confirm the resulting claim" hint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* fix(summary): scope Planned Work and Actual Delivery to confirmed tasks (#88, t13)

devague summary rendered every task on the plan -- including rejected ones --
into both Planned Work and the Actual Delivery table, so a plan carrying 19
confirmed and 68 rejected tasks (rebuilt twice after scope changes) emitted 87
undifferentiated rows. A rejected task paired with `<fill: status>` invites
recording a planning decision as a delivery failure, which is a different fact.

Planned Work and Actual Delivery (and the --json summary_data equivalent) now
iterate confirmed tasks only, with a single line preserving the rejected count
without padding either list ("N tasks were rejected during planning -- see
`devague plan show`"). A proposed task -- neither the confirmed contract nor
an explicit rejection -- is excluded from both lists and from the rejected
count, so an open decision is never folded into a closed one. The --pr wave
map is unchanged (dependency_waves already excludes rejected tasks), pinned
here with a dedicated regression test. Also applies the #87 md_safe_text
escaper at every verbatim interpolation site in this renderer (task
summaries, deviation what/reason, announcement/after-state text).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* feat(plan): per-target deferral so milestone-scoped plans converge (#85, t9)

Add `devague plan defer <target-id> --reason "<text>"` (+ `--undo` to
reverse it) so a plan intentionally scoped to early milestones is not
forced to fake coverage of a later-milestone target just to satisfy the
gate — the exact perverse incentive #85 reported (a reviewer catching a
task that "claims coverage ... but only installs and reports the
counter").

- plan.py: CoverageTarget gains deferred/deferred_reason (schema v4,
  reserved by t2); Plan.defer_target/undefer_target mirror
  resolve_risk's fail-closed contract (unknown id / already-deferred /
  not-deferred all raise).
- plan_convergence.py: _missing_coverage excludes deferred targets from
  blockers; _parked_items now also lists each deferred target labeled
  distinctly ("deferred: coverage target cN ... — <reason>") so `plan
  status`/`converge --json` can tell "deliberately deferred" apart from
  "not yet covered".
- cli/_commands/plan.py: new `defer` subcommand + PLAN_MOVES entry;
  _live/_live_frame_and_targets now carry persisted deferral state
  across every live-frame re-derive (targets_from_frame builds fresh
  CoverageTarget instances with no memory of prior deferrals — without
  this merge, converge/export/status would silently drop a recorded
  deferral on the next call).
- cli/_status.py: emit_status now prints parked_items in text mode too
  (a pre-existing gap — JSON already carried them) so `plan status`
  visibly shows deferred targets, not just JSON.
- render/plan_md.py: new "Deferred targets" section naming each with
  its reason; applies the t1 escaper (md_safe_text) at every verbatim
  site (task heading/instruction/acceptance, risk text, announcement,
  title) — closes the MD050 regression from the #87 comment for
  underscore-bearing task text.

Verified against real markdownlint-cli2 (0 errors) and an end-to-end
`devague` CLI smoke run in a scratch dir (defer -> converge -> export ->
status -> undo), plus the full pytest suite (760 passed) and
black/isort/flake8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* fix(export): render all parks, resolved questions, dead seeds; escape verbatim text (#93, #49, #83, #87, t3)

spec_md.py's park-kind filter only ever surfaced follow_up/out_of_scope,
silently dropping every open unknown_nonblocking/unknown_blocking park —
exactly the residual-risk kind that legitimately coexists with a converged
frame (#93, #49). It now renders every open park kind, grouped and labeled.

Hard questions previously carried no resolved marker and rendered
regardless of parent-claim status, so a resolved question read as an open
blocker and a rejected claim's question still leaked into the artifact
(#49, #83). Both are fixed: resolved questions get a "(resolved)" marker,
and any claim.status == "rejected" drops its hard questions entirely.

A scope entry whose seeds cite a claim that was later rejected now renders
a visible "(rejected)" marker instead of a bare dead id (#84's fourth
acceptance criterion, c33/h26).

Every verbatim interpolation site now composes the t1 escaper
(md_safe_text) with the existing autolink_urls/heading_safe passes via new
_safe()/_safe_heading() helpers, fixing MD037/MD050 on underscore/dunder
identifiers throughout the exported spec (#87).

Flips the pinned test_render.py:301-306 (nonblocking parks now render) and
adds regression coverage for all four acceptance criteria, including a
real markdownlint-cli2 double-export byte-stability check. Re-exporting
docs/specs/2026-07-28-issue-backlog-sweep.md (a live corpus mixing
backticked and bare identifiers) produces the expected one-time
presentational diff — committed alongside, per park v5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* chore(export): re-export the 2026-07-17 plan artifact through the new escaper (#87)

Proves the t1/t9 escaping fix end-to-end: three pre-existing MD037 errors in a
committed artifact from PR #81 clear on re-export, with no source text change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* fix(reject): cascade over honesty conditions and hard questions (#83, t5)

Frame.reject cascades a claim's rejection onto its still-live honesty
conditions (status -> rejected) and reports both cascaded honesty and
hard-question ids so the CLI can echo what it took with it
(`c21 -> rejected (also rejected: h3, q1)`). The cascade fires only on
the transition into rejected, so re-rejecting an already-rejected claim
reports nothing (idempotent, no double-reporting).

_assumption_warnings now skips rejected assumptions (only a still-proposed
one is actionable "unconfirmed"), and devague review naturally stops
listing an honesty condition once its parent claim's rejection cascades
over it -- no renderer changes needed, since proposed_honesty() already
filters on status == "proposed".

* fix(plan): validate --dep at task creation (#86, t10)

`plan task --dep` and `depend <tN> --on <tM>` now refuse a self-cycle
(the about-to-be-assigned/naming-itself task id) and an unknown task id
at authoring time, with an actionable hint, instead of silently
recording a broken edge that only surfaces much later as a bare
`dependency cycle: tN -> tN` at `plan converge`/`plan waves`.

`depend --remove` is untouched by the new checks (it must still repair
a dangling dep predating this fix), and the plan_convergence gate for
multi-task cycles is left alone — this is creation-time feedback, not
a replacement for it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* feat(export): mark confirmed claims contested by an approved deviation (#92, t14)

Adds a read-only derivation (devague/contested.py) joining a frame's confirmed
claims to any approved `devague deviate` record naming them in --affects,
since the deviation ledger knew a claim had been contradicted by execution
but nothing rendered that back-reference (the spec, `show`, and `status` all
stayed silent). Per the #92 maintainer ruling the spec itself is never
rewritten -- this only ever derives a pointer at render time: `export` gets a
rich per-claim marker (announcement blockquote, Requirements, and every
generic claim section), `show`/`status` gain a summary "contested: ..." line
in both text and --json, and the plan engine's own status is untouched (no
"contested" key at all).

The join crosses the frame/plan boundary the instruction called out: frames
carry no reverse pointer to the plans seeded from them, so every plan slug is
enumerated and filtered on frame_slug before its delivery ledger is read.
Fails open throughout -- a plan or delivery file that is missing, truncated,
or declares a newer schema degrades to "no markers from that source" plus a
stderr diagnostic, never a crash or a refused export; zero frame/plan/
delivery mutation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* feat(plan): transactional multi-id confirm/reject (#86, t11)

plan confirm/reject now accept N ids (nargs="+") and apply them
transactionally — every id is validated against the plan first, and if
any is unknown nothing is changed, matching the frame-side contract
(confirm.py's _run). Argument errors raised inside a devague plan
<move> parser now hint at `devague plan explain <move>` instead of
the generic `<prog> --help`, scoped so top-level and other flat-verb
errors keep their existing hint unchanged.

* feat(amend): correct a claim or scope finding without id churn (#84, t6)

New `devague amend <cN> --text/--kind [--reason]` and `devague scope
--amend <sN> --finding` edit a claim or scope entry in place — same id,
same honesty conditions/hard questions/instruction, same inbound
scope-entry seeds. The only prior route (reject + recapture) churned the
id and left scope-entry seeds dangling. Amending a confirmed claim flips
it back to proposed and echoes the transition, mirroring the
`interrogate --instruction` precedent; origin is never touched (no flag
reaches it). A lightweight `Claim.revisions` trail (text/kind/reason)
records what an amend superseded, added without a schema_version bump
since `from_dict` loads it tolerantly.

* feat(scope): --seeds accepts hard-question ids (#84, t7)

Frame.add_scope_entry validates a seed against find_claim first and the
new find_hard_question second, so a claim-attached hard question id
(q*) records alongside claim ids (c*) -- unknown ids of either kind
still refuse with the existing "unknown seed claim id" error and
"run 'devague show'" hint. render/spec_md._seed_label renders a
question seed as `(question)`, or `(question, resolved)` once
answered, distinct from the existing claim/rejected-claim markers.

This closes the "smaller, related gap" in issue #84: the /scope
skill's own routing table sends a "genuinely unknown, needs a user
decision" finding to the `question` move rather than `capture`, so a
scope entry recording that finding previously had no way to link its
provenance.

* feat(plan): risk --amend corrects a stale risk in place (#84, t12)

Add `Plan.amend_risk` and `devague plan risk --amend RID --text "<corrected>"`
so a risk whose prose names a task id that rotated (rejected + recreated
during a scope change) can be corrected in place instead of resolving it
just to record a duplicate. Preserves id, kind, task link, and resolution
state verbatim -- a resolved risk stays resolved after its text is fixed.

* docs(learn): teach every surface shipped in the sweep (#52, t17)

interrogate --resolve, amend, scope --amend (+ --seeds accepting q*
hard-question ids), plan defer, plan risk --amend, transactional multi-id
plan confirm/reject, live plan cover/--covers validation, the flat reject
cascade, and the scope subagent fan-out threshold were all shipped ahead of
learn/explain catching up — devague explain amend even 404'd with "unknown
move: amend" before this. Also fixes plan.py's cmd_plan_learn, which still
named "six operator skills" and omitted challenge from the list.

Closes the #52 acceptance criterion: learn/explain document the resolve
path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* docs: sweep README/CLAUDE/skills docs + changelog for the fifteen-issue sweep (t18)

Bump 0.20.1 -> 0.21.0 (minor: feature release) and describe the shipped
surface across every teaching doc.

- CHANGELOG.md: one 0.21.0 entry mapping each of the fifteen issues to what
  changed (#48 #49 #52 #79 #82 #83 #84 #85 #86 #87 #88 #90 #91 #92 #93).
- CLAUDE.md: new Status entry; working-backwards and spec->plan method lists
  gain amend / interrogate --resolve / scope --seeds q* / plan defer /
  risk --amend / multi-id plan confirm / live-frame cover; gate 2 now names
  its durable split artifact; module inventory picks up amend.py,
  contested.py, _md_safety.py and plan defer.
- README.md: verb lists, the "nothing is deleted to make a gate go green"
  close-out rule, and the reject cascade.
- docs/skills.md: scope subagent fan-out and the split-plan --write artifact;
  fixes the pre-existing drift that said briefs quote plan show --json /
  the plan-md (SKILL.md is right: plan waves --json is the single source).
- docs/skill-sources.md: version stamps for scope and assign-to-workforce.
- docs/spec-contract.md: schema_version 4 on both stores (check-before-parse),
  ClaimRevision, HardQuestion.resolution, CoverageTarget.deferred, the new
  moves and their validation errors, plus render-time contracts (md-safety,
  contested-by-deviation).
- docs/llm-guidance.md + think/scope SKILL.md: the stale --seeds claim-ids-only
  wording, and the three new close-out/correct-in-place rules.

Docs and version only — no behavior change.

* test: end-to-end verification + issue closure map (t19)

Adversarial verification of all fifteen issues in the sweep, each checked
against its own stated acceptance criteria and — where the issue carried
repro steps — by running the reporter's sequence against the built CLI in a
scratch dir rather than by reading the tests.

Closure map: docs/deliveries/2026-07-28-issue-backlog-sweep-closure-map.md
(967 tests green, 98.37% coverage, 0 markdownlint errors including the
force-linted .claude/skills/** files).

Three defects the per-task agents did not catch, fixed here with tests:

- #94 (regression this release introduced): md_safe_text and autolink_urls
  were composed in OPPOSITE orders by spec_md vs plan_md/summary_md, and
  both corrupted a URL containing an underscore — the plan order backticked
  inside the link, the spec order truncated it at the first underscore,
  silently pointing a committed artifact's link at the wrong address.
  md_safe_text now carves out URLs exactly as it already carved out code
  spans, so both orders produce identical intact links.
- #49 was only half closed: a resolved hard question rendered "(resolved)"
  but dropped the recorded decision text, while the parks renderer already
  rendered its own. The issue asked for "a pointer to the claim/decision
  that answered them", so the export now renders
  "(resolved: <decision>)" when interrogate --resolve --decision recorded one.
- spec-to-plan/SKILL.md was never swept and still taught `plan reject` as
  single-id with a shell loop — the exact workaround #86 removed — in the
  skill guildmaster re-broadcasts to the mesh. Moves table corrected and
  pinned against `devague plan --help` by a new test.

The markdownlint integration test now exports the repo's own real
issue-backlog-sweep frame and plan as lint corpus, per the task instruction.

Two CHANGELOG factual errors corrected: "Three of those" listed only two
workarounds (#87's lint ignores were missing), and the swept-surfaces list
omitted spec-to-plan.

* chore(deviate): commit the delivery ledger so d1/d2 reproduce for a fresh clone

The contested-marker derivation (#92) reads .devague/deliveries/<plan-slug>.json;
without it committed, a fresh clone cannot reproduce the markers. Flagged by t19.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* docs(delivery): delivery summary for the issue-backlog-sweep run

Eight-section accountability artifact built from the devague summary skeleton:
19/19 tasks delivered, 2 approved deviations (d1/d2, execution-order only),
3 defects found and fixed by t19's cross-task verification, and the honest
remaining-work list including the two follow-up issues opened during the run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

* refactor: address SonarCloud findings on the sweep's new modules

- contested.py: extract _delivery_for_frame and _markers_from_delivery so
  find_contested_markers drops from cognitive complexity 24 to under the 15
  allowed (python:S3776).
- _md_safety.py: name the file-extension alternation instead of relying on
  implicit string concatenation in the regex (python:S5799).

Behavior unchanged; 967 tests still pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtUixLUjgEt51PXS9TPVt3

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment