task-pipeline-skill 1.97.1 → 1.99.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 +283 -0
- package/HOW-IT-WORKS.md +17 -13
- package/README.md +13 -10
- 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 +11 -4
- 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 +22 -7
- package/plugins/task-pipeline/skills/task-pipeline/references/certification.md +49 -44
- package/plugins/task-pipeline/skills/task-pipeline/references/continuity.md +6 -3
- 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 +188 -32
- 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 +49 -2
- 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 +36 -28
- package/plugins/task-pipeline/skills/task-pipeline/references/waves.md +8 -5
- package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +79 -22
- package/plugins/task-pipeline/skills/task-pipeline/scripts/lessons.py +1229 -110
- 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/lesson.schema.json +38 -10
- 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
|
@@ -75,6 +75,22 @@ act. **Step 11 runs stage 8's verification trio** on what was released. Steps 12
|
|
|
75
75
|
with stage 10. No gate in [`stages.md`](stages.md) is skipped. The cycle changes when each
|
|
76
76
|
stage runs, and over what.
|
|
77
77
|
|
|
78
|
+
**Before each cycle, check for parallel work on the same nodes.** In every target repository,
|
|
79
|
+
list the open PRs and the recently merged ones by branch prefix. Include other CLIs' prefixes,
|
|
80
|
+
such as `codex/`, `fix/` and `release/`, and do not stop at your own tool's live sessions:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
gh pr list -R <owner>/<repo> --state open --json number,headRefName,title
|
|
84
|
+
gh pr list -R <owner>/<repo> --state merged -L 20 --json number,headRefName,mergedAt
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Each node has one owner. When work has to be split, record the split on the PR you would
|
|
88
|
+
otherwise collide with. *A lesson of release iteration 1 (2026-10-13): fix PRs from other
|
|
89
|
+
tools' branches landed on nodes a fix cycle was rebuilding, and a later cycle was briefed on
|
|
90
|
+
a PR that had already merged. Source: the iteration's run record, which is private, and
|
|
91
|
+
[task-pipeline#125](https://github.com/ssheleg/task-pipeline/pull/125), which carries the
|
|
92
|
+
rule.*
|
|
93
|
+
|
|
78
94
|
## Module dossiers
|
|
79
95
|
|
|
80
96
|
**One dossier per cluster.** The inventory is grouped by the part of the system each item
|
|
@@ -159,28 +175,25 @@ its slowest node before checking its fastest.
|
|
|
159
175
|
|
|
160
176
|
**The number of rounds follows the node's risk class.** The class is recorded on the node
|
|
161
177
|
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.
|
|
178
|
+
It is never lowered to save time. **A round is the blind tiers the class owes, plus one
|
|
179
|
+
fix cycle** ([`certification.md`](certification.md)), and a clean round ends the node
|
|
180
|
+
unless the class is R3 or the surface is flagship:
|
|
181
|
+
|
|
182
|
+
| Class | Touches | Tiers | Rounds |
|
|
183
|
+
|---|---|---|---|
|
|
184
|
+
| R1 | docs, config, a mechanical change, one isolated file | one | 1 |
|
|
185
|
+
| R2 | an ordinary feature or fix inside one module | three | 1 |
|
|
186
|
+
| 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) |
|
|
187
|
+
|
|
188
|
+
A flagship surface owes four tiers and two rounds, one adversarial, whatever its class.
|
|
189
|
+
|
|
190
|
+
- Each round's fix cycle stays inside [`loop-guard.md`](loop-guard.md)'s caps, and the
|
|
191
|
+
certification ceiling stops the node: it is parked with its evidence and a board row.
|
|
192
|
+
- `graph.py close` refuses a node until its owed rounds have passed, and an R3 or
|
|
193
|
+
flagship node until one of them was recorded adversarial
|
|
194
|
+
([`certification.md`](certification.md) → *Rounds owed by risk class*).
|
|
195
|
+
- A round after a fix re-runs every tier the class owes; a round on an unchanged tree is
|
|
196
|
+
refused unless it is the other kind. Only passing rounds count.
|
|
184
197
|
- Readers stay blind ([`certification.md`](certification.md) → *Blind*). Each reads the
|
|
185
198
|
diff, the packet and the contract, never the builder's reasoning.
|
|
186
199
|
|
|
@@ -262,13 +275,152 @@ private to its project and never leaves it.
|
|
|
262
275
|
**Layer 1 — every install, no service.** The project retro and [`learned.md`](learned.md),
|
|
263
276
|
as before, plus a **local index** of every project's lessons on this machine:
|
|
264
277
|
`scripts/lessons.py`, one JSON line per class, a pure function of the retros
|
|
265
|
-
(`lessons.py rebuild --roots <dir>` re-derives it).
|
|
266
|
-
its
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
278
|
+
(`lessons.py rebuild --roots <dir>` re-derives it). **A rebuild never removes or moves
|
|
279
|
+
anything on its own.** It derives every lesson from the retros it can read under the roots and
|
|
280
|
+
depth given, and from every git worktree of each checkout it finds. Worktrees under
|
|
281
|
+
`.worktrees/` or `.claude/worktrees/` are included, even though the walk skips
|
|
282
|
+
dot-directories.
|
|
283
|
+
|
|
284
|
+
An origin is one project's copy of a lesson, `(project, retro)`. It records its
|
|
285
|
+
**provenance**: the checkouts it was derived from, by real path. That record stays in the
|
|
286
|
+
local index; it is never emitted, and `show` omits it. Provenance decides only the reason an
|
|
287
|
+
origin is carried, never a removal.
|
|
288
|
+
|
|
289
|
+
**Every previous origin the run does not re-derive is carried.** Nothing is removed and nothing
|
|
290
|
+
is moved: no origin is dropped because a checkout read its retro and no longer found the entry,
|
|
291
|
+
and no memory id moves to another project's origin. **`rebuild --prune-carried` is the only way
|
|
292
|
+
an origin leaves the index**. `--prune-carried-reason <reason>` narrows it to one reason. It
|
|
293
|
+
needs `--prune-carried`, and it offers only the reasons listed below; anything else is a usage
|
|
294
|
+
error, exit 2. A
|
|
295
|
+
carried origin keeps everything recorded on it, including its memory id, and is marked
|
|
296
|
+
`carried: <reason>`, named in the summary. It is re-derived and un-marked as soon as a checkout
|
|
297
|
+
holding it reads again. One thing is re-keyed rather than carried, on positive evidence only:
|
|
298
|
+
an origin written before provenance existed (v1.95.0 keyed a project on the smallest root
|
|
299
|
+
commit of HEAD) whose `retro#anchor`, under the same class key, this run re-derives under
|
|
300
|
+
exactly one other key, and in the checkout that re-derived it: its own commit resolves there,
|
|
301
|
+
v1.95.0 would have computed exactly its key there, and the commit descends from the root the new
|
|
302
|
+
key names (its own history, not a history the repository merged). It is the same repository, so
|
|
303
|
+
it takes the new key and its memory id follows; `add` does the same for its own checkout. It is
|
|
304
|
+
never re-keyed when it has provenance, when its project key is held by any checkout read this
|
|
305
|
+
run or by any origin of the previous index that has provenance (so the verb and the roots
|
|
306
|
+
given never decide who owns an id), when two keys could claim it, or when the copy under the new key holds a different memory
|
|
307
|
+
id (then it stays carried, and that is named). A monorepo that merged another repository's
|
|
308
|
+
history therefore never takes that repository's origin.
|
|
309
|
+
|
|
310
|
+
A re-key can only judge the projects already seen, so ownership is made recoverable: the
|
|
311
|
+
re-keyed origin records its source key as `rekeyedFrom`. The first time that project is read
|
|
312
|
+
with an origin of the same class key (by `add` or `rebuild`), or records the id itself, it
|
|
313
|
+
takes the id back; the other origin keeps its place without it, and a `reclaimed:` line says
|
|
314
|
+
so. Read means an origin with provenance that is not carried: a v1.95.0 origin under that key
|
|
315
|
+
that could not be re-keyed is not a read, so the run that re-keys never hands the id back to
|
|
316
|
+
it. Whichever verb runs first, the final owner is the project whose key the legacy store used,
|
|
317
|
+
once that project is read. If that project already holds its own id for the class, nothing
|
|
318
|
+
moves, and that is named. An origin that loses its id to the one-project or the one-class rule
|
|
319
|
+
loses `rekeyedFrom` with it, so does an origin `record --replace` gives another id, and a
|
|
320
|
+
re-key records a source only for an id it moved from the legacy origin; and no verb writes an index line the loader would refuse: `save`
|
|
321
|
+
refuses instead, the previous index intact. The reasons are:
|
|
322
|
+
|
|
323
|
+
- `checkout not read`: the checkout was not discovered, or a problem is owned by it. That
|
|
324
|
+
problem may be an unreadable retro, a refused `git worktree list`, a shallow clone, a broken
|
|
325
|
+
configuration, or a retro that changed while the rebuild ran (an `add` landing mid-run).
|
|
326
|
+
- `retro file not read`: the checkout read completely, but that file was not read, for
|
|
327
|
+
example because it lies outside the artifact root or was moved.
|
|
328
|
+
- `no checkout read holds it`: the checkout read the file, holds the same project, and the
|
|
329
|
+
entry is no longer there.
|
|
330
|
+
- `commit does not resolve`, `retro cites a commit that does not resolve` or
|
|
331
|
+
`lesson refused`: the entry is there but could not be derived. A dangling commit is routine
|
|
332
|
+
after a squash merge and a deleted branch.
|
|
333
|
+
- `no provenance`: the origin was written before provenance existed, and no re-derived origin
|
|
334
|
+
proves it is the same repository's.
|
|
335
|
+
- `checkout holds another project`: every checkout in its provenance now holds a different
|
|
336
|
+
project key, and the origin's own commit does not resolve there. A path is not an identity.
|
|
337
|
+
The same repository under another key still holds the commit, so it is not a stranger;
|
|
338
|
+
`add` applies the same rule to its own checkout's old-key origin.
|
|
339
|
+
|
|
340
|
+
The project key is the repository's own: the first-parent root of its default branch
|
|
341
|
+
(`origin/HEAD`, then `main`, then `master`, then the branch `init.defaultBranch` names).
|
|
342
|
+
Without any of them it is the main worktree's HEAD, read in the common directory, so every
|
|
343
|
+
worktree gets one answer; then this checkout's HEAD. It is computed once per git common directory. A worktree on a branch that merged an unrelated history, or a
|
|
344
|
+
checkout sitting on an orphan `gh-pages` branch, is still its project.
|
|
345
|
+
|
|
346
|
+
**Counting.** `seen`, `projects` and `status` count re-derived origins and every carried origin
|
|
347
|
+
except `checkout holds another project`. Those are kept, with their memory ids, but not counted.
|
|
348
|
+
So a temporarily unreadable root never demotes a lesson two projects hold, and a stranger at a
|
|
349
|
+
path, or one checkout under a new root commit, never makes a phantom second project. Derived
|
|
350
|
+
fields are recomputed from the origins whenever the index is read; they are never trusted from
|
|
351
|
+
disk.
|
|
352
|
+
|
|
353
|
+
**Shallow clones.** A shallow checkout is never a source. It is a problem owned by that
|
|
354
|
+
checkout, nothing is derived from it under its unstable key, and its origins are carried as
|
|
355
|
+
`checkout not read`. A shallow check that does not answer is refused the same way, and since
|
|
356
|
+
it taught nothing, it does not count as something read. `add`, `emit-observatory` and
|
|
357
|
+
`record` refuse in a shallow clone, exit 1 and write nothing, and say to deepen it with
|
|
358
|
+
`git fetch --unshallow`.
|
|
359
|
+
|
|
360
|
+
**Known limit.** A project whose root commit changes, through a re-init or an unshallow, starts
|
|
361
|
+
new store records: its old origins are carried as `checkout holds another project` with their
|
|
362
|
+
memory ids, and the new origins start without one. The operator re-attaches by running
|
|
363
|
+
`record` with the old id on the new origin, from the same checkout. That is allowed, because
|
|
364
|
+
the new origin is of the same class key and the origin holding the id is carried as `checkout
|
|
365
|
+
holds another project` with this checkout in its provenance. From any other checkout it is
|
|
366
|
+
refused.
|
|
367
|
+
|
|
368
|
+
Another checkout of the same project, even one reading the same retro path, decides nothing
|
|
369
|
+
for an origin it never held. Problems carry their owner, a checkout or none, and are never
|
|
370
|
+
matched by path prefix.
|
|
371
|
+
|
|
372
|
+
**Memory ids.** A memory id belongs to `(project, classKey)`, the store's own identity, and
|
|
373
|
+
never to a retro anchor:
|
|
374
|
+
|
|
375
|
+
- A rotated or retitled entry keeps its id on the re-derived origin. The old anchor is carried.
|
|
376
|
+
- A class key changed inside one entry does not inherit another key's id.
|
|
377
|
+
- One id is one `(project, classKey)`. `record` refuses each of the following, exits 1 and
|
|
378
|
+
writes nothing:
|
|
379
|
+
- an id that another class key holds;
|
|
380
|
+
- an id that another project's origin holds (the refusal names that project), unless that
|
|
381
|
+
origin is carried as `checkout holds another project` and this checkout is in its
|
|
382
|
+
provenance (the re-attach above);
|
|
383
|
+
- a revision older than the one held;
|
|
384
|
+
- without `--replace`, a different id over the one the origin already holds.
|
|
385
|
+
|
|
386
|
+
It also refuses an id the index cannot hold, such as `mem:`, a quote, a backslash or a
|
|
387
|
+
space: that exits 2.
|
|
388
|
+
- One id is one project's store record. Two projects may hold it only while one of them is
|
|
389
|
+
carried as `checkout holds another project`. When a rebuild would leave it on two live
|
|
390
|
+
projects (that checkout came back), the id stays on the earlier holder: the project the
|
|
391
|
+
previous index carried as `checkout holds another project` with it, else the smaller project
|
|
392
|
+
key. The other origin keeps its place without the id, and the run names it. `add` applies the
|
|
393
|
+
same rule against the index as it read it, so an add from an old clone never leaves the id on
|
|
394
|
+
both projects either.
|
|
395
|
+
- Merged duplicate lines name every id they drop. Of two copies of one origin the higher
|
|
396
|
+
revision wins; on equal revisions, the lexically smaller id.
|
|
397
|
+
- Ids compare on the part before `@`, revisions start at 1, and the highest revision wins.
|
|
398
|
+
|
|
399
|
+
`emit-observatory` and `record` both use the origin of the project that holds the id. The run
|
|
400
|
+
refuses to write if a memory id would vanish other than through `--prune-carried`.
|
|
401
|
+
|
|
402
|
+
Unreadable sources are named under `not read`, by name rather than by absolute path. Every
|
|
403
|
+
root, checkout, repository top, file given and directory is shown by its name — the index files
|
|
404
|
+
home-relative, such as `~/.sshlg-skills/lessons/index.jsonl` — in every verb's messages, on stdout
|
|
405
|
+
and stderr alike (the session's notes and a refusal too). `path` alone prints the path: that is
|
|
406
|
+
its answer. A git question about one commit in one checkout is asked once per run. Only absolute paths are replaced, and only as whole
|
|
407
|
+
path components, so a root given as `.` changes no other text. The index is split on `\n` only, and
|
|
408
|
+
lesson/1 text refuses every line separator, including U+2028, U+2029 and U+0085. So no value
|
|
409
|
+
can make a good index read as corrupt. The run
|
|
410
|
+
exits 0 whenever anything was read: a retro, a checkout discovered in full, or a shallow
|
|
411
|
+
checkout found. A readable root that holds no checkout also exits 0: the index is written,
|
|
412
|
+
and every origin is carried. The run exits 1 only when nothing could be read because every
|
|
413
|
+
root was missing or unreadable; then no index, lock directory or migration is touched. A held
|
|
414
|
+
lock or an unreadable index is its own refusal. On the default path the one-time merge of a
|
|
415
|
+
standalone index runs first, as for every verb.
|
|
416
|
+
|
|
417
|
+
So does setting a corrupt index aside:
|
|
418
|
+
- The file is kept whole as `.corrupt-<date>`.
|
|
419
|
+
- Every line that still parses is salvaged into the live index.
|
|
420
|
+
- Two lines for one class key are merged.
|
|
421
|
+
- A memory id that only a bad line held is named in the output.
|
|
422
|
+
|
|
423
|
+
Unknown is never measured zero, and never measured gone.
|
|
272
424
|
Two projects holding one class key make
|
|
273
425
|
one lesson seen twice, status `candidate`. The index path is a parameter — `--index`, then
|
|
274
426
|
`$TASK_PIPELINE_LESSONS`, then `~/.sshlg-skills/lessons/index.jsonl` **when
|
|
@@ -293,7 +445,8 @@ rule 2 (no host identifiers) for exactly two fields, and nothing else:
|
|
|
293
445
|
repository's SHA names nothing to someone without the clone; **a public repository's SHA
|
|
294
446
|
is searchable** and leads to the repository, so it stays in this field and never in text;
|
|
295
447
|
- **`origin.project`, the pseudonymous project key** — `p-` and 12 hex digits of the
|
|
296
|
-
repository's root commit
|
|
448
|
+
repository's root commit (the first-parent root of its default branch, so every worktree
|
|
449
|
+
and branch of one repository shares it) — stable across clones and never the repository's name, because
|
|
297
450
|
lessons are read into *other* projects' briefs and a repository name can be a customer's
|
|
298
451
|
or a product's. The shared store receives the same key, never a `project:<slug>` id.
|
|
299
452
|
|
|
@@ -301,7 +454,10 @@ Rules 1, 3, 4 and 5 still bind every text field, on the estate as off it: no pat
|
|
|
301
454
|
no code or value, no person's, company's, customer's or product's name, and a title that
|
|
302
455
|
states the class rather than the incident. Never, in either layer, a transcript or a URL.
|
|
303
456
|
`lessons.py` refuses what a pattern can see (paths, URLs, addresses, credential shapes, long
|
|
304
|
-
hex or base64 runs)
|
|
457
|
+
hex or base64 runs, line breaks) in every text field and in a stage id given as a string (at
|
|
458
|
+
most 32 characters, never blank), and any field lesson/1 does not name. A stage id an older
|
|
459
|
+
index holds that fails those checks is normalised when the index is read (one line, cut to 32
|
|
460
|
+
characters, or `stage`), and said, so the line and its memory id are never set aside; a name it cannot see stays the
|
|
305
461
|
writer's duty. Off the estate (`retro.publish`) the relaxation ends: the whole origin is
|
|
306
462
|
stripped.
|
|
307
463
|
|
|
@@ -384,7 +540,7 @@ release run performs, not unrelated deployments, messages or human-only actions.
|
|
|
384
540
|
|---|---|
|
|
385
541
|
| "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
542
|
| "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
|
|
543
|
+
| "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
544
|
| "Ask the operator now, it is quick" | Each question is a human round. Queue it and take another node |
|
|
389
545
|
| "The agent hit a rate limit, start it again" | Resume it with its context. Relaunching pays for the same reading twice |
|
|
390
546
|
| "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
|
|
@@ -430,7 +476,8 @@ private to this project.
|
|
|
430
476
|
**A lesson is not a publication, but the redaction list below binds it too — with one
|
|
431
477
|
relaxation.** On this machine or estate (the local index, a shared store), rule 2 is relaxed
|
|
432
478
|
for exactly two fields: the evidence commit and the pseudonymous project key (`p-` and 12 hex
|
|
433
|
-
of the root commit
|
|
479
|
+
of the root commit — the first-parent root of the default branch — the same key in the store,
|
|
480
|
+
never the repository's name or a
|
|
434
481
|
`project:<slug>` id). A private repository's SHA names nothing; a public repository's SHA is
|
|
435
482
|
searchable, so it stays in its field and never in text. Rules 1, 3, 4 and 5 still bind every
|
|
436
483
|
text field: no path at all, no code or value, no person's, company's, customer's or
|
|
@@ -18,7 +18,7 @@ Writing the approved design down so a zero-context implementer — human or suba
|
|
|
18
18
|
- Module dossier — when the run is one brick of a platform
|
|
19
19
|
- Self-review — before showing it
|
|
20
20
|
- Locked contracts are decisions — the register, not only the spec
|
|
21
|
-
- GATE (
|
|
21
|
+
- GATE (auto)
|
|
22
22
|
|
|
23
23
|
## Order of operations
|
|
24
24
|
|
|
@@ -280,13 +280,14 @@ spec section instead of restating the reasoning there.
|
|
|
280
280
|
and the next one supersedes it; an id survives. If this stage settled something and
|
|
281
281
|
no entry names it, the run has agreed to decide it again later.
|
|
282
282
|
|
|
283
|
-
## GATE (
|
|
283
|
+
## GATE (auto)
|
|
284
284
|
|
|
285
285
|
> "Spec written and committed to `<path>`. Review it and tell me if anything should
|
|
286
286
|
> change before I write the implementation plan."
|
|
287
287
|
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
Where the brief records a standing authorization (the default gate is `auto` since
|
|
289
|
+
1.98.0), an independent reader reviews the spec and the run continues; otherwise wait
|
|
290
|
+
for the operator. Changes requested → apply, re-run the self-review, and review again.
|
|
290
291
|
|
|
291
292
|
The gate is not only the operator's word: **every section carries `covers: REQ-…`
|
|
292
293
|
and every REQ in the brief appears in at least one section.** For UI tasks it
|