feat: stack development in three phases - stack-dev (design), stack-build, stack-close, handed over through tracker states (#168)
stack-build and stack-close carry disable-model-invocation, so each phase change is the operator's slash command; no skill offers a mid-session /model or /effort switch. Mode rules and the phase table move to instructions/dev/stack-mode.md, publish and CI waiting to instructions/dev/publish-and-ci.md, the ready definition to issue-tracking.md. Files changed: - AGENTS.md - CHANGES.md - DEVELOPMENT.md - README.md - VERSION - docs/model-and-effort-selection.md - instructions/CONTRACT.md - instructions/dev/commonplace-kb.md - instructions/dev/dev-setup.md - instructions/dev/doc-pull-through.md - instructions/dev/issue-tracking.md - instructions/dev/publish-and-ci.md - instructions/dev/stack-build/SKILL.md - instructions/dev/stack-close/SKILL.md - instructions/dev/stack-dev/SKILL.md - instructions/dev/stack-mode.md - instructions/dev/testing-conventions.md - instructions/dev/version-parts.md - tools/chemenu/commands/git_publish.py - tools/chemenu/tests/test_git_publish.py - tools/chemenu/tests/test_instructions_cmd.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
This commit is contained in:
1 parent
80488bee38
commit
d8224ee2ab
21 files changed
+631
-331
No files matched your search
@@ -1,98 +1,70 @@
|
||||
---
|
||||
name: stack-close
|
||||
description: Closes out a stack-dev work package after its publish has landed - rewrites the issue body to its final state, checks for docs/ staleness, and names which model ran which phase of the session. Use right after a stack-dev session's tools/wikitool publish succeeds, or when resuming a package that was published but never closed.
|
||||
description: Closes out a stack work package once its publish has a green CI run - checks the issue body's final state, checks for docs/ and contract staleness, and records which model and effort ran each phase in the closing comment. Started only by the operator as /stack-close, after stack-build has ended its phase.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Stack Close
|
||||
|
||||
**Purpose:** Carry out the unchecked closing phase of a stack-development work package, as its
|
||||
own skill rather than a break `stack-dev` has to remember to ask for mid-flow.
|
||||
own skill rather than a step the build session has to remember to take on its own.
|
||||
|
||||
**Trigger:** A `stack-dev` session's `tools/wikitool publish` just succeeded - `stack-dev` ends
|
||||
there and hands off here rather than continuing into this phase in the same breath. Also: `publish`
|
||||
printed its stack-machinery note ("this publish touched stack machinery...") and nothing has
|
||||
closed the work package it belongs to yet; or a package was published in an earlier session and
|
||||
never went through this skill (the gap this split exists to make impossible to skip past
|
||||
silently - see `instructions/dev/issue-tracking.md`'s note that a closed body is the version
|
||||
everyone reads afterwards and nobody revisits).
|
||||
**Trigger:** The operator runs `/stack-close`, after `stack-build` ended its phase with a green
|
||||
CI run on the published commit. Nothing else starts this skill: the frontmatter's
|
||||
`disable-model-invocation` keeps Claude Code from invoking it, and in a harness that ignores that
|
||||
key this sentence is the rule. Also for a package that was published in an earlier session and
|
||||
never closed - the operator starts it the same way.
|
||||
|
||||
**This directory is dev-only.** Same boundary as `stack-dev`
|
||||
(its own `instructions/dev/stack-dev/SKILL.md` has the full reasoning) - `dist export` prunes
|
||||
`instructions/dev/` wholesale, so this skill never reaches a distributed instance.
|
||||
`instructions/dev/stack-mode.md` has the rules of a stack-development session and the three
|
||||
phases this one ends; read it first when this session started cold.
|
||||
|
||||
## Why this is a separate skill, not `stack-dev`'s step 6
|
||||
## Why this is a separate skill, and why the operator starts it
|
||||
|
||||
The two phases around the mechanical middle of a stack-dev session have no mechanical guard at
|
||||
all - `pytest`, `docs verify` and `instructions verify` cover the code and tests in between, and
|
||||
The phases around the checked middle of a work package have no mechanical guard at all -
|
||||
`pytest`, `docs verify`, `instructions verify` and CI cover the code and tests in between, and
|
||||
nothing covers a changelog entry's accuracy, a `docs/` page's staleness, or an issue body's final
|
||||
state (see `docs/model-and-effort-selection.md`). Asking the
|
||||
same session to notice it has crossed into that second unchecked stretch - as a prose break inside
|
||||
`stack-dev`'s own step 6 - failed twice in a row on this stack (Gitea #42, then #30): both times
|
||||
the session knew the rule and skipped past it anyway, because nothing in the moment forced the
|
||||
question. Splitting the phase into its own skill does not add a check either - `wikitool` still
|
||||
does not know this tracker exists and must not learn (see
|
||||
`instructions/dev/issue-tracking.md` § What no tool checks) - but it removes the thing that
|
||||
was actually failing: the closing *procedure* is no longer sitting in the session's context as a
|
||||
next step to run past - it exists only inside a skill someone has to invoke.
|
||||
state (see `docs/model-and-effort-selection.md`). Asking the same session to notice it has
|
||||
crossed into that unchecked stretch - as a prose break inside one long skill - failed twice in a
|
||||
row on this stack (Gitea #42, then #30): both times the session knew the rule and skipped past
|
||||
it anyway, because nothing in the moment forced the question. The first split (Gitea #47) moved
|
||||
the closing procedure into its own skill, so it no longer sat in the session's context as a next
|
||||
step to run past - but the trigger stayed a sentence: the build skill told the agent to "invoke
|
||||
it now", and a prose model-switch offer at the same point never once led to a switch (Gitea #50).
|
||||
|
||||
**Be precise about what that does and does not buy**, because the honest version is weaker than
|
||||
"now it cannot be skipped". What did **not** change is the trigger: `stack-dev`'s "invoke it now"
|
||||
is still a sentence, and `publish`'s stack-machinery note is deliberately generic enough not to
|
||||
name this skill at all. Two of the three links in that chain remain self-discipline. The split
|
||||
narrows the failure, it does not close it - treat a session that reaches this text as the
|
||||
mechanism having worked *this time*, not as proof that it always will.
|
||||
|
||||
See Gitea #47 for the full incident history and the rejected alternative (a
|
||||
model-switched subagent - not buildable in Claude Code, where a fork inherits the parent's model
|
||||
and a fresh subagent starts without the session's context).
|
||||
Since Gitea #168 the trigger is the operator's slash command, and the skill cannot be invoked by
|
||||
the agent at all in Claude Code. **Be precise about what that buys.** The phase change is now a
|
||||
real stop rather than a sentence in the output, and it is where the operator decides on context
|
||||
and model. What moved is the risk #47 named: forgetting the close is now the operator's failure,
|
||||
not the agent's. Two signals stay to catch it - `publish`'s note that what follows CI is checked
|
||||
by nothing, and an open issue on the board whose criteria are ticked and whose CI is green. Treat
|
||||
a session that reaches this text as the mechanism having worked *this time*, not as proof that it
|
||||
always will.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Offer the model switch back up, once, and keep working either way.** A model of the
|
||||
message, not a script to quote: say it in the instance's KB language, per `AGENTS.md`
|
||||
§ File naming.
|
||||
1. **Check the handover you start from.** The body names a green CI run on the published commit
|
||||
(`stack-build` step 6). If it does not, or the run is red, this is not the closing phase yet:
|
||||
say so and recommend `/stack-build #N`. A red run is never closed over.
|
||||
|
||||
> From here on no mechanical check applies - nothing verifies the issue body, `docs/`
|
||||
> staleness, or the changelog prose. If you want to switch back to Opus, now is the moment.
|
||||
This phase is meant to run on Opus at high effort - a lower effort gives up multi-file
|
||||
consistency first, which is exactly what the staleness check in step 3 needs. If this session
|
||||
runs on something else, say so once and carry on; offer no `/model` or `/effort` switch (see
|
||||
`instructions/dev/stack-mode.md` § Sessions and models), and record it in step 4.
|
||||
|
||||
**Never block on the answer.** The change is already published; a session that stops here
|
||||
leaves exactly the state this skill exists to prevent.
|
||||
|
||||
2. **Rewrite the issue body to its final state, then close.** The test is what a reader who
|
||||
opens the closed issue tomorrow would conclude:
|
||||
2. **Check that the body is in its final state.** `stack-build` kept it current at three fixed
|
||||
points, so this is a check, not a rewrite - but the test is still what a reader who opens the
|
||||
closed issue tomorrow would conclude:
|
||||
|
||||
- every acceptance criterion ticked, or struck with the reason it was dropped
|
||||
- proposals that were decided read as decided; a "to decide" section has become the decision
|
||||
and its reasoning
|
||||
and its reasoning; an open, non-blocking question carries its answer
|
||||
- nothing left in the present tense about a defect that no longer exists
|
||||
- what was verified is named - which checks ran, which CI run - not a commit hash alone
|
||||
|
||||
Then one short comment naming what changed against the previous state, and nothing else -
|
||||
`instructions/dev/issue-tracking.md` steps 2-3 and 7 have the full shape; this is that
|
||||
procedure, run at the point this skill exists to guarantee it actually gets run.
|
||||
|
||||
**Close only once CI on the published commit is green, and wait for it this way.** The
|
||||
push-triggered runs take about **4 minutes**; a push that moved `VERSION` to a suffix-free
|
||||
release adds about **1 minute** for the release job. So:
|
||||
|
||||
- Read the runs for the published commit's SHA through the authenticated Gitea connection
|
||||
`ENVIRONMENT.md` lists (the Gitea MCP's `actions_run_read`, `list_runs`), right after
|
||||
`publish`, to get their ids. **Never anonymously via `curl`:** Gitea answers the Actions
|
||||
API with `401 token is required` even for this public repo.
|
||||
- Check again after about 4 minutes (5 with a release job), then once a minute.
|
||||
- **Give up after 15 minutes** and hand the open runs to the user by id and link, rather than
|
||||
waiting on.
|
||||
- Any shell loop that polls instead must **end on the first non-2xx status or missing field**
|
||||
and print the raw response. A loop that treats an error as "not finished yet" never ends;
|
||||
that happened in the #139 close-out.
|
||||
|
||||
A red run is not closed over: report it, and the package stays open until it is fixed.
|
||||
|
||||
Fix what is off by rewriting the body (`instructions/dev/issue-tracking.md` steps 2 and 7).
|
||||
**A closing report in a comment does not satisfy this**, however thorough: it reads as
|
||||
complete to whoever writes it and leaves a body still phrased as open work. Nothing mechanical
|
||||
catches it, which is why this is a step - and now a whole skill - rather than a habit. #44 and
|
||||
#45 both closed exactly this way on the old, single-skill shape, the second an hour after the
|
||||
rule was first written down.
|
||||
complete to whoever writes it and leaves a body still phrased as open work. #44 and #45 both
|
||||
closed exactly this way, the second an hour after the rule was first written down.
|
||||
|
||||
3. **Check whether a `docs/` page, a contract, or a new human doc went stale.** A `docs/` page
|
||||
carries no normative sentence, so nothing verifies it by construction (AGENTS.md § File
|
||||
@@ -107,7 +79,7 @@ and a fresh subagent starts without the session's context).
|
||||
|
||||
**If the published diff touches an installation instruction, read the human guide against
|
||||
it once more.** Which instructions those are and which human document answers for each is
|
||||
the pull-through table's row in `instructions/dev/doc-pull-through.md` - `stack-dev` step 5
|
||||
the pull-through table's row in `instructions/dev/doc-pull-through.md` - `stack-build` step 5
|
||||
applied it before the publish; this is the second reading, after. A deviation found here is
|
||||
filed as a follow-up issue naming both files and the sentence that disagrees, rather than
|
||||
fixed in this phase: the pull-through before the publish missed it, and that miss is worth a
|
||||
@@ -118,42 +90,48 @@ and a fresh subagent starts without the session's context).
|
||||
is generated (AGENTS.md invariant 1), `docs verify` fails on stale exactly as on missing, and
|
||||
a pull-through in this phase is a common way to move a heading without noticing.
|
||||
|
||||
**A pull-through of its own needs its own bump.** This phase runs *after* `stack-dev` step 4
|
||||
has already bumped the version, and the documents it touches are frequently the ones CI's
|
||||
version gate watches - `types/`, `instructions/`, `tools/`, `AGENTS.md`, any
|
||||
`<stage>/CONTRACT.md`. A commit into one of those without a `VERSION` line fails the gate
|
||||
(`.gitea/workflows/ci.yml`, "Version gate"), whatever the session meant it as. Reading the
|
||||
edit as "only documentation" is the trap: `types/source.md` is a document *and* a shipped
|
||||
behaviour description, and the gate is scoped by path, not by intent. So run
|
||||
`tools/wikitool version bump --patch` in the same breath as the pull-through commit - it
|
||||
only advances the running candidate's counter - rather than discovering it from a red run
|
||||
after the issue is already closed.
|
||||
**A pull-through of its own needs its own bump, publish and CI run.** The documents this
|
||||
phase touches are frequently the ones CI's version gate watches - `types/`, `instructions/`,
|
||||
`tools/`, `AGENTS.md`, any `<stage>/CONTRACT.md`. A commit into one of those without a
|
||||
`VERSION` line fails the gate (`.gitea/workflows/ci.yml`, "Version gate"), whatever the
|
||||
session meant it as. Reading the edit as "only documentation" is the trap: `types/source.md`
|
||||
is a document *and* a shipped behaviour description, and the gate is scoped by path, not by
|
||||
intent. So run `tools/wikitool version bump --patch` in the same breath as the pull-through -
|
||||
it only advances the running candidate's counter - then publish and wait per
|
||||
`instructions/dev/publish-and-ci.md`, and name that run in the body too.
|
||||
|
||||
4. **Name which model ran which phase - not only this one.** This is the handover in full, not
|
||||
a note about the tail alone: state the model for the design/version-part/boundary-judgment
|
||||
phase (`stack-dev` step 3), for the mechanical middle (code, tests, the version bump), and for
|
||||
this closing phase - all three, even when they are all the same model. A handover that only
|
||||
flags a cheap-model *closing* phase stays silent exactly when the earlier, equally unchecked
|
||||
design phase also ran cheap and nobody offered the switch back then either; naming all three
|
||||
every time is what keeps that omission from being the quiet default.
|
||||
4. **Record the handover, then close.** The closing comment carries the one changelog line
|
||||
`instructions/dev/issue-tracking.md` step 3 asks for, and the handover for **every phase**,
|
||||
not only this one:
|
||||
|
||||
| Phase | Model | Effort | Own session? | Context overflowed or compacted? |
|
||||
|---|---|---|---|---|
|
||||
| 1 Design (`stack-dev`) | | | | |
|
||||
| 2 Build (`stack-build`) | | | | |
|
||||
| 3 Closing (`stack-close`) | | | | |
|
||||
|
||||
plus the issue's `size/` label. Fill every cell, even when all three phases ran the same
|
||||
model in one session - a handover that only flags the unusual case stays silent exactly when
|
||||
an equally unchecked phase also ran cheap. Phases from an earlier session are named from the
|
||||
record (the issue's comments, `CHANGES.md`), not from memory, and marked unknown where the
|
||||
record does not say. These rows are the evidence `docs/model-and-effort-selection.md`'s
|
||||
phase guide is checked against.
|
||||
|
||||
Then close the issue.
|
||||
|
||||
## Decision points
|
||||
|
||||
- **The work package spans several sessions?** Run this skill once, at the point the package is
|
||||
actually finished and its last publish has landed - not after every individual publish. A
|
||||
package still open across sessions keeps its body current per
|
||||
`instructions/dev/issue-tracking.md` step 2 in the meantime; that is maintenance, not
|
||||
closing.
|
||||
actually finished and its last publish has a green run - not after every individual publish.
|
||||
- **Resuming a package whose publish landed in an earlier, already-ended session?** Run this
|
||||
skill now, on whatever model the current session is - do not reopen the earlier session to run
|
||||
it "correctly." The handover in step 4 names the earlier phases from the historical record
|
||||
(the issue's comments, `CHANGES.md`) rather than from memory.
|
||||
it "correctly." Step 4 names the earlier phases from the record.
|
||||
- **Nothing to close - the session's own exploration, no publish happened?** This skill does not
|
||||
apply; there is no package to rewrite a body for.
|
||||
apply; there is no package to close.
|
||||
|
||||
## Scope
|
||||
|
||||
Follows a `stack-dev` session's publish. Not for wiki content work - use
|
||||
`wiki-ingest`/`wiki-query`/`wiki-manage`/`wiki-lint`/`wiki-status`/`gtd-weekly-review` for that,
|
||||
whose own closing conventions (`kb/log.md`, page provenance) are unrelated to this tracker-body
|
||||
procedure.
|
||||
Follows `stack-build`'s green CI run (`instructions/dev/stack-build/SKILL.md`). Not for wiki
|
||||
content work - use `wiki-ingest`/`wiki-query`/`wiki-manage`/`wiki-lint`/`wiki-status`/
|
||||
`gtd-weekly-review` for that, whose own closing conventions (`kb/log.md`, page provenance) are
|
||||
unrelated to this tracker-body procedure.
|
||||
Reference in new issue
Block a user