Skip to content

Add spec→plan engine; split /devague skill into /think + /spec-to-plan#8

Merged
OriNachum merged 3 commits into
mainfrom
feat/plan-engine-think-rename
May 23, 2026
Merged

Add spec→plan engine; split /devague skill into /think + /spec-to-plan#8
OriNachum merged 3 commits into
mainfrom
feat/plan-engine-think-rename

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

What

Adds devague's second deterministic engine — the spec→plan engine, a
structural peer of the existing frame engine (idea→spec) — and reframes the
operator skills into a clear two-leg pipeline:

vague idea → /think → buildable spec → /spec-to-plan → buildable plan → build

Plan engine (new, mirrors the frame engine 1:1)

  • devague/plan.pyPlan / Task / PlanRisk / CoverageTarget domain; same _next id allocation and user-only confirm rule as frame.py. targets_from_frame() derives coverage targets from a converged frame's confirmed claims + honesty conditions.
  • devague/plan_convergence.py — gate (reuses ConvergenceResult): every coverage target covered by a confirmed task, every confirmed task has acceptance criteria, no task left proposed, dependency graph acyclic (deterministic cycle path), no blocking risk.
  • devague/plan_store.py — JSON under .devague/plans/ (slug == frame slug, refuse-on-collision).
  • devague/render/plan_md.py — topologically-ordered buildable plan-md (not in the Frame-typed render registry; called directly).
  • devague/cli/_commands/plan.py + _plans.py — nested devague plan <move> group: new/task/accept/depend/cover/confirm/reject/risk/converge/export/show/list/learn/explain, all --json.
  • plan new requires a converged frame; converge/export re-evaluate against the live frame and refuse on drift (source frame deleted or regressed below convergence).

Skill reframe

  • Renamed the published /devague skill → /think (idea→spec); "devague" stays a trigger keyword.
  • Added /spec-to-plan (spec→plan), a portable wrapper over devague plan with a status next-move helper.
  • Product / CLI / repo name stays devague. README reframed to make clear devague is a CLI (the PyPI-facing identity); the agent framing stays in CLAUDE.md.

Tests / quality

  • 147 tests pass; coverage 96.6% (gate 95%). flake8 / black / isort / markdownlint / bandit clean.
  • Version 0.3.3 → 0.4.0.

Downstream note (alignment delta)

Touches CLAUDE.md, docs/skill-sources.md, and .claude/skills/. The skill rename (devaguethink) plus the new spec-to-plan sibling means steward must relearn the skill names when it re-vendors and broadcasts to the AgentCulture mesh — recorded in docs/skill-sources.md.

Design: docs/superpowers/specs/2026-05-23-devague-spec-to-plan-design.md

  • devague (Claude)

Adds a deterministic plan engine as a structural peer of the frame engine
(idea→spec): Plan domain model, a convergence gate (coverage + acceptance +
acyclic dependencies + blocking risk, reusing ConvergenceResult), a JSON store
under .devague/plans/, a topologically-ordered plan-md renderer, and a nested
`devague plan` CLI group (new/task/accept/depend/cover/confirm/reject/risk/
converge/export/show/list/learn/explain). `plan new` requires a converged
source frame; converge/export re-evaluate against the live frame and refuse on
drift (deleted or regressed).

Reframes the operator skills: renames the published /devague skill to /think
(idea→spec; "devague" stays a trigger keyword) and adds a new /spec-to-plan
skill (spec→plan) that drives `devague plan`. The product/CLI/repo name stays
`devague`. Updates docs/skill-sources.md, CLAUDE.md, README.md, and adds a
design doc. Bumps version to 0.4.0; coverage 96.6% (gate 95%).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Add spec→plan engine; split /devague skill into /think + /spec-to-plan

✨ Enhancement 🧪 Tests 📝 Documentation

Grey Divider

Walkthroughs

Description
• Introduces the **spec→plan engine**, a structural peer of the existing frame engine, completing
  the two-leg deterministic pipeline: `vague idea → /think → buildable spec → /spec-to-plan →
  buildable plan → build`
• Adds new domain modules (plan.py, plan_convergence.py, plan_store.py) mirroring the frame
  engine 1:1, with task lifecycle management, coverage target derivation from converged frames, and
  deterministic cycle detection
• Implements the plan convergence gate ensuring all targets are covered, tasks have acceptance
  criteria, no proposed tasks remain, dependency graph is acyclic, and no blocking risks exist
