devague 0.11.0__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.0 → devague-0.12.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +8 -5
  2. {devague-0.11.0 → devague-0.12.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +4 -4
  3. {devague-0.11.0 → devague-0.12.0}/.claude/skills/think/scripts/think.sh +3 -3
  4. {devague-0.11.0 → devague-0.12.0}/CHANGELOG.md +26 -0
  5. {devague-0.11.0 → devague-0.12.0}/CLAUDE.md +11 -0
  6. {devague-0.11.0 → devague-0.12.0}/PKG-INFO +1 -1
  7. devague-0.12.0/devague/cli/_commands/learn.py +407 -0
  8. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/plan.py +5 -1
  9. {devague-0.11.0 → devague-0.12.0}/docs/llm-guidance.md +3 -0
  10. devague-0.12.0/docs/skills.md +179 -0
  11. {devague-0.11.0 → devague-0.12.0}/pyproject.toml +1 -1
  12. devague-0.12.0/tests/test_cli_learn.py +150 -0
  13. {devague-0.11.0 → devague-0.12.0}/uv.lock +1 -1
  14. devague-0.11.0/devague/cli/_commands/learn.py +0 -187
  15. devague-0.11.0/tests/test_cli_learn.py +0 -40
  16. {devague-0.11.0 → devague-0.12.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  17. {devague-0.11.0 → devague-0.12.0}/.claude/skills/cicd/SKILL.md +0 -0
  18. {devague-0.11.0 → devague-0.12.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  19. {devague-0.11.0 → devague-0.12.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  20. {devague-0.11.0 → devague-0.12.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  21. {devague-0.11.0 → devague-0.12.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  22. {devague-0.11.0 → devague-0.12.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  23. {devague-0.11.0 → devague-0.12.0}/.claude/skills/communicate/SKILL.md +0 -0
  24. {devague-0.11.0 → devague-0.12.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  25. {devague-0.11.0 → devague-0.12.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  26. {devague-0.11.0 → devague-0.12.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  27. {devague-0.11.0 → devague-0.12.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  28. {devague-0.11.0 → devague-0.12.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  29. {devague-0.11.0 → devague-0.12.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  30. {devague-0.11.0 → devague-0.12.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  31. {devague-0.11.0 → devague-0.12.0}/.claude/skills/run-tests/SKILL.md +0 -0
  32. {devague-0.11.0 → devague-0.12.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  33. {devague-0.11.0 → devague-0.12.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  34. {devague-0.11.0 → devague-0.12.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  35. {devague-0.11.0 → devague-0.12.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  36. {devague-0.11.0 → devague-0.12.0}/.claude/skills/think/SKILL.md +0 -0
  37. {devague-0.11.0 → devague-0.12.0}/.claude/skills/version-bump/SKILL.md +0 -0
  38. {devague-0.11.0 → devague-0.12.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  39. {devague-0.11.0 → devague-0.12.0}/.claude/skills.local.yaml.example +0 -0
  40. {devague-0.11.0 → devague-0.12.0}/.devague/current_plan +0 -0
  41. {devague-0.11.0 → devague-0.12.0}/.devague/frames/devague-0-6-0-ships-the-human-review-loop-devague.json +0 -0
  42. {devague-0.11.0 → devague-0.12.0}/.devague/frames/devague-now-ships-a-documented-spec-contract-every.json +0 -0
  43. {devague-0.11.0 → devague-0.12.0}/.devague/frames/devague-turns-a-converged-plan-into-parallel-simpl.json +0 -0
  44. {devague-0.11.0 → devague-0.12.0}/.devague/plans/devague-0-6-0-ships-the-human-review-loop-devague.json +0 -0
  45. {devague-0.11.0 → devague-0.12.0}/.devague/plans/devague-now-ships-a-documented-spec-contract-every.json +0 -0
  46. {devague-0.11.0 → devague-0.12.0}/.devague/plans/devague-turns-a-converged-plan-into-parallel-simpl.json +0 -0
  47. {devague-0.11.0 → devague-0.12.0}/.flake8 +0 -0
  48. {devague-0.11.0 → devague-0.12.0}/.github/workflows/publish.yml +0 -0
  49. {devague-0.11.0 → devague-0.12.0}/.github/workflows/security-checks.yml +0 -0
  50. {devague-0.11.0 → devague-0.12.0}/.github/workflows/tests.yml +0 -0
  51. {devague-0.11.0 → devague-0.12.0}/.gitignore +0 -0
  52. {devague-0.11.0 → devague-0.12.0}/.markdownlint-cli2.yaml +0 -0
  53. {devague-0.11.0 → devague-0.12.0}/.pre-commit-config.yaml +0 -0
  54. {devague-0.11.0 → devague-0.12.0}/LICENSE +0 -0
  55. {devague-0.11.0 → devague-0.12.0}/README.md +0 -0
  56. {devague-0.11.0 → devague-0.12.0}/culture.yaml +0 -0
  57. {devague-0.11.0 → devague-0.12.0}/devague/__init__.py +0 -0
  58. {devague-0.11.0 → devague-0.12.0}/devague/__main__.py +0 -0
  59. {devague-0.11.0 → devague-0.12.0}/devague/cli/__init__.py +0 -0
  60. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/__init__.py +0 -0
  61. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/capture.py +0 -0
  62. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/confirm.py +0 -0
  63. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/converge.py +0 -0
  64. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/explain.py +0 -0
  65. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/export.py +0 -0
  66. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/interrogate.py +0 -0
  67. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/list_frames.py +0 -0
  68. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/new.py +0 -0
  69. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/park.py +0 -0
  70. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/question.py +0 -0
  71. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/reject.py +0 -0
  72. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/review.py +0 -0
  73. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/show.py +0 -0
  74. {devague-0.11.0 → devague-0.12.0}/devague/cli/_commands/status.py +0 -0
  75. {devague-0.11.0 → devague-0.12.0}/devague/cli/_errors.py +0 -0
  76. {devague-0.11.0 → devague-0.12.0}/devague/cli/_frames.py +0 -0
  77. {devague-0.11.0 → devague-0.12.0}/devague/cli/_output.py +0 -0
  78. {devague-0.11.0 → devague-0.12.0}/devague/cli/_paths.py +0 -0
  79. {devague-0.11.0 → devague-0.12.0}/devague/cli/_plans.py +0 -0
  80. {devague-0.11.0 → devague-0.12.0}/devague/cli/_status.py +0 -0
  81. {devague-0.11.0 → devague-0.12.0}/devague/convergence.py +0 -0
  82. {devague-0.11.0 → devague-0.12.0}/devague/frame.py +0 -0
  83. {devague-0.11.0 → devague-0.12.0}/devague/plan.py +0 -0
  84. {devague-0.11.0 → devague-0.12.0}/devague/plan_convergence.py +0 -0
  85. {devague-0.11.0 → devague-0.12.0}/devague/plan_store.py +0 -0
  86. {devague-0.11.0 → devague-0.12.0}/devague/questions_io.py +0 -0
  87. {devague-0.11.0 → devague-0.12.0}/devague/render/__init__.py +0 -0
  88. {devague-0.11.0 → devague-0.12.0}/devague/render/frame_md.py +0 -0
  89. {devague-0.11.0 → devague-0.12.0}/devague/render/plan_md.py +0 -0
  90. {devague-0.11.0 → devague-0.12.0}/devague/render/review_md.py +0 -0
  91. {devague-0.11.0 → devague-0.12.0}/devague/render/spec_md.py +0 -0
  92. {devague-0.11.0 → devague-0.12.0}/devague/store.py +0 -0
  93. {devague-0.11.0 → devague-0.12.0}/docs/assign-to-workforce-worked-example.md +0 -0
  94. {devague-0.11.0 → devague-0.12.0}/docs/examples/contract-example.json +0 -0
  95. {devague-0.11.0 → 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.0 → devague-0.12.0}/docs/plans/2026-05-23-devague-now-ships-a-documented-spec-contract-every.md +0 -0
  97. {devague-0.11.0 → devague-0.12.0}/docs/plans/2026-05-23-devague-turns-a-converged-plan-into-parallel-simpl.md +0 -0
  98. {devague-0.11.0 → devague-0.12.0}/docs/reviews/spec-contract-frame-review.md +0 -0
  99. {devague-0.11.0 → devague-0.12.0}/docs/skill-sources.md +0 -0
  100. {devague-0.11.0 → devague-0.12.0}/docs/spec-contract.md +0 -0
  101. {devague-0.11.0 → 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.0 → devague-0.12.0}/docs/specs/2026-05-23-devague-now-ships-a-documented-spec-contract-every.md +0 -0
  103. {devague-0.11.0 → devague-0.12.0}/docs/specs/2026-05-23-devague-turns-a-converged-plan-into-parallel-simpl.md +0 -0
  104. {devague-0.11.0 → devague-0.12.0}/docs/superpowers/plans/2026-05-22-specifix-onboarding.md +0 -0
  105. {devague-0.11.0 → devague-0.12.0}/docs/superpowers/plans/2026-05-23-devague-rename.md +0 -0
  106. {devague-0.11.0 → devague-0.12.0}/docs/superpowers/plans/2026-05-23-devague-working-backwards-engine.md +0 -0
  107. {devague-0.11.0 → devague-0.12.0}/docs/superpowers/specs/2026-05-22-specifix-onboarding-design.md +0 -0
  108. {devague-0.11.0 → devague-0.12.0}/docs/superpowers/specs/2026-05-23-devague-spec-to-plan-design.md +0 -0
  109. {devague-0.11.0 → devague-0.12.0}/docs/superpowers/specs/2026-05-23-devague-working-backwards-design.md +0 -0
  110. {devague-0.11.0 → devague-0.12.0}/sonar-project.properties +0 -0
  111. {devague-0.11.0 → devague-0.12.0}/tests/__init__.py +0 -0
  112. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_affordances.py +0 -0
  113. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_chassis.py +0 -0
  114. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_converge_export.py +0 -0
  115. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_errors.py +0 -0
  116. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_moves.py +0 -0
  117. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_output.py +0 -0
  118. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_paths.py +0 -0
  119. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_plan.py +0 -0
  120. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_question.py +0 -0
  121. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_review.py +0 -0
  122. {devague-0.11.0 → devague-0.12.0}/tests/test_cli_status.py +0 -0
  123. {devague-0.11.0 → devague-0.12.0}/tests/test_contract.py +0 -0
  124. {devague-0.11.0 → devague-0.12.0}/tests/test_convergence.py +0 -0
  125. {devague-0.11.0 → devague-0.12.0}/tests/test_frame.py +0 -0
  126. {devague-0.11.0 → devague-0.12.0}/tests/test_offline.py +0 -0
  127. {devague-0.11.0 → devague-0.12.0}/tests/test_package.py +0 -0
  128. {devague-0.11.0 → devague-0.12.0}/tests/test_plan.py +0 -0
  129. {devague-0.11.0 → devague-0.12.0}/tests/test_plan_convergence.py +0 -0
  130. {devague-0.11.0 → devague-0.12.0}/tests/test_plan_store.py +0 -0
  131. {devague-0.11.0 → devague-0.12.0}/tests/test_render.py +0 -0
  132. {devague-0.11.0 → devague-0.12.0}/tests/test_render_plan.py +0 -0
  133. {devague-0.11.0 → devague-0.12.0}/tests/test_review_loop_integration.py +0 -0
  134. {devague-0.11.0 → devague-0.12.0}/tests/test_review_loop_invariants.py +0 -0
  135. {devague-0.11.0 → devague-0.12.0}/tests/test_spec_to_plan_skill.py +0 -0
  136. {devague-0.11.0 → devague-0.12.0}/tests/test_store.py +0 -0
  137. {devague-0.11.0 → devague-0.12.0}/tests/test_think_skill.py +0 -0
@@ -91,12 +91,15 @@ cmd_split_plan() {
91
91
  done
92
92
 
93
93
  local waves_json tmp_err waves_rc old_exit_trap
94
- tmp_err="$(mktemp)"
95
- # Clean up the temp file on any exit path — including a signal between its
96
- # creation and the explicit removal below WITHOUT permanently changing the
97
- # script's process-global EXIT handling: capture any prior EXIT trap, install
98
- # ours, then restore it once the file is safely gone (#30; PR #31 review).
94
+ # Clean up the temp file on any exit path — including a signal after its
95
+ # creation WITHOUT permanently changing the script's process-global EXIT
96
+ # handling. Capture any prior EXIT trap BEFORE mktemp (that capture forks a
97
+ # subshell, so doing it first keeps it out of the untracked-file window),
98
+ # then install our cleanup trap on the line immediately after mktemp, and
99
+ # restore the prior trap once the file is safely gone (#30; PR #31 review;
100
+ # devague#32).
99
101
  old_exit_trap="$(trap -p EXIT)"
102
+ tmp_err="$(mktemp)"
100
103
  trap 'rm -f "$tmp_err"' EXIT
101
104
  set +e
102
105
  waves_json="$("${DEVAGUE[@]}" plan waves --json "${extra_args[@]}" 2>"$tmp_err")"
@@ -3,10 +3,10 @@
3
3
  #
4
4
  # The skill is named `spec-to-plan`; the product/CLI it drives is `devague` (the
5
5
  # idea→spec half lives in the sibling /think skill). This wrapper forwards every
6
- # move to `devague plan <move>` verbatim, and adds one value-add subcommand
7
- # `status` that reads the plan convergence gate and names the recommended next
8
- # move. It is the forward leg: seed a plan from a *converged* frame, then work it
9
- # into a buildable plan.
6
+ # move to `devague plan <move>` verbatim including the `status` verb the CLI
7
+ # internalised in 0.11.0 (devague#30/#31), which reads the plan convergence gate
8
+ # and names the recommended next move. It is the forward leg: seed a plan from a
9
+ # *converged* frame, then work it into a buildable plan.
10
10
  #
11
11
  # Origin: authored and maintained in agentculture/devague. steward pulls this
12
12
  # skill from here and broadcasts it to the rest of the AgentCulture mesh, so it
@@ -5,9 +5,9 @@
5
5
  # half lives in the sibling /spec-to-plan skill, which drives `devague plan`).
6
6
  # devague turns a vague feature idea into a buildable spec by working backwards.
7
7
  # This wrapper is the agent-facing operator for the deterministic devague CLI:
8
- # it resolves the CLI portably, forwards every move verbatim, and adds one
9
- # value-add subcommand — `status` that reads the convergence gate and names
10
- # the recommended next move.
8
+ # it resolves the CLI portably and forwards every move verbatim including the
9
+ # `status` verb the CLI internalised in 0.11.0 (devague#30/#31), which reads the
10
+ # convergence gate and names the recommended next move.
11
11
  #
12
12
  # Origin: authored and maintained in agentculture/devague. steward pulls this
13
13
  # skill from here and broadcasts it to the rest of the AgentCulture mesh, so it
@@ -5,6 +5,32 @@ 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
+
24
+ ## [0.11.1] - 2026-05-24
25
+
26
+ ### Changed
27
+
28
+ - Trio skill-script header comments (`think.sh`, `spec-to-plan.sh`) no longer claim the wrapper *adds* a `status` subcommand — since 0.11.0 it is forwarded verbatim like every other move (devague#32, steward PR #58 review).
29
+
30
+ ### Fixed
31
+
32
+ - `assign-to-workforce.sh` now installs its `mktemp` cleanup `EXIT` trap on the line immediately after creating the temp file, capturing the prior trap beforehand so the subshell-forking capture no longer sits inside the untracked-file window (devague#32).
33
+
8
34
  ## [0.11.0] - 2026-05-24
9
35
 
10
36
  ### Added
@@ -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.0
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.