devague 0.10.0__tar.gz → 0.11.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 (135) hide show
  1. {devague-0.10.0 → devague-0.11.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +9 -1
  2. {devague-0.10.0 → devague-0.11.0}/.claude/skills/spec-to-plan/SKILL.md +15 -7
  3. {devague-0.10.0 → devague-0.11.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +7 -104
  4. {devague-0.10.0 → devague-0.11.0}/.claude/skills/think/SKILL.md +19 -14
  5. devague-0.11.0/.claude/skills/think/scripts/think.sh +101 -0
  6. {devague-0.10.0 → devague-0.11.0}/CHANGELOG.md +14 -0
  7. {devague-0.10.0 → devague-0.11.0}/CLAUDE.md +16 -5
  8. {devague-0.10.0 → devague-0.11.0}/PKG-INFO +1 -1
  9. {devague-0.10.0 → devague-0.11.0}/devague/cli/__init__.py +2 -0
  10. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/learn.py +1 -0
  11. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/plan.py +42 -0
  12. devague-0.11.0/devague/cli/_commands/status.py +53 -0
  13. devague-0.11.0/devague/cli/_status.py +99 -0
  14. {devague-0.10.0 → devague-0.11.0}/pyproject.toml +1 -1
  15. devague-0.11.0/tests/test_cli_status.py +204 -0
  16. {devague-0.10.0 → devague-0.11.0}/uv.lock +1 -1
  17. devague-0.10.0/.claude/skills/think/scripts/think.sh +0 -205
  18. {devague-0.10.0 → devague-0.11.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  19. {devague-0.10.0 → devague-0.11.0}/.claude/skills/cicd/SKILL.md +0 -0
  20. {devague-0.10.0 → devague-0.11.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  21. {devague-0.10.0 → devague-0.11.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  22. {devague-0.10.0 → devague-0.11.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  23. {devague-0.10.0 → devague-0.11.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  24. {devague-0.10.0 → devague-0.11.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  25. {devague-0.10.0 → devague-0.11.0}/.claude/skills/communicate/SKILL.md +0 -0
  26. {devague-0.10.0 → devague-0.11.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  27. {devague-0.10.0 → devague-0.11.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  28. {devague-0.10.0 → devague-0.11.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  29. {devague-0.10.0 → devague-0.11.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  30. {devague-0.10.0 → devague-0.11.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  31. {devague-0.10.0 → devague-0.11.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  32. {devague-0.10.0 → devague-0.11.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  33. {devague-0.10.0 → devague-0.11.0}/.claude/skills/run-tests/SKILL.md +0 -0
  34. {devague-0.10.0 → devague-0.11.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  35. {devague-0.10.0 → devague-0.11.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  36. {devague-0.10.0 → devague-0.11.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  37. {devague-0.10.0 → devague-0.11.0}/.claude/skills/version-bump/SKILL.md +0 -0
  38. {devague-0.10.0 → devague-0.11.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  39. {devague-0.10.0 → devague-0.11.0}/.claude/skills.local.yaml.example +0 -0
  40. {devague-0.10.0 → devague-0.11.0}/.devague/current_plan +0 -0
  41. {devague-0.10.0 → devague-0.11.0}/.devague/frames/devague-0-6-0-ships-the-human-review-loop-devague.json +0 -0
  42. {devague-0.10.0 → devague-0.11.0}/.devague/frames/devague-now-ships-a-documented-spec-contract-every.json +0 -0
  43. {devague-0.10.0 → devague-0.11.0}/.devague/frames/devague-turns-a-converged-plan-into-parallel-simpl.json +0 -0
  44. {devague-0.10.0 → devague-0.11.0}/.devague/plans/devague-0-6-0-ships-the-human-review-loop-devague.json +0 -0
  45. {devague-0.10.0 → devague-0.11.0}/.devague/plans/devague-now-ships-a-documented-spec-contract-every.json +0 -0
  46. {devague-0.10.0 → devague-0.11.0}/.devague/plans/devague-turns-a-converged-plan-into-parallel-simpl.json +0 -0
  47. {devague-0.10.0 → devague-0.11.0}/.flake8 +0 -0
  48. {devague-0.10.0 → devague-0.11.0}/.github/workflows/publish.yml +0 -0
  49. {devague-0.10.0 → devague-0.11.0}/.github/workflows/security-checks.yml +0 -0
  50. {devague-0.10.0 → devague-0.11.0}/.github/workflows/tests.yml +0 -0
  51. {devague-0.10.0 → devague-0.11.0}/.gitignore +0 -0
  52. {devague-0.10.0 → devague-0.11.0}/.markdownlint-cli2.yaml +0 -0
  53. {devague-0.10.0 → devague-0.11.0}/.pre-commit-config.yaml +0 -0
  54. {devague-0.10.0 → devague-0.11.0}/LICENSE +0 -0
  55. {devague-0.10.0 → devague-0.11.0}/README.md +0 -0
  56. {devague-0.10.0 → devague-0.11.0}/culture.yaml +0 -0
  57. {devague-0.10.0 → devague-0.11.0}/devague/__init__.py +0 -0
  58. {devague-0.10.0 → devague-0.11.0}/devague/__main__.py +0 -0
  59. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/__init__.py +0 -0
  60. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/capture.py +0 -0
  61. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/confirm.py +0 -0
  62. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/converge.py +0 -0
  63. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/explain.py +0 -0
  64. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/export.py +0 -0
  65. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/interrogate.py +0 -0
  66. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/list_frames.py +0 -0
  67. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/new.py +0 -0
  68. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/park.py +0 -0
  69. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/question.py +0 -0
  70. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/reject.py +0 -0
  71. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/review.py +0 -0
  72. {devague-0.10.0 → devague-0.11.0}/devague/cli/_commands/show.py +0 -0
  73. {devague-0.10.0 → devague-0.11.0}/devague/cli/_errors.py +0 -0
  74. {devague-0.10.0 → devague-0.11.0}/devague/cli/_frames.py +0 -0
  75. {devague-0.10.0 → devague-0.11.0}/devague/cli/_output.py +0 -0
  76. {devague-0.10.0 → devague-0.11.0}/devague/cli/_paths.py +0 -0
  77. {devague-0.10.0 → devague-0.11.0}/devague/cli/_plans.py +0 -0
  78. {devague-0.10.0 → devague-0.11.0}/devague/convergence.py +0 -0
  79. {devague-0.10.0 → devague-0.11.0}/devague/frame.py +0 -0
  80. {devague-0.10.0 → devague-0.11.0}/devague/plan.py +0 -0
  81. {devague-0.10.0 → devague-0.11.0}/devague/plan_convergence.py +0 -0
  82. {devague-0.10.0 → devague-0.11.0}/devague/plan_store.py +0 -0
  83. {devague-0.10.0 → devague-0.11.0}/devague/questions_io.py +0 -0
  84. {devague-0.10.0 → devague-0.11.0}/devague/render/__init__.py +0 -0
  85. {devague-0.10.0 → devague-0.11.0}/devague/render/frame_md.py +0 -0
  86. {devague-0.10.0 → devague-0.11.0}/devague/render/plan_md.py +0 -0
  87. {devague-0.10.0 → devague-0.11.0}/devague/render/review_md.py +0 -0
  88. {devague-0.10.0 → devague-0.11.0}/devague/render/spec_md.py +0 -0
  89. {devague-0.10.0 → devague-0.11.0}/devague/store.py +0 -0
  90. {devague-0.10.0 → devague-0.11.0}/docs/assign-to-workforce-worked-example.md +0 -0
  91. {devague-0.10.0 → devague-0.11.0}/docs/examples/contract-example.json +0 -0
  92. {devague-0.10.0 → devague-0.11.0}/docs/llm-guidance.md +0 -0
  93. {devague-0.10.0 → devague-0.11.0}/docs/plans/2026-05-23-devague-0-6-0-ships-the-human-review-loop-devague.md +0 -0
  94. {devague-0.10.0 → devague-0.11.0}/docs/plans/2026-05-23-devague-now-ships-a-documented-spec-contract-every.md +0 -0
  95. {devague-0.10.0 → devague-0.11.0}/docs/plans/2026-05-23-devague-turns-a-converged-plan-into-parallel-simpl.md +0 -0
  96. {devague-0.10.0 → devague-0.11.0}/docs/reviews/spec-contract-frame-review.md +0 -0
  97. {devague-0.10.0 → devague-0.11.0}/docs/skill-sources.md +0 -0
  98. {devague-0.10.0 → devague-0.11.0}/docs/spec-contract.md +0 -0
  99. {devague-0.10.0 → devague-0.11.0}/docs/specs/2026-05-23-devague-0-6-0-ships-the-human-review-loop-devague.md +0 -0
  100. {devague-0.10.0 → devague-0.11.0}/docs/specs/2026-05-23-devague-now-ships-a-documented-spec-contract-every.md +0 -0
  101. {devague-0.10.0 → devague-0.11.0}/docs/specs/2026-05-23-devague-turns-a-converged-plan-into-parallel-simpl.md +0 -0
  102. {devague-0.10.0 → devague-0.11.0}/docs/superpowers/plans/2026-05-22-specifix-onboarding.md +0 -0
  103. {devague-0.10.0 → devague-0.11.0}/docs/superpowers/plans/2026-05-23-devague-rename.md +0 -0
  104. {devague-0.10.0 → devague-0.11.0}/docs/superpowers/plans/2026-05-23-devague-working-backwards-engine.md +0 -0
  105. {devague-0.10.0 → devague-0.11.0}/docs/superpowers/specs/2026-05-22-specifix-onboarding-design.md +0 -0
  106. {devague-0.10.0 → devague-0.11.0}/docs/superpowers/specs/2026-05-23-devague-spec-to-plan-design.md +0 -0
  107. {devague-0.10.0 → devague-0.11.0}/docs/superpowers/specs/2026-05-23-devague-working-backwards-design.md +0 -0
  108. {devague-0.10.0 → devague-0.11.0}/sonar-project.properties +0 -0
  109. {devague-0.10.0 → devague-0.11.0}/tests/__init__.py +0 -0
  110. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_affordances.py +0 -0
  111. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_chassis.py +0 -0
  112. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_converge_export.py +0 -0
  113. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_errors.py +0 -0
  114. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_learn.py +0 -0
  115. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_moves.py +0 -0
  116. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_output.py +0 -0
  117. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_paths.py +0 -0
  118. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_plan.py +0 -0
  119. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_question.py +0 -0
  120. {devague-0.10.0 → devague-0.11.0}/tests/test_cli_review.py +0 -0
  121. {devague-0.10.0 → devague-0.11.0}/tests/test_contract.py +0 -0
  122. {devague-0.10.0 → devague-0.11.0}/tests/test_convergence.py +0 -0
  123. {devague-0.10.0 → devague-0.11.0}/tests/test_frame.py +0 -0
  124. {devague-0.10.0 → devague-0.11.0}/tests/test_offline.py +0 -0
  125. {devague-0.10.0 → devague-0.11.0}/tests/test_package.py +0 -0
  126. {devague-0.10.0 → devague-0.11.0}/tests/test_plan.py +0 -0
  127. {devague-0.10.0 → devague-0.11.0}/tests/test_plan_convergence.py +0 -0
  128. {devague-0.10.0 → devague-0.11.0}/tests/test_plan_store.py +0 -0
  129. {devague-0.10.0 → devague-0.11.0}/tests/test_render.py +0 -0
  130. {devague-0.10.0 → devague-0.11.0}/tests/test_render_plan.py +0 -0
  131. {devague-0.10.0 → devague-0.11.0}/tests/test_review_loop_integration.py +0 -0
  132. {devague-0.10.0 → devague-0.11.0}/tests/test_review_loop_invariants.py +0 -0
  133. {devague-0.10.0 → devague-0.11.0}/tests/test_spec_to_plan_skill.py +0 -0
  134. {devague-0.10.0 → devague-0.11.0}/tests/test_store.py +0 -0
  135. {devague-0.10.0 → devague-0.11.0}/tests/test_think_skill.py +0 -0
@@ -90,8 +90,14 @@ cmd_split_plan() {
90
90
  shift
91
91
  done
92
92
 
93
- local waves_json tmp_err waves_rc
93
+ local waves_json tmp_err waves_rc old_exit_trap
94
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).
99
+ old_exit_trap="$(trap -p EXIT)"
100
+ trap 'rm -f "$tmp_err"' EXIT
95
101
  set +e
96
102
  waves_json="$("${DEVAGUE[@]}" plan waves --json "${extra_args[@]}" 2>"$tmp_err")"
97
103
  waves_rc=$?
@@ -99,6 +105,8 @@ cmd_split_plan() {
99
105
  local waves_err
100
106
  waves_err="$(cat "$tmp_err")"
101
107
  rm -f "$tmp_err"
108
+ trap - EXIT
109
+ eval "${old_exit_trap}" # empty string is a no-op; re-installs a prior trap if any
102
110
 
103
111
  if [ "$waves_rc" -ne 0 ]; then
104
112
  printf '%s\n' "$waves_err" >&2
@@ -41,8 +41,9 @@ bash .claude/skills/spec-to-plan/scripts/spec-to-plan.sh status
41
41
 
42
42
  It resolves the CLI portably — an installed `devague` on `PATH` (the normal
43
43
  case), falling back to `uv run devague` inside the devague checkout, else an
44
- install hint. Every move except `status` is forwarded verbatim as `devague plan
45
- <move>`, so you can equally call the CLI directly (`devague plan <move> …`).
44
+ install hint. Every move including `status` is forwarded verbatim as
45
+ `devague plan <move>`, so you can equally call the CLI directly
46
+ (`devague plan <move> …`).
46
47
 
47
48
  ### Moves
48
49
 
@@ -58,17 +59,24 @@ install hint. Every move except `status` is forwarded verbatim as `devague plan
58
59
  | `converge` | Evaluate the gate against the **live** source frame; list remaining gaps. |
59
60
  | `export` | Write the buildable plan to `docs/plans/` — only after `converge` passes. |
60
61
  | `waves` | Emit deterministic dependency waves (`{plan, waves}`) — scheduling metadata only, *not* orchestration. Read-only, works on an in-progress plan; refuses a cyclic/dangling graph. Devague describes the graph; an operator decides how to run it (#20). |
62
+ | `status` | Read-only: where the plan stands + the recommended next move, re-checked against the live frame (`--json` too). |
61
63
  | `show` / `list` | Render a plan / list plans (`--json` for raw state). |
62
64
  | `learn` / `explain <move>` | Teach the method / explain one move. |
63
65
 
64
66
  Risk kinds (shared with the frame engine): `unknown_nonblocking`,
65
67
  `unknown_blocking`, `out_of_scope`, `follow_up`.
66
68
 
67
- ### `status` — the next-move helper
68
-
69
- `status` is a wrapper-only verb. It reads `devague plan converge --json` +
70
- `devague plan list --json` and prints where the current plan stands, the
71
- remaining gaps, and the recommended next move derived from the first gap.
69
+ ### `status` — the next-move verb
70
+
71
+ `status` is a first-class, **read-only** CLI verb (`devague plan status`,
72
+ internalised from this wrapper in 0.11.0 issue
73
+ [#30](https://github.com/agentculture/devague/issues/30)). It composes
74
+ `devague plan list` + `devague plan converge` and prints where the current plan
75
+ stands, the remaining gaps, and the recommended next move derived from the first
76
+ gap. Like `converge`/`export` it re-checks the **live** source frame (so frame
77
+ drift surfaces as an error), but it never mutates state. Pass `--json` for the
78
+ structured payload (`{plan, total, ready_for_plan, blockers, warnings,
79
+ parked_items, required_next_moves}`).
72
80
 
73
81
  ```text
74
82
  plan: my-feature (1 plan total)
@@ -67,6 +67,7 @@ Moves (forwarded to `devague plan`; run `devague plan learn` for the method):
67
67
  converge check whether the plan can export
68
68
  export write the buildable plan (only after converge passes)
69
69
  waves emit deterministic dependency waves (scheduling metadata, not orchestration)
70
+ status where the plan stands + the recommended next move
70
71
  show / list render a plan / list plans
71
72
  learn teach the method | explain <move> explain one move
72
73
 
@@ -74,122 +75,24 @@ Plans persist under .devague/ in the current directory — run from the repo you
74
75
  are speccing. Results go to stdout, diagnostics to stderr; pass --json to any
75
76
  move for structured output.
76
77
 
77
- Note: `status` is a wrapper-only verb; everything else is forwarded verbatim as
78
- `devague plan <move>`, so new plan moves work without editing this script.
78
+ Note: every move including `status` is forwarded verbatim as `devague plan
79
+ <move>`, so new plan moves work without editing this script. (`status` was
80
+ internalised into the CLI in devague 0.11.0; it is no longer wrapper-only.)
79
81
 
80
82
  Prior leg: a plan is seeded from a converged frame produced by the /think skill.
81
83
  EOF
82
84
  }
83
85
 
84
- # ── status: read the plan convergence gate and recommend the next move ──────
85
- cmd_status() {
86
- local list_json conv_out conv_err conv_rc req_plan="" prev="" tmp_err
87
-
88
- for arg in "$@"; do
89
- case "$prev" in --plan) req_plan="$arg" ;; esac
90
- case "$arg" in --plan=*) req_plan="${arg#--plan=}" ;; esac
91
- prev="$arg"
92
- done
93
-
94
- list_json="$("${DEVAGUE[@]}" plan list --json 2>/dev/null || true)"
95
-
96
- tmp_err="$(mktemp)"
97
- set +e
98
- conv_out="$("${DEVAGUE[@]}" plan converge --json "$@" 2>"$tmp_err")"
99
- conv_rc=$?
100
- set -e
101
- conv_err="$(cat "$tmp_err")"
102
- rm -f "$tmp_err"
103
-
104
- DEVAGUE_LIST_JSON="$list_json" \
105
- DEVAGUE_CONV_JSON="$conv_out" \
106
- DEVAGUE_CONV_ERR="$conv_err" \
107
- DEVAGUE_CONV_RC="$conv_rc" \
108
- DEVAGUE_REQ_PLAN="$req_plan" \
109
- python3 - <<'PY'
110
- import json
111
- import os
112
- import sys
113
-
114
-
115
- def load(name):
116
- raw = os.environ.get(name, "").strip()
117
- if not raw:
118
- return None
119
- try:
120
- return json.loads(raw)
121
- except json.JSONDecodeError:
122
- return None
123
-
124
-
125
- lst = load("DEVAGUE_LIST_JSON") or {}
126
- conv = load("DEVAGUE_CONV_JSON")
127
- conv_err = os.environ.get("DEVAGUE_CONV_ERR", "").strip()
128
- req_plan = os.environ.get("DEVAGUE_REQ_PLAN", "").strip()
129
- try:
130
- conv_rc = int(os.environ.get("DEVAGUE_CONV_RC", "0") or "0")
131
- except ValueError:
132
- conv_rc = 0
133
-
134
- plans = lst.get("plans") or []
135
- current = lst.get("current")
136
-
137
- if not plans:
138
- print("no plans yet — seed one from a converged frame:")
139
- print(' devague plan new --frame "<slug>"')
140
- print(" (the frame must have converged in the /think skill first)")
141
- sys.exit(0)
142
-
143
- shown = req_plan or current or "(none selected)"
144
- total = len(plans)
145
- print(f"plan: {shown} ({total} plan{'s' if total != 1 else ''} total)")
146
-
147
- if conv is None:
148
- # A non-zero converge exit is a real error (deleted/regressed source frame,
149
- # bad --plan) — relay devague's own error:/hint: lines instead of masking it.
150
- if conv_rc != 0 and conv_err:
151
- sys.stderr.write(conv_err + "\n")
152
- sys.exit(conv_rc)
153
- print("convergence: unknown (could not evaluate the plan)")
154
- print("next move: devague plan show # inspect the plan")
155
- sys.exit(0)
156
-
157
- if conv.get("ready_for_plan"):
158
- print("convergence: PASSED ✓")
159
- for w in conv.get("warnings") or []:
160
- print(f" ⚠ {w}")
161
- print("next move: devague plan export # write the buildable plan")
162
- sys.exit(0)
163
-
164
- blockers = conv.get("blockers") or []
165
- print(f"convergence: NOT passed — {len(blockers)} gap(s):")
166
- for b in blockers:
167
- print(f" - {b}")
168
- for w in conv.get("warnings") or []:
169
- print(f" ⚠ {w}")
170
-
171
- moves = conv.get("required_next_moves") or []
172
- if moves:
173
- print()
174
- print("recommended next move (first gap):")
175
- print(f" {moves[0]}")
176
- PY
177
- }
178
-
179
86
  main() {
180
87
  case "${1:-help}" in
181
88
  help | -h | --help)
182
89
  usage
183
90
  return 0
184
91
  ;;
185
- status)
186
- shift
187
- resolve_devague
188
- cmd_status "$@"
189
- ;;
190
92
  *)
191
- # Forward everything else to `devague plan <move>` verbatim, so the
192
- # CLI's own parser owns the plan surface.
93
+ # Forward every move to `devague plan <move>` verbatim including the
94
+ # internalised `status` verb (`devague plan status`) — so the CLI's own
95
+ # parser owns the plan surface.
193
96
  resolve_devague
194
97
  exec "${DEVAGUE[@]}" plan "$@"
195
98
  ;;
@@ -31,8 +31,9 @@ fixed sequence of prompts. **You (the agent) choose the next move; the CLI just
31
31
  tracks state and tells you what's still missing.** Run `devague learn` for the
32
32
  canonical ten-stage arc and `devague explain <move>` for any single move.
33
33
 
34
- This skill is the operator: a portable wrapper plus one helper (`status`) that
35
- reads the convergence gate and tells you the recommended next move.
34
+ This skill is the operator: a portable wrapper that resolves the CLI and
35
+ forwards every move verbatim including `status`, the read-only verb that reads
36
+ the convergence gate and tells you the recommended next move.
36
37
 
37
38
  ## How to run
38
39
 
@@ -47,9 +48,9 @@ bash .claude/skills/think/scripts/think.sh status
47
48
  It resolves the CLI portably — an installed `devague` on `PATH` (the normal
48
49
  case), falling back to `uv run devague` when you are inside the devague checkout.
49
50
  If neither resolves it prints an install hint (`uv tool install devague`). Every
50
- move except `status` is forwarded verbatim, so you can equally call the CLI
51
- directly (`devague <move> …`) when it is installed; the wrapper exists for
52
- portable resolution and the `status` helper.
51
+ move including `status` is forwarded verbatim, so you can equally call the
52
+ CLI directly (`devague <move> …`) when it is installed; the wrapper exists only
53
+ for portable resolution.
53
54
 
54
55
  ### Moves
55
56
 
@@ -64,6 +65,7 @@ portable resolution and the `status` helper.
64
65
  | `park "<text>" --kind <kind>` | Move uncertainty into first-class open vagueness instead of forcing an answer. |
65
66
  | `converge` | Evaluate the gate; list remaining gaps. |
66
67
  | `export` | Write the buildable spec to `docs/specs/` — only after `converge` passes. |
68
+ | `status` | Read-only: where the frame stands + the recommended next move (`--json` too). |
67
69
  | `show` / `list` | Render a frame / list frames (`--json` for raw state). |
68
70
  | `learn` / `explain <move>` | Teach the method / explain one move. |
69
71
 
@@ -84,15 +86,18 @@ per-move input/output/transition/error contract are documented in
84
86
  live shape of any move, run it with `--json` (or `devague learn --json` /
85
87
  `devague explain <move>`).
86
88
 
87
- ### `status` — the next-move helper
88
-
89
- `status` is a wrapper-only verb (the CLI has no `status`). It reads
90
- `converge --json` + `list --json` and prints where the current frame stands, the
91
- remaining gaps, and the recommended next move. `converge --json` emits the
92
- structured result `{ready_for_spec, blockers, warnings, parked_items,
93
- required_next_moves}` (issue [#5](https://github.com/agentculture/devague/issues/5));
94
- the helper reads `ready_for_spec`, lists the `blockers` and `warnings`, and shows
95
- `required_next_moves[0]` as the recommended move no longer deriving it itself.
89
+ ### `status` — the next-move verb
90
+
91
+ `status` is a first-class, **read-only** CLI verb (`devague status`, internalised
92
+ from this wrapper in 0.11.0 issue
93
+ [#30](https://github.com/agentculture/devague/issues/30)). It composes
94
+ `list` + `converge` and prints where the current frame stands, the remaining
95
+ gaps, and the recommended next move; it never mutates state (the
96
+ `drafting`↔`converged` transition stays in `converge`). It reports
97
+ `ready_for_spec`, lists the `blockers` and `warnings`, and shows
98
+ `required_next_moves[0]` as the recommended move. Pass `--json` for the same
99
+ fields as a structured payload (`{frame, total, ready_for_spec, blockers,
100
+ warnings, parked_items, required_next_moves}`).
96
101
 
97
102
  ```text
98
103
  frame: my-feature (1 frame total)
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env bash
2
+ # think.sh — drive devague's working-backwards idea→spec engine (the /think skill).
3
+ #
4
+ # The skill is named `think`; the product/CLI it drives is `devague` (the spec→plan
5
+ # half lives in the sibling /spec-to-plan skill, which drives `devague plan`).
6
+ # devague turns a vague feature idea into a buildable spec by working backwards.
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.
11
+ #
12
+ # Origin: authored and maintained in agentculture/devague. steward pulls this
13
+ # skill from here and broadcasts it to the rest of the AgentCulture mesh, so it
14
+ # is written to run anywhere — portable bash, no devague-checkout assumptions.
15
+ #
16
+ # Frames persist under .devague/ in the current directory, so run from the repo
17
+ # you are speccing.
18
+
19
+ set -euo pipefail
20
+
21
+ # ── resolve the devague CLI (mesh-first, then local-dev fallback) ───────────
22
+ DEVAGUE=()
23
+ resolve_devague() {
24
+ if command -v devague >/dev/null 2>&1; then
25
+ DEVAGUE=(devague) # installed tool — the normal mesh case
26
+ return 0
27
+ fi
28
+ # Local-dev fallback: inside the devague checkout, run via uv.
29
+ local dir="$PWD"
30
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
31
+ if [ -f "$dir/pyproject.toml" ] \
32
+ && grep -q '^name = "devague"' "$dir/pyproject.toml" 2>/dev/null; then
33
+ if command -v uv >/dev/null 2>&1; then
34
+ DEVAGUE=(uv run devague)
35
+ return 0
36
+ fi
37
+ break
38
+ fi
39
+ dir=$(dirname "$dir")
40
+ done
41
+ cat >&2 <<'EOF'
42
+ error: devague CLI not found.
43
+ hint: install it with `uv tool install devague` (or `pipx install devague`),
44
+ or run from inside the devague checkout with `uv` available.
45
+ https://github.com/agentculture/devague
46
+ EOF
47
+ return 1
48
+ }
49
+
50
+ usage() {
51
+ cat <<'EOF'
52
+ think.sh — drive devague's working-backwards idea→spec engine (the /think skill).
53
+
54
+ Usage:
55
+ think.sh <move> [args...] forward a devague move
56
+ think.sh status [--frame S] where the frame stands + the next move
57
+ think.sh help this help
58
+
59
+ Moves (forwarded to the devague CLI; run `devague learn` for the full method):
60
+ new start a frame from the announcement ("pretend it shipped")
61
+ capture record + classify a claim (--kind audience|after_state|...)
62
+ interrogate pressure-test a claim (--honesty / --hard-question / --risk)
63
+ confirm confirm a claim or honesty condition (USER-only decision)
64
+ reject reject a claim or honesty condition
65
+ park record open vagueness instead of forcing an answer
66
+ converge check whether the frame can export a spec
67
+ export write the buildable spec (only after converge passes)
68
+ status where the frame stands + the recommended next move
69
+ show / list render a frame / list frames
70
+ learn teach the method | explain <move> explain one move
71
+
72
+ Frames persist under .devague/ in the current directory — run from the repo
73
+ you are speccing. Results go to stdout, diagnostics to stderr; pass --json to
74
+ any move for structured output.
75
+
76
+ Note: every move — including `status` — is forwarded verbatim to the devague
77
+ CLI, so new devague moves work without editing this script. (`status` was
78
+ internalised into the CLI in devague 0.11.0; it is no longer wrapper-only.)
79
+
80
+ Next leg: once a frame exports a spec, hand off to the /spec-to-plan skill
81
+ (`devague plan ...`) to turn that spec into a buildable plan.
82
+ EOF
83
+ }
84
+
85
+ main() {
86
+ case "${1:-help}" in
87
+ help | -h | --help)
88
+ usage
89
+ return 0
90
+ ;;
91
+ *)
92
+ # Forward every move to the CLI verbatim (including --version, the
93
+ # internalised `status` verb, and any future devague move), so its
94
+ # own parser owns the surface.
95
+ resolve_devague
96
+ exec "${DEVAGUE[@]}" "$@"
97
+ ;;
98
+ esac
99
+ }
100
+
101
+ main "$@"
@@ -5,6 +5,20 @@ 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.11.0] - 2026-05-24
9
+
10
+ ### Added
11
+
12
+ - `devague status` and `devague plan status` — first-class, read-only CLI verbs that compose `list` + `converge` and report the convergence verdict, remaining gaps, and the recommended next move (`--json` too). Internalised from the `think` / `spec-to-plan` skill wrappers (#30); they never mutate state and the plan verb re-checks the live source frame for drift. Shared renderer in `devague/cli/_status.py`.
13
+
14
+ ### Changed
15
+
16
+ - The `think` / `spec-to-plan` skill wrappers are now thin: `status` is forwarded verbatim like every other move instead of being a wrapper-only verb implemented in embedded Python. This removed their `mktemp` temp-file and stdout-ordering hazards entirely (#30, Qodo via steward).
17
+
18
+ ### Fixed
19
+
20
+ - `assign-to-workforce.sh` no longer leaks its `mktemp` temp file if interrupted by a signal — cleanup is now registered with an `EXIT` trap (#30). Its split-plan orchestration presentation deliberately stays out of the deterministic CLI (#20).
21
+
8
22
  ## [0.10.0] - 2026-05-24
9
23
 
10
24
  ### Added
@@ -4,6 +4,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Status
6
6
 
7
+ **`status` internalised into the CLI (0.11.0, #30).** The next-move helper that
8
+ used to live as embedded Python inside the `think` / `spec-to-plan` skill
9
+ wrappers is now a first-class, read-only CLI verb — `devague status` and
10
+ `devague plan status` (sharing `devague/cli/_status.py`). Both compose
11
+ `list` + `converge` and report the verdict, gaps, and recommended next move
12
+ (`--json` too); neither mutates state. This removed the wrappers' `mktemp` +
13
+ embedded-stdout hazards (Qodo via steward); the trio's remaining `mktemp` (in
14
+ `assign-to-workforce.sh`, whose orchestration presentation deliberately stays
15
+ out of the deterministic CLI) gained a cleanup trap.
16
+
7
17
  **Human Review Loop landed (0.6.0, #17).** `devague review` (+ `--json`) lists
8
18
  every proposed claim + honesty condition with ids — un-gated by convergence,
9
19
  never mutating state — and writes a non-authoritative artifact to
@@ -26,12 +36,12 @@ required_next_moves}` (plans: `ready_for_plan`) — a hard break from the old
26
36
  The **frame engine** (idea→spec) — Frame domain model, JSON store, convergence
27
37
  gate, renderer registry, and the flat moves `new` / `capture` / `interrogate` /
28
38
  `confirm` / `reject` / `review` / `question` / `park` / `converge` / `export` /
29
- `show` / `list` / `learn` / `explain`. The **plan engine** (spec→plan) is its
30
- structural peer:
39
+ `status` / `show` / `list` / `learn` / `explain`. The **plan engine** (spec→plan)
40
+ is its structural peer:
31
41
  `devague/plan.py`, `plan_convergence.py`, `plan_store.py`, `render/plan_md.py`,
32
42
  and the nested group `devague plan <move>` (`new` / `task` / `accept` / `depend`
33
43
  / `cover` / `confirm` / `reject` / `risk` / `converge` / `export` / `waves` /
34
- `show` / `list` / `learn` / `explain`). The two operator skills are `/think` (idea→spec,
44
+ `status` / `show` / `list` / `learn` / `explain`). The two operator skills are `/think` (idea→spec,
35
45
  renamed from `/devague`) and `/spec-to-plan` (spec→plan). Coverage ≥ 95 %; all
36
46
  linters pass. Run `git ls-files` to see the real surface.
37
47
 
@@ -202,8 +212,9 @@ that unless the user asks otherwise. The established sibling shape is:
202
212
  split, `--json` support).
203
213
  - `devague/cli/_commands/` — one module per verb, each exposing `register()`.
204
214
  Frame verbs: `new`, `capture`, `interrogate`, `confirm`, `reject`, `park`,
205
- `converge`, `export`, `show`, `list`, `learn`, `explain`. The plan engine adds
206
- one module, `_commands/plan.py`, registering the nested `plan` subcommand group.
215
+ `converge`, `export`, `status`, `show`, `list`, `learn`, `explain` (`status`
216
+ shares `cli/_status.py` with the plan engine). The plan engine adds one module,
217
+ `_commands/plan.py`, registering the nested `plan` subcommand group.
207
218
  - Frame engine: `devague/frame.py`, `convergence.py`, `store.py`,
208
219
  `render/{spec_md,frame_md}.py`. Plan engine (its peer): `devague/plan.py`,
209
220
  `plan_convergence.py`, `plan_store.py`, `render/plan_md.py`, `cli/_plans.py`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devague
3
- Version: 0.10.0
3
+ Version: 0.11.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
@@ -69,6 +69,7 @@ def _build_parser() -> argparse.ArgumentParser:
69
69
  from devague.cli._commands import reject as _reject_cmd
70
70
  from devague.cli._commands import review as _review_cmd
71
71
  from devague.cli._commands import show as _show_cmd
72
+ from devague.cli._commands import status as _status_cmd
72
73
 
73
74
  _learn_cmd.register(sub)
74
75
  _explain_cmd.register(sub)
@@ -83,6 +84,7 @@ def _build_parser() -> argparse.ArgumentParser:
83
84
  _converge_cmd.register(sub)
84
85
  _export_cmd.register(sub)
85
86
  _plan_cmd.register(sub)
87
+ _status_cmd.register(sub)
86
88
  _show_cmd.register(sub)
87
89
  _list_cmd.register(sub)
88
90
 
@@ -16,6 +16,7 @@ MOVES = {
16
16
  "park": "Move uncertainty into first-class open vagueness instead of forcing an answer.",
17
17
  "converge": "Check whether the frame is solid enough to export a spec.",
18
18
  "export": "Write the buildable spec — only once the frame converges.",
19
+ "status": "Report where the frame stands + the recommended next move (read-only).",
19
20
  "show": "Render the Announcement Frame.",
20
21
  "list": "List frames.",
21
22
  }
@@ -21,6 +21,7 @@ from devague.cli._frames import resolve as resolve_frame
21
21
  from devague.cli._output import emit_result
22
22
  from devague.cli._paths import dated_name
23
23
  from devague.cli._plans import resolve_plan
24
+ from devague.cli._status import StatusLabels, emit_empty, emit_status
24
25
  from devague.convergence import evaluate as evaluate_frame
25
26
  from devague.frame import Frame
26
27
  from devague.plan import RISK_KINDS, Plan, dependency_waves, targets_from_frame, to_dict
@@ -46,10 +47,22 @@ PLAN_MOVES = {
46
47
  "converge": "Check whether the plan can export, against the live frame.",
47
48
  "export": "Write the buildable plan — only once the plan converges.",
48
49
  "waves": "Emit deterministic dependency waves (scheduling metadata, not orchestration).",
50
+ "status": "Report where the plan stands + the recommended next move (read-only).",
49
51
  "show": "Render the plan.",
50
52
  "list": "List plans.",
51
53
  }
52
54
 
55
+ _STATUS_LABELS = StatusLabels(
56
+ noun="plan",
57
+ ready_key="ready_for_plan",
58
+ export_move="devague plan export # write the buildable plan",
59
+ empty_text=(
60
+ "no plans yet — seed one from a converged frame:\n"
61
+ ' devague plan new --frame "<slug>"\n'
62
+ " (the frame must have converged in the /think skill first)"
63
+ ),
64
+ )
65
+
53
66
 
54
67
  # ── shared helpers ──────────────────────────────────────────────────────────
55
68
  def _load_source_frame(slug: str) -> Frame:
@@ -319,6 +332,31 @@ def cmd_plan_waves(args: argparse.Namespace) -> int:
319
332
  return 0
320
333
 
321
334
 
335
+ def cmd_plan_status(args: argparse.Namespace) -> int:
336
+ """Where the plan stands + the recommended next move — read-only.
337
+
338
+ Re-evaluates against the **live** source frame (like ``converge``/``export``),
339
+ so frame drift surfaces as a real error on stderr before any stdout. Internalised
340
+ from the ``spec-to-plan`` skill wrapper (issue #30); unlike ``converge`` it never
341
+ persists the ``drafting``↔``converged`` transition.
342
+ """
343
+ json_mode = getattr(args, "json", False)
344
+ slugs = plan_store.list_slugs()
345
+ if not slugs:
346
+ emit_empty(_STATUS_LABELS, json_mode=json_mode)
347
+ else:
348
+ # resolve_plan + _live raise here (before any stdout) on a bad --plan or a
349
+ # source frame that regressed below convergence — routed to stderr.
350
+ plan = resolve_plan(args.plan)
351
+ _frame, targets = _live(plan)
352
+ plan.targets = targets # in-memory refresh only; status does not save
353
+ result = evaluate_plan(plan, targets=targets)
354
+ emit_status(
355
+ _STATUS_LABELS, selected=plan.slug, total=len(slugs), result=result, json_mode=json_mode
356
+ )
357
+ return 0
358
+
359
+
322
360
  def cmd_plan_show(args: argparse.Namespace) -> int:
323
361
  plan = resolve_plan(args.plan)
324
362
  if getattr(args, "json", False):
@@ -465,6 +503,10 @@ def register(sub: argparse._SubParsersAction) -> None:
465
503
  _plan_opt(pwv)
466
504
  pwv.set_defaults(func=cmd_plan_waves)
467
505
 
506
+ pst = psub.add_parser("status", help="Where the plan stands + the recommended next move.")
507
+ _plan_opt(pst)
508
+ pst.set_defaults(func=cmd_plan_status)
509
+
468
510
  psh = psub.add_parser("show", help="Render the plan.")
469
511
  _plan_opt(psh)
470
512
  psh.set_defaults(func=cmd_plan_show)
@@ -0,0 +1,53 @@
1
+ """``devague status`` — where the frame stands + the recommended next move.
2
+
3
+ A read-only value-add verb that composes ``list`` and ``converge``: it reports
4
+ the selected frame, the convergence verdict (blockers and warnings), and, when
5
+ not converged, the next move for the first gap. This logic used to live in the
6
+ ``think`` skill wrapper's embedded Python (issue #30); internalising it makes it
7
+ deterministic and unit-testable and removes the wrapper's temp-file +
8
+ stdout-ordering hazards. ``status`` never mutates state — like ``review`` it only
9
+ reports (the ``drafting``↔``converged`` transition stays in ``converge``).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+
16
+ from devague import store
17
+ from devague.cli._frames import resolve
18
+ from devague.cli._status import StatusLabels, emit_empty, emit_status
19
+ from devague.convergence import evaluate
20
+
21
+ _LABELS = StatusLabels(
22
+ noun="frame",
23
+ ready_key="ready_for_spec",
24
+ export_move="devague export # write the buildable spec",
25
+ empty_text=(
26
+ "no frames yet — start one:\n"
27
+ ' devague new "<announcement>"\n'
28
+ " first question: \"What's the announcement? Pretend this shipped"
29
+ ' successfully — what would you announce?"'
30
+ ),
31
+ )
32
+
33
+
34
+ def cmd_status(args: argparse.Namespace) -> int:
35
+ json_mode = getattr(args, "json", False)
36
+ slugs = store.list_slugs()
37
+ if not slugs:
38
+ emit_empty(_LABELS, json_mode=json_mode)
39
+ else:
40
+ # A bad --frame raises here (before any stdout) so the error reaches stderr.
41
+ frame = resolve(args.frame)
42
+ result = evaluate(frame)
43
+ emit_status(
44
+ _LABELS, selected=frame.slug, total=len(slugs), result=result, json_mode=json_mode
45
+ )
46
+ return 0
47
+
48
+
49
+ def register(sub: argparse._SubParsersAction) -> None:
50
+ p = sub.add_parser("status", help="Where the frame stands + the recommended next move.")
51
+ p.add_argument("--frame", help="Frame slug (default: current).")
52
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
53
+ p.set_defaults(func=cmd_status)