• Adds nested devague plan  CLI group with 14 moves (new, task, accept, depend, cover,
  confirm, reject, risk, converge, export, show, list, learn, explain), all
  supporting --json output
• Enforces frame-drift detection in converge/export moves to refuse on source frame deletion or
  regression below convergence
• Renames the /devague operator skill to /think (idea→spec leg) and introduces new
  /spec-to-plan skill (spec→plan leg) with portable bash wrapper and status helper
• Adds topological plan markdown renderer with task dependencies, coverage targets, and acceptance
  criteria
• Includes comprehensive test coverage (147 tests, 96.6% coverage) for plan model, convergence gate,
  persistence, CLI integration, and skill wrappers
• Updates documentation (README, CLAUDE.md, design spec, skill-sources.md) to clarify devague as a
  CLI tool with dual-engine architecture and agent integration via operator skills
• Bumps version from 0.3.3 to 0.4.0
Diagram
flowchart LR
  idea["Vague idea"]
  think["/think skill<br/>idea→spec engine"]
  spec["Buildable spec"]
  spec_to_plan["/spec-to-plan skill<br/>spec→plan engine"]
  plan["Buildable plan"]
  build["Build"]
  
  idea -- "converged frame" --> think
  think -- "converged frame" --> spec
  spec -- "converged plan" --> spec_to_plan
  spec_to_plan -- "converged plan" --> plan
  plan --> build

Loading

File Changes

1. devague/plan.py ✨ Enhancement +179/-0

Plan domain model with task lifecycle and target derivation

• Introduces the Plan domain model with Task, PlanRisk, and CoverageTarget dataclasses,
 mirroring the frame engine structure
• Implements targets_from_frame() to derive coverage targets from a converged frame's confirmed
 claims and honesty conditions
• Provides task lifecycle methods (add_task, add_acceptance, add_dep, add_cover, add_risk)
 with deduplication and sequential ID allocation
• Includes JSON serialization helpers (to_dict, from_dict) for persistence

devague/plan.py


2. devague/plan_convergence.py ✨ Enhancement +120/-0

Plan convergence gate with cycle detection and gap reporting

• Implements the plan convergence gate, evaluating whether a plan is ready to export
• Checks coverage (all targets covered by confirmed tasks), acceptance criteria, task resolution,
 dependency integrity, and blocking risks
• Includes deterministic cycle detection via iterative DFS with white/gray/black coloring
• Reuses ConvergenceResult shape for consistency with the frame engine

devague/plan_convergence.py


3. devague/plan_store.py ✨ Enhancement +60/-0

Plan persistence layer with slug validation and current tracking

• Provides JSON persistence for plans under .devague/plans/ with slug-based file naming
• Implements save, load, list_slugs, and current_slug operations mirroring the frame store
• Validates slugs on load to reject tampered internal or frame slug references
• Manages created and updated timestamps automatically

devague/plan_store.py


View more (21)
4. devague/render/plan_md.py ✨ Enhancement +91/-0

Plan markdown renderer with topological task ordering

• Renders a converged plan as buildable markdown with topologically ordered tasks
• Includes task dependencies, coverage targets, and acceptance criteria in the output
• Gracefully handles missing source frames and cycles (renders remaining tasks in stored order)
• Outputs risks section with optional task attachment

devague/render/plan_md.py


5. devague/cli/_commands/plan.py ✨ Enhancement +433/-0

Plan engine CLI with 14 moves and frame-drift guards

• Implements the nested devague plan  subcommand group with 14 moves: new, task, accept,
 depend, cover, confirm, reject, risk, converge, export, show, list, learn,
 explain
• Enforces convergence requirement for plan new and frame-drift detection in converge/export
 via _live() helper
• All moves support --json output; includes helper functions for task/target resolution and error
 handling
• Registers subparsers with _DevagueArgumentParser to maintain structured error routing

devague/cli/_commands/plan.py


6. devague/cli/_plans.py ✨ Enhancement +29/-0

Plan slug resolver for CLI moves

• Provides resolve_plan() helper to resolve a plan slug from explicit --plan argument or current
 plan pointer
• Mirrors the frame resolver pattern; raises structured DevagueError for missing/invalid slugs

devague/cli/_plans.py


7. devague/cli/__init__.py ✨ Enhancement +2/-0

CLI registration for plan subcommand group

• Registers the new plan command module in the main CLI parser
• Imports _plan_cmd and calls _plan_cmd.register(sub) to add the nested subcommand group

devague/cli/init.py


8. tests/test_plan.py 🧪 Tests +102/-0

Plan model unit tests with ID allocation and target derivation

• Tests plan model: sequential ID allocation, origin-driven initial status, task/target lookup,
 deduplication of acceptance/deps/covers
• Validates targets_from_frame() inclusion rules (confirmed spec-affecting claims + confirmed
 honesty conditions only)
• Tests JSON round-trip preservation of nested fields

tests/test_plan.py


9. tests/test_plan_convergence.py 🧪 Tests +98/-0

Plan convergence gate unit tests with cycle detection

• Tests all convergence gate conditions: no tasks, uncovered targets, missing acceptance criteria,
 proposed tasks, dangling/cyclic dependencies, blocking risks
• Validates deterministic cycle path reporting (e.g., t1 -> t2 -> t1)
• Tests live-target override to catch frame drift

tests/test_plan_convergence.py


10. tests/test_plan_store.py 🧪 Tests +67/-0

Plan store persistence and validation tests

• Tests save/load round-trip, current-plan tracking, and slug validation
• Validates rejection of tampered internal/frame slugs and coexistence with same-slug frames

tests/test_plan_store.py


11. tests/test_render_plan.py 🧪 Tests +71/-0

Plan markdown renderer unit tests

• Tests topological ordering of tasks by dependencies, rendering of acceptance criteria and coverage
 targets
• Validates announcement blockquote from frame, risks section, rejection of rejected tasks
• Tests graceful rendering despite cycles (no crash)

tests/test_render_plan.py


12. tests/test_cli_plan.py 🧪 Tests +291/-0

Plan CLI integration tests with convergence and drift guards

• Comprehensive CLI integration tests covering all 14 plan moves with text and --json output
• Tests convergence gating, frame-drift detection (deleted/regressed source frame), and collision
 prevention
• Tests task/target resolution errors, inline flags, and learn/explain moves

tests/test_cli_plan.py


13. tests/test_spec_to_plan_skill.py 🧪 Tests +124/-0

spec-to-plan skill wrapper smoke tests

• Smoke tests for the spec-to-plan skill wrapper script via subprocess
• Tests portable CLI resolution, help output, status helper, and full session convergence/export
• Validates install-hint messaging when CLI is unavailable

tests/test_spec_to_plan_skill.py


14. .claude/skills/spec-to-plan/scripts/spec-to-plan.sh ✨ Enhancement +226/-0

spec-to-plan skill wrapper with status helper

• Portable bash wrapper for the plan engine; resolves devague CLI (installed or via uv run)
• Forwards all moves to devague plan  verbatim; adds status helper that reads convergence gate
 and suggests next move
• Includes deterministic cycle path reporting and gap-specific move suggestions

.claude/skills/spec-to-plan/scripts/spec-to-plan.sh


15. .claude/skills/spec-to-plan/SKILL.md 📝 Documentation +149/-0

spec-to-plan skill documentation

• Documents the spec-to-plan skill: purpose, moves, hard rules, and worked example
• Explains the convergence gate, frame-drift guards, and the status next-move helper
• Clarifies that the skill is first-party (origin = devague) and re-vendored by steward

.claude/skills/spec-to-plan/SKILL.md


16. .claude/skills/think/scripts/think.sh ✨ Enhancement +10/-5

Rename devague skill to think with forward-leg reference

• Renames the skill from devague to think (the idea→spec leg)
• Updates help text and usage examples to reflect the new skill name
• Adds note about the sibling /spec-to-plan skill for the forward leg

.claude/skills/think/scripts/think.sh


17. .claude/skills/think/SKILL.md 📝 Documentation +25/-18

think skill documentation with spec-to-plan handoff

• Renames skill from devague to think; updates description and documentation
• Clarifies that the skill drives the devague CLI (product name unchanged)
• References the sibling /spec-to-plan skill for the spec→plan leg
• Updates worked example and explains the next-leg handoff

.claude/skills/think/SKILL.md


18. tests/test_think_skill.py 🧪 Tests +6/-5

Update think skill tests for renamed skill

• Updates test file to reference the renamed think skill (was devague)
• Adjusts script path from .claude/skills/devague/ to .claude/skills/think/
• Updates help text assertion to match new skill messaging

tests/test_think_skill.py


19. docs/superpowers/specs/2026-05-23-devague-spec-to-plan-design.md 📝 Documentation +198/-0

spec-to-plan design specification document

• Comprehensive design document for the plan engine and skill reframe
• Covers architecture (domain model, convergence gate, store, renderer, CLI), moves, frame-drift
 guards, and acceptance criteria
