diff --git a/AGENTS.md b/AGENTS.md index cbea8ca..b64604b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -335,8 +335,9 @@ see [Gates](#gates). 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 -`stack-dev` skill, nested under [instructions/dev/](instructions/dev/) along with the -procedures it routes to. Setting up a clone of this origin repository for that work - the demo +`stack-dev` skill, the entry to that work's three phases (`stack-dev`, `stack-build`, +`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 [instructions/dev/dev-setup.md](instructions/dev/dev-setup.md). Never present in a distributed instance. diff --git a/CHANGES.md b/CHANGES.md index 47f9fd8..368d442 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 @@ -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 - 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) +- Stack-Entwicklung in drei Phasen: stack-dev (Design), stack-build, stack-close - Übergabe über den Tracker, kein Modellwechsel in der Sitzung **Low impact** - 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 +### 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` appended its `Files changed:` list to the end of `--message`. git reads trailers only diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 0d899c8..9992795 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -164,12 +164,15 @@ Tracker laufen lässt und was ein roter Lauf bedeutet, steht in ## Stack-Entwicklung als eigener Sitzungstyp -Der `stack-dev`-Skill (`instructions/dev/`, nur in diesem Ursprungs-Repo vorhanden) fasst die -Regeln für eine Sitzung, die den Stack selbst statt Wiki-Inhalt bearbeitet: wann -Quellenbindung nicht gilt, wo Design endet und die mechanische Phase beginnt (mit dem -Modellwechsel-Hinweis), und endet mit dem Publish. Die Schlussphase - Issue-Body als Rewrite -statt Kommentar, `docs/`-Veralterung, die Modell-Handover-Zeile über die ganze Sitzung - liegt -seit `4.6.0` in einem eigenen Folge-Skill, `stack-close`, den `stack-dev` an dieser Stelle -übergibt statt sie als weiteren eigenen Schritt zu führen. Siehe -[instructions/dev/issue-tracking.md](instructions/dev/issue-tracking.md) für den -Issue-Tracker selbst. +Drei Skills (`instructions/dev/`, nur in diesem Ursprungs-Repo vorhanden) führen eine Sitzung, +die den Stack selbst statt Wiki-Inhalt bearbeitet, durch drei Phasen: `stack-dev` arbeitet das +Issue aus, bis sein Body „ready“ ist, `stack-build` baut, publiziert und wartet auf einen grünen +CI-Lauf, `stack-close` prüft den Endzustand des Bodys und veraltete `docs/`- und Contract-Prosa +und schließt das Issue. Übergeben wird über den Zustand im Tracker, nicht über den Kontext einer +Sitzung: Jeder Phasenwechsel geht in derselben Sitzung oder nach `/clear`. `stack-dev` greift +automatisch; `stack-build` und `stack-close` startet nur der Betreiber per Slash-Kommando +(`/stack-build #N`, `/stack-close`) - an genau dieser Stelle fällt die Wahl, ob es in derselben +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). diff --git a/README.md b/README.md index 607df26..f8b67eb 100644 --- a/README.md +++ b/README.md @@ -450,9 +450,9 @@ tools/wikitool -h 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 -under `instructions/dev/` (never present in a distributed instance - `tools/CONTRACT.md` -explains why). +itself is a separate session type with its own rules, covered by the `stack-dev`, +`stack-build` and `stack-close` skills nested under `instructions/dev/` (never present in a +distributed instance - `tools/CONTRACT.md` explains why). ### MCP read server (optional) diff --git a/VERSION b/VERSION index b616d03..860cd84 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.0.0-beta.23 +8.0.0-beta.24 diff --git a/docs/model-and-effort-selection.md b/docs/model-and-effort-selection.md index 6af570a..b7f55a7 100644 --- a/docs/model-and-effort-selection.md +++ b/docs/model-and-effort-selection.md @@ -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 phase guide below is built on. + +## 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) + + ## Phase guide -This repo's own stack-development work splits the axis into three phases, one per switch point -in its `stack-dev`/`stack-close` skills: +This repo's own stack-development work splits the axis into three phases, one skill each - +`stack-dev` (design), `stack-build` (build) and `stack-close` (closing) - handed over through +states in the issue tracker rather than inside one session: | 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-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: 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 | -The middle stack-dev row is where the tokens are and where the checks are, so it is the one worth -running cheaper. The two rows around it are short - minutes, not hours - so keeping them on the -stronger model costs little and protects the only work in the session that fails silently. +The two unchecked rows are short - minutes, not hours - so keeping them on the strongest model +costs little and protects the only work that fails silently. The checked middle row is where the +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 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 -if someone offers the switch at the moment a phase changes, once, without turning it into a -debate. +## Model per session, not per phase + +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. + +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. + ## Subagent models @@ -70,9 +110,10 @@ choice a session *can* make on its own: 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 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 - an issue close on a model the session cannot change itself. Naming which model ran which phase - in the handover keeps the gap visible instead of silent. +- The phase changed and the session runs on a model or effort the table would not pick: keep + working, and cut the session at the next handover rather than switching mid-flow - never block a + 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 diff --git a/instructions/CONTRACT.md b/instructions/CONTRACT.md index 411c153..3c7ac71 100644 --- a/instructions/CONTRACT.md +++ b/instructions/CONTRACT.md @@ -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 instance (`instructions/dev/` above). -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`. A new skill takes the prefix of the family it belongs to, or opens a new one deliberately - never 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`, `wiki-status` and `gtd-weekly-review` all clear that bar. -Dev-instance-only: `stack-dev` and `stack-close` sit under the same threshold, for the same -reason. +Dev-instance-only: `stack-dev`, `stack-build` and `stack-close` sit under the same threshold, +for the same reason. None of this is counted by number on purpose: a per-skill step count is a claim about a file this diff --git a/instructions/dev/commonplace-kb.md b/instructions/dev/commonplace-kb.md index 86679e5..b742e47 100644 --- a/instructions/dev/commonplace-kb.md +++ b/instructions/dev/commonplace-kb.md @@ -24,5 +24,6 @@ carries it (see [tools/CONTRACT.md](../../tools/CONTRACT.md) for what `dist expo ## 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/`. diff --git a/instructions/dev/dev-setup.md b/instructions/dev/dev-setup.md index 6255649..d2a3c3a 100644 --- a/instructions/dev/dev-setup.md +++ b/instructions/dev/dev-setup.md @@ -25,8 +25,8 @@ development material under `instructions/dev/` and `commonplace/`, and no instance. 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 - only in this repository. + sync`. That publishes `stack-dev`, `stack-build` and `stack-close` along with the content skills; + all three exist only in this repository. 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 diff --git a/instructions/dev/doc-pull-through.md b/instructions/dev/doc-pull-through.md index 6fc9b35..81dd8eb 100644 --- a/instructions/dev/doc-pull-through.md +++ b/instructions/dev/doc-pull-through.md @@ -18,8 +18,8 @@ way; see Gitea #89 for one). ## When to run -Before `tools/wikitool docs verify`/`publish` in a `stack-dev` session that changed behaviour - -`stack-dev` step 5 sends you here. Read the table below and update every row whose surface you +Before `tools/wikitool docs verify`/`publish` in a build session that changed behaviour - +`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. ## 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 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 - - [`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. - **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 @@ -84,7 +84,7 @@ touched; a row that does not apply needs no action. ## 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 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. diff --git a/instructions/dev/issue-tracking.md b/instructions/dev/issue-tracking.md index 824e60a..930d82e 100644 --- a/instructions/dev/issue-tracking.md +++ b/instructions/dev/issue-tracking.md @@ -26,6 +26,7 @@ issues at that URL, which is exactly why `dist export` excludes - [When to run](#when-to-run) - [Steps](#steps) - [Incoming stubs](#incoming-stubs) +- [Ready to build](#ready-to-build) - [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) - [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). - **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. +- 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 moved. - 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 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 A rename is not finished when the tree is green. Renaming a package, a path, @@ -356,7 +383,7 @@ it in a `` block ([instructions/CONTRACT.md](../CON `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 -`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` - config, not documentation. diff --git a/instructions/dev/publish-and-ci.md b/instructions/dev/publish-and-ci.md new file mode 100644 index 0000000..05234f7 --- /dev/null +++ b/instructions/dev/publish-and-ci.md @@ -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 `/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. diff --git a/instructions/dev/stack-build/SKILL.md b/instructions/dev/stack-build/SKILL.md new file mode 100644 index 0000000..b81a32e --- /dev/null +++ b/instructions/dev/stack-build/SKILL.md @@ -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 "" --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 ). 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. diff --git a/instructions/dev/stack-close/SKILL.md b/instructions/dev/stack-close/SKILL.md index 2d2e834..1349c48 100644 --- a/instructions/dev/stack-close/SKILL.md +++ b/instructions/dev/stack-close/SKILL.md @@ -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 - `/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 `/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. diff --git a/instructions/dev/stack-dev/SKILL.md b/instructions/dev/stack-dev/SKILL.md index 87da566..3b100e5 100644 --- a/instructions/dev/stack-dev/SKILL.md +++ b/instructions/dev/stack-dev/SKILL.md @@ -1,206 +1,85 @@ --- 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 -type schema, the instruction/skill layer - rather than wiki content, and switch the rules that -apply accordingly. +type schema, the instruction/skill layer - rather than wiki content, switch the rules that apply +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 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 -`tools/wikitool dist export` - nothing here ever reaches a distributed instance, and there is -no restore path. If you are in a distributed instance, this skill should not be present at all; -stack development happens in the origin repo instead (see AGENTS.md's routing line). - -## 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). +This is the first of three phases. The build (`stack-build`) and the closing (`stack-close`) +are separate skills that only the operator starts - this one never runs them, and never builds +past the design. `instructions/dev/stack-mode.md` has the phases, their handovers and why they +are split that way. ## Steps -1. **Confirm the mode.** If the task is ambiguous between "extend the tool" and "operate the - wiki", ask rather than guess - the two have different rules for the same directories. -2. **Consult `instructions/dev/` for the concrete procedure.** Currently: - `instructions/dev/commonplace-kb.md` - vendored knowledge base on agent context - engineering, memory and deploy-time learning; consult before a design decision in those - 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**. +1. **Confirm the mode, then read `instructions/dev/stack-mode.md`.** If the task is ambiguous + between "extend the tool" and "operate the wiki", ask rather than guess - the two have + different rules for the same directories. The mode file holds the rules that change, and the + catalogue of dev-only procedures (§ Where the procedures are); consult what it names for the + task at hand rather than re-deriving it. - So when the design is settled - the issue body says what will be built, the open questions are - answered - stop and say so, in one sentence that names what the mechanical stretch does **not** - cover. The message below is a model of what to say, not a script to quote: say it in the - instance's KB language, per `AGENTS.md` § File naming. +2. **Find or open the work package.** One Gitea issue per package + (`instructions/dev/issue-tracking.md`). Read its body as the current spec, and correct it + first where the tree or a comment proves it wrong. A `status/incoming` stub is worked out per + 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 - - > except the changelog prose (step 4), any `docs/` page you touch, new human-facing - > documentation, and the prose half of an instruction. If you are on Opus, now is the moment - > for `/model sonnet` at effort `high`. +3. **Work the design out in the body, not beside it.** Whatever this session establishes - a + decision and its reasoning, a root cause, a rejected approach, the files involved - goes into + the body as it is settled (`instructions/dev/issue-tracking.md` step 2), with one changelog + 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 - setting an agent applies. Offer it once and keep working either way; a session that argues - about its own model has already cost more than the difference. If the design turns out not to - be settled after all - a boundary crossing surfaces, an assumption breaks - that is a reason to - offer the switch back up, not to decide it alone. +4. **Name the version part.** Apply the drop-in test in `instructions/dev/version-parts.md` + and write the result into the body. Whether a change is a drop-in replacement has no + mechanical guard, so it is decided here, not at the bump. A change that crosses the + compatibility boundary goes to the user with what breaks, what an instance has to do about + 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 - 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 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. +5. **End the phase at a ready body.** Check the body against + `instructions/dev/issue-tracking.md` § Ready to build. If it fails, say which point is open + and stay in this phase. If it passes, recommend one of two ways on, and stop: - Effort is the cheaper lever than the model, and `high` is the floor for anything touching more - than one file or a contract. Full table and reasoning: - `docs/model-and-effort-selection.md`. + - **A long design session** - much exploration, a defect analysis, a stub worked out from + scratch: `/clear`, then `/stack-build #N`. The build starts lean, and a cold start is the + 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/`, - `instructions/`, `AGENTS.md` or a `CONTRACT.md` reaches every future instance, so it needs a - version and a changelog entry: + Close with the fixed line, in the instance's KB language per `AGENTS.md` § File naming: - ```bash - tools/wikitool version bump --patch --title "" --impact medium - ``` + > #N is ready. Next: `/stack-build #N` - here, or after `/clear`. - `--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. 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 ""` - 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 ""` 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 `/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. + **Do not run `stack-build` yourself, and do not start building.** The phase change is the + operator's moment to decide on context and model; a design session that carries on into code + takes that decision away. Offer no `/model` or `/effort` switch either - see + `instructions/dev/stack-mode.md` § Sessions and models. ## Decision points -- **Touches both stack code and wiki content in one session?** Apply this skill's 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. -- **The change turns out not to be a drop-in replacement?** 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. 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. +- **Nothing to build - the session answered a question or filed a follow-up?** The phase ends + with the issue in whatever state it reached, body current; there is no `/stack-build` line to + give. +- **The task is a one-line fix that seems not to need a design?** It still needs a body that + says what is fixed and which version part it takes - which for a real one-liner is a short + body, written in minutes. The cut to `stack-build` can then happen in the same session. +- **The design turns out to cross the compatibility boundary?** Do not decide it alone - step 4. ## Scope 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 -(`instructions/setup-instance.md`) or -a fresh clone of this repo (`instructions/bootstrap.md`). Not for closing a work package after -its publish has landed - that is `stack-close` (`instructions/dev/stack-close/SKILL.md`). +(`instructions/setup-instance.md`) or a fresh clone of this repo (`instructions/bootstrap.md`). +Not for building a ready body (`stack-build`, `instructions/dev/stack-build/SKILL.md`) or closing +a published package (`stack-close`, `instructions/dev/stack-close/SKILL.md`). diff --git a/instructions/dev/stack-mode.md b/instructions/dev/stack-mode.md new file mode 100644 index 0000000..ea6d4f6 --- /dev/null +++ b/instructions/dev/stack-mode.md @@ -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). + + +## 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) + + +## 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). diff --git a/instructions/dev/testing-conventions.md b/instructions/dev/testing-conventions.md index 5fc6b02..508aec7 100644 --- a/instructions/dev/testing-conventions.md +++ b/instructions/dev/testing-conventions.md @@ -186,6 +186,7 @@ Whenever you add or change a test under `tools/chemenu/tests/`. ## Scope 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`, -`instructions verify`, pytest). CI runs the suite once, unhardened, because the fixture makes a -second hardened run redundant; see the note on the Tests step in `.gitea/workflows/ci.yml`. +expectations for a stack change are the local checks in [publish-and-ci.md](publish-and-ci.md) +(`docs verify`, `instructions verify`, pytest), run from `stack-build`. CI runs the suite once, +unhardened, because the fixture makes a second hardened run redundant; see the note on the Tests +step in `.gitea/workflows/ci.yml`. diff --git a/instructions/dev/version-parts.md b/instructions/dev/version-parts.md index 2e7a26f..0c219c0 100644 --- a/instructions/dev/version-parts.md +++ b/instructions/dev/version-parts.md @@ -92,7 +92,8 @@ a new one, and only `version release` turns it into something the release workfl ## 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 the three-line test below is usually enough. diff --git a/tools/chemenu/commands/git_publish.py b/tools/chemenu/commands/git_publish.py index a06b0f8..b17d5d6 100644 --- a/tools/chemenu/commands/git_publish.py +++ b/tools/chemenu/commands/git_publish.py @@ -393,20 +393,21 @@ STACK_MACHINERY_PREFIXES = ("tools/", "types/", "instructions/") STACK_MACHINERY_NAMES = ("AGENTS.md",) STACK_MACHINERY_NOTE = ( - "Note: this publish touched stack machinery. What a stack-dev session " - "does next - closing prose, a changelog entry's accuracy, whether a " - "docs/ page went stale - is not covered by docs verify, instructions " - "verify, or pytest. No tool checks it; a session has to." + "Note: this publish touched stack machinery. CI on the pushed commit is " + "the last mechanical check still to come. What follows a green run - " + "closing prose, a changelog entry's accuracy, whether a docs/ page went " + "stale - is covered by no tool; a session has to check it." ) def touches_stack_machinery(changed_files: list[str]) -> bool: """Whether `changed_files` includes a path under the stack version's own scope - `tools/`, `types/`, `instructions/`, `AGENTS.md`, or a path ending - in `CONTRACT.md` at any depth. A publish in this class is, by construction - of the `stack-dev`/`stack-close` split, always followed by the unchecked - closing phase - `STACK_MACHINERY_NOTE` times a reminder to land exactly - there, for any session, not only one that read the skill that names it. + in `CONTRACT.md` at any depth. A publish in this class is followed first by + CI - still a mechanical check - and then by the unchecked closing phase of + the `stack-dev`/`stack-build`/`stack-close` split. `STACK_MACHINERY_NOTE` + 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 `/CONTRACT.md` only: this decides whether to print a sentence, diff --git a/tools/chemenu/tests/test_git_publish.py b/tools/chemenu/tests/test_git_publish.py index 568334b..0001ed2 100644 --- a/tools/chemenu/tests/test_git_publish.py +++ b/tools/chemenu/tests/test_git_publish.py @@ -395,7 +395,7 @@ def test_below_threshold_publish_goes_straight_through(repo): 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.""" (repo / "instructions").mkdir() (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 +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): _write_files(repo, 1) _publish(message="ordinary content") diff --git a/tools/chemenu/tests/test_instructions_cmd.py b/tools/chemenu/tests/test_instructions_cmd.py index c00a6d0..433f25e 100644 --- a/tools/chemenu/tests/test_instructions_cmd.py +++ b/tools/chemenu/tests/test_instructions_cmd.py @@ -64,7 +64,7 @@ def test_the_real_repo_publishes_every_skill(): """Guards the actual layout, not a fixture: these are the skills the harness is expected to offer, one per naming family - `wiki-` 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.""" names = {p.name for p in instructions_cmd.skill_dirs()} assert { @@ -75,10 +75,26 @@ def test_the_real_repo_publishes_every_skill(): "wiki-status", "gtd-weekly-review", "stack-dev", + "stack-build", "stack-close", } <= 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 = [ "super productivity", "azure devops",