task-pipeline-skill 1.97.1 → 1.98.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/HOW-IT-WORKS.md +17 -13
- package/README.md +12 -9
- package/SKILL-CARD.md +1 -1
- package/cursor/rules/task-pipeline.mdc +22 -14
- package/evals/task-pipeline.evals.json +3 -3
- package/package.json +1 -1
- package/plugins/task-pipeline/.claude-plugin/plugin.json +2 -2
- package/plugins/task-pipeline/commands/task-pipeline.md +14 -219
- package/plugins/task-pipeline/skills/evidence-docs/SKILL.md +8 -1
- package/plugins/task-pipeline/skills/evidence-docs/references/GENERATED.md +1 -1
- package/plugins/task-pipeline/skills/evidence-docs/references/learned.md +24 -1
- package/plugins/task-pipeline/skills/evidence-docs/references/retrospective.md +21 -576
- package/plugins/task-pipeline/skills/project-audit/SKILL.md +8 -1
- package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +162 -230
- package/plugins/task-pipeline/skills/task-pipeline/graph.schema.json +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.example.json +9 -9
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.schema.json +10 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/acceptance.md +2 -2
- package/plugins/task-pipeline/skills/task-pipeline/references/brainstorm.md +3 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/build.md +7 -7
- package/plugins/task-pipeline/skills/task-pipeline/references/certification.md +49 -44
- package/plugins/task-pipeline/skills/task-pipeline/references/doctrine-map.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/finish-line.md +13 -8
- package/plugins/task-pipeline/skills/task-pipeline/references/iteration.md +20 -23
- package/plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md +5 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/learned.md +24 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/loop-guard.md +17 -7
- package/plugins/task-pipeline/skills/task-pipeline/references/retrospective.md +47 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/spec.md +5 -4
- package/plugins/task-pipeline/skills/task-pipeline/references/stage-00-intake.md +39 -25
- package/plugins/task-pipeline/skills/task-pipeline/references/stage-05-dev.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +31 -20
- package/plugins/task-pipeline/skills/task-pipeline/references/waves.md +6 -4
- package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +77 -21
- package/plugins/task-pipeline/skills/task-pipeline/scripts/retro_budget.py +305 -0
- package/plugins/task-pipeline/skills/task-pipeline/scripts/short_path.py +70 -0
- package/plugins/task-pipeline/skills/task-pipeline/scripts/waves.py +22 -10
- package/plugins/task-pipeline/skills/task-pipeline/templates/canvas.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/templates/retro-archive.md +9 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/retro.md +10 -1
|
@@ -160,13 +160,20 @@
|
|
|
160
160
|
"finish": {
|
|
161
161
|
"type": "object",
|
|
162
162
|
"additionalProperties": false,
|
|
163
|
-
"description": "The iteration's budget at its finish line. Absent, the defaults hold: 3 rounds and 2 releases. A round is every exit check once plus one independent reading over the scope; only new blocking findings count. Reaching either budget before done — a blocker open, or
|
|
163
|
+
"description": "The iteration's budget at its finish line. Absent, the defaults hold: 3 rounds and 2 releases. A round is every exit check once plus one independent reading over the scope; only new blocking findings count. Reaching either budget before done — a blocker open, or a clean round still owed — stops the iteration and hands it back; no round runs past it without the operator's go, given with the numbers. Doctrine: references/finish-line.md; verdict: scripts/waves.py finish.",
|
|
164
164
|
"properties": {
|
|
165
165
|
"maxRounds": {
|
|
166
166
|
"type": "integer",
|
|
167
|
-
"minimum":
|
|
167
|
+
"minimum": 1,
|
|
168
168
|
"default": 3,
|
|
169
|
-
"description": "Finish-line rounds per iteration, at least
|
|
169
|
+
"description": "Finish-line rounds per iteration, at least cleanRounds: done needs that many consecutive rounds with no new blocking finding. Rotating the audit axis happens inside this budget and never adds a round."
|
|
170
|
+
},
|
|
171
|
+
"cleanRounds": {
|
|
172
|
+
"type": "integer",
|
|
173
|
+
"minimum": 1,
|
|
174
|
+
"maximum": 2,
|
|
175
|
+
"default": 1,
|
|
176
|
+
"description": "Consecutive rounds with no new blocking finding that end the iteration: 1, or 2 when it carries an R3 or flagship node (since 1.98.0; two for every iteration was the default before). Passed to waves.py finish as --clean-rounds."
|
|
170
177
|
},
|
|
171
178
|
"maxReleases": {
|
|
172
179
|
"type": "integer",
|
|
@@ -20,7 +20,7 @@ requirement.** It is what turns the pipeline from a funnel into a circle.
|
|
|
20
20
|
- The retrospective — the run's last act
|
|
21
21
|
- A `verified by` name is a claim until it resolves
|
|
22
22
|
- A seam is not a deliverable, and REQ rows are written against deliverables
|
|
23
|
-
- GATE (
|
|
23
|
+
- GATE (auto)
|
|
24
24
|
- When the answer is "something's missing"
|
|
25
25
|
- A project of several repositories
|
|
26
26
|
|
|
@@ -329,7 +329,7 @@ client write on the other — the table says so under `unlooked` rather than sho
|
|
|
329
329
|
green rows. Two green halves reporting a working whole is exactly the shape this file
|
|
330
330
|
spends its length refusing.
|
|
331
331
|
|
|
332
|
-
## GATE (
|
|
332
|
+
## GATE (auto)
|
|
333
333
|
|
|
334
334
|
> **Acceptance policy `AP-1` · owner: the operator of the project running this pipeline ·
|
|
335
335
|
> in force since 2026-08-20 · supersedes: the unversioned ladder that stood before it.**
|
|
@@ -22,7 +22,7 @@ approved design — not at code.
|
|
|
22
22
|
- UI detection — a required output
|
|
23
23
|
- The approved design is a set of decisions — record them
|
|
24
24
|
- User paths are a design output, not a spec detail
|
|
25
|
-
- GATE (
|
|
25
|
+
- GATE (auto)
|
|
26
26
|
- Rationalizations
|
|
27
27
|
|
|
28
28
|
## The hard gate
|
|
@@ -139,9 +139,9 @@ Feed them into [`spec.md`](spec.md)'s UX track — super-ux turns them into flow
|
|
|
139
139
|
screens and traced scenarios. **Do not draft scenario IDs here**; that is the
|
|
140
140
|
chain's job, and two sources for one scenario is worse than one.
|
|
141
141
|
|
|
142
|
-
## GATE (
|
|
142
|
+
## GATE (auto)
|
|
143
143
|
|
|
144
|
-
The operator
|
|
144
|
+
The design is approved (the operator's go, or the standing authorization recorded in the brief; `auto` by default since 1.98.0), **the UI verdict is recorded**, and — where that verdict is *yes* — **the paths, the states and the error paths are named** (above) rather than deferred to the spec. **Every REQ in the brief is answered by the design** — a requirement the design doesn't address
|
|
145
145
|
is either covered now or explicitly dropped by the operator, with the drop written
|
|
146
146
|
into the carry-over ledger. For a platform, the module map
|
|
147
147
|
([`decomposition.md`](decomposition.md)) is committed and approved as part of this
|
|
@@ -176,7 +176,7 @@ overall.
|
|
|
176
176
|
- **The final whole-branch review.** If the run is on a tier below the most capable
|
|
177
177
|
one available, say so and offer to run this one review there; if the operator
|
|
178
178
|
declines or the tier doesn't exist, run it on the confirmed model and note it.
|
|
179
|
-
- **Fix-loop
|
|
179
|
+
- **Fix-loop round 3.** One tier above the implementer that got stuck, when the
|
|
180
180
|
environment has one and the override map or the operator allows it; otherwise
|
|
181
181
|
say so and rely on fresh eyes alone.
|
|
182
182
|
|
|
@@ -407,7 +407,7 @@ may say what the reviewer actually saw, and a syntax lint recorded as a blind
|
|
|
407
407
|
review is refused by `certify` by name.
|
|
408
408
|
|
|
409
409
|
**The boundary with certification:** this review closes a **prose-plan task** —
|
|
410
|
-
one reviewer, the
|
|
410
|
+
one reviewer, the three-round cap of §4.5. A **work-graph node** is closed by the
|
|
411
411
|
three blind tiers and `graph.py certify` instead
|
|
412
412
|
([`certification.md`](certification.md), ceiling 3); the artifact the queue is —
|
|
413
413
|
plan or graph — decides which protocol closes the work, never whichever is
|
|
@@ -444,7 +444,7 @@ Two routes leave before the loop starts:
|
|
|
444
444
|
without asking.
|
|
445
445
|
|
|
446
446
|
Everything else loops. One round = one fix dispatch + one scoped re-review.
|
|
447
|
-
**
|
|
447
|
+
**Three rounds maximum per task**, then the open findings become blocker rows and the run moves on.
|
|
448
448
|
|
|
449
449
|
**The loop guard runs alongside the counter** ([`loop-guard.md`](loop-guard.md)):
|
|
450
450
|
log every repeat touch (`touch: <file> — round N — reason: <finding id>`) and trip
|
|
@@ -453,13 +453,13 @@ the same reason, or when a finding already ADDRESSED reappears. A tripped guard
|
|
|
453
453
|
not another round: stop, name the two shapes, escalate to the layer that owns the
|
|
454
454
|
conflict, then re-check in a planned order.
|
|
455
455
|
|
|
456
|
-
- **Rounds 1–
|
|
456
|
+
- **Rounds 1–2:** resume the original implementer with the open findings verbatim —
|
|
457
457
|
its context is intact. If the harness can't message a live subagent, dispatch a
|
|
458
458
|
fresh one with the brief path, the report path and the findings; the report file
|
|
459
459
|
is the persistent memory either way.
|
|
460
|
-
- **
|
|
460
|
+
- **Round 3:** fresh implementer, one tier up if available, framed as: "a prior
|
|
461
461
|
implementer attempted this task N times; you own it now — read the report file
|
|
462
|
-
for what was tried." A loop that survives
|
|
462
|
+
for what was tried." A loop that survives two resumes usually means the
|
|
463
463
|
implementer can't see its own problem.
|
|
464
464
|
- **Every round:** the implementer fixes, re-runs the tests covering the amended
|
|
465
465
|
code, appends its fix report to the same report file, returns the short contract.
|
|
@@ -471,7 +471,7 @@ conflict, then re-check in a planned order.
|
|
|
471
471
|
breakage joins the open list; out-of-scope observations go to the ledger as
|
|
472
472
|
deferred minors — they never extend the loop.
|
|
473
473
|
- **Ledger, every round:**
|
|
474
|
-
`Task <N>: fix round <R>/
|
|
474
|
+
`Task <N>: fix round <R>/3 (<X> addressed, <Y> open — <one-liners>; commits <a7>..<b7>)`
|
|
475
475
|
|
|
476
476
|
**In a subagent run, never fix findings yourself in the controller session** —
|
|
477
477
|
controller fixes skip review and pollute the context you need for coordination. In a
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
A node is not closed by one agent's opinion. It is closed by **three independent
|
|
4
4
|
readings at escalating visibility**, and the run may not advance until all three
|
|
5
|
-
pass.
|
|
5
|
+
pass. **The cost follows the risk** (since 1.98.0): an **R1** node certifies with **one**
|
|
6
|
+
blind tier (`unit` by default; `graph.py certify` accepts any one of the three), and a
|
|
7
|
+
clean round ends a node unless it is **R3 or a flagship surface** (*Rounds owed by risk
|
|
8
|
+
class*, below).
|
|
6
9
|
|
|
7
10
|
**The boundary: this closes a node of the work graph.** Where the queue is a
|
|
8
11
|
prose plan rather than a graph, a task closes through the per-task review instead
|
|
9
12
|
([`build.md`](build.md) §4.4 and [`review.md`](review.md) — one reviewer, a
|
|
10
|
-
|
|
13
|
+
three-round fix loop with an explicit breaker). Two closure protocols with no
|
|
11
14
|
boundary sentence were how a run picked whichever was cheaper; the artifact the
|
|
12
15
|
queue is — graph or plan — is what decides, not the mood of the closer.
|
|
13
16
|
|
|
@@ -208,14 +211,18 @@ erase the only evidence that a node is churning.
|
|
|
208
211
|
|
|
209
212
|
The cycle is: `certify` → fail → the `breaks` findings become nodes (each already
|
|
210
213
|
carrying its `check`) → fix → `certify` again, round `N+1`. Same three tiers, same
|
|
211
|
-
blind dispatch. A tier that passed in an earlier round is **re-run
|
|
212
|
-
fix is a new change
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
blind dispatch. A tier that passed in an earlier round is **re-run** after a fix,
|
|
215
|
+
because the fix is a new change. **It is never re-run on unchanged input:** `certify`
|
|
216
|
+
records the tree each round read and refuses a round of the same kind on the same
|
|
217
|
+
tree, before writing anything. A tier's second answer about a tree it already read is
|
|
218
|
+
its first answer again, and asking twice is the loop.
|
|
219
|
+
|
|
220
|
+
**At the ceiling the gate STOPS** ([`loop-guard.md`](loop-guard.md), since 1.98.0).
|
|
221
|
+
`--ceiling` defaults to 3 failing rounds. The round that reaches it still reports the
|
|
222
|
+
tiers, names the tier that has failed **every** round, and then **parks the node** with
|
|
223
|
+
the evidence (failing tiers, the first break and its check) as its reason, exit `7`.
|
|
224
|
+
The run records a blocker row with that evidence and moves on to the next node. It
|
|
225
|
+
used to measure only, and a loop that is measured is still a loop:
|
|
219
226
|
|
|
220
227
|
- the same tier every round → the level is being misread, or the node is the wrong
|
|
221
228
|
shape. Not one fix away. Re-plan the node, do not attempt another failing round. (An R3
|
|
@@ -227,30 +234,29 @@ has failed **every** round. A run spinning on one level needs the operator to se
|
|
|
227
234
|
|
|
228
235
|
In a release iteration a node carries `risk` (R1–R3, [`work-graph.md`](work-graph.md);
|
|
229
236
|
[`iteration.md`](iteration.md) → *Verification starts when a node lands*), and the class
|
|
230
|
-
decides how many **passing** rounds the node owes before `close` accepts it: **R1 one
|
|
231
|
-
|
|
232
|
-
flagship
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
(`certification.kinds
|
|
239
|
-
all is refused by `close` too
|
|
237
|
+
decides how many **passing** rounds the node owes before `close` accepts it: **R1 one
|
|
238
|
+
(with one blind tier), R2 one, R3 two, of which one is adversarial.** A node on a
|
|
239
|
+
**flagship** surface owes two as well, one of them adversarial, whatever its class. A
|
|
240
|
+
round is the blind tiers the class owes (three; four on a flagship or product surface;
|
|
241
|
+
one on R1) plus one fix cycle. `graph.py certify` prints `round N: k of M owed passing
|
|
242
|
+
rounds — not closable yet` until the count is met (`k of M owed passing rounds —
|
|
243
|
+
closable` once it is), and `graph.py close` refuses the verdict until then. **Only
|
|
244
|
+
passing rounds count**, read from `certification.history`, and for R3 or a flagship one
|
|
245
|
+
of them must be recorded with `--kind adversarial` (`certification.kinds`). A node with
|
|
246
|
+
a `risk` and no certification at all is refused by `close` too. R2 owed two blind
|
|
247
|
+
rounds until 1.98.0, and a clean round did not cancel the next: the second reading of
|
|
248
|
+
an unchanged diff found nothing it had not already been asked.
|
|
240
249
|
|
|
241
250
|
**What a round after a clean round reads.** The node's **whole diff at the new HEAD** — not
|
|
242
|
-
the fix diff alone, and not the previous round's reports.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
output only when rounds fail.
|
|
252
|
-
|
|
253
|
-
**The adversarial round** (one of R3's three, on another model where one is available):
|
|
251
|
+
the fix diff alone, and not the previous round's reports. On an unchanged tree the only
|
|
252
|
+
round that may follow a clean one is the other kind: the adversarial round asks a
|
|
253
|
+
different question of the same change.
|
|
254
|
+
|
|
255
|
+
**Owed rounds and the ceiling are different counts.** `--ceiling` counts *failing*
|
|
256
|
+
rounds and stops the node at the limit; owed rounds count *passing* ones. An R3 node
|
|
257
|
+
that passes its two is not near any ceiling.
|
|
258
|
+
|
|
259
|
+
**The adversarial round** (one of R3's two, on another model where one is available):
|
|
254
260
|
1. **Plant defects in the change** — off-by-one in a limit, an inverted check, a dropped
|
|
255
261
|
error path — and require the node's own checks to fail on each. A plant that passes is a
|
|
256
262
|
finding against the check, not against the plant.
|
|
@@ -261,19 +267,18 @@ output only when rounds fail.
|
|
|
261
267
|
3. **Try the abuse cases** a caller outside the trust boundary would: inputs at and past
|
|
262
268
|
every bound, replays, a revoked identity, an out-of-order sequence.
|
|
263
269
|
|
|
264
|
-
It is a different question, not a stricter reading: on 2026-10-12 one iteration's
|
|
265
|
-
round on a spend-limits node found three breaks of a money invariant that two
|
|
266
|
-
had passed. Record it with `certify --kind adversarial`.
|
|
270
|
+
It is a different question, not a stricter reading: on 2026-10-12 one iteration's
|
|
271
|
+
adversarial round on a spend-limits node found three breaks of a money invariant that two
|
|
272
|
+
blind rounds had passed. That is why R3 keeps it and drops the second blind round. Record it with `certify --kind adversarial`.
|
|
267
273
|
|
|
268
274
|
## What this costs, said out loud
|
|
269
275
|
|
|
270
|
-
Three agents per node instead of one
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
`
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
afterwards**, and only one of them is evidence.
|
|
276
|
+
Three agents per node instead of one, four on a flagship or product surface, one on R1.
|
|
277
|
+
The tiers are dispatched in parallel, so the wall-clock cost is roughly one reading and
|
|
278
|
+
the token cost is the tier count. Where a tier runs and finds nothing it says so in
|
|
279
|
+
`scope` and `not_examined`, because **a skipped level and a clean level are
|
|
280
|
+
indistinguishable afterwards**. What the class does not owe is not run: an R1 node's
|
|
281
|
+
second and third tiers are not skipped levels, they are levels its risk never reached.
|
|
277
282
|
|
|
278
283
|
## Rationalizations
|
|
279
284
|
|
|
@@ -281,5 +286,5 @@ afterwards**, and only one of them is evidence.
|
|
|
281
286
|
|---|---|
|
|
282
287
|
| «All three would say the same thing» | Then all three say it, at a cost you already know, and the run has three signatures instead of one guess about what the other two would have found |
|
|
283
288
|
| «The seam tier can read the unit report first — it saves tokens» | It saves tokens by removing the second opinion. The reports are cheap; the independence is the product |
|
|
284
|
-
| «Tier 3 passed last round, skip it» | The fix is a new change. A tier's pass is about the tree it read, and that tree moved |
|
|
285
|
-
| «Round 4 will get it» |
|
|
289
|
+
| «Tier 3 passed last round, skip it» | The fix is a new change. A tier's pass is about the tree it read, and that tree moved. On an unchanged tree the round is refused instead |
|
|
290
|
+
| «Round 4 will get it» | There is no round 4: the ceiling parks the node with its evidence. The same tier failing three times is a planning defect wearing a verification failure's clothes |
|
|
@@ -49,7 +49,7 @@ Custom profiles use their own declared stages and doctrine, with the kernel inta
|
|
|
49
49
|
| 3 Spec | `references/spec.md` |
|
|
50
50
|
| 4 Plan | `references/planning.md` |
|
|
51
51
|
| the queue the loop walks | `references/work-graph.md` |
|
|
52
|
-
| 5–8 · how a **work-graph node** is CLOSED — three blind readings at three distances, all three required, plus a fourth `visual` reading on a flagship or product surface (ceiling 3); a **prose-plan task** closes through `review.md` instead — one reviewer,
|
|
52
|
+
| 5–8 · how a **work-graph node** is CLOSED — three blind readings at three distances, all three required, plus a fourth `visual` reading on a flagship or product surface (ceiling 3); a **prose-plan task** closes through `review.md` instead — one reviewer, three-round cap | `references/certification.md` |
|
|
53
53
|
| 5 Build (worktree, subagents, fix loop) | `references/build.md` + `references/review.md` |
|
|
54
54
|
| 5–6 TDD + suite gate | `references/tdd.md` |
|
|
55
55
|
| 5, 6, 8 The browser — the look, the spec suite, and the difference | `references/browser.md` |
|
|
@@ -25,7 +25,8 @@ fields, written at stage 0 and confirmed with the brief:
|
|
|
25
25
|
released, installed or deployed. A branch is not that artefact.
|
|
26
26
|
- **Scope** — the modules, paths and REQ ids this iteration may change, as a list.
|
|
27
27
|
- **Budget** — `run.finish.maxRounds` (default 3) and `run.finish.maxReleases` (default 2),
|
|
28
|
-
from `pipeline.json` ([`../pipeline.schema.json`](../pipeline.schema.json))
|
|
28
|
+
from `pipeline.json` ([`../pipeline.schema.json`](../pipeline.schema.json)), and
|
|
29
|
+
`run.finish.cleanRounds`: 1, or 2 when the iteration carries an R3 or flagship node.
|
|
29
30
|
- **Base** — the commit the iteration started from.
|
|
30
31
|
|
|
31
32
|
Widening the scope is a recorded revision of the brief, with the operator's go. A finding is
|
|
@@ -71,12 +72,15 @@ A **round** is every exit check run once, plus one independent reading over the
|
|
|
71
72
|
audit axis, an observer, or the post-release readback. Rotating the axis
|
|
72
73
|
([`audit.md`](audit.md)) never adds a round. Only new blocking findings count.
|
|
73
74
|
|
|
74
|
-
- **Done** — every exit check green on the artefact, and
|
|
75
|
-
|
|
75
|
+
- **Done** — every exit check green on the artefact, and one round with no new blocking
|
|
76
|
+
finding; two consecutive ones when the iteration carries an R3 or flagship node, the
|
|
77
|
+
second of which may read only the last fix's diff. Two for every iteration was the
|
|
78
|
+
default until 1.98.0, and a clean round that changed nothing bought a second reading
|
|
79
|
+
of the same tree.
|
|
76
80
|
- **Diverging** — new blocking findings did not fall from one round to the next, or
|
|
77
81
|
self-inflicted ≥ new. Run [`loop-guard.md`](loop-guard.md)'s break protocol.
|
|
78
82
|
- **Budget spent** — `maxRounds` or `maxReleases` reached before done: a blocker open, or the
|
|
79
|
-
|
|
83
|
+
clean round still owed. Stop and hand the iteration back: each open blocker becomes a
|
|
80
84
|
top board row marked `needs-operator`. No round runs past the budget without the operator's
|
|
81
85
|
go, given with the numbers; a done reached past it without that go is still `budget`.
|
|
82
86
|
|
|
@@ -94,15 +98,16 @@ round: <N> — <reading> — new blocking <k>
|
|
|
94
98
|
|
|
95
99
|
```
|
|
96
100
|
python3 scripts/waves.py finish --inventory <inventory.json> --ledger .task-pipeline/run.md \
|
|
97
|
-
--max-rounds 3 --max-releases 2 --releases <n> --checks <k>/<m> --artefact <name>@<sha>
|
|
101
|
+
--max-rounds 3 --max-releases 2 --releases <n> --checks <k>/<m> --artefact <name>@<sha> \
|
|
102
|
+
[--clean-rounds 2]
|
|
98
103
|
```
|
|
99
104
|
|
|
100
105
|
`--rounds N` may stand in for `--ledger`; with neither the count is unknown and the script
|
|
101
106
|
refuses. Exit codes:
|
|
102
107
|
|
|
103
|
-
- `0` — done: the last
|
|
104
|
-
check is green on the artefact.
|
|
105
|
-
- `3` — continue: a blocker open, a
|
|
108
|
+
- `0` — done: the last `--clean-rounds` rounds (1 by default) found no new blocking item,
|
|
109
|
+
none is open, and every exit check is green on the artefact.
|
|
110
|
+
- `3` — continue: a blocker open, a clean round owed, or no `--checks` given.
|
|
106
111
|
- `5` — budget: reached before done, or done past it without `--operator-go`.
|
|
107
112
|
- `6` — blocked: diverging; run the loop guard's break protocol.
|
|
108
113
|
- `1` — malformed: the inventory, the ledger or the arguments, each reason named.
|
|
@@ -159,28 +159,25 @@ its slowest node before checking its fastest.
|
|
|
159
159
|
|
|
160
160
|
**The number of rounds follows the node's risk class.** The class is recorded on the node
|
|
161
161
|
as `risk` (R1–R3, [`work-graph.md`](work-graph.md)), computed from what the node touches.
|
|
162
|
-
It is never lowered to save time. **A round is the
|
|
163
|
-
[`certification.md`](certification.md)
|
|
164
|
-
the
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
-
|
|
180
|
-
|
|
181
|
-
→ a passing tier is re-run). A clean round does not cancel the next one, and only passing
|
|
182
|
-
rounds count. R3's adversarial round asks a different question — it hunts for a planted
|
|
183
|
-
defect — and always runs; it counts only when it passes.
|
|
162
|
+
It is never lowered to save time. **A round is the blind tiers the class owes, plus one
|
|
163
|
+
fix cycle** ([`certification.md`](certification.md)), and a clean round ends the node
|
|
164
|
+
unless the class is R3 or the surface is flagship:
|
|
165
|
+
|
|
166
|
+
| Class | Touches | Tiers | Rounds |
|
|
167
|
+
|---|---|---|---|
|
|
168
|
+
| R1 | docs, config, a mechanical change, one isolated file | one | 1 |
|
|
169
|
+
| R2 | an ordinary feature or fix inside one module | three | 1 |
|
|
170
|
+
| R3 | security, cryptography, money, a data migration, a public contract, a seam across repositories, concurrency | three | 2: one blind, one adversarial (plants defects, tries abuse cases, on another model where one is available) |
|
|
171
|
+
|
|
172
|
+
A flagship surface owes four tiers and two rounds, one adversarial, whatever its class.
|
|
173
|
+
|
|
174
|
+
- Each round's fix cycle stays inside [`loop-guard.md`](loop-guard.md)'s caps, and the
|
|
175
|
+
certification ceiling stops the node: it is parked with its evidence and a board row.
|
|
176
|
+
- `graph.py close` refuses a node until its owed rounds have passed, and an R3 or
|
|
177
|
+
flagship node until one of them was recorded adversarial
|
|
178
|
+
([`certification.md`](certification.md) → *Rounds owed by risk class*).
|
|
179
|
+
- A round after a fix re-runs every tier the class owes; a round on an unchanged tree is
|
|
180
|
+
refused unless it is the other kind. Only passing rounds count.
|
|
184
181
|
- Readers stay blind ([`certification.md`](certification.md) → *Blind*). Each reads the
|
|
185
182
|
diff, the packet and the contract, never the builder's reasoning.
|
|
186
183
|
|
|
@@ -384,7 +381,7 @@ release run performs, not unrelated deployments, messages or human-only actions.
|
|
|
384
381
|
|---|---|
|
|
385
382
|
| "This bug is tiny, fix it now" | Tiny fixes are what move a seam twice. It joins the inventory, or it is one of the hotfix lane's five entries |
|
|
386
383
|
| "Wait for the whole wave, then verify" | A barrier idles every finished node. Each node is certified when it lands |
|
|
387
|
-
| "R3 found nothing in round
|
|
384
|
+
| "R3 found nothing in its blind round, skip the adversarial round" | The adversarial round, in any position, asks a different question: it hunts for a planted defect. A clean blind round does not answer it |
|
|
388
385
|
| "Ask the operator now, it is quick" | Each question is a human round. Queue it and take another node |
|
|
389
386
|
| "The agent hit a rate limit, start it again" | Resume it with its context. Relaunching pays for the same reading twice |
|
|
390
387
|
| "No Observatory here, so no learning loop" | The project retro and learned.md are the loop's first layer, and every install has them |
|
|
@@ -51,7 +51,7 @@ makes the grill's answers *checkable* instead of merely confident.
|
|
|
51
51
|
| 4b | **The task register, for its *state*** | `docs/ROADMAP.md`, a board, a backlog, the tracker `CLAUDE.md` names | **what is open right now** — read with a command, never from memory; see *Carried-in claims* |
|
|
52
52
|
| 5 | **Product/UX docs** | `docs/ux/` (super-ux chain), `README`, runbooks | user-facing behavior that is already specified |
|
|
53
53
|
| 6 | **Pipeline history** | `<artifacts>/specs/`, `plans/`, past `-carryover.md` | what a previous run of this pipeline decided or deferred |
|
|
54
|
-
| 7 | **The retro, in force** | `<artifacts>/retro.md` ([`retrospective.md`](retrospective.md)) | what previous runs got wrong here — **read in full**: standing instructions (capped at ten) and run stamps (one line each), both
|
|
54
|
+
| 7 | **The retro, in force** | `<artifacts>/retro.md` ([`retrospective.md`](retrospective.md)) | what previous runs got wrong here — **read in full**: standing instructions (capped at ten) and run stamps (one line each), both under the floor's 12 000-byte budget, whose bytes and bytes/4 tokens `scripts/retro_budget.py check` prints into this row; the *Recent log* is **queried** by the task's nouns, like the archive — nothing caps it |
|
|
55
55
|
| 7a | **The retro archive** | `<artifacts>/retro/YYYY-QN.md` | *have we been bitten by this class before?* — **queried** by the task's nouns, never read end to end |
|
|
56
56
|
| 7b | **Lessons from other projects** | the local index every install has (`scripts/lessons.py query`), and a shared lesson store only where the session lists one — an Observatory — detected by its tools, never by a path ([`iteration.md`](iteration.md) → *The learning loop*, the read recipe) | *what did other projects' agents learn about this class?* — **queried** by the task's 3–6 nouns, at most ten lines from both; a lesson that fires is cited `[lesson L-…]` or `[lesson mem:…]`. The ledger names each layer's state: `local lessons: N match | absent | unavailable (no python3)`; `shared lessons: N match | 0 match (filter unknown) | unfilterable | absent`, where the first `observatory_search` call is the probe — and nothing else changes |
|
|
57
57
|
| 8 | **The knowledge wiki** | see below | distilled cross-project knowledge, prior sessions, why decisions were made |
|
|
@@ -79,8 +79,10 @@ Rules for the list:
|
|
|
79
79
|
|
|
80
80
|
`<artifacts>/retro.md` ([`retrospective.md`](retrospective.md)) is the one
|
|
81
81
|
harvested source whose binding part is **read in full rather than queried**: the
|
|
82
|
-
standing instructions are capped at ten
|
|
83
|
-
|
|
82
|
+
standing instructions are capped at ten rows of at most 600 characters, the run stamps
|
|
83
|
+
at ten lines of at most 300, and the two together at 12 000 bytes
|
|
84
|
+
([`retrospective.md`](retrospective.md) → *The floor has a byte budget*), precisely so
|
|
85
|
+
that this is cheap. Its narrative log is queried, not read — an uncapped section
|
|
84
86
|
inside a source that binds is how the capped part stops being read. They are what
|
|
85
87
|
previous runs of this pipeline got wrong *in this project* — the rules no check
|
|
86
88
|
could decide — and they bind this run.
|
|
@@ -58,6 +58,13 @@ to be enforced and is not is the same failure as a gate that prints `FAIL` and e
|
|
|
58
58
|
| 30 | **A gate scoped by exclusion judges every newcomer silently** | any lint, format or check configured with an ignore list rather than an include list | scope by what it COVERS; where the tool has no such switch, assert the covered set and fail on additions, deriving the allowlist by measuring the tool's own reach | the failure message names the intruder and the remedy, because the operator meets it mid-release |
|
|
59
59
|
| 31 | **A gate needs a third verdict — could not measure** | any check that can time out, lose a dependency, or run on a contended machine | distinguish *failed* from *could not measure* with different exit codes, and say which; neither is a verdict about the code | the refusal names its remedy, and a test that waits for a DURATION rather than an event is treated as a defect in the test |
|
|
60
60
|
| 32 | **A test whose assertion is implied by compilation is not a test** | any test whose name promises a behaviour | delete the subject: if that is a type or import error rather than a red test, the test is decoration | removing one asks what the name PROMISED and whether anything checks it — four of five such cases here were covered elsewhere and the fifth was a real gap only visible because the name was read as a claim |
|
|
61
|
+
| 33 | **Snapshot before you undo** | any `git checkout`, `git restore`, `git stash` or `reset` in a tree that may hold uncommitted work you did not write | copy the affected files first (to a dated folder, or `git diff > <file>.patch`) and restore from that copy, never from git | the run names the copy it took before the first destructive command |
|
|
62
|
+
| 34 | **One answer for inputs that should differ is a broken instrument** | any shell measurement, zsh above all | quote every expansion (`"$var"`, `'*.py'`), read data from files rather than inline literals, and feed it two inputs that should answer differently | the two answers differ, or the instrument is rewritten before its answer is used |
|
|
63
|
+
| 35 | **Release the tree you tested** | any tag or release | run the release workflow's own suite on the exact commit the tag will name, and select the CI run by that tag, never by branch | the run the tag triggered is green and its head SHA is the tagged commit |
|
|
64
|
+
| 36 | **A fix after a passing review is unreviewed** | any change made after a review or verifier passed | re-read the delta (`git diff <reviewed>..HEAD`) before merge | the merge names the reviewed SHA and who read the delta |
|
|
65
|
+
| 37 | **Call the real service once before writing its fake** | any mock, stub or fixture of an external API, CLI or service | make one real call, or read a recorded real response, and shape the fake from it | the fake cites the response it was shaped from |
|
|
66
|
+
| 38 | **Close blockers yourself; park only human gates** | any blocker about to be handed back | try every route the run holds (an API, a CLI, a credential door) first; park only a sign-in, consent, payment, typed secret or recorded operator decision | every handed-back item is a human gate, listed in one block with the route that failed |
|
|
67
|
+
| 39 | **Never retry an unchanged failing action** | any failed command, check or approach | change the input or the approach before trying again; after two failed approaches, change approach or record a blocker | no identical retry in the run ledger, and a third attempt names what changed |
|
|
61
68
|
|
|
62
69
|
---
|
|
63
70
|
|
|
@@ -163,6 +170,17 @@ that document is checked. This one governs a fact that crossed a **session bound
|
|
|
163
170
|
reported as current — the case where there is no document to check, only a memory that reads like
|
|
164
171
|
one.
|
|
165
172
|
|
|
173
|
+
**33 to 39 · The 2026-10-20 retro audit.** Read across ~54 retrospectives from ~50
|
|
174
|
+
projects, the same lessons recurred under different words, each in at least the number
|
|
175
|
+
of runs given: uncommitted work overwritten by a checkout, restore or stash with no copy
|
|
176
|
+
taken (17); a zsh glob or `=` left unquoted, so a command failed or a loop printed one
|
|
177
|
+
count for every file (8; this release's own run hit both); a tag cut from a tree the
|
|
178
|
+
release suite never ran on, or a CI run picked by branch rather than by tag (24, with
|
|
179
|
+
immutable tags burned); a fix landed after the review passed and merged unread; a fake
|
|
180
|
+
shaped from documentation that the real service contradicted (11); a hand-back listing
|
|
181
|
+
steps the agent could have done itself (12); and the same failing action retried
|
|
182
|
+
unchanged, round after round. None of them was new to the project that hit it again.
|
|
183
|
+
|
|
166
184
|
---
|
|
167
185
|
|
|
168
186
|
## The two that are not in the table, and why
|
|
@@ -214,6 +232,11 @@ answer would have exposed it in a minute.
|
|
|
214
232
|
| any command run to establish a fact — 6 Tests · 9 Docs · 10 Acceptance · 5 review | 19 — the output asserted non-empty and shaped as expected, and **quoted** rather than concluded from, [`audit.md`](audit.md) → *Silence is not a reading*; the reviewer's half is in [`review.md`](review.md) |
|
|
215
233
|
| 0 Harvest · 10 Acceptance | 20 — the **consumer** read to learn which copy ships, never the copies compared against each other, [`audit.md`](audit.md) → *Two copies, and which one wins*; asked as row `0 Duplicates` of [`grill.md`](grill.md) → *The autonomy sweep* |
|
|
216
234
|
| 10 Retro · **any gate or check you order** | 21 — each input traced to the step that writes it, and that step proven upstream, [`retrospective.md`](retrospective.md) → *Stamp first, then prune, then write* |
|
|
235
|
+
| any stage · a destructive git command · a shell measurement | 33, 34 |
|
|
236
|
+
| 7 Release · 5 Dev after review | 35, 36 |
|
|
237
|
+
| 5 Dev · 6 Tests · a fake of an external service | 37 |
|
|
238
|
+
| 10 Acceptance · the hand-back | 38 |
|
|
239
|
+
| every stage · any retry | 39, [`loop-guard.md`](loop-guard.md) |
|
|
217
240
|
|
|
218
241
|
**This file is the shipped list; a project keeps its own.** Every rule in the table
|
|
219
242
|
above was earned on someone else's build and travels with the skill. The lessons *your*
|
|
@@ -276,7 +299,7 @@ evidence that something left, and the log below says what.
|
|
|
276
299
|
|
|
277
300
|
### Retired
|
|
278
301
|
|
|
279
|
-
**Numbers issued so far:
|
|
302
|
+
**Numbers issued so far: 39.** This is the high-water mark, and it is the only number
|
|
280
303
|
this file states about itself — deliberately, because the gap that proves a rule left
|
|
281
304
|
cannot be computed from the table alone: **deleting the highest-numbered rule shrinks
|
|
282
305
|
the maximum with it, and no gap ever opens.** That false negative shipped in the first
|
|
@@ -22,7 +22,7 @@ searches.
|
|
|
22
22
|
|
|
23
23
|
- Bookkeeping — the thing that makes detection mechanical
|
|
24
24
|
- Detection — any one of these trips the guard
|
|
25
|
-
- The review loop
|
|
25
|
+
- The review loop: a cap that stops, and says why
|
|
26
26
|
- The re-render loop — a budget of one, two at most
|
|
27
27
|
- The break protocol
|
|
28
28
|
- When to stop and hand back
|
|
@@ -66,12 +66,20 @@ stage's gate — which is the whole difference between a rule and a rule that ru
|
|
|
66
66
|
loop (another task, another module) already closed in this run — two owners for
|
|
67
67
|
one file.
|
|
68
68
|
|
|
69
|
-
Caps that trip the guard by themselves: **
|
|
69
|
+
Caps that trip the guard by themselves: **3 fix rounds** per task
|
|
70
70
|
([`build.md`](build.md)), **2 re-entries** per stage per artifact, **3 passes** per
|
|
71
|
-
module in the program loop,
|
|
72
|
-
|
|
71
|
+
module in the program loop, **3 review rounds** per artifact, and **3 failing
|
|
72
|
+
certification rounds** per node ([`certification.md`](certification.md)). Each one
|
|
73
|
+
**stops**: the remaining findings become blocker rows with their evidence and the run
|
|
74
|
+
moves on (since 1.98.0; the review cap used to measure only).
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
**Two rules hold under every cap.** Never retry an unchanged failing action: the same
|
|
77
|
+
command on the same input gives the same answer, so a retry is a loop with no
|
|
78
|
+
information in it. After two failed approaches to one problem, change the approach or
|
|
79
|
+
record a blocker; a third attempt of the same shape is the loop this file exists to
|
|
80
|
+
end ([`learned.md`](learned.md) rule 39).
|
|
81
|
+
|
|
82
|
+
## The review loop: a cap that stops, and says why
|
|
75
83
|
|
|
76
84
|
The caps above govern loops that **edit**. A review loop does both: the reader finds,
|
|
77
85
|
the run fixes, the reader reads again. It had no cap at all until 2026-08-10, and this
|
|
@@ -98,8 +106,10 @@ review cap reached — 3 rounds — artifact: test/validate.py
|
|
|
98
106
|
- **Self-inflicted ≥ new** — the axis is exhausted ([`audit.md`](audit.md) → *Every
|
|
99
107
|
pass changes the axis*). Stop. Every remaining finding becomes a board row with its
|
|
100
108
|
evidence ([`backlog.md`](backlog.md)); none is dropped.
|
|
101
|
-
- **New > self-inflicted** — the reader is still paying
|
|
102
|
-
|
|
109
|
+
- **New > self-inflicted** — the reader is still paying, and the loop still stops: the
|
|
110
|
+
open findings become board rows with the numbers beside them. A further round runs
|
|
111
|
+
only on the operator's explicit request, which is read when given and never waited
|
|
112
|
+
for.
|
|
103
113
|
|
|
104
114
|
At the level of a whole iteration only new **blocking** findings argue for continuing; a
|
|
105
115
|
deferred or noise finding is paid for on the board, not with another round
|
|
@@ -18,7 +18,7 @@ justifies reading it protects one section while the file below it doubles.
|
|
|
18
18
|
|
|
19
19
|
| Artifact | Parts | How it is read |
|
|
20
20
|
|---|---|---|
|
|
21
|
-
| `docs/evidence/retro.md` — **one per project** | **Standing instructions** (max **10**) · **Run stamps** (max **10**, oldest rotate out) | stage 0, **in full
|
|
21
|
+
| `docs/evidence/retro.md` — **one per project** | **Standing instructions** (max **10**) · **Run stamps** (max **10**, oldest rotate out) | stage 0, **in full**: both are bounded by a **cap** and, since 1.98.0, by a **byte budget** (*The floor has a byte budget*) |
|
|
22
22
|
| the same file's **Recent log** | narrative entries, **uncapped by design** — the heading said *entries from the last five run stamps* until 2026-08-20 while the section held 25 going back nine days, a bound in a heading that nothing enforced | stage 0, **queried** by the task's nouns. It said *in full* until 2026-08-10, when it measured **74%** of the file: an uncapped section inside a binding source is what makes the capped part get skimmed |
|
|
23
23
|
| `docs/evidence/retro/YYYY-QN.md` — the archive | every entry and every retirement ever written, append-only | **queried** by the task's nouns; never read end to end |
|
|
24
24
|
|
|
@@ -35,6 +35,8 @@ file exists to stop.
|
|
|
35
35
|
- Never amend a commit a record already names
|
|
36
36
|
- A release stamp names the TAG, and the merge commit — never the branch's own SHA
|
|
37
37
|
- The stamp table is capped at ten, and *one line per run* was never a cap
|
|
38
|
+
- The floor has a byte budget
|
|
39
|
+
- Before a standing row: search, cite, send upstream
|
|
38
40
|
- `publish:` is a line in the verdict, not a silence
|
|
39
41
|
- Rotation — the archive is how pruning stops losing things
|
|
40
42
|
- Three grades of fix — take the highest one that can work
|
|
@@ -66,8 +68,16 @@ Required fields, and none of them is optional:
|
|
|
66
68
|
| **Root cause** | why the pipeline permitted it. "The agent was careless" is not a cause — it is the absence of one, and it produces no fix |
|
|
67
69
|
| **Fix** | one of the three grades below |
|
|
68
70
|
| **The check** | what would have caught this the first time. If the honest answer is "nothing yet", that is the fix, and it is grade 1 |
|
|
71
|
+
| **Class** | one of: `exit-code`, `unwatched-check`, `release`, `collision`, `destructive-undo`, `claim-by-say-so`, `stale-doc`, `wrong-copy`, `sibling`, `truncated-output`, `shell-instrument`, `handback`, `loop`. **Recurrence is counted by class**, so a class seen before is named as such |
|
|
72
|
+
| **Cost** | what it cost: rounds, burned tags, time. An entry with no cost cannot be weighed against the check that would prevent it |
|
|
69
73
|
| **Commit** | the short SHA of the change that fixed it |
|
|
70
74
|
|
|
75
|
+
The classes come from the 2026-10-20 audit of ~54 retrospectives across ~50 projects,
|
|
76
|
+
where the same lessons recurred under different words: a gate's exit code read through
|
|
77
|
+
a pipe (≥26 runs), a check trusted before it was seen failing (≥26), a tag cut from a
|
|
78
|
+
tree the release suite never ran on (≥24), a shared checkout (≥20), uncommitted work
|
|
79
|
+
overwritten (≥17). Words hid the recurrence; a class key makes it a count.
|
|
80
|
+
|
|
71
81
|
## Every lesson carries its commit
|
|
72
82
|
|
|
73
83
|
A standing instruction carries **two** SHAs — `Commit` (the change that introduced
|
|
@@ -177,6 +187,38 @@ like every other rotation. **The count is printed at the prune**, beside the sta
|
|
|
177
187
|
instructions' own count, so a table that stops rotating is visible rather than merely
|
|
178
188
|
large.
|
|
179
189
|
|
|
190
|
+
## The floor has a byte budget
|
|
191
|
+
|
|
192
|
+
A cap of ten rows never bounded their **size**. Measured 2026-10-20 across ~50
|
|
193
|
+
projects, the floor stage 0 reads in full (standing instructions plus run stamps) ran
|
|
194
|
+
from ~3k to **~76k tokens**; one floor was 304 KB, and this repository's own was
|
|
195
|
+
27 620 bytes. A row that carries its incident and a stamp that carries its release
|
|
196
|
+
narrative are how a capped list grows anyway.
|
|
197
|
+
|
|
198
|
+
- **Budget:** the floor stays under **12 000 bytes**. A standing row is at most **600
|
|
199
|
+
characters**: the rule, why (one clause), the retire-when trigger and the id; the
|
|
200
|
+
story lives in the archive. A run stamp is **one line of at most 300 characters**.
|
|
201
|
+
- **Stage 0 prints it:** `python3 scripts/retro_budget.py check` gives the floor's bytes
|
|
202
|
+
and bytes/4 tokens, beside the source ledger's retro row.
|
|
203
|
+
- **Gate 10 fails over budget.** Before the new entry is written, `retro_budget.py
|
|
204
|
+
migrate --write` moves the narrative (an over-long stamp's verdict, a standing row's
|
|
205
|
+
`Because`) **verbatim** into the quarter's archive, each block carrying the sha256 of
|
|
206
|
+
its text, and leaves a pointer; `retro_budget.py verify` proves every pointer. An
|
|
207
|
+
instruction still over 600 characters after its story moved is rewritten by hand.
|
|
208
|
+
- **A prune that retires nothing writes its count into the stamp row**, as
|
|
209
|
+
`prune: 0 retired`, and no paragraph. Repeated "nothing to prune" notes were a
|
|
210
|
+
measured share of the floor.
|
|
211
|
+
|
|
212
|
+
## Before a standing row: search, cite, send upstream
|
|
213
|
+
|
|
214
|
+
- **Search first:** `references/learned.md` and the lesson index
|
|
215
|
+
(`scripts/lessons.py query`, by the class key and the nouns). A rule already there is
|
|
216
|
+
**cited by its number** (`learned.md` rule 22), never restated in a row.
|
|
217
|
+
- **A lesson standing in two projects goes upstream in the same run:** the entry gets
|
|
218
|
+
its `**Lesson:**` line and `scripts/lessons.py add`, and a universal rule is proposed
|
|
219
|
+
for `learned.md` (*Publishing the insight*, below). Two projects holding one rule is
|
|
220
|
+
a fork nobody named.
|
|
221
|
+
|
|
180
222
|
## `publish:` is a line in the verdict, not a silence
|
|
181
223
|
|
|
182
224
|
Reported from another project, and the report is about this file: an operator asked, after
|
|
@@ -265,6 +307,10 @@ computable, which is why it goes first:
|
|
|
265
307
|
printf '%s · %s\n' "$(date +%F)" "$(git rev-parse --short HEAD)" >> docs/evidence/retro.md
|
|
266
308
|
```
|
|
267
309
|
|
|
310
|
+
**The budget runs between the prune and the entry:** stamp, prune, `retro_budget.py
|
|
311
|
+
check` (and `migrate --write` when it fails), then write. An entry written onto a floor
|
|
312
|
+
already over budget is the growth the budget exists to stop.
|
|
313
|
+
|
|
268
314
|
## The prune — mandatory, and it runs after the stamp
|
|
269
315
|
|
|
270
316
|
A lesson that lands in a cluttered file is a lesson nobody will reach — so the prune still runs
|