• Includes worked example, testing strategy, and deferred features

docs/superpowers/specs/2026-05-23-devague-spec-to-plan-design.md


20. CLAUDE.md 📝 Documentation +52/-14

CLAUDE.md updated for plan engine and skill reframe

• Updates status to reflect both frame and plan engines (v0.4.0)
• Documents the plan engine architecture and workflow (5-step process)
• Clarifies the two operator skills: /think (idea→spec) and /spec-to-plan (spec→plan)
• Updates project intent to describe the full two-leg pipeline

CLAUDE.md


21. docs/skill-sources.md 📝 Documentation +13/-5

skill-sources.md updated for think and spec-to-plan

• Updates origin-skills table to reflect skill rename (devaguethink) and new spec-to-plan
 skill
• Clarifies that both skills are first-party (origin = devague) and re-vendored by steward
• Notes that think was renamed in 0.4.0 and steward must relearn the new names

docs/skill-sources.md


22. pyproject.toml ⚙️ Configuration changes +2/-2

Version bump to 0.4.0 with updated description

• Bumps version from 0.3.3 to 0.4.0
• Updates description to mention both spec and plan legs

pyproject.toml


23. README.md 📝 Documentation +38/-3

Reframe README to emphasize CLI identity and dual-engine pipeline

• Reframed README to clarify that devague is a deterministic CLI tool (not an agent), emphasizing
 its two-engine pipeline: frame engine (idea→spec) and plan engine (spec→plan)
• Added installation instructions and visual pipeline diagram
• Documented the two engines with their respective CLI command groups and convergence gates
• Added section on agent integration via /think and /spec-to-plan operator skills

README.md


24. CHANGELOG.md 📝 Documentation +12/-0

Document 0.4.0 release with plan engine and skill rename

• Added version 0.4.0 release notes documenting the new spec→plan engine with its domain modules,
 convergence gate, and JSON store
• Documented the nested devague plan CLI group with all subcommands and frame drift detection
• Recorded the skill rename from devague to /think and introduction of new /spec-to-plan skill
• Noted that product/CLI/repo name remains devague while only the skill identity changed

CHANGELOG.md


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented May 23, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. plan export writes docs/plans/ 📘 Rule violation ⚙ Maintainability
Description
The new devague plan export command writes its exported artifact to docs/plans/, but the
compliance rule requires exports to be written under docs/specs/. This can break downstream
tooling and violates the mandated export location policy.
Code

devague/cli/_commands/plan.py[R250-266]

Evidence
PR Compliance ID 738298 requires the export command to write output under docs/specs/. In
cmd_plan_export, the export path is constructed under docs/plans/ and written there via
out_path.write_text(...).

