feat: stack development in three phases - stack-dev (design), stack-build, stack-close, handed over through tracker states (#168)
CI / verify (push) Successful in 5m15s
CI / pwsh (push) Successful in 2m2s
Release / release (push) Successful in 34s

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:
torbenandClaude Opus 5.5 committed 2026-10-02 20:33:07 +02:00
1 parent 80488bee38
commit d8224ee2ab
21 files changed
+631 -331

No files matched your search

+3 -2
View File
@@ -335,8 +335,9 @@ see [Gates](#gates).
Extending `tools/wikitool`, the type schema, or the instruction/skill layer itself (rather than Extending `tools/wikitool`, the type schema, or the instruction/skill layer itself (rather than
operating on wiki content) is a different session type with different rules - see the operating on wiki content) is a different session type with different rules - see the
`stack-dev` skill, nested under [instructions/dev/](instructions/dev/) along with the `stack-dev` skill, the entry to that work's three phases (`stack-dev`, `stack-build`,
procedures it routes to. Setting up a clone of this origin repository for that work - the demo `stack-close`), nested under [instructions/dev/](instructions/dev/) along with the
procedures they route to. Setting up a clone of this origin repository for that work - the demo
corpus, the preflight, `dist export` as a build and test tool - is corpus, the preflight, `dist export` as a build and test tool - is
[instructions/dev/dev-setup.md](instructions/dev/dev-setup.md). Never present in a distributed [instructions/dev/dev-setup.md](instructions/dev/dev-setup.md). Never present in a distributed
instance. instance.
+38 -1
View File
@@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse.
--- ---
## 8.0.0-beta.23 - 2026-10-02 - publish keeps a closing trailer block of --message last, so git reads Co-Authored-By again (#149) ## 8.0.0-beta.24 - 2026-10-02 - Stack-Entwicklung in drei Phasen: stack-dev (Design), stack-build, stack-close - Übergabe über den Tracker, kein Modellwechsel in der Sitzung
**Author:** Torben Nehmer **Author:** Torben Nehmer
@@ -98,6 +98,7 @@ concern - readable here, never shipped as something to parse.
- INSTALL.md an die Installationsinstruktionen gekoppelt: Voraussetzungen generiert, Setup-Fragen geprüft - INSTALL.md an die Installationsinstruktionen gekoppelt: Voraussetzungen generiert, Setup-Fragen geprüft
- tools/bugreport: Starter für den Bugreport-Sammler, überspringt die Store-Aliase (#166) - tools/bugreport: Starter für den Bugreport-Sammler, überspringt die Store-Aliase (#166)
- publish keeps a closing trailer block of --message last, so git reads Co-Authored-By again (#149) - publish keeps a closing trailer block of --message last, so git reads Co-Authored-By again (#149)
- Stack-Entwicklung in drei Phasen: stack-dev (Design), stack-build, stack-close - Übergabe über den Tracker, kein Modellwechsel in der Sitzung
**Low impact** **Low impact**
- version bump no longer points at version release in its output - version bump no longer points at version release in its output
@@ -135,6 +136,42 @@ concern - readable here, never shipped as something to parse.
- preflight.ps1: the asset-mode error helper is Exit-Asset, so PSScriptAnalyzer passes - preflight.ps1: the asset-mode error helper is Exit-Asset, so PSScriptAnalyzer passes
<!-- /wikitool:bumps --> <!-- /wikitool:bumps -->
### Stack-Entwicklung in drei Phasen: `stack-dev` (Design), `stack-build`, `stack-close` (#168)
Bisher hatte die Stack-Entwicklung zwei Skills für drei Phasen und koppelte jeden Phasenwechsel an
einen Modellwechsel mitten in der Sitzung: `stack-dev` umfasste Design und Bau und bot am Übergang
`/model sonnet` an, `stack-close` bot `/model opus` an und wartete auch noch auf CI. Der Wechsel
fand in der Praxis nie statt (#50), er hätte den Prompt-Cache verworfen, und die Bauphase passt
nicht in Sonnets Kontext (#151 B lief in die Kompaktierung). Ein roter CI-Lauf machte außerdem die
Abschlussphase wieder zur Bauphase.
Jetzt hat jede Phase ihren Skill, und übergeben wird über einen Zustand im Tracker statt über den
Kontext einer Sitzung:
- `stack-dev` ist der Design-Skill und bleibt der automatische Einstieg. Er endet an einem Body,
der **ready** ist (`instructions/dev/issue-tracking.md` § Ready to build), und nennt dem
Betreiber `/stack-build #N`.
- `stack-build` (neu) prüft zuerst, ob der Body ready ist, baut, bumpt, zieht die Dokumente nach,
publiziert und wartet auf grünes CI. Den Body pflegt er an drei festen Stellen: bei einer
Abweichung, nach dem Publish, bei grünem CI.
- `stack-close` prüft den Endzustand des Bodys und veraltete `docs/`- und Contract-Prosa, dann
schließt er das Issue. Der Schließkommentar nennt pro Phase Modell, Effort, Sitzungsgrenze und
Kontextüberlauf, dazu `size/`.
`stack-build` und `stack-close` tragen `disable-model-invocation: true`. In Claude Code kann sie
deshalb nur der Betreiber starten, und jeder Phasenwechsel ist ein echter Halt, an dem er über
Weitermachen, `/clear` oder ein anderes Modell entscheidet. Keiner der drei Skills bietet noch
einen `/model`- oder `/effort`-Wechsel in der Sitzung an. Laut Anthropics Doku zum Prompt-Caching
invalidiert auch eine Effort-Änderung den gecachten Gesprächsverlauf, daher wird auch dort an
einer Übergabe geschnitten statt umgeschaltet.
Gemeinsames steht je einmal in eigenen Instruktionen: die Mode-Regeln, die Phasentabelle und der
Katalog der Dev-Verfahren in `instructions/dev/stack-mode.md`, die lokalen Checks, `publish` und
das CI-Warten in `instructions/dev/publish-and-ci.md`. Die Notiz, die `publish` nach einem
Stack-Publish druckt, sagt jetzt, dass CI noch kommt und erst danach die ungeprüfte Strecke
beginnt. `docs/model-and-effort-selection.md` beschreibt die Modellwahl pro Sitzung und nennt
Sonnet nicht mehr als Standard für die Bauphase.
### `publish` keeps a closing trailer block of `--message` last (#149) ### `publish` keeps a closing trailer block of `--message` last (#149)
`publish` appended its `Files changed:` list to the end of `--message`. git reads trailers only `publish` appended its `Files changed:` list to the end of `--message`. git reads trailers only
+12 -9
View File
@@ -164,12 +164,15 @@ Tracker laufen lässt und was ein roter Lauf bedeutet, steht in
## Stack-Entwicklung als eigener Sitzungstyp ## Stack-Entwicklung als eigener Sitzungstyp
Der `stack-dev`-Skill (`instructions/dev/`, nur in diesem Ursprungs-Repo vorhanden) fasst die Drei Skills (`instructions/dev/`, nur in diesem Ursprungs-Repo vorhanden) führen eine Sitzung,
Regeln für eine Sitzung, die den Stack selbst statt Wiki-Inhalt bearbeitet: wann die den Stack selbst statt Wiki-Inhalt bearbeitet, durch drei Phasen: `stack-dev` arbeitet das
Quellenbindung nicht gilt, wo Design endet und die mechanische Phase beginnt (mit dem Issue aus, bis sein Body „ready“ ist, `stack-build` baut, publiziert und wartet auf einen grünen
Modellwechsel-Hinweis), und endet mit dem Publish. Die Schlussphase - Issue-Body als Rewrite CI-Lauf, `stack-close` prüft den Endzustand des Bodys und veraltete `docs/`- und Contract-Prosa
statt Kommentar, `docs/`-Veralterung, die Modell-Handover-Zeile über die ganze Sitzung - liegt und schließt das Issue. Übergeben wird über den Zustand im Tracker, nicht über den Kontext einer
seit `4.6.0` in einem eigenen Folge-Skill, `stack-close`, den `stack-dev` an dieser Stelle Sitzung: Jeder Phasenwechsel geht in derselben Sitzung oder nach `/clear`. `stack-dev` greift
übergibt statt sie als weiteren eigenen Schritt zu führen. Siehe automatisch; `stack-build` und `stack-close` startet nur der Betreiber per Slash-Kommando
[instructions/dev/issue-tracking.md](instructions/dev/issue-tracking.md) für den (`/stack-build #N`, `/stack-close`) - an genau dieser Stelle fällt die Wahl, ob es in derselben
Issue-Tracker selbst. Sitzung weitergeht oder in einer neuen, auf welchem Modell. Einen Modellwechsel mitten in der
Sitzung bietet keiner der drei an. Regeln und Phasentabelle stehen in
[instructions/dev/stack-mode.md](instructions/dev/stack-mode.md), der Issue-Tracker selbst in
[instructions/dev/issue-tracking.md](instructions/dev/issue-tracking.md).
+3 -3
View File
@@ -450,9 +450,9 @@ tools/wikitool <command> -h
<!-- dist:strip-start --> <!-- dist:strip-start -->
Dev-instance-only: extending `tools/wikitool`, the type schema, or the instruction/skill layer Dev-instance-only: extending `tools/wikitool`, the type schema, or the instruction/skill layer
itself is a separate session type with its own rules, covered by the `stack-dev` skill nested itself is a separate session type with its own rules, covered by the `stack-dev`,
under `instructions/dev/` (never present in a distributed instance - `tools/CONTRACT.md` `stack-build` and `stack-close` skills nested under `instructions/dev/` (never present in a
explains why). distributed instance - `tools/CONTRACT.md` explains why).
<!-- dist:strip-end --> <!-- dist:strip-end -->
### MCP read server (optional) ### MCP read server (optional)
+1 -1
View File
@@ -1 +1 @@
8.0.0-beta.23 8.0.0-beta.24
+54 -13
View File
@@ -14,11 +14,23 @@ one more round. Work behind nothing but a session reading prose does not surface
ships, and stays until someone happens to notice. That asymmetry, not task size, is what the ships, and stays until someone happens to notice. That asymmetry, not task size, is what the
phase guide below is built on. phase guide below is built on.
<!-- wikitool:toc -->
## Contents
- [Phase guide](#phase-guide)
- [Model per session, not per phase](#model-per-session-not-per-phase)
- [Subagent models](#subagent-models)
- [`/code-review` effort](#code-review-effort)
- [When it's unclear](#when-its-unclear)
- [Scope](#scope)
<!-- /wikitool:toc -->
## Phase guide ## Phase guide
<!-- dist:strip-start --> <!-- dist:strip-start -->
This repo's own stack-development work splits the axis into three phases, one per switch point This repo's own stack-development work splits the axis into three phases, one skill each -
in its `stack-dev`/`stack-close` skills: `stack-dev` (design), `stack-build` (build) and `stack-close` (closing) - handed over through
states in the issue tracker rather than inside one session:
<!-- dist:strip-end --> <!-- dist:strip-end -->
| Phase / task | What would catch a mistake | Suggested model | Effort | | Phase / task | What would catch a mistake | Suggested model | Effort |
@@ -27,20 +39,48 @@ in its `stack-dev`/`stack-close` skills:
| `wiki-lint` | `lint` itself is the check | Sonnet | default | | `wiki-lint` | `lint` itself is the check | Sonnet | default |
| `wiki-ingest`, `wiki-manage`, judgment-heavy `wiki-query` | `lint` and `docs verify`, partly | Sonnet | high | | `wiki-ingest`, `wiki-manage`, judgment-heavy `wiki-query` | `lint` and `docs verify`, partly | Sonnet | high |
| Stack dev: design, the version part, a boundary-crossing judgment | nothing mechanical | Opus | high | | Stack dev: design, the version part, a boundary-crossing judgment | nothing mechanical | Opus | high |
| Stack dev: code, tests, mechanical doc sync | `pytest`, `docs verify`, `instructions verify`, CI | Sonnet | high | | Stack dev: code, tests, mechanical doc sync, waiting for CI | `pytest`, `docs verify`, `instructions verify`, CI | Opus (open - see below) | high; medium for a small change |
| Stack dev: closing an issue, `docs/` staleness, changelog prose | nothing, by construction | Opus | high | | Stack dev: closing an issue, `docs/` staleness, changelog prose | nothing, by construction | Opus | high |
The middle stack-dev row is where the tokens are and where the checks are, so it is the one worth The two unchecked rows are short - minutes, not hours - so keeping them on the strongest model
running cheaper. The two rows around it are short - minutes, not hours - so keeping them on the costs little and protects the only work that fails silently. The checked middle row is where the
stronger model costs little and protects the only work in the session that fails silently. tokens are, which makes it the tempting one to run cheaper. It is also the row with the least
settled answer: a build phase of this stack reads a lot of the tree, and a smaller context window
does not hold it - it runs into compaction, which costs more than the cheaper model saves. Sonnet
at high effort remains possible, but only as a session of its own (below). Which default the
middle row should have is left to the record: every closed work package names the model, effort
and session shape of each phase, and the table follows that evidence rather than the other way
round.
**Effort is the cheaper lever than the model.** A reduced effort level is what gives up **Effort is the cheaper lever than the model.** A reduced effort level is what gives up
multi-file consistency first, so `high` is a reasonable floor for anything touching more than one multi-file consistency first, so `high` is a reasonable floor for anything touching more than one
file or a contract; `default` suits a single-file mechanical edit with a test behind it. file or a contract; `default` or `medium` suits a small mechanical change with a test behind it.
A session cannot switch its own model - that is the user's `/model` - so this table only pays off ## Model per session, not per phase
if someone offers the switch at the moment a phase changes, once, without turning it into a
debate. A session cannot switch its own model - that is the user's `/model` - and it should not be asked
to mid-flow either. Two reasons:
- **A switch throws away the prompt cache.** A cache entry belongs to the model that wrote it,
so a new model starts the session's whole history from cold. The same holds for effort: changing it always invalidates
the cached message history - by far the largest part of a long session - and, on some models,
the tool and system prefix too (Anthropic's prompt-caching documentation lists effort and the
thinking configuration among what invalidates the cache). A switch from one effort to another
costs the same re-read of the whole session as a switch of model.
- **An offered switch is rarely taken.** A sentence in the output at the moment a phase changes
is easy to read past - for the agent writing it and for the user reading it - and the session
just carries on in whatever it started as.
So the choice is made once, when a session starts, and phases that want different models or
effort levels are separated by a session boundary instead: `/clear`, then the next phase in a
session started the right way. That is only cheap if the next phase does not depend on the
previous session's context - which is why the handover has to live somewhere outside the session
(an issue body, a page) and be kept current at fixed points, not reconstructed at the end.
<!-- dist:strip-start -->
In this repo the handover points are a ready issue body (design → build) and a green CI run with
the body updated (build → closing); the two later skills can only be started by the user's slash
command, so each phase change is a real stop at which that choice is made.
<!-- dist:strip-end -->
## Subagent models ## Subagent models
@@ -70,9 +110,10 @@ choice a session *can* make on its own:
reflexively over-provisioning is a standing cost every session pays. reflexively over-provisioning is a standing cost every session pays.
- Not sure whether a phase is checked: treat it as unchecked - a needless Opus phase costs money - Not sure whether a phase is checked: treat it as unchecked - a needless Opus phase costs money
once, an unchecked Sonnet phase can ship something nobody looks at again. once, an unchecked Sonnet phase can ship something nobody looks at again.
- Mid-session and the phase changed but nobody switched: keep working - never block a publish or - The phase changed and the session runs on a model or effort the table would not pick: keep
an issue close on a model the session cannot change itself. Naming which model ran which phase working, and cut the session at the next handover rather than switching mid-flow - never block a
in the handover keeps the gap visible instead of silent. publish or an issue close on a choice the session cannot make itself. Naming which model and
effort ran which phase in the handover keeps the gap visible instead of silent.
## Scope ## Scope
+4 -3
View File
@@ -239,7 +239,8 @@ that layer is named GTD rather than folded into `wiki-`), and `stack-` for the s
development, nested under `instructions/dev/` and therefore never present in a distributed development, nested under `instructions/dev/` and therefore never present in a distributed
instance (`instructions/dev/` above). instance (`instructions/dev/` above).
<!-- dist:strip-start --> <!-- dist:strip-start -->
Dev-instance-only: the two skills in that family today are `stack-dev` and `stack-close`. Dev-instance-only: the three skills in that family today are `stack-dev`, `stack-build` and
`stack-close`.
<!-- dist:strip-end --> <!-- dist:strip-end -->
A new skill takes the prefix of the family it belongs to, or opens a new one deliberately - never A new skill takes the prefix of the family it belongs to, or opens a new one deliberately - never
a bare name. a bare name.
@@ -350,8 +351,8 @@ symmetry: every other skill's flow is short enough, and fails loudly enough step
reader cannot lose the thread even without a checklist - `wiki-manage`'s two flows, `wiki-query`, reader cannot lose the thread even without a checklist - `wiki-manage`'s two flows, `wiki-query`,
`wiki-status` and `gtd-weekly-review` all clear that bar. `wiki-status` and `gtd-weekly-review` all clear that bar.
<!-- dist:strip-start --> <!-- dist:strip-start -->
Dev-instance-only: `stack-dev` and `stack-close` sit under the same threshold, for the same Dev-instance-only: `stack-dev`, `stack-build` and `stack-close` sit under the same threshold,
reason. for the same reason.
<!-- dist:strip-end --> <!-- dist:strip-end -->
None of this is counted by number on purpose: a per-skill step count is a claim about a file this None of this is counted by number on purpose: a per-skill step count is a claim about a file this
+2 -1
View File
@@ -24,5 +24,6 @@ carries it (see [tools/CONTRACT.md](../../tools/CONTRACT.md) for what `dist expo
## Scope ## Scope
Only relevant while working in [stack-dev](stack-dev/SKILL.md) mode. Not part of the wiki Only relevant in a stack-development session ([stack-mode.md](stack-mode.md)), mostly while
designing. Not part of the wiki
content pipeline, and not linked from anything outside `instructions/dev/`. content pipeline, and not linked from anything outside `instructions/dev/`.
+2 -2
View File
@@ -25,8 +25,8 @@ development material under `instructions/dev/` and `commonplace/`, and no
instance. instance.
2. **Run [bootstrap.md](../bootstrap.md)** - the preflight, then `tools/wikitool instructions 2. **Run [bootstrap.md](../bootstrap.md)** - the preflight, then `tools/wikitool instructions
sync`. That publishes `stack-dev` and `stack-close` along with the content skills; both exist sync`. That publishes `stack-dev`, `stack-build` and `stack-close` along with the content skills;
only in this repository. all three exist only in this repository.
3. **Record the environment** (bootstrap.md step 5). Here it is worth the minute: which harness, 3. **Record the environment** (bootstrap.md step 5). Here it is worth the minute: which harness,
that `gitea-mcp` reaches the tracker and CI, which remote `publish` talks to. Every stack-dev that `gitea-mcp` reaches the tracker and CI, which remote `publish` talks to. Every stack-dev
+5 -5
View File
@@ -18,8 +18,8 @@ way; see Gitea #89 for one).
## When to run ## When to run
Before `tools/wikitool docs verify`/`publish` in a `stack-dev` session that changed behaviour - Before `tools/wikitool docs verify`/`publish` in a build session that changed behaviour -
`stack-dev` step 5 sends you here. Read the table below and update every row whose surface you `stack-build` step 5 sends you here. Read the table below and update every row whose surface you
touched; a row that does not apply needs no action. touched; a row that does not apply needs no action.
## Steps ## Steps
@@ -76,7 +76,7 @@ touched; a row that does not apply needs no action.
- **Unsure whether a `docs/` page's reasoning moved?** Read it. A `docs/` page carries no - **Unsure whether a `docs/` page's reasoning moved?** Read it. A `docs/` page carries no
normative sentence and nothing verifies it by construction (AGENTS.md § File naming), so an normative sentence and nothing verifies it by construction (AGENTS.md § File naming), so an
unsure guess defaults to reading the page rather than skipping the question - unsure guess defaults to reading the page rather than skipping the question -
[`stack-close`](stack-close/SKILL.md) step 3 asks it again at the end of the session as a [`stack-close`](stack-close/SKILL.md) step 3 asks it again in the closing phase as a
backstop, not as the only time it is asked. backstop, not as the only time it is asked.
- **The surface is a whole new stage, collection, or gate?** The table's rows are the steady - **The surface is a whole new stage, collection, or gate?** The table's rows are the steady
state; a new row-worthy category is itself a change to this instruction - add the row here state; a new row-worthy category is itself a change to this instruction - add the row here
@@ -84,7 +84,7 @@ touched; a row that does not apply needs no action.
## Scope ## Scope
Applies to `stack-dev` sessions only - wiki content changes have their own provenance and Applies to stack-development sessions only - wiki content changes have their own provenance and
cross-reference rules (`kb/CONTRACT.md`, `wiki-manage`), which already pull the relevant pages cross-reference rules (`kb/CONTRACT.md`, `wiki-manage`), which already pull the relevant pages
through as part of the normal skill. Not a replacement for `stack-close` step 3, which re-asks through as part of the normal skill. Not a replacement for `stack-close` step 3, which re-asks
the `docs/`-staleness question after publish as the second, session-final check. the `docs/`-staleness question after the green CI run as the second, final check.
+28 -1
View File
@@ -26,6 +26,7 @@ issues at that URL, which is exactly why `dist export` excludes
- [When to run](#when-to-run) - [When to run](#when-to-run)
- [Steps](#steps) - [Steps](#steps)
- [Incoming stubs](#incoming-stubs) - [Incoming stubs](#incoming-stubs)
- [Ready to build](#ready-to-build)
- [Renames and other decay in the tracker](#renames-and-other-decay-in-the-tracker) - [Renames and other decay in the tracker](#renames-and-other-decay-in-the-tracker)
- [Citing an issue in the repo](#citing-an-issue-in-the-repo) - [Citing an issue in the repo](#citing-an-issue-in-the-repo)
- [What no tool checks](#what-no-tool-checks) - [What no tool checks](#what-no-tool-checks)
@@ -45,6 +46,8 @@ issues at that URL, which is exactly why `dist export` excludes
(§ Incoming stubs). (§ Incoming stubs).
- **While working on one:** the body is updated as the state moves, not at the - **While working on one:** the body is updated as the state moves, not at the
end (step 2). A session that is interrupted leaves the body as its handover. end (step 2). A session that is interrupted leaves the body as its handover.
- Ending a design session, or starting a build from a body: check that it is
ready (§ Ready to build).
- Prioritising: deciding what to pick up next, or re-labelling after the ground - Prioritising: deciding what to pick up next, or re-labelling after the ground
moved. moved.
- Closing one: the body is rewritten to its final state first, and only then - Closing one: the body is rewritten to its final state first, and only then
@@ -310,6 +313,30 @@ no publish - it is tracker work, and several stubs can be worked out in one pass
What comes *after* it is an ordinary work package, picked up on its merits like What comes *after* it is an ordinary work package, picked up on its merits like
any other. any other.
## Ready to build
A body is **ready** when a session that has never seen the design conversation can build from
it alone. That is the handover from design to build ([stack-mode.md](stack-mode.md) § The
three phases): `stack-dev` ends by checking it, and `stack-build` checks it again as its first
step. All four hold:
- **The body says what will be built**, and its acceptance criteria are checkable properties
(step 1), not activities.
- **No open question is left in it.** It carries `kind/build`, and neither `status/incoming`
nor `status/unconfirmed`. A question that is genuinely not blocking may stay, marked as such
and with the answer's consequence stated either way - "check X while building; if it does
not hold, do Y" is a decision, "X is unclear" is not.
- **The version part is named** ([version-parts.md](version-parts.md)). Whether a change is a
drop-in replacement is a judgment with no mechanical guard, so it is made while designing, not
discovered at the bump.
- **The files or surfaces involved are named**, so the build starts from the tree rather than
from a search for where the change belongs.
A body that fails one of these goes back to design. It is not built around: the gap a build
session fills on its own is the same gap a `status/incoming` stub leaves (§ Incoming stubs),
only better disguised. The useful side effect of the cut between the two phases is that it
tests this definition - if a cold session cannot build from the body, it was not ready.
## Renames and other decay in the tracker ## Renames and other decay in the tracker
A rename is not finished when the tree is green. Renaming a package, a path, A rename is not finished when the tree is green. Renaming a package, a path,
@@ -356,7 +383,7 @@ it in a `<!-- dist:strip-start/end -->` block ([instructions/CONTRACT.md](../CON
`tools/**/*.py` is deliberately outside all of this. A code comment addresses whoever edits that `tools/**/*.py` is deliberately outside all of this. A code comment addresses whoever edits that
line, and that only ever happens in the origin repo, because `dist export` prunes the line, and that only ever happens in the origin repo, because `dist export` prunes the
`stack-dev` skill together with this directory; a distributed `tools/` tree is runtime `stack-` skills together with this directory; a distributed `tools/` tree is runtime
machinery, not reading material. The same holds for `.gitignore` and `tools/.coveragerc` - machinery, not reading material. The same holds for `.gitignore` and `tools/.coveragerc` -
config, not documentation. config, not documentation.
+59
View File
@@ -0,0 +1,59 @@
---
type: types/instruction.md
name: publish-and-ci
description: How a stack change is published and how its CI run is waited for - the local checks first, tools/wikitool publish, reading the runs through the authenticated Gitea connection rather than curl, how long to wait, when to give up, and why a red run means the build phase is not over.
---
# Publish a stack change and wait for its CI run
The local checks cover what they cover on this machine; CI covers the same checks plus a full
`setup-instance.md` replay against a fresh `dist export` (`.gitea/workflows/ci.yml`). A green run
on the published commit is therefore the end of the checked stretch of a work package, not a
formality after it - which is why waiting for it belongs to the phase that wrote the code
(`stack-build`), and a red run sends the work back there rather than into the closing phase.
## When to run
- `stack-build`, every time it publishes - its last step.
- `stack-close`, when its own pull-through of a stale document publishes something of its own
(that skill's step 3 says when).
## Steps
1. **Run the local checks, explicitly rather than assumed:**
```bash
tools/wikitool docs verify
tools/wikitool instructions verify
```
plus the relevant `pytest` run in `tools/` ([testing-conventions.md](testing-conventions.md)).
A red check here is fixed before anything is published.
2. **Publish with `tools/wikitool publish`.** The gates apply as everywhere (AGENTS.md § Gates);
an exit 42 is shown to the user verbatim and waited on. When the changeset touches `tools/`,
`types/`, `instructions/`, `AGENTS.md` or a `<stage>/CONTRACT.md`, `publish` prints a
one-line note that CI is the last mechanical check still to come and that what follows it is
covered by none. A push to `main` that moves `VERSION` additionally triggers a tagged release.
**CI does the tagging** - a session never creates a tag, which is what keeps AGENTS.md
invariant 5 intact.
3. **Wait for CI on the published commit, 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.
- 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.
- Keep the polling out of the main context where the harness allows it - a background
command or a fork - so a long wait does not fill the session with run listings.
4. **A red run is not waited past.** Read the failing job's log, fix the cause, and go back to
step 1: this is still build work, whichever skill published. The phase that published ends
only on a green run, and the issue stays open until there is one.
+121
View File
@@ -0,0 +1,121 @@
---
name: stack-build
description: Builds a stack work package whose Gitea issue body is ready - code, tests, version bump, document pull-through, publish, and waiting for a green CI run, keeping the issue body current at fixed points along the way. Started only by the operator as /stack-build #N, after stack-dev has ended its design phase.
disable-model-invocation: true
---
# Stack Build
**Purpose:** Carry a designed work package through the checked stretch of its life - from a
ready issue body to a green CI run on the published commit - and leave the body current enough
that the closing phase, or anyone else, can work from it without this session's context.
**Trigger:** The operator runs `/stack-build #N`. 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. This session may be the continuation of the design
session, or a fresh one after `/clear` - assume the second, and work from the body.
## Steps
1. **Read `instructions/dev/stack-mode.md`.** It holds the rules that change in a
stack-development session and the catalogue of dev-only procedures; a cold session has
neither yet.
2. **Check that the body is ready, before anything else.** Read issue #N's body against
`instructions/dev/issue-tracking.md` § Ready to build. If a point fails, stop: name it, and
recommend `/stack-dev` to finish the design. **Do not build around the gap** - a build session
that answers an open design question on its own produces a change that matches its own
reading of the body, recorded nowhere as a decision.
3. **Build the change and its tests.** Follow `instructions/dev/testing-conventions.md` before
adding or changing a test, and the other procedures the mode file names for the surface you
touch.
**"Covered by tests" means covered by the tests that exist, not by the tests that should
exist.** Whether the right test was written is itself a judgment call with no mechanical
guard: two data-destroying bugs in `upstream merge` (Gitea #30) shipped past a green
`pytest`/`docs verify`/`instructions verify`/CI because no test exercised the case, not
because the code for the tested case was worse. Where the body names a destructive step, its
invariant is a test (issue-tracking.md step 1).
**Body upkeep, first fixed point - a deviation goes into the body at once.** An assumption
that turns out false, a criterion that moves, an approach dropped: rewrite the body where it
stands, in this session, not at the end
(`instructions/dev/issue-tracking.md` step 2 has the rule; this is where it applies).
A deviation that reopens the design - a boundary crossing, a decision the body did not make -
is not settled here: stop and put it to the operator, as in step 2.
4. **Raise the version, if the change ships.** A change under `tools/`, `types/`,
`instructions/`, `AGENTS.md` or a `CONTRACT.md` reaches every future instance, so it needs a
version and a changelog entry. The part was named in the body during design; bump that part:
```bash
tools/wikitool version bump --patch --title "<what changed>" --impact medium
```
`--impact high|medium|low` (default `medium`) grades this bump in the changelog entry's own
list - `tools/wikitool version regrade` corrects it later if the candidate's overall shape
changes the read on an earlier one; see `instructions/dev/version-parts.md` § The candidate
model.
Never edit `VERSION` or the entry's heading by hand - `bump` writes both, and `docs verify`
fails a tree where they disagree. Then write the entry's body - `bump` deliberately leaves it
empty, the same way `new` leaves the prose. A `--major` bump needs `--breaking` and either a
migration document or `--no-migration`; `instructions/dev/version-parts.md` has all of it.
Prose-only changes (`README.md`, `INSTALL.md`, `EVALS.md`) and the workflows under `.gitea/`
do not need a bump - CI's version gate is scoped to what changes behaviour.
5. **Pull through every document that makes a claim about the surface you touched.**
`instructions/dev/doc-pull-through.md` has the table of which document that is, per surface,
and its step 3 for the one part that is not prose: a reference file whose headings moved needs
`tools/wikitool docs toc --apply`, never a hand-written list. Prose you write here is English,
whatever language the session is held in - `AGENTS.md` § File naming has both language rules.
6. **Publish and wait for CI** per `instructions/dev/publish-and-ci.md`: the local checks,
`tools/wikitool publish`, then the run on the published commit.
**Body upkeep, second fixed point - after the publish:** tick the criteria the publish met,
and name the version and the commit in the body.
**Third fixed point - CI green:** name the run in the body as what verified the change. A red
run is not this point: it is step 3 again, then this step again.
Then the one changelog comment for this session's worth of change
(`instructions/dev/issue-tracking.md` step 3).
7. **End the phase.** Recommend how the closing phase should run, and stop:
- **This session ran on Opus at high effort:** continue here with `/stack-close` - what was
built is still in context, and that is what the closing phase checks against.
- **It ran on another model or a lower effort:** `/clear`, then `/stack-close` in a new
session on Opus at high effort, which works from the body and the diff. That is why the
three fixed points above are not optional.
Close with the fixed line, in the instance's KB language per `AGENTS.md` § File naming:
> #N is published and CI is green (run <id>). Next: `/stack-close` - here, or after `/clear`.
**Do not run `stack-close` yourself.** Offer no `/model` or `/effort` switch either - see
`instructions/dev/stack-mode.md` § Sessions and models.
## Decision points
- **The change turns out not to be a drop-in replacement after all?** Do not bump across the
boundary on your own initiative. Every existing instance pays for a breaking change once, by
hand, so the user decides whether it is worth that: show them what breaks, what an instance
has to do about it, and the alternatives (avoid the break with a shim, defer and batch it with
the next one, or split it behind a deprecation window), then recommend one and wait for a
go-ahead. `instructions/dev/version-parts.md` step 4 has the full shape, and the body takes the
answer as a design change (step 3's first fixed point).
- **The package needs several build sessions?** Each one ends with the body current and its own
changelog comment; the phase ends once, at the green run after the last publish.
- **CI gave up after 15 minutes, or is red for a reason outside this change?** Hand the open or
failing runs to the operator by id and link. The phase is not over, and the closing line in
step 7 is not given.
## Scope
Only for a body that is ready - the design is `stack-dev`
(`instructions/dev/stack-dev/SKILL.md`), the closing after a green run is `stack-close`
(`instructions/dev/stack-close/SKILL.md`). Not for wiki content work.
+77 -99
View File
@@ -1,98 +1,70 @@
--- ---
name: stack-close 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 # Stack Close
**Purpose:** Carry out the unchecked closing phase of a stack-development work package, as its **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 **Trigger:** The operator runs `/stack-close`, after `stack-build` ended its phase with a green
there and hands off here rather than continuing into this phase in the same breath. Also: `publish` CI run on the published commit. Nothing else starts this skill: the frontmatter's
printed its stack-machinery note ("this publish touched stack machinery...") and nothing has `disable-model-invocation` keeps Claude Code from invoking it, and in a harness that ignores that
closed the work package it belongs to yet; or a package was published in an earlier session and key this sentence is the rule. Also for a package that was published in an earlier session and
never went through this skill (the gap this split exists to make impossible to skip past never closed - the operator starts it the same way.
silently - see `instructions/dev/issue-tracking.md`'s note that a closed body is the version
everyone reads afterwards and nobody revisits).
**This directory is dev-only.** Same boundary as `stack-dev` `instructions/dev/stack-mode.md` has the rules of a stack-development session and the three
(its own `instructions/dev/stack-dev/SKILL.md` has the full reasoning) - `dist export` prunes phases this one ends; read it first when this session started cold.
`instructions/dev/` wholesale, so this skill never reaches a distributed instance.
## 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 The phases around the checked middle of a work package have no mechanical guard at all -
all - `pytest`, `docs verify` and `instructions verify` cover the code and tests in between, and `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 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 state (see `docs/model-and-effort-selection.md`). Asking the same session to notice it has
same session to notice it has crossed into that second unchecked stretch - as a prose break inside crossed into that unchecked stretch - as a prose break inside one long skill - failed twice in a
`stack-dev`'s own step 6 - failed twice in a row on this stack (Gitea #42, then #30): both times row on this stack (Gitea #42, then #30): both times the session knew the rule and skipped past
the session knew the rule and skipped past it anyway, because nothing in the moment forced the it anyway, because nothing in the moment forced the question. The first split (Gitea #47) moved
question. Splitting the phase into its own skill does not add a check either - `wikitool` still the closing procedure into its own skill, so it no longer sat in the session's context as a next
does not know this tracker exists and must not learn (see step to run past - but the trigger stayed a sentence: the build skill told the agent to "invoke
`instructions/dev/issue-tracking.md` § What no tool checks) - but it removes the thing that it now", and a prose model-switch offer at the same point never once led to a switch (Gitea #50).
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.
**Be precise about what that does and does not buy**, because the honest version is weaker than Since Gitea #168 the trigger is the operator's slash command, and the skill cannot be invoked by
"now it cannot be skipped". What did **not** change is the trigger: `stack-dev`'s "invoke it now" the agent at all in Claude Code. **Be precise about what that buys.** The phase change is now a
is still a sentence, and `publish`'s stack-machinery note is deliberately generic enough not to real stop rather than a sentence in the output, and it is where the operator decides on context
name this skill at all. Two of the three links in that chain remain self-discipline. The split and model. What moved is the risk #47 named: forgetting the close is now the operator's failure,
narrows the failure, it does not close it - treat a session that reaches this text as the not the agent's. Two signals stay to catch it - `publish`'s note that what follows CI is checked
mechanism having worked *this time*, not as proof that it always will. 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
See Gitea #47 for the full incident history and the rejected alternative (a always will.
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).
## Steps ## Steps
1. **Offer the model switch back up, once, and keep working either way.** A model of the 1. **Check the handover you start from.** The body names a green CI run on the published commit
message, not a script to quote: say it in the instance's KB language, per `AGENTS.md` (`stack-build` step 6). If it does not, or the run is red, this is not the closing phase yet:
§ File naming. 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/` This phase is meant to run on Opus at high effort - a lower effort gives up multi-file
> staleness, or the changelog prose. If you want to switch back to Opus, now is the moment. 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 2. **Check that the body is in its final state.** `stack-build` kept it current at three fixed
leaves exactly the state this skill exists to prevent. 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:
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:
- every acceptance criterion ticked, or struck with the reason it was dropped - 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 - 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 - 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 - 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 - Fix what is off by rewriting the body (`instructions/dev/issue-tracking.md` steps 2 and 7).
`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.
**A closing report in a comment does not satisfy this**, however thorough: it reads as **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 complete to whoever writes it and leaves a body still phrased as open work. #44 and #45 both
catches it, which is why this is a step - and now a whole skill - rather than a habit. #44 and closed exactly this way, the second an hour after the rule was first written down.
#45 both closed exactly this way on the old, single-skill shape, 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 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 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 **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 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 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 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 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 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 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 **A pull-through of its own needs its own bump, publish and CI run.** The documents this
has already bumped the version, and the documents it touches are frequently the ones CI's phase touches are frequently the ones CI's version gate watches - `types/`, `instructions/`,
version gate watches - `types/`, `instructions/`, `tools/`, `AGENTS.md`, any `tools/`, `AGENTS.md`, any `<stage>/CONTRACT.md`. A commit into one of those without a
`<stage>/CONTRACT.md`. A commit into one of those without a `VERSION` line fails the gate `VERSION` line fails the gate (`.gitea/workflows/ci.yml`, "Version gate"), whatever the
(`.gitea/workflows/ci.yml`, "Version gate"), whatever the session meant it as. Reading the session meant it as. Reading the edit as "only documentation" is the trap: `types/source.md`
edit as "only documentation" is the trap: `types/source.md` is a document *and* a shipped is a document *and* a shipped behaviour description, and the gate is scoped by path, not by
behaviour description, and the gate is scoped by path, not by intent. So run intent. So run `tools/wikitool version bump --patch` in the same breath as the pull-through -
`tools/wikitool version bump --patch` in the same breath as the pull-through commit - it it only advances the running candidate's counter - then publish and wait per
only advances the running candidate's counter - rather than discovering it from a red run `instructions/dev/publish-and-ci.md`, and name that run in the body too.
after the issue is already closed.
4. **Name which model ran which phase - not only this one.** This is the handover in full, not 4. **Record the handover, then close.** The closing comment carries the one changelog line
a note about the tail alone: state the model for the design/version-part/boundary-judgment `instructions/dev/issue-tracking.md` step 3 asks for, and the handover for **every phase**,
phase (`stack-dev` step 3), for the mechanical middle (code, tests, the version bump), and for not only this one:
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 | Phase | Model | Effort | Own session? | Context overflowed or compacted? |
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. | 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 ## Decision points
- **The work package spans several sessions?** Run this skill once, at the point the package is - **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 actually finished and its last publish has a green run - not after every individual publish.
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.
- **Resuming a package whose publish landed in an earlier, already-ended session?** Run this - **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 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 it "correctly." Step 4 names the earlier phases from the record.
(the issue's comments, `CHANGES.md`) rather than from memory.
- **Nothing to close - the session's own exploration, no publish happened?** This skill does not - **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 ## Scope
Follows a `stack-dev` session's publish. Not for wiki content work - use Follows `stack-build`'s green CI run (`instructions/dev/stack-build/SKILL.md`). Not for wiki
`wiki-ingest`/`wiki-query`/`wiki-manage`/`wiki-lint`/`wiki-status`/`gtd-weekly-review` for that, content work - use `wiki-ingest`/`wiki-query`/`wiki-manage`/`wiki-lint`/`wiki-status`/
whose own closing conventions (`kb/log.md`, page provenance) are unrelated to this tracker-body `gtd-weekly-review` for that, whose own closing conventions (`kb/log.md`, page provenance) are
procedure. unrelated to this tracker-body procedure.
+56 -177
View File
@@ -1,206 +1,85 @@
--- ---
name: stack-dev name: stack-dev
description: Switches a session into tool-development mode - extending tools/wikitool, the compiler, the type schema, or the instruction/skill layer itself, instead of operating on wiki content. Use when the user asks to add a wikitool command, change a type-spec, fix or extend the compiler, or otherwise work on the stack rather than ingest/query/manage/lint the wiki. description: Switches a session into tool-development mode and runs its design phase - extending tools/wikitool, the compiler, the type schema, or the instruction/skill layer itself, instead of operating on wiki content. Use when the user asks to add a wikitool command, change a type-spec, fix or extend the compiler, triage or work out a stack issue, or otherwise work on the stack rather than ingest/query/manage/lint the wiki.
--- ---
# Stack Development Mode # Stack Development Mode - Design
**Purpose:** Recognize a session that is about the tool stack itself - `tools/wikitool`, the **Purpose:** Recognize a session that is about the tool stack itself - `tools/wikitool`, the
type schema, the instruction/skill layer - rather than wiki content, and switch the rules that type schema, the instruction/skill layer - rather than wiki content, switch the rules that apply
apply accordingly. accordingly, and carry a work package through its design phase: to an issue body a cold session
can build from.
**Trigger:** The user asks to add or change a `wikitool` command, extend the compiler, change a **Trigger:** The user asks to add or change a `wikitool` command, extend the compiler, change a
type-spec, or work on `instructions/`/`types/`/`tools/` as code rather than as a place to run type-spec, or work on `instructions/`/`types/`/`tools/` as code rather than as a place to run
`wiki-ingest`/`wiki-query`/`wiki-manage`/`wiki-lint`/`wiki-status` against. `wiki-ingest`/`wiki-query`/`wiki-manage`/`wiki-lint`/`wiki-status` against. Also: triaging a
`status/incoming` stub, refreshing an old spec against today's tree, or analysing a defect in the
stack before anything is fixed.
**This directory is dev-only.** `instructions/dev/` is excluded wholesale by This is the first of three phases. The build (`stack-build`) and the closing (`stack-close`)
`tools/wikitool dist export` - nothing here ever reaches a distributed instance, and there is are separate skills that only the operator starts - this one never runs them, and never builds
no restore path. If you are in a distributed instance, this skill should not be present at all; past the design. `instructions/dev/stack-mode.md` has the phases, their handovers and why they
stack development happens in the origin repo instead (see AGENTS.md's routing line). are split that way.
## What changes in this mode
- **Source-binding does not apply to code.** AGENTS.md invariant 3 ("never file an unsourced
answer into the wiki") governs `kb/` content, not the code you write to extend the stack.
Ordinary software-engineering judgment applies to `tools/chemenu/*.py`, `types/*`,
`instructions/*` - it does not need a `raw/` source or a citation.
- **Test and review conventions from `instructions/dev/` apply instead**, once written down
there (step 2 below lists what currently exists). Until a given convention has its own
instruction file, follow the existing test files' own patterns
(`tools/chemenu/tests/`) rather than inventing a new one silently.
- **Everything outside this directory still applies.** The tool error contract, the gates, and
"never hand-edit generated files" (AGENTS.md invariants 1, 5-8) are about how the tool
behaves at runtime, not about developing it, but they still bind normal session conduct
(e.g. still use `tools/wikitool publish`, still respect the gates, when the session also
touches wiki content).
## Steps ## Steps
1. **Confirm the mode.** If the task is ambiguous between "extend the tool" and "operate the 1. **Confirm the mode, then read `instructions/dev/stack-mode.md`.** If the task is ambiguous
wiki", ask rather than guess - the two have different rules for the same directories. between "extend the tool" and "operate the wiki", ask rather than guess - the two have
2. **Consult `instructions/dev/` for the concrete procedure.** Currently: different rules for the same directories. The mode file holds the rules that change, and the
`instructions/dev/commonplace-kb.md` - vendored knowledge base on agent context catalogue of dev-only procedures (§ Where the procedures are); consult what it names for the
engineering, memory and deploy-time learning; consult before a design decision in those task at hand rather than re-deriving it.
areas.
`instructions/dev/issue-tracking.md` - open work lives in Gitea issues, one per work
package, labelled `area/`, `kind/`, `prio/` and `size/`. There is no `TODO.md`. **The body
of the issue you are working on is this session's plan file:** keep it current as the state
moves, so an interrupted session leaves a body the next one can resume from, *and* rewrite it
to its final state before closing. Both halves bind; the second is what
`stack-close` (`instructions/dev/stack-close/SKILL.md`) carries out once this skill's own work is published -
see step 6 below. An issue labelled `status/incoming` is the exception to all of that: it is a
human's stub, not a spec, and it is **never implemented as it stands** - it gets worked out and
triaged first. Read this file before filing something for later, before editing or closing an
issue, before picking up an incoming stub, or before deciding what to pick up next.
`instructions/dev/testing-conventions.md` - the suite runs against a deliberately
empty machine; what the autouse fixture already neutralizes, and what a test still has to
establish itself. Read it before adding or changing a test.
`instructions/dev/tracker-testing.md` - how the task-tracker adapters are tested against a
real Super Productivity and CalDAV server: the `live_tracker` suite, the profile procedure for
a tracker of your own, the nightly workflow (which you dispatch yourself after touching the
Super Productivity surface), what a red night means, and refreshing the recorded fixtures.
Read it before changing an adapter under `tools/chemenu/tasks/`.
`instructions/dev/version-parts.md` - which part a change bumps: the drop-in test, the
catalogue of breaks that cross the compatibility boundary with `kb/` untouched, and what to
put in front of the user before a breaking bump. Read it before step 4.
`instructions/dev/corpus-policy.md` - what "curated enough" means for the shared
demo/testbed `kb/`, the measurable floors that define it, and what a reactive fix may and may
not do to corpus content. Read it before judging whether the corpus can exercise a change, or
before any fix that would touch `kb/` content.
`instructions/dev/dev-setup.md` - setting up a clone of the origin repo for this work, what
differs from an instance there (telemetry on, demo persona, no release stamp), and
`dist export` as a build and test tool rather than an install path. Read it in a fresh clone,
or before testing a change to the install path.
`instructions/dev/doc-pull-through.md` - which document makes a claim about a touched
surface (a `wikitool` command, a stage's rules, an `AGENTS.md` rule/gate/invariant, a
README-shaped human doc, a `docs/` page's reasoning) and therefore needs updating alongside
the code, since `docs verify` never reads a cell's prose. Read it before step 6.
More instructions are added here incrementally as stack-development needs come up - this
list grows without needing this skill file to change shape.
3. **Settle the design before building - and break there for the model switch.** These are two
different kinds of work, and the split is not stylistic: design, the version part and any
boundary judgment have **no** mechanical guard, while the code and tests that follow are mostly
covered - `pytest`, `docs verify`, `instructions verify` and CI catch a mistake **in what they
cover**.
So when the design is settled - the issue body says what will be built, the open questions are 2. **Find or open the work package.** One Gitea issue per package
answered - stop and say so, in one sentence that names what the mechanical stretch does **not** (`instructions/dev/issue-tracking.md`). Read its body as the current spec, and correct it
cover. The message below is a model of what to say, not a script to quote: say it in the first where the tree or a comment proves it wrong. A `status/incoming` stub is worked out per
instance's KB language, per `AGENTS.md` § File naming. that file's § Incoming stubs before anything else happens to it.
> The plan is settled. From here the work is mostly mechanical and covered by tests/CI - 3. **Work the design out in the body, not beside it.** Whatever this session establishes - a
> except the changelog prose (step 4), any `docs/` page you touch, new human-facing decision and its reasoning, a root cause, a rejected approach, the files involved - goes into
> documentation, and the prose half of an instruction. If you are on Opus, now is the moment the body as it is settled (`instructions/dev/issue-tracking.md` step 2), with one changelog
> for `/model sonnet` at effort `high`. comment for the session's worth of change (step 3). A question only the user can answer is
asked in the chat and stays a question in the body until it is answered; it is never settled
by a guess.
**You cannot make this switch yourself** - the session's model is the user's `/model`, not a 4. **Name the version part.** Apply the drop-in test in `instructions/dev/version-parts.md`
setting an agent applies. Offer it once and keep working either way; a session that argues and write the result into the body. Whether a change is a drop-in replacement has no
about its own model has already cost more than the difference. If the design turns out not to mechanical guard, so it is decided here, not at the bump. A change that crosses the
be settled after all - a boundary crossing surfaces, an assumption breaks - that is a reason to compatibility boundary goes to the user with what breaks, what an instance has to do about
offer the switch back up, not to decide it alone. it, and the alternatives, before the body can be ready (version-parts.md step 4).
**"Covered by tests" means covered by the tests that exist, not by the tests that should 5. **End the phase at a ready body.** Check the body against
exist.** Whether the right test was written is itself a judgment call with no mechanical `instructions/dev/issue-tracking.md` § Ready to build. If it fails, say which point is open
guard: two data-destroying bugs in `upstream merge` (Gitea #30) shipped past a green and stay in this phase. If it passes, recommend one of two ways on, and stop:
`pytest`/`docs verify`/`instructions verify`/CI because no test exercised the case, not
because a weaker model wrote worse code for the case that *was* tested. This is not a third
break - it is a caveat on this one: the middle phase stays the cheaper phase to run on, but its
test suite is only as complete as the judgment that wrote it, and that judgment is unchecked
the same way the design phase is.
Effort is the cheaper lever than the model, and `high` is the floor for anything touching more - **A long design session** - much exploration, a defect analysis, a stub worked out from
than one file or a contract. Full table and reasoning: scratch: `/clear`, then `/stack-build #N`. The build starts lean, and a cold start is the
`docs/model-and-effort-selection.md`. real test of whether the body is ready.
- **A short one** - an existing spec refreshed against the tree: continue in this session
with `/stack-build #N`.
4. **Raise the version, if the change ships.** A change under `tools/`, `types/`, Close with the fixed line, in the instance's KB language per `AGENTS.md` § File naming:
`instructions/`, `AGENTS.md` or a `CONTRACT.md` reaches every future instance, so it needs a
version and a changelog entry:
```bash > #N is ready. Next: `/stack-build #N` - here, or after `/clear`.
tools/wikitool version bump --patch --title "<what changed>" --impact medium
```
`--impact high|medium|low` (default `medium`) grades this bump in the changelog entry's own **Do not run `stack-build` yourself, and do not start building.** The phase change is the
list - `tools/wikitool version regrade` corrects it later if the candidate's overall shape operator's moment to decide on context and model; a design session that carries on into code
changes the read on an earlier one; see takes that decision away. Offer no `/model` or `/effort` switch either - see
`instructions/dev/version-parts.md` § The candidate model. `instructions/dev/stack-mode.md` § Sessions and models.
Never edit `VERSION` or the entry's heading by hand - `bump` writes both, and `docs verify`
fails a tree where they disagree. Pick the part by whether the new version is a **drop-in
replacement** for the old one - not by whether content has to be migrated:
| Change | Part |
|--------|------|
| Fix, no interface change | `--patch` |
| New capability, still drop-in in both directions | `--minor` |
| **Not a drop-in replacement** - any hand-work by the user or a migration script, or a downgrade that no longer works | `--major` |
Content migration is one way to land in the last row, not the definition of it: a rename of
the update path, the artefact, an import name, a flag or an envvar breaks a swap with `kb/`
entirely untouched. The full test, the catalogue of such breaks, and what to put in front of
the user first are in `instructions/dev/version-parts.md` - **read it before choosing
`--major`.**
A `--major` bump therefore needs two things recorded. `--breaking "<what stops working>"`
is required on every boundary-crossing bump; on top of it, a migration document for the new
version - written per `instructions/migrate-corpus.md` - or
`--no-migration "<reason>"` when no content actually has to change. `bump` refuses without
either, and so does `docs verify`: an instance learning that it must migrate, with nothing
telling it how, is a dead end.
Then write the entry's body - `bump` deliberately leaves it empty, the same way `new` leaves
the prose.
Prose-only changes (`README.md`, `INSTALL.md`, `EVALS.md`) and the workflows under `.gitea/`
do not need a bump - CI's version gate is scoped to what changes behaviour.
5. **Pull through every document that makes a claim about the surface you touched - `docs verify`
checks a cell's presence, never its prose.** `instructions/dev/doc-pull-through.md` has
the table of which document that is, per surface, and its step 3 for the one part of the
pull-through that is *not* prose: a reference file whose headings moved needs
`tools/wikitool docs toc --apply`, never a hand-written list.
Prose you write here is English, whatever language the session is being held in -
`AGENTS.md` § File naming has both language rules and the line between prose and quoted
vocabulary.
6. **Verify, then publish.** `tools/wikitool docs verify`, `tools/wikitool instructions verify`,
and the relevant `pytest` run in `tools/` - the same checks any stack change must pass, run
explicitly rather than assumed. CI (`.gitea/workflows/ci.yml`) runs these plus a full
`setup-instance.md` replay against a fresh `dist export`; a push to `main` that moves `VERSION`
additionally triggers a tagged release. **CI does the tagging** - a session never creates a
tag, which is what keeps AGENTS.md invariant 5 intact.
Publish with `tools/wikitool publish`. When the changeset touches `tools/`, `types/`,
`instructions/`, `AGENTS.md` or a `<stage>/CONTRACT.md`, `publish` itself prints a one-line
reminder that the phase past this point is not covered by any of the checks above - that line
is the cue that this skill's own job just ended.
**This skill stops here.** The closing phase - rewriting the issue body to its final state,
checking for `docs/` staleness, and naming which model ran which phase of the session - lives
in `stack-close` (`instructions/dev/stack-close/SKILL.md`), not in a further step of this one. Invoke it now;
do not fold its work into this session under this skill's rules, and do not treat "the change
is published" as this work package being done.
## Decision points ## Decision points
- **Touches both stack code and wiki content in one session?** Apply this skill's rules to the - **Nothing to build - the session answered a question or filed a follow-up?** The phase ends
code changes and the normal content skills' rules to the content changes - they are not with the issue in whatever state it reached, body current; there is no `/stack-build` line to
mutually exclusive within a session, only per change. give.
- **The change turns out not to be a drop-in replacement?** Do not bump across the boundary on - **The task is a one-line fix that seems not to need a design?** It still needs a body that
your own initiative. Every existing instance pays for a breaking change once, by hand, so the says what is fixed and which version part it takes - which for a real one-liner is a short
user decides whether it is worth that: show them what breaks, what an instance has to do about body, written in minutes. The cut to `stack-build` can then happen in the same session.
it, and the alternatives (avoid the break with a shim, defer and batch it with the next one, - **The design turns out to cross the compatibility boundary?** Do not decide it alone - step 4.
or split it behind a deprecation window), then recommend one and wait for a go-ahead.
`instructions/dev/version-parts.md` step 4 has the full shape. A surfacing boundary crossing
is also a reason to offer the model switch back up (step 3): the judgment it needs has no
mechanical guard, and `docs verify` only checks that a crossing documents itself, never that the
part was chosen correctly.
## Scope ## Scope
Not for wiki content work - use `wiki-ingest`/`wiki-query`/`wiki-manage`/`wiki-lint`/ Not for wiki content work - use `wiki-ingest`/`wiki-query`/`wiki-manage`/`wiki-lint`/
`wiki-status`/`gtd-weekly-review` for that. Not for setting up a new instance `wiki-status`/`gtd-weekly-review` for that. Not for setting up a new instance
(`instructions/setup-instance.md`) or (`instructions/setup-instance.md`) or a fresh clone of this repo (`instructions/bootstrap.md`).
a fresh clone of this repo (`instructions/bootstrap.md`). Not for closing a work package after Not for building a ready body (`stack-build`, `instructions/dev/stack-build/SKILL.md`) or closing
its publish has landed - that is `stack-close` (`instructions/dev/stack-close/SKILL.md`). a published package (`stack-close`, `instructions/dev/stack-close/SKILL.md`).
+122
View File
@@ -0,0 +1,122 @@
---
type: types/instruction.md
name: stack-mode
description: What changes when a session works on the stack itself rather than on wiki content - which rules stop and start applying, the three phases a work package moves through and the tracker state that hands each one over, where the dev-only procedures are, and why a model is chosen per session rather than per phase.
---
# Rules for a stack-development session
Shared by the three skills of the `stack-` family - `stack-dev` (design), `stack-build` (build)
and `stack-close` (closing). Each of them can be the first thing a session runs: `/stack-build #N`
after a `/clear` starts cold, with none of `stack-dev`'s context. So each skill loads this file at
its entry, rather than one of them carrying these rules for the other two.
**This directory is dev-only.** `instructions/dev/` is excluded wholesale by
`tools/wikitool dist export` - nothing here ever reaches a distributed instance, and there is no
restore path. If you are in a distributed instance, none of these skills should be present at
all; stack development happens in the origin repo instead (see AGENTS.md's routing line).
<!-- wikitool:toc -->
## Contents
- [The three phases](#the-three-phases)
- [What changes in this mode](#what-changes-in-this-mode)
- [Where the procedures are](#where-the-procedures-are)
- [Sessions and models](#sessions-and-models)
<!-- /wikitool:toc -->
## The three phases
A work package is one Gitea issue, and it moves through three phases. A skill is a unit of
procedure; a session is a unit of context and model. The two are deliberately not the same
thing: each phase ends in a **state in the tracker**, and the next phase starts from that state,
so every handover works either in the same session or after a `/clear`.
| Phase | Skill | Invoked by | Ends with (the handover) | What catches a mistake |
|---|---|---|---|---|
| 1 Design/triage | `stack-dev` | the harness on a matching task, or `/stack-dev` | the issue body is **ready** ([issue-tracking.md](issue-tracking.md) § Ready to build) | nothing mechanical |
| 2 Build | `stack-build` | **only** the operator: `/stack-build #N` | a **green CI run** on the published commit, body current | `pytest`, `docs verify`, `instructions verify`, CI |
| 3 Closing | `stack-close` | **only** the operator: `/stack-close` | body in its final state, issue closed | nothing mechanical |
`stack-build` and `stack-close` carry `disable-model-invocation: true` in their frontmatter, so in
Claude Code only the operator can start them. That is the point of the split: each phase change is
the moment the operator decides whether to continue in this session, `/clear` first, or start the
next session on a different model - and a skill the agent could invoke itself would take that
moment away again. `instructions sync` copies the frontmatter unchanged; the other harnesses
ignore the key, so there the skill's own prose is the only thing that holds the line.
**So no skill tells the agent to run the next one.** A phase ends with a fixed line naming the
slash command for the operator, and stops.
## What changes in this mode
- **Source-binding does not apply to code.** AGENTS.md invariant 3 ("never file an unsourced
answer into the wiki") governs `kb/` content, not the code you write to extend the stack.
Ordinary software-engineering judgment applies to `tools/chemenu/*.py`, `types/*`,
`instructions/*` - it does not need a `raw/` source or a citation.
- **Test and review conventions from `instructions/dev/` apply instead**, once written down
there (§ Where the procedures are, below). Until a given convention has its own instruction
file, follow the existing test files' own patterns (`tools/chemenu/tests/`) rather than
inventing a new one silently.
- **Everything outside this directory still applies.** The tool error contract, the gates, and
"never hand-edit generated files" (AGENTS.md invariants 1, 5-8) are about how the tool
behaves at runtime, not about developing it, but they still bind normal session conduct
(e.g. still use `tools/wikitool publish`, still respect the gates, when the session also
touches wiki content).
- **A session that touches both stack code and wiki content** applies these rules to the code
changes and the normal content skills' rules to the content changes - they are not mutually
exclusive within a session, only per change.
## Where the procedures are
- [issue-tracking.md](issue-tracking.md) - open work lives in Gitea issues, one per work
package, labelled `area/`, `kind/`, `prio/` and `size/`. There is no `TODO.md`. **The body of
the issue you are working on is the plan file** of every phase: kept current as the state
moves, so an interrupted session leaves a body the next one can resume from, and rewritten to
its final state before closing. It also defines when a body is ready to build. An issue
labelled `status/incoming` is a human's stub, not a spec, and is **never implemented as it
stands**. Read it before filing something for later, before editing or closing an issue,
before picking up an incoming stub, or before deciding what to pick up next.
- [version-parts.md](version-parts.md) - which part a change bumps: the drop-in test, the
catalogue of breaks that cross the compatibility boundary with `kb/` untouched, and what to put
in front of the user before a breaking bump. Read it when the design names the part, and again
before the bump.
- [testing-conventions.md](testing-conventions.md) - the suite runs against a deliberately empty
machine; what the autouse fixture already neutralizes, and what a test still has to establish
itself. Read it before adding or changing a test.
- [tracker-testing.md](tracker-testing.md) - how the task-tracker adapters are tested against a
real Super Productivity and CalDAV server: the `live_tracker` suite, the profile procedure for
a tracker of your own, the nightly workflow (which you dispatch yourself after touching the
Super Productivity surface), what a red night means, and refreshing the recorded fixtures.
Read it before changing an adapter under `tools/chemenu/tasks/`.
- [doc-pull-through.md](doc-pull-through.md) - which document makes a claim about a touched
surface (a `wikitool` command, a stage's rules, an `AGENTS.md` rule/gate/invariant, a
README-shaped human doc, a `docs/` page's reasoning) and therefore needs updating alongside
the code, since `docs verify` never reads a cell's prose. Read it before publishing.
- [publish-and-ci.md](publish-and-ci.md) - the local checks, `publish`, and waiting for the CI
run on the published commit. Read it whenever a phase publishes.
- [corpus-policy.md](corpus-policy.md) - what "curated enough" means for the shared demo/testbed
`kb/`, the measurable floors that define it, and what a reactive fix may and may not do to
corpus content. Read it before judging whether the corpus can exercise a change, or before any
fix that would touch `kb/` content.
- [dev-setup.md](dev-setup.md) - setting up a clone of the origin repo for this work, what
differs from an instance there (telemetry on, demo persona, no release stamp), and
`dist export` as a build and test tool rather than an install path. Read it in a fresh clone,
or before testing a change to the install path.
- [commonplace-kb.md](commonplace-kb.md) - vendored knowledge base on agent context engineering,
memory and deploy-time learning; consult before a design decision in those areas.
More instructions are added here as stack-development needs come up - this list grows without
any of the three skills having to change shape.
## Sessions and models
**A model is chosen per session, never switched inside one.** Neither `/model` nor `/effort`
is offered mid-session: either change throws away the prompt cache for everything the session
has read so far, and Sonnet's smaller context window does not hold a build phase of this stack.
Where two phases should run on different models, the cut goes at a handover - `/clear`, then the
next phase's slash command in a session started on the right model - and the tracker state is
what carries the work across it.
Which model suits which phase, and the reasoning, is
`docs/model-and-effort-selection.md`. What every phase records, so that reasoning can be checked
against practice, is `stack-close`'s handover (its own step 4).
+4 -3
View File
@@ -186,6 +186,7 @@ Whenever you add or change a test under `tools/chemenu/tests/`.
## Scope ## Scope
Applies to `tools/chemenu/tests/` only. It says nothing about what to test - the test/review Applies to `tools/chemenu/tests/` only. It says nothing about what to test - the test/review
expectations for a stack change are the `stack-dev` skill's step 6 (`docs verify`, expectations for a stack change are the local checks in [publish-and-ci.md](publish-and-ci.md)
`instructions verify`, pytest). CI runs the suite once, unhardened, because the fixture makes a (`docs verify`, `instructions verify`, pytest), run from `stack-build`. CI runs the suite once,
second hardened run redundant; see the note on the Tests step in `.gitea/workflows/ci.yml`. unhardened, because the fixture makes a second hardened run redundant; see the note on the Tests
step in `.gitea/workflows/ci.yml`.
+2 -1
View File
@@ -92,7 +92,8 @@ a new one, and only `version release` turns it into something the release workfl
## When to run ## When to run
Before every `tools/wikitool version bump` - the `stack-dev` skill's step 4 sends you here. When a design names the version part (`stack-dev` step 4), and again before every
`tools/wikitool version bump` (`stack-build` step 4).
Read it in full the first time a change looks like it might be boundary-crossing; afterwards Read it in full the first time a change looks like it might be boundary-crossing; afterwards
the three-line test below is usually enough. the three-line test below is usually enough.
+9 -8
View File
@@ -393,20 +393,21 @@ STACK_MACHINERY_PREFIXES = ("tools/", "types/", "instructions/")
STACK_MACHINERY_NAMES = ("AGENTS.md",) STACK_MACHINERY_NAMES = ("AGENTS.md",)
STACK_MACHINERY_NOTE = ( STACK_MACHINERY_NOTE = (
"Note: this publish touched stack machinery. What a stack-dev session " "Note: this publish touched stack machinery. CI on the pushed commit is "
"does next - closing prose, a changelog entry's accuracy, whether a " "the last mechanical check still to come. What follows a green run - "
"docs/ page went stale - is not covered by docs verify, instructions " "closing prose, a changelog entry's accuracy, whether a docs/ page went "
"verify, or pytest. No tool checks it; a session has to." "stale - is covered by no tool; a session has to check it."
) )
def touches_stack_machinery(changed_files: list[str]) -> bool: def touches_stack_machinery(changed_files: list[str]) -> bool:
"""Whether `changed_files` includes a path under the stack version's own """Whether `changed_files` includes a path under the stack version's own
scope - `tools/`, `types/`, `instructions/`, `AGENTS.md`, or a path ending scope - `tools/`, `types/`, `instructions/`, `AGENTS.md`, or a path ending
in `CONTRACT.md` at any depth. A publish in this class is, by construction in `CONTRACT.md` at any depth. A publish in this class is followed first by
of the `stack-dev`/`stack-close` split, always followed by the unchecked CI - still a mechanical check - and then by the unchecked closing phase of
closing phase - `STACK_MACHINERY_NOTE` times a reminder to land exactly the `stack-dev`/`stack-build`/`stack-close` split. `STACK_MACHINERY_NOTE`
there, for any session, not only one that read the skill that names it. names both, in that order, for any session, not only one that read the
skills that name them.
Deliberately a shade broader than CI's version gate, which matches Deliberately a shade broader than CI's version gate, which matches
`<one-segment>/CONTRACT.md` only: this decides whether to print a sentence, `<one-segment>/CONTRACT.md` only: this decides whether to print a sentence,
+12 -1
View File
@@ -395,7 +395,7 @@ def test_below_threshold_publish_goes_straight_through(repo):
def test_publish_notes_stack_machinery_after_success(repo, capsys): def test_publish_notes_stack_machinery_after_success(repo, capsys):
"""The closing-phase reminder lands exactly once, after the OK line, and """The CI-then-closing-phase reminder lands exactly once, after the OK line, and
only when the changeset actually falls under version-parts.md's scope.""" only when the changeset actually falls under version-parts.md's scope."""
(repo / "instructions").mkdir() (repo / "instructions").mkdir()
(repo / "instructions/example.md").write_text("x\n", encoding="utf-8") (repo / "instructions/example.md").write_text("x\n", encoding="utf-8")
@@ -405,6 +405,17 @@ def test_publish_notes_stack_machinery_after_success(repo, capsys):
assert out.count(STACK_MACHINERY_NOTE) == 1 assert out.count(STACK_MACHINERY_NOTE) == 1
def test_stack_machinery_note_puts_ci_before_the_unchecked_phase():
"""A stack publish is followed by CI first - a mechanical check - and only
after a green run by the phase nothing checks. The note must not claim the
unchecked phase starts right at the publish (#168): that sent CI waiting
into the closing phase, where a red run turned it back into a build."""
ci = STACK_MACHINERY_NOTE.find("CI")
unchecked = STACK_MACHINERY_NOTE.find("covered by no tool")
assert ci != -1 and unchecked != -1
assert ci < unchecked
def test_publish_stays_quiet_for_ordinary_content(repo, capsys): def test_publish_stays_quiet_for_ordinary_content(repo, capsys):
_write_files(repo, 1) _write_files(repo, 1)
_publish(message="ordinary content") _publish(message="ordinary content")
+17 -1
View File
@@ -64,7 +64,7 @@ def test_the_real_repo_publishes_every_skill():
"""Guards the actual layout, not a fixture: these are the skills the """Guards the actual layout, not a fixture: these are the skills the
harness is expected to offer, one per naming family - `wiki-` for the harness is expected to offer, one per naming family - `wiki-` for the
knowledge pipeline, `gtd-` for the commitment layer, `stack-` for the knowledge pipeline, `gtd-` for the commitment layer, `stack-` for the
stack's own development. The `stack-` pair is nested under stack's own development. The `stack-` trio is nested under
instructions/dev/, discovered the same way as the top-level ones.""" instructions/dev/, discovered the same way as the top-level ones."""
names = {p.name for p in instructions_cmd.skill_dirs()} names = {p.name for p in instructions_cmd.skill_dirs()}
assert { assert {
@@ -75,10 +75,26 @@ def test_the_real_repo_publishes_every_skill():
"wiki-status", "wiki-status",
"gtd-weekly-review", "gtd-weekly-review",
"stack-dev", "stack-dev",
"stack-build",
"stack-close", "stack-close",
} <= names } <= names
def test_only_the_operator_starts_the_build_and_close_phases():
"""#168: the phase changes are the operator's slash command, so
`stack-build` and `stack-close` carry `disable-model-invocation: true`,
while `stack-dev` - the entry point the harness has to find on its own -
does not. `sync` copies the frontmatter byte for byte, which the drift
check in `verify` already holds the published copies to."""
skills = {p.name: p for p in instructions_cmd.skill_dirs()}
flags = {}
for name in ("stack-dev", "stack-build", "stack-close"):
frontmatter, error = instructions_cmd._read_frontmatter(skills[name] / "SKILL.md")
assert frontmatter is not None, error
flags[name] = frontmatter.get("disable-model-invocation", False)
assert flags == {"stack-dev": False, "stack-build": True, "stack-close": True}
_NO_PROVIDER_FORBIDDEN_TERMS = [ _NO_PROVIDER_FORBIDDEN_TERMS = [
"super productivity", "super productivity",
"azure devops", "azure devops",