devague 0.11.1__tar.gz → 0.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. {devague-0.11.1 → devague-0.12.0}/CHANGELOG.md +16 -0
  2. {devague-0.11.1 → devague-0.12.0}/CLAUDE.md +11 -0
  3. {devague-0.11.1 → devague-0.12.0}/PKG-INFO +1 -1
  4. devague-0.12.0/devague/cli/_commands/learn.py +407 -0
  5. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/plan.py +5 -1
  6. {devague-0.11.1 → devague-0.12.0}/docs/llm-guidance.md +3 -0
  7. devague-0.12.0/docs/skills.md +179 -0
  8. {devague-0.11.1 → devague-0.12.0}/pyproject.toml +1 -1
  9. devague-0.12.0/tests/test_cli_learn.py +150 -0
  10. {devague-0.11.1 → devague-0.12.0}/uv.lock +1 -1
  11. devague-0.11.1/devague/cli/_commands/learn.py +0 -187
  12. devague-0.11.1/tests/test_cli_learn.py +0 -40
  13. {devague-0.11.1 → devague-0.12.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  14. {devague-0.11.1 → devague-0.12.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  15. {devague-0.11.1 → devague-0.12.0}/.claude/skills/cicd/SKILL.md +0 -0
  16. {devague-0.11.1 → devague-0.12.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  17. {devague-0.11.1 → devague-0.12.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  18. {devague-0.11.1 → devague-0.12.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  19. {devague-0.11.1 → devague-0.12.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  20. {devague-0.11.1 → devague-0.12.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  21. {devague-0.11.1 → devague-0.12.0}/.claude/skills/communicate/SKILL.md +0 -0
  22. {devague-0.11.1 → devague-0.12.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  23. {devague-0.11.1 → devague-0.12.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  24. {devague-0.11.1 → devague-0.12.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  25. {devague-0.11.1 → devague-0.12.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  26. {devague-0.11.1 → devague-0.12.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  27. {devague-0.11.1 → devague-0.12.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  28. {devague-0.11.1 → devague-0.12.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  29. {devague-0.11.1 → devague-0.12.0}/.claude/skills/run-tests/SKILL.md +0 -0
  30. {devague-0.11.1 → devague-0.12.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  31. {devague-0.11.1 → devague-0.12.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  32. {devague-0.11.1 → devague-0.12.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  33. {devague-0.11.1 → devague-0.12.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  34. {devague-0.11.1 → devague-0.12.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  35. {devague-0.11.1 → devague-0.12.0}/.claude/skills/think/SKILL.md +0 -0
  36. {devague-0.11.1 → devague-0.12.0}/.claude/skills/think/scripts/think.sh +0 -0
  37. {devague-0.11.1 → devague-0.12.0}/.claude/skills/version-bump/SKILL.md +0 -0
  38. {devague-0.11.1 → devague-0.12.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  39. {devague-0.11.1 → devague-0.12.0}/.claude/skills.local.yaml.example +0 -0
  40. {devague-0.11.1 → devague-0.12.0}/.devague/current_plan +0 -0
  41. {devague-0.11.1 → devague-0.12.0}/.devague/frames/devague-0-6-0-ships-the-human-review-loop-devague.json +0 -0
  42. {devague-0.11.1 → devague-0.12.0}/.devague/frames/devague-now-ships-a-documented-spec-contract-every.json +0 -0
  43. {devague-0.11.1 → devague-0.12.0}/.devague/frames/devague-turns-a-converged-plan-into-parallel-simpl.json +0 -0
  44. {devague-0.11.1 → devague-0.12.0}/.devague/plans/devague-0-6-0-ships-the-human-review-loop-devague.json +0 -0
  45. {devague-0.11.1 → devague-0.12.0}/.devague/plans/devague-now-ships-a-documented-spec-contract-every.json +0 -0
  46. {devague-0.11.1 → devague-0.12.0}/.devague/plans/devague-turns-a-converged-plan-into-parallel-simpl.json +0 -0
  47. {devague-0.11.1 → devague-0.12.0}/.flake8 +0 -0
  48. {devague-0.11.1 → devague-0.12.0}/.github/workflows/publish.yml +0 -0
  49. {devague-0.11.1 → devague-0.12.0}/.github/workflows/security-checks.yml +0 -0
  50. {devague-0.11.1 → devague-0.12.0}/.github/workflows/tests.yml +0 -0
  51. {devague-0.11.1 → devague-0.12.0}/.gitignore +0 -0
  52. {devague-0.11.1 → devague-0.12.0}/.markdownlint-cli2.yaml +0 -0
  53. {devague-0.11.1 → devague-0.12.0}/.pre-commit-config.yaml +0 -0
  54. {devague-0.11.1 → devague-0.12.0}/LICENSE +0 -0
  55. {devague-0.11.1 → devague-0.12.0}/README.md +0 -0
  56. {devague-0.11.1 → devague-0.12.0}/culture.yaml +0 -0
  57. {devague-0.11.1 → devague-0.12.0}/devague/__init__.py +0 -0
  58. {devague-0.11.1 → devague-0.12.0}/devague/__main__.py +0 -0
  59. {devague-0.11.1 → devague-0.12.0}/devague/cli/__init__.py +0 -0
  60. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/__init__.py +0 -0
  61. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/capture.py +0 -0
  62. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/confirm.py +0 -0
  63. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/converge.py +0 -0
  64. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/explain.py +0 -0
  65. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/export.py +0 -0
  66. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/interrogate.py +0 -0
  67. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/list_frames.py +0 -0
  68. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/new.py +0 -0
  69. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/park.py +0 -0
  70. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/question.py +0 -0
  71. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/reject.py +0 -0
  72. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/review.py +0 -0
  73. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/show.py +0 -0
  74. {devague-0.11.1 → devague-0.12.0}/devague/cli/_commands/status.py +0 -0
  75. {devague-0.11.1 → devague-0.12.0}/devague/cli/_errors.py +0 -0
  76. {devague-0.11.1 → devague-0.12.0}/devague/cli/_frames.py +0 -0
  77. {devague-0.11.1 → devague-0.12.0}/devague/cli/_output.py +0 -0
  78. {devague-0.11.1 → devague-0.12.0}/devague/cli/_paths.py +0 -0
  79. {devague-0.11.1 → devague-0.12.0}/devague/cli/_plans.py +0 -0
  80. {devague-0.11.1 → devague-0.12.0}/devague/cli/_status.py +0 -0
  81. {devague-0.11.1 → devague-0.12.0}/devague/convergence.py +0 -0
  82. {devague-0.11.1 → devague-0.12.0}/devague/frame.py +0 -0
  83. {devague-0.11.1 → devague-0.12.0}/devague/plan.py +0 -0
  84. {devague-0.11.1 → devague-0.12.0}/devague/plan_convergence.py +0 -0
  85. {devague-0.11.1 → devague-0.12.0}/devague/plan_store.py +0 -0
  86. {devague-0.11.1 → devague-0.12.0}/devague/questions_io.py +0 -0
  87. {devague-0.11.1 → devague-0.12.0}/devague/render/__init__.py +0 -0
  88. {devague-0.11.1 → devague-0.12.0}/devague/render/frame_md.py +0 -0
  89. {devague-0.11.1 → devague-0.12.0}/devague/render/plan_md.py +0 -0
  90. {devague-0.11.1 → devague-0.12.0}/devague/render/review_md.py +0 -0
  91. {devague-0.11.1 → devague-0.12.0}/devague/render/spec_md.py +0 -0
  92. {devague-0.11.1 → devague-0.12.0}/devague/store.py +0 -0
  93. {devague-0.11.1 → devague-0.12.0}/docs/assign-to-workforce-worked-example.md +0 -0
  94. {devague-0.11.1 → devague-0.12.0}/docs/examples/contract-example.json +0 -0
  95. {devague-0.11.1 → devague-0.12.0}/docs/plans/2026-05-23-devague-0-6-0-ships-the-human-review-loop-devague.md +0 -0
  96. {devague-0.11.1 → devague-0.12.0}/docs/plans/2026-05-23-devague-now-ships-a-documented-spec-contract-every.md +0 -0
  97. {devague-0.11.1 → devague-0.12.0}/docs/plans/2026-05-23-devague-turns-a-converged-plan-into-parallel-simpl.md +0 -0
  98. {devague-0.11.1 → devague-0.12.0}/docs/reviews/spec-contract-frame-review.md +0 -0
  99. {devague-0.11.1 → devague-0.12.0}/docs/skill-sources.md +0 -0
  100. {devague-0.11.1 → devague-0.12.0}/docs/spec-contract.md +0 -0
  101. {devague-0.11.1 → devague-0.12.0}/docs/specs/2026-05-23-devague-0-6-0-ships-the-human-review-loop-devague.md +0 -0
  102. {devague-0.11.1 → devague-0.12.0}/docs/specs/2026-05-23-devague-now-ships-a-documented-spec-contract-every.md +0 -0
  103. {devague-0.11.1 → devague-0.12.0}/docs/specs/2026-05-23-devague-turns-a-converged-plan-into-parallel-simpl.md +0 -0
  104. {devague-0.11.1 → devague-0.12.0}/docs/superpowers/plans/2026-05-22-specifix-onboarding.md +0 -0
  105. {devague-0.11.1 → devague-0.12.0}/docs/superpowers/plans/2026-05-23-devague-rename.md +0 -0
  106. {devague-0.11.1 → devague-0.12.0}/docs/superpowers/plans/2026-05-23-devague-working-backwards-engine.md +0 -0
  107. {devague-0.11.1 → devague-0.12.0}/docs/superpowers/specs/2026-05-22-specifix-onboarding-design.md +0 -0
  108. {devague-0.11.1 → devague-0.12.0}/docs/superpowers/specs/2026-05-23-devague-spec-to-plan-design.md +0 -0
  109. {devague-0.11.1 → devague-0.12.0}/docs/superpowers/specs/2026-05-23-devague-working-backwards-design.md +0 -0
  110. {devague-0.11.1 → devague-0.12.0}/sonar-project.properties +0 -0
  111. {devague-0.11.1 → devague-0.12.0}/tests/__init__.py +0 -0
  112. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_affordances.py +0 -0
  113. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_chassis.py +0 -0
  114. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_converge_export.py +0 -0
  115. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_errors.py +0 -0
  116. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_moves.py +0 -0
  117. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_output.py +0 -0
  118. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_paths.py +0 -0
  119. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_plan.py +0 -0
  120. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_question.py +0 -0
  121. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_review.py +0 -0
  122. {devague-0.11.1 → devague-0.12.0}/tests/test_cli_status.py +0 -0
  123. {devague-0.11.1 → devague-0.12.0}/tests/test_contract.py +0 -0
  124. {devague-0.11.1 → devague-0.12.0}/tests/test_convergence.py +0 -0
  125. {devague-0.11.1 → devague-0.12.0}/tests/test_frame.py +0 -0
  126. {devague-0.11.1 → devague-0.12.0}/tests/test_offline.py +0 -0
  127. {devague-0.11.1 → devague-0.12.0}/tests/test_package.py +0 -0
  128. {devague-0.11.1 → devague-0.12.0}/tests/test_plan.py +0 -0
  129. {devague-0.11.1 → devague-0.12.0}/tests/test_plan_convergence.py +0 -0
  130. {devague-0.11.1 → devague-0.12.0}/tests/test_plan_store.py +0 -0
  131. {devague-0.11.1 → devague-0.12.0}/tests/test_render.py +0 -0
  132. {devague-0.11.1 → devague-0.12.0}/tests/test_render_plan.py +0 -0
  133. {devague-0.11.1 → devague-0.12.0}/tests/test_review_loop_integration.py +0 -0
  134. {devague-0.11.1 → devague-0.12.0}/tests/test_review_loop_invariants.py +0 -0
  135. {devague-0.11.1 → devague-0.12.0}/tests/test_spec_to_plan_skill.py +0 -0
  136. {devague-0.11.1 → devague-0.12.0}/tests/test_store.py +0 -0
  137. {devague-0.11.1 → devague-0.12.0}/tests/test_think_skill.py +0 -0
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/). This project
6
6
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.12.0] - 2026-05-24
9
+
10
+ ### Added
11
+
12
+ - `devague learn` now teaches skill authoring (#34): an optional topic arg —
13
+ `devague learn skills` (and `skills:all` / `skills:NAME`) — emits a
14
+ self-contained recipe for authoring the three operator skills (think /
15
+ spec-to-plan / assign-to-workforce) in any runtime, framed as consent-gated,
16
+ no-clobber instructions the agent follows. The CLI never writes skill files
17
+ (#20); the agent does, with user consent. Bare `devague learn` appends the
18
+ condensed authoring section.
19
+ - `docs/skills.md`: new canonical authoring guide (file layout, frontmatter incl.
20
+ the `type:` gotcha for culture backends, the portable resolver pattern, the
21
+ skill-to-devague contract, and the three human gates), referenced from
22
+ `docs/llm-guidance.md` and `devague plan learn`.
23
+
8
24
  ## [0.11.1] - 2026-05-24
9
25
 
10
26
  ### Changed
@@ -4,6 +4,17 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Status
6
6
 
7
+ **`learn` now teaches skill authoring (0.12.0, #34).** `devague learn` gained an
8
+ optional topic arg: `devague learn skills` (and `skills:all` / `skills:<name>`)
9
+ emits a self-contained recipe for authoring the three operator skills (`think` /
10
+ `spec-to-plan` / `assign-to-workforce`) in any runtime — file layout, frontmatter
11
+ (incl. the `type:` gotcha for culture backends), the portable resolver pattern,
12
+ the skill↔devague contract, and the consent + no-clobber rules. It is framed as
13
+ instructions the *agent* follows: the CLI never writes skill files (it stays
14
+ deterministic and non-orchestrating, #20); the agent creates them with user
15
+ consent. Bare `devague learn` appends the condensed authoring section. Canonical
16
+ long-form guide: `docs/skills.md`.
17
+
7
18
  **`status` internalised into the CLI (0.11.0, #30).** The next-move helper that
8
19
  used to live as embedded Python inside the `think` / `spec-to-plan` skill
9
20
  wrappers is now a first-class, read-only CLI verb — `devague status` and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devague
3
- Version: 0.11.1
3
+ Version: 0.12.0
4
4
  Summary: devague — turns a vague feature idea into a buildable spec, then a buildable plan.
5
5
  Project-URL: Homepage, https://github.com/agentculture/devague
6
6
  Project-URL: Issues, https://github.com/agentculture/devague/issues
@@ -0,0 +1,407 @@
1
+ """``devague learn`` — teach the working-backwards method and the moves."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import __version__
8
+ from devague.cli._errors import EXIT_USER_ERROR, DevagueError
9
+ from devague.cli._output import emit_result
10
+
11
+ MOVES = {
12
+ "new": "Start a frame from the announcement (pretend it shipped).",
13
+ "capture": "Record and classify a claim (audience, after_state, boundary, ...).",
14
+ "interrogate": "Pressure-test a claim: honesty conditions, hard questions, contradictions.",
15
+ "confirm": "Confirm a claim or honesty condition (user-only — no fabricated rigor).",
16
+ "reject": "Reject a claim or honesty condition.",
17
+ "park": "Move uncertainty into first-class open vagueness instead of forcing an answer.",
18
+ "converge": "Check whether the frame is solid enough to export a spec.",
19
+ "export": "Write the buildable spec — only once the frame converges.",
20
+ "status": "Report where the frame stands + the recommended next move (read-only).",
21
+ "show": "Render the Announcement Frame.",
22
+ "list": "List frames.",
23
+ }
24
+
25
+ FIRST_QUESTION = "What's the announcement?"
26
+ SUPPORTING_PROMPT = (
27
+ "Pretend this shipped successfully. What would you announce to users, "
28
+ "teammates, or yourself?"
29
+ )
30
+
31
+ # The portable, runtime-agnostic operating contract for any assisting model
32
+ # (devague#19). The full version lives in the guidance doc; this is the core
33
+ # surfaced in every `learn`. These rules are what make convergence mean something.
34
+ #
35
+ # `docs/` is not shipped in the wheel (only the `devague` package is), and an
36
+ # installed devague is operated from an arbitrary repo — so a bare relative path
37
+ # wouldn't resolve for most consumers. The portable, always-resolvable reference
38
+ # is the canonical URL; the in-repo path is kept for contributors.
39
+ GUIDANCE_DOC_URL = "https://github.com/agentculture/devague/blob/main/docs/llm-guidance.md"
40
+ GUIDANCE_DOC_REPO_PATH = "docs/llm-guidance.md"
41
+
42
+ # What devague is NOT — the framing that keeps it from degrading into a form.
43
+ NOT_A = (
44
+ "a wizard (no fixed prompt sequence)",
45
+ "a scripted questionnaire (you don't read questions off a form)",
46
+ "a PRD generator (it never invents content to fill a template)",
47
+ )
48
+
49
+ # Assign-to-workforce invocation guidance: when and how to fan out a converged
50
+ # plan's waves to a workforce. This is a cited skill convention, not an
51
+ # orchestration engine (devague#20). The three human gates ensure spec, plan,
52
+ # and PR pass human review.
53
+ ASSIGN_TO_WORKFORCE_GUIDANCE = {
54
+ "title": "Assign-to-workforce: parallel plan execution",
55
+ "when_to_fan_out": (
56
+ "Once a plan converges (all targets covered, all tasks have acceptance "
57
+ "criteria, dependency graph is acyclic), you can fan out its waves to "
58
+ "parallel agents working in isolated git worktrees."
59
+ ),
60
+ "prerequisites": ("A converged plan with deterministic dependency waves (devague plan waves)."),
61
+ "human_gates": (
62
+ "The human approves at exactly three points: (1) the exported spec, "
63
+ "(2) the implementation split plan (plan/tasks map, per-task "
64
+ "agent/model assignment, go/no-go to workforce), and (3) the final PR. "
65
+ "The human is NOT in the per-task worktree-merge loop."
66
+ ),
67
+ "worktree_isolation": (
68
+ "Each task runs in an isolated git worktree (one per task per wave). "
69
+ "This keeps file-contention safe: overlapping same-file changes "
70
+ "surface as merge conflicts at reconcile time, not live races."
71
+ ),
72
+ "main_agent_merge_gate": (
73
+ "The main agent gates each subagent worktree merge with TDD: tests "
74
+ "pass before AND after merge. No human per-task merge decision."
75
+ ),
76
+ "tdd_acceptance_criteria": (
77
+ "Each task carries TDD acceptance criteria (tests first) scoped "
78
+ "tightly enough for a simpler/cheaper model to build. The tests "
79
+ "validate that the task was built correctly — no re-deriving the "
80
+ "design needed."
81
+ ),
82
+ "not_orchestration": (
83
+ "devague itself does not orchestrate: it does not spawn subagents, "
84
+ "manage worktrees, mark tasks done, or pick a backend. Orchestration "
85
+ "is a cited skill convention (assign-to-workforce), not part of the "
86
+ "deterministic CLI."
87
+ ),
88
+ }
89
+
90
+ # The anti-fabrication rules. Agent-agnostic: repo-specific agreements live in
91
+ # your agent's main instruction file (AGENTS.md, CLAUDE.md, a system prompt, …),
92
+ # not here.
93
+ OPERATING_RULES = (
94
+ "LLM proposals stay proposed — capture your own ideas with --origin llm; "
95
+ "never confirm your own proposal. Confirmation is a user-only decision.",
96
+ "Honesty conditions route through the user — propose freely with "
97
+ "'interrogate --honesty'; the user owns whether each one holds.",
98
+ "Park real unknowns instead of papering over them — 'park' a genuine "
99
+ "unknown rather than writing confident prose that hides the gap.",
100
+ "Converge, don't vibe — 'export' is gated on 'converge'; resolve every "
101
+ "listed gap instead of declaring readiness on a hunch.",
102
+ "Order is adaptive — the ten stages are an artifact shape, not a mandatory "
103
+ "conversation order; capture what the user gives you and circle back.",
104
+ )
105
+
106
+ # The canonical guided sequence (devague#4). The engine is move-driven, not a
107
+ # rigid wizard — this is the recommended arc, with the move that advances each.
108
+ STAGES = [
109
+ ("Announcement", "what are we saying shipped?", "new"),
110
+ ("Audience", "who needs this?", "capture --kind audience"),
111
+ ("After", "what changed for them?", "capture --kind after_state"),
112
+ ("Matter", "why is it worth doing?", "capture --kind why_it_matters"),
113
+ ("Before", "what pain made this necessary?", "capture --kind before_state"),
114
+ ("Honest", "what must be true for the announcement to be honest?", "interrogate --honesty"),
115
+ ("FAQ", "what hard questions remain?", "interrogate --hard-question"),
116
+ ("Boundaries", "what are we not promising?", "capture --kind boundary"),
117
+ ("Success", "how will we know?", "capture --kind success_signal"),
118
+ ("Spec", "what should be built?", "converge -> export"),
119
+ ]
120
+
121
+ _TEXT = (
122
+ "devague turns a vague idea into a buildable spec by working backwards.\n\n"
123
+ f"First question: {FIRST_QUESTION}\n"
124
+ f" {SUPPORTING_PROMPT}\n\n"
125
+ "Start from that announcement, then build an Announcement Frame by capturing\n"
126
+ "claims, interrogating them, parking what's still vague, and converging.\n"
127
+ "The arc emerges from the moves; it is not a fixed wizard. You (the agent)\n"
128
+ "choose the next move; devague tracks state. LLM-proposed claims and honesty\n"
129
+ "conditions stay 'proposed' until the user confirms them.\n\n"
130
+ "Guided stages (the recommended sequence — drive them with the moves):\n"
131
+ + "\n".join(
132
+ f" {i:>2}. {name:<13} {prompt} [{move}]"
133
+ for i, (name, prompt, move) in enumerate(STAGES, 1)
134
+ )
135
+ + "\n\nMoves:\n"
136
+ + "\n".join(f" {name:<11} {desc}" for name, desc in MOVES.items())
137
+ + "\n\ndevague is NOT:\n"
138
+ + "\n".join(f" - {n}" for n in NOT_A)
139
+ + "\n\nOperating rules (the anti-fabrication contract — do not violate):\n"
140
+ + "\n".join(f" - {r}" for r in OPERATING_RULES)
141
+ + "\n\n"
142
+ + "Assign-to-workforce: parallel plan execution via subagent-driven development\n"
143
+ f" When: {ASSIGN_TO_WORKFORCE_GUIDANCE['when_to_fan_out']}\n"
144
+ f" Prerequisites: {ASSIGN_TO_WORKFORCE_GUIDANCE['prerequisites']}\n"
145
+ f" Human gates (3): {ASSIGN_TO_WORKFORCE_GUIDANCE['human_gates']}\n"
146
+ f" Safety: {ASSIGN_TO_WORKFORCE_GUIDANCE['worktree_isolation']}\n"
147
+ f" Main agent: {ASSIGN_TO_WORKFORCE_GUIDANCE['main_agent_merge_gate']}\n"
148
+ f" TDD: {ASSIGN_TO_WORKFORCE_GUIDANCE['tdd_acceptance_criteria']}\n"
149
+ f" Scope: {ASSIGN_TO_WORKFORCE_GUIDANCE['not_orchestration']}\n"
150
+ + "\n\nFull portable guidance for any assisting model:\n"
151
+ f" {GUIDANCE_DOC_URL}\n"
152
+ f" (in the devague repo: {GUIDANCE_DOC_REPO_PATH})\n"
153
+ "Agent-agnostic; your repo-specific agreements live in your agent's main\n"
154
+ "instruction file — AGENTS.md, CLAUDE.md, a system prompt — not there."
155
+ )
156
+
157
+
158
+ # --- Authoring the three operator skills (devague#34) -----------------------
159
+ #
160
+ # `devague learn` knows the operator skills exist but never taught an agent how
161
+ # to *author* them. This section closes that gap: it is written as instructions
162
+ # the assisting agent follows — devague itself never writes skill files (it stays
163
+ # deterministic and non-orchestrating, #20); the agent does, with user consent.
164
+ #
165
+ # As with GUIDANCE_DOC_URL, neither `docs/` nor `.claude/skills/` ship in the
166
+ # wheel, so the canonical source is referenced by URL — always resolvable for an
167
+ # agent operating an installed devague from an arbitrary repo.
168
+ SKILLS_DOC_URL = "https://github.com/agentculture/devague/blob/main/docs/skills.md"
169
+ SKILLS_DOC_REPO_PATH = "docs/skills.md"
170
+ _SKILLS_BROWSE = "https://github.com/agentculture/devague/blob/main/.claude/skills"
171
+ _SKILLS_RAW = "https://raw.githubusercontent.com/agentculture/devague/main/.claude/skills"
172
+
173
+ # Consent + no-clobber rules — the agent behavior `learn` enforces.
174
+ SKILLS_CONSENT = (
175
+ "These are instructions for you, the assisting agent — not a CLI side effect. "
176
+ "devague never writes skill files; you do.",
177
+ "If the user explicitly asked you to set up or learn the operator skills, "
178
+ "create them now using the recipe and per-skill sources below.",
179
+ "If you reached this by default (e.g. a bare 'devague learn'), ASK the user "
180
+ "for permission before creating anything.",
181
+ "Never overwrite an existing skill of the same name — if <skills>/<name>/ "
182
+ "already exists, stop and surface the conflict instead of clobbering it.",
183
+ )
184
+
185
+ # The authoring recipe. Structured so `--json` can carry it verbatim.
186
+ SKILL_AUTHORING = {
187
+ "layout": (
188
+ "Each skill is one directory in your runtime's skills folder "
189
+ "(Claude Code: .claude/skills/<name>/):\n"
190
+ " <skills>/<name>/SKILL.md frontmatter + the operating doc\n"
191
+ " <skills>/<name>/scripts/<name>.sh portable CLI resolver (executable)"
192
+ ),
193
+ "frontmatter": (
194
+ "SKILL.md opens with YAML frontmatter:\n"
195
+ " name: <name>\n"
196
+ " description: >\n"
197
+ " one paragraph — what it does, when to use it, and that it is\n"
198
+ " authored in agentculture/devague.\n"
199
+ " type: command # REQUIRED by culture/agex backends; a SKILL.md\n"
200
+ " # without it is SILENTLY SKIPPED. Harmless on claude-code."
201
+ ),
202
+ "resolver": (
203
+ "scripts/<name>.sh resolves the devague CLI portably and forwards every "
204
+ "move verbatim:\n"
205
+ " 1. an installed 'devague' on PATH (the normal case);\n"
206
+ " 2. else 'uv run devague' when inside a devague checkout;\n"
207
+ " 3. else print the hint 'uv tool install devague' and exit non-zero.\n"
208
+ "Copy the exact script from the per-skill source below — don't hand-write it."
209
+ ),
210
+ "contract": (
211
+ "The skill drives the deterministic CLI and adds no logic of its own:\n"
212
+ " - drive devague through its moves; never edit .devague/ state by hand;\n"
213
+ " - LLM-proposed claims/honesty conditions stay 'proposed' — the user confirms;\n"
214
+ " - three human gates only — the exported spec, the implementation split\n"
215
+ " plan, and the final PR. devague never orchestrates (#20)."
216
+ ),
217
+ }
218
+
219
+ # The three operator skills, in workflow order.
220
+ OPERATOR_SKILLS = (
221
+ {
222
+ "name": "think",
223
+ "leg": "idea -> spec (working backwards)",
224
+ "role": (
225
+ "Drives the flat devague verbs. Start from the announcement, capture "
226
+ "and classify claims, interrogate them, park open vagueness, and export "
227
+ "a spec only once the frame converges."
228
+ ),
229
+ },
230
+ {
231
+ "name": "spec-to-plan",
232
+ "leg": "spec -> plan (working forwards)",
233
+ "role": (
234
+ "Drives the 'devague plan' group. Seed a plan from a converged frame, "
235
+ "cover every target with TDD-accepted tasks in an acyclic order, and "
236
+ "export a plan only once it converges."
237
+ ),
238
+ },
239
+ {
240
+ "name": "assign-to-workforce",
241
+ "leg": "plan -> parallel implementation",
242
+ "role": (
243
+ "Reads 'devague plan waves' and fans out one agent per task per wave in "
244
+ "isolated git worktrees, with main-agent TDD-gated merges. Three human "
245
+ "gates; devague never orchestrates (#20)."
246
+ ),
247
+ },
248
+ )
249
+
250
+ _SKILL_NAMES = tuple(s["name"] for s in OPERATOR_SKILLS)
251
+
252
+
253
+ def _skill_source(name: str) -> dict[str, str]:
254
+ """Canonical, always-resolvable source locations for one skill's files."""
255
+ return {
256
+ "browse": f"{_SKILLS_BROWSE}/{name}/SKILL.md",
257
+ "skill_md_raw": f"{_SKILLS_RAW}/{name}/SKILL.md",
258
+ "script_raw": f"{_SKILLS_RAW}/{name}/scripts/{name}.sh",
259
+ "repo_path": f".claude/skills/{name}/",
260
+ }
261
+
262
+
263
+ def _skills_payload(names: tuple[str, ...]) -> dict[str, object]:
264
+ """Structured skills-authoring payload for the selected skill names."""
265
+ return {
266
+ "consent": list(SKILLS_CONSENT),
267
+ "authoring": SKILL_AUTHORING,
268
+ "operator_skills": [
269
+ {**s, **_skill_source(s["name"])} for s in OPERATOR_SKILLS if s["name"] in names
270
+ ],
271
+ "doc_url": SKILLS_DOC_URL,
272
+ "doc_repo_path": SKILLS_DOC_REPO_PATH,
273
+ }
274
+
275
+
276
+ def _skills_text(names: tuple[str, ...], *, full: bool) -> str:
277
+ """Render the skills-authoring section. `full` adds per-skill source URLs."""
278
+ parts = [
279
+ "Authoring your operator skills (create them with user consent)",
280
+ "=============================================================",
281
+ "",
282
+ "devague is driven by three operator skills. Recreate them in your own "
283
+ "runtime so you can drive devague the same way everywhere.",
284
+ "",
285
+ "Consent — read before creating anything:",
286
+ ]
287
+ parts += [f" - {c}" for c in SKILLS_CONSENT]
288
+ parts += [
289
+ "",
290
+ "Recipe:",
291
+ SKILL_AUTHORING["layout"],
292
+ "",
293
+ SKILL_AUTHORING["frontmatter"],
294
+ "",
295
+ SKILL_AUTHORING["resolver"],
296
+ "",
297
+ SKILL_AUTHORING["contract"],
298
+ "",
299
+ "The three skills:",
300
+ ]
301
+ for s in OPERATOR_SKILLS:
302
+ if s["name"] not in names:
303
+ continue
304
+ parts.append(f" {s['name']} [{s['leg']}]")
305
+ parts.append(f" {s['role']}")
306
+ if full:
307
+ src = _skill_source(s["name"])
308
+ parts.append(f" SKILL.md: {src['skill_md_raw']}")
309
+ parts.append(f" script: {src['script_raw']}")
310
+ parts.append(f" browse: {src['browse']}")
311
+ parts.append("")
312
+ if not full:
313
+ parts.append(
314
+ "Run 'devague learn skills:all' for the source URLs of all three, or "
315
+ "'devague learn skills:<name>' for one."
316
+ )
317
+ parts.append("")
318
+ parts += [
319
+ f"Full authoring guide: {SKILLS_DOC_URL}",
320
+ f" (in the devague repo: {SKILLS_DOC_REPO_PATH})",
321
+ ]
322
+ return "\n".join(parts)
323
+
324
+
325
+ def _resolve_topic(topic: str | None) -> tuple[str, tuple[str, ...]]:
326
+ """Map a learn topic to (mode, skill-names). Raises on an unknown topic."""
327
+ if topic is None:
328
+ return "bare", _SKILL_NAMES
329
+ if topic == "skills":
330
+ return "skills", _SKILL_NAMES
331
+ if topic.startswith("skills:"):
332
+ sub = topic.split(":", 1)[1]
333
+ if sub == "all":
334
+ return "skills_all", _SKILL_NAMES
335
+ if sub in _SKILL_NAMES:
336
+ return "skill", (sub,)
337
+ raise DevagueError(
338
+ EXIT_USER_ERROR,
339
+ f"unknown skill: {sub}",
340
+ "skills: " + ", ".join(_SKILL_NAMES) + ", or 'all'",
341
+ )
342
+ raise DevagueError(
343
+ EXIT_USER_ERROR,
344
+ f"unknown learn topic: {topic}",
345
+ "topics: skills, skills:all, skills:<name>",
346
+ )
347
+
348
+
349
+ def cmd_learn(args: argparse.Namespace) -> int:
350
+ mode, names = _resolve_topic(getattr(args, "topic", None))
351
+ json_mode = getattr(args, "json", False)
352
+
353
+ if mode == "bare":
354
+ if json_mode:
355
+ emit_result(
356
+ {
357
+ "tool": "devague",
358
+ "version": __version__,
359
+ "first_question": FIRST_QUESTION,
360
+ "supporting_prompt": SUPPORTING_PROMPT,
361
+ "stages": [
362
+ {"step": i, "name": name, "prompt": prompt, "move": move}
363
+ for i, (name, prompt, move) in enumerate(STAGES, 1)
364
+ ],
365
+ "moves": list(MOVES),
366
+ "not_a": list(NOT_A),
367
+ "operating_rules": list(OPERATING_RULES),
368
+ "guidance_doc": GUIDANCE_DOC_URL,
369
+ "guidance_doc_repo_path": GUIDANCE_DOC_REPO_PATH,
370
+ "assign_to_workforce": ASSIGN_TO_WORKFORCE_GUIDANCE,
371
+ "skills": _skills_payload(names),
372
+ "summary": _TEXT,
373
+ },
374
+ json_mode=True,
375
+ )
376
+ else:
377
+ emit_result(_TEXT + "\n\n" + _skills_text(names, full=False), json_mode=False)
378
+ else:
379
+ # A skills topic: emit just the authoring section. The JSON envelope
380
+ # carries the same tool/version identity as the bare payload so the
381
+ # whole `learn` command family shares one schema (Qodo PR #35).
382
+ full = mode in ("skills_all", "skill")
383
+ if json_mode:
384
+ emit_result(
385
+ {
386
+ "tool": "devague",
387
+ "version": __version__,
388
+ "topic": getattr(args, "topic", None),
389
+ **_skills_payload(names),
390
+ },
391
+ json_mode=True,
392
+ )
393
+ else:
394
+ emit_result(_skills_text(names, full=full), json_mode=False)
395
+ return 0
396
+
397
+
398
+ def register(sub: argparse._SubParsersAction) -> None:
399
+ p = sub.add_parser("learn", help="Teach devague's method and how to author its skills.")
400
+ p.add_argument(
401
+ "topic",
402
+ nargs="?",
403
+ default=None,
404
+ help="Optional: 'skills', 'skills:all', or 'skills:<name>' to teach skill authoring.",
405
+ )
406
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
407
+ p.set_defaults(func=cmd_learn)
@@ -394,7 +394,11 @@ def cmd_plan_learn(args: argparse.Namespace) -> int:
394
394
  "anti-fabrication rule as claims. A plan exports only once it converges:\n"
395
395
  "every target covered by a confirmed task, every confirmed task has acceptance\n"
396
396
  "criteria, the dependency graph is acyclic, and no blocking risk remains.\n\n"
397
- "Moves:\n" + "\n".join(f" {name:<9} {desc}" for name, desc in PLAN_MOVES.items())
397
+ "Moves:\n"
398
+ + "\n".join(f" {name:<9} {desc}" for name, desc in PLAN_MOVES.items())
399
+ + "\n\nTo author the operator skills (think / spec-to-plan / "
400
+ "assign-to-workforce) in\nyour own runtime, run 'devague learn skills' "
401
+ "(with user consent)."
398
402
  )
399
403
  if getattr(args, "json", False):
400
404
  emit_result(
@@ -126,5 +126,8 @@ same stream. Exit code is `0` on success, non-zero on user error (with a
126
126
  - **Entity model + per-move contract:** [`spec-contract.md`](spec-contract.md).
127
127
  - **Live shape of any move:** run it with `--json`, or `devague learn` /
128
128
  `devague explain <move>`.
129
+ - **Authoring the operator skills:** [`skills.md`](skills.md), or run `devague
130
+ learn skills` — how to create the `think` / `spec-to-plan` /
131
+ `assign-to-workforce` skills in your own runtime (with user consent).
129
132
  - **Repo-specific working agreements:** your agent's main instruction file
130
133
  (`AGENTS.md`, `CLAUDE.md`, system prompt, …) — not this document.
@@ -0,0 +1,179 @@
1
+ # Authoring devague's operator skills
2
+
3
+ This is the canonical guide for **authoring the three devague operator skills**
4
+ in an agent runtime — what files they need, where they live, the entry-point
5
+ shape, and the contract between a skill and devague's state. It is the long-form
6
+ companion to `devague learn skills`, which surfaces a condensed, always-available
7
+ version of the same recipe.
8
+
9
+ These three skills are devague's **outbound** skills — devague is their
10
+ origin/upstream (it dogfoods them to drive its own CLI), and steward re-vendors
11
+ them to the rest of the AgentCulture mesh under the `cite, don't import` policy.
12
+ See [`skill-sources.md`](skill-sources.md) for the provenance map. This guide is
13
+ about *creating* them in a runtime that doesn't have them yet.
14
+
15
+ ## Consent — instructions for the assisting agent
16
+
17
+ devague **never writes skill files**; it stays a deterministic, non-orchestrating
18
+ CLI (#20). *You*, the agent, create the files — with the user in the loop:
19
+
20
+ - If the user explicitly asked you to set up or learn the operator skills,
21
+ create them now using the recipe below.
22
+ - If you reached this by default (for example a bare `devague learn`), **ask the
23
+ user for permission** before creating anything.
24
+ - **Never overwrite** an existing skill of the same name. If
25
+ `<skills>/<name>/` already exists, stop and surface the conflict to the user
26
+ instead of clobbering it.
27
+
28
+ ## Minimum file structure
29
+
30
+ Each skill is one directory in your runtime's skills folder. For the Claude Code
31
+ backend that is `.claude/skills/<name>/`:
32
+
33
+ ```text
34
+ <skills>/<name>/
35
+ ├── SKILL.md # YAML frontmatter + the operating doc
36
+ └── scripts/
37
+ └── <name>.sh # portable CLI resolver (executable)
38
+ ```
39
+
40
+ `SKILL.md` opens with YAML frontmatter:
41
+
42
+ ```yaml
43
+ ---
44
+ name: <name>
45
+ description: >
46
+ One paragraph — what the skill does, when to use it (the trigger phrases),
47
+ and that it is authored in agentculture/devague.
48
+ type: command
49
+ ---
50
+ ```
51
+
52
+ `type: command` is **required by the culture/agex backends** — a `SKILL.md`
53
+ without it is *silently skipped* by `backends/claude_code/probe.py`. It is
54
+ harmless on the `claude-code` backend, so always include it for portability.
55
+
56
+ The body of `SKILL.md` (after the frontmatter) is the operating doc: a short
57
+ intro, a "How to run" section, a moves/commands table, the hard rules, the
58
+ output contract, and a worked example. The three canonical skills are the
59
+ reference for this shape — copy from the sources linked below rather than
60
+ inventing structure.
61
+
62
+ ## The entry-point script — portable CLI resolver
63
+
64
+ `scripts/<name>.sh` resolves the devague CLI portably and **forwards every move
65
+ verbatim**, so the CLI's own parser owns the surface and new devague moves work
66
+ without editing the script. The resolution order is:
67
+
68
+ 1. an installed `devague` on `PATH` (the normal mesh case);
69
+ 2. else `uv run devague` when inside a devague checkout (walk up to a
70
+ `pyproject.toml` whose `name = "devague"`);
71
+ 3. else print the hint `uv tool install devague` and exit non-zero.
72
+
73
+ The shape (copy the exact script from the per-skill source — don't hand-write it):
74
+
75
+ ```bash
76
+ #!/usr/bin/env bash
77
+ set -euo pipefail
78
+
79
+ DEVAGUE=()
80
+ resolve_devague() {
81
+ if command -v devague >/dev/null 2>&1; then
82
+ DEVAGUE=(devague) # installed tool — the normal mesh case
83
+ return 0
84
+ fi
85
+ # Local-dev fallback: inside the devague checkout, run via uv.
86
+ local dir="$PWD"
87
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
88
+ if [ -f "$dir/pyproject.toml" ] \
89
+ && grep -q '^name = "devague"' "$dir/pyproject.toml" 2>/dev/null; then
90
+ command -v uv >/dev/null 2>&1 && { DEVAGUE=(uv run devague); return 0; }
91
+ break
92
+ fi
93
+ dir=$(dirname "$dir")
94
+ done
95
+ echo "error: devague CLI not found." >&2
96
+ echo "hint: install it with \`uv tool install devague\`." >&2
97
+ return 1
98
+ }
99
+
100
+ resolve_devague
101
+ exec "${DEVAGUE[@]}" "$@"
102
+ ```
103
+
104
+ `assign-to-workforce` adds one orchestration layer on top of this resolver (a
105
+ `split-plan` subcommand that renders `devague plan waves --json` as a human-facing
106
+ table); the underlying `waves` call is still forwarded verbatim.
107
+
108
+ ## The skill ↔ devague contract
109
+
110
+ A skill drives the deterministic CLI and adds no business logic of its own:
111
+
112
+ - **Drive devague through its moves; never edit `.devague/` state by hand.** The
113
+ CLI owns the frame/plan JSON under `.devague/`. A skill reads results from
114
+ stdout (`--json` for structured payloads) and acts on them — it does not poke
115
+ at the store directly.
116
+ - **LLM-proposed claims and honesty conditions stay `proposed`.** Confirmation is
117
+ a user-only decision; the agent surfaces proposals and lets the user confirm or
118
+ reject. This anti-fabrication contract is what makes convergence mean something
119
+ (see [`llm-guidance.md`](llm-guidance.md)).
120
+ - **Three human gates only:** the exported spec, the implementation split plan
121
+ (task map + per-task agent/model proposal + go/no-go), and the final PR.
122
+ devague never orchestrates — it only *describes* the dependency graph via
123
+ `devague plan waves` (#20). The fan-out, worktree management, and TDD-gated
124
+ merges live in the `assign-to-workforce` skill and the operating agent, not in
125
+ the CLI and not in a CI runner.
126
+
127
+ ## The three operator skills
128
+
129
+ | Skill | Leg | What it drives |
130
+ |-------|-----|----------------|
131
+ | `think` | idea → spec (working backwards) | the flat `devague <move>` verbs |
132
+ | `spec-to-plan` | spec → plan (working forwards) | the `devague plan <move>` group |
133
+ | `assign-to-workforce` | plan → parallel implementation | reads `devague plan waves` (read-only) |
134
+
135
+ ### `think` — idea → buildable spec
136
+
137
+ Start from the announcement ("pretend it shipped — what would you announce?"),
138
+ build an Announcement Frame by capturing and classifying claims, pressure-test
139
+ them with honesty conditions and hard questions, park genuine unknowns as
140
+ first-class open vagueness, and `export` a spec only once the frame **converges**.
141
+
142
+ - Source:
143
+ [`.claude/skills/think/`](https://github.com/agentculture/devague/blob/main/.claude/skills/think/SKILL.md)
144
+ (`SKILL.md` + `scripts/think.sh`).
145
+
146
+ ### `spec-to-plan` — converged spec → buildable plan
147
+
148
+ Seed a plan from a **converged** frame (`devague plan new --frame <slug>`), add
149
+ tasks that cover every coverage target with acceptance criteria and an acyclic
150
+ dependency order, park unknowns as first-class risks, and `export` only once the
151
+ plan converges. Coaches small, file-disjoint, TDD-accepted tasks so the
152
+ downstream fan-out can run wide waves.
153
+
154
+ - Source:
155
+ [`.claude/skills/spec-to-plan/`](https://github.com/agentculture/devague/blob/main/.claude/skills/spec-to-plan/SKILL.md)
156
+ (`SKILL.md` + `scripts/spec-to-plan.sh`).
157
+
158
+ ### `assign-to-workforce` — converged plan → parallel implementation
159
+
160
+ Reads `devague plan waves` (deterministic, read-only scheduling metadata) and
161
+ fans out independent tasks to **one agent per task per wave in isolated git
162
+ worktrees**, with **main-agent TDD-gated merges** (the task's tests pass before
163
+ *and* after the merge). Exactly three human gates; the final PR uses the `cicd`
164
+ skill (`agex pr open`).
165
+
166
+ - Source:
167
+ [`.claude/skills/assign-to-workforce/`](https://github.com/agentculture/devague/blob/main/.claude/skills/assign-to-workforce/SKILL.md)
168
+ (`SKILL.md` + `scripts/assign-to-workforce.sh`).
169
+
170
+ ## See also
171
+
172
+ - `devague learn skills` / `devague learn skills:all` / `devague learn
173
+ skills:<name>` — the condensed, always-available form of this guide, with the
174
+ canonical source URLs for each skill (works for an agent operating an installed
175
+ devague with no checkout).
176
+ - [`llm-guidance.md`](llm-guidance.md) — the portable operating contract every
177
+ operator skill upholds.
178
+ - [`skill-sources.md`](skill-sources.md) — provenance and the `cite, don't
179
+ import` vendoring policy.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devague"
3
- version = "0.11.1"
3
+ version = "0.12.0"
4
4
  description = "devague — turns a vague feature idea into a buildable spec, then a buildable plan."
5
5
  readme = "README.md"
6
6
  license = "MIT"