Rule 738298: Export command writes to docs/specs directory
devague/cli/_commands/plan.py[250-266]

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 plan export` currently writes exported plan markdown to `docs/plans/`, but compliance requires export output paths to be under `docs/specs/`.

## Issue Context
The export handler constructs `PLANS_OUT_DIR = Path("docs/plans")` and writes `<slug>.md` there.

## Fix Focus Areas
- devague/cli/_commands/plan.py[250-266]

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


2. Rejected deps bypass gate ✓ Resolved 🐞 Bug ≡ Correctness
Description
plan_convergence.evaluate() validates dependencies against all tasks (including rejected), but
the plan markdown renderer omits rejected tasks; this can allow devague plan export to succeed
while emitting a plan where a rendered task depends on a task id that never appears in the output.
Code

devague/plan_convergence.py[R86-97]

Evidence
The gate’s dependency integrity check uses ids = {t.id for t in plan.tasks} (includes rejected),
so a confirmed task depending on a rejected task is not flagged. The renderer filters out rejected
tasks (tasks = [t for t in plan.tasks if t.status != "rejected"]) and still prints dependency
lines, so exported markdown can reference task ids that never appear as tasks in the document.

devague/plan_convergence.py[86-97]
devague/render/plan_md.py[53-64]
devague/render/plan_md.py[78-83]
devague/cli/_commands/plan.py[250-266]
tests/test_render_plan.py[57-61]

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 plan convergence gate treats `rejected` tasks as valid dependency nodes, while the exported plan markdown intentionally omits rejected tasks. This mismatch can produce an exported plan whose rendered task dependency lines reference tasks that are not present anywhere in the exported output.

## Issue Context
- `devague/render/plan_md.py` filters rejected tasks out before topological ordering/rendering, but still prints each remaining task’s `deps`.
- `devague/plan_convergence.py` currently builds the dependency node set from *all* `plan.tasks`, so deps pointing to rejected tasks are not considered “unknown”, and cycles/dangling deps inside rejected tasks can also incorrectly affect convergence.

## Fix Focus Areas
- devague/plan_convergence.py[49-97]
- devague/render/plan_md.py[53-83]
- tests/test_plan_convergence.py[1-120]
- tests/test_render_plan.py[57-71]

## Suggested fix
1. In `plan_convergence._missing_dep_integrity()` and `_find_cycle()`, compute the dependency graph over **active tasks** only (e.g., tasks where `status != "rejected"`).
2. Treat a dependency edge from an active task to a rejected task as an integrity failure (e.g., `task t1 depends on rejected task t2`), because that prerequisite will not appear in the exported plan.
3. Ensure rejected-only cycles/dangling deps do not block convergence if rejected tasks are excluded from the exported artifact.
4. Add unit tests:
  - confirmed task depends on rejected task -> convergence fails with a clear message.
  - cycle only among rejected tasks -> does not block convergence of remaining active tasks.

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



Remediation recommended

3. Slug mismatch not rejected 🐞 Bug ☼ Reliability
Description
plan_store.load() validates that the stored plan.slug is filesystem-safe but does not verify it
matches the requested slug/filename, so a corrupted plan JSON can silently “rename” itself and
subsequent save()/export operations can write to a different plan file (potentially overwriting
another plan).
Code

devague/plan_store.py[R41-48]

Evidence
load(slug) reads a file determined by the argument slug, but returns the deserialized plan without
checking that plan.slug matches that argument. Later, save(plan) writes using
path_for(plan.slug), so any mismatch can redirect writes to a different plan filename.

devague/plan_store.py[30-48]

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

## Issue description
`plan_store.load(slug)` loads `.devague/plans/<slug>.json` but does not verify that the JSON’s internal `plan.slug` equals the requested `slug`. If the file is corrupted/tampered (even with a *safe* slug), the CLI can end up operating on a plan whose `slug` differs from the file it was loaded from, and then `plan_store.save(plan)` will write to `.devague/plans/<plan.slug>.json`—potentially overwriting an unrelated plan.

## Issue Context
The store already defends against path traversal via `validate_slug()`, but it does not defend against *in-repo data integrity* issues like a safe-but-mismatched slug.

## Fix Focus Areas
- devague/plan_store.py[30-48]
- tests/test_plan_store.py[1-70]

## Suggested fix
1. In `plan_store.load(slug)`, after `from_dict(...)` and `validate_slug(plan.slug)`, assert `plan.slug == slug`.
2. Given the 1:1 link contract (plan slug == frame slug), also consider validating `plan.frame_slug == plan.slug` (or at least `plan.frame_slug == slug`) and raising a `ValueError` if not.
3. Add a unit test that edits `.devague/plans/demo.json` to contain a different *safe* slug (e.g., `"slug": "other"`) and assert `plan_store.load("demo")` raises.

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


Grey Divider

Qodo Logo

- cmd_plan_show: single return path (S3516 — was always returning 0 from
  two branches).
- Hoist duplicated argparse help literals into _JSON_HELP / _TASK_ID_HELP
  constants (S1192).
- Split _find_cycle into _walk_from + _find_cycle to cut cognitive
  complexity below the threshold (S3776).

No behavior change; 147 tests pass, coverage 96.7%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread devague/cli/_commands/plan.py
Comment thread devague/plan_convergence.py
A rejected task is omitted from the exported plan-md, but the convergence gate
previously built the dependency node set from *all* tasks — so a confirmed task
depending on a rejected one passed the gate yet rendered a `depends on: tN` line
whose target never appears in the output.

_missing_dep_integrity now operates over active (non-rejected) tasks: a dep on a
rejected task is a distinct integrity failure ("depends on rejected task"), and
cycles/dangling deps living only among rejected tasks no longer block
convergence. _find_cycle takes the active task list. +2 tests.

Re Qodo #3292382004 (export to docs/plans/): working as designed — plans export
to docs/plans/, not docs/specs/ (the spec artifact); see the reply on the PR.

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

Copy link
Copy Markdown

@OriNachum
OriNachum merged commit 5efb965 into main May 23, 2026
8 checks passed
@OriNachum
OriNachum deleted the feat/plan-engine-think-rename branch May 23, 2026 07:51
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.

1 participant