task-pipeline-skill 1.78.3 → 1.79.1
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 +114 -2
- package/README.md +3 -1
- package/SKILL-CARD.md +1 -1
- package/package.json +2 -2
- package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/task-pipeline/hooks/build-gate.sh +6 -1
- package/plugins/task-pipeline/hooks/gate-observer.sh +16 -12
- package/plugins/task-pipeline/hooks/release-gate.sh +70 -9
- package/plugins/task-pipeline/skills/evidence-docs/SKILL.md +32 -2
- package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.example.json +3 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/backlog.md +6 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/prioritisation.md +165 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +20 -0
- package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/templates/brief.md +4 -3
- package/plugins/task-pipeline/skills/task-pipeline/templates/retro.md +6 -4
- package/plugins/task-pipeline/skills/task-pipeline/templates/run.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v1.79.1 — the probe that a healthy release disarmed
|
|
4
|
+
|
|
5
|
+
The first round of `v1.79.0` failed in the release job on the
|
|
6
|
+
mentioned-vs-declared probe — not because that guard regressed, but because
|
|
7
|
+
v1.79.0 was the first release in seven to carry a run stamp, which emptied the
|
|
8
|
+
trailing set the probe's mutation lived in. A plant that inherits its
|
|
9
|
+
precondition from repository history switches itself off the moment the history
|
|
10
|
+
improves; the healthiest possible release is exactly what disarmed it.
|
|
11
|
+
|
|
12
|
+
The plant now manufactures the guard's own refusal state rather than a proxy
|
|
13
|
+
for it: it computes the trailing set exactly the way the guard does — tag
|
|
14
|
+
ranges walked against the stamp-row commits, matching the stamp-row shape,
|
|
15
|
+
because one draft matched "any line naming the sha" and deleted a standing
|
|
16
|
+
instruction's `Fired at` cell instead — un-stamps the newest stamped release
|
|
17
|
+
when nothing trails, mentions every trailing release unbolded, and then
|
|
18
|
+
un-bolds the declarations. The proxy version died twice on the way here: first
|
|
19
|
+
on the inherited precondition, then on a bold span that truthfully named a
|
|
20
|
+
stamped release. Watched both ways on both trees: the correct guard refuses, a
|
|
21
|
+
deliberately regressed guard that reads mentions as declarations accepts.
|
|
22
|
+
|
|
23
|
+
**`v1.79.0` is a dead tag.** Its round shipped nothing — the suite failed before
|
|
24
|
+
the release step, publish was skipped, npm never saw it — and the tag ruleset
|
|
25
|
+
forbids deleting the tag, so it stays pointed at a tree whose own release suite
|
|
26
|
+
refuses it. The version is burned, not reused: this release carries the whole
|
|
27
|
+
v1.79.0 payload below, plus the probe repair.
|
|
28
|
+
|
|
29
|
+
Guards: 417 → **417** — the repaired plant changed shape, not count.
|
|
30
|
+
|
|
31
|
+
## v1.79.0 — the gate that could not see, and the example that disarmed it
|
|
32
|
+
|
|
33
|
+
Four defects in the release machinery, each demonstrated live before it was
|
|
34
|
+
fixed, plus the two commits that had been sitting on `main` unreleased — one
|
|
35
|
+
version string was serving different trees through different channels.
|
|
36
|
+
|
|
37
|
+
**A blind release gate now fails closed.** `printf '' | release-gate.sh` exited
|
|
38
|
+
0 with no output, with a run in flight — an empty or unparseable hook payload
|
|
39
|
+
was a silent skip, so the gate allowed every release for as long as its stdin
|
|
40
|
+
plumbing was broken. That is the exact shape this repository's own retro names:
|
|
41
|
+
a component that never receives its input fails open and is indistinguishable
|
|
42
|
+
from approval. With a ledger present the gate now refuses (exit 2), names the
|
|
43
|
+
payload failure and the next step, and appends an `event: gate-blind` line to
|
|
44
|
+
the ledger so the blindness survives the refusal scrolling away. With no run in
|
|
45
|
+
flight it stays silent, as before — installing the plugin still changes nothing
|
|
46
|
+
in an ungoverned repository.
|
|
47
|
+
|
|
48
|
+
**A green lint stage can no longer stand in for the tests stage.** The stage
|
|
49
|
+
scan took the first stage that was declared `tests` *or* merely carried a gate
|
|
50
|
+
command — so a lint stage declared before the tests stage captured the gate,
|
|
51
|
+
and its green observation released a tag with the suite never run. The scan is
|
|
52
|
+
two-pass now: a stage the project declared as `tests` outranks any stage that
|
|
53
|
+
happens to carry a command. The observer had the same first-match break — only
|
|
54
|
+
the first command-bearing stage was ever recorded — and now records every
|
|
55
|
+
declared command under its own stage id, so the real suite's runs leave the
|
|
56
|
+
trace the release gate corroborates against.
|
|
57
|
+
|
|
58
|
+
**`npm run publish` is a project's script, not the registry act.** `"publish"
|
|
59
|
+
anywhere in npm's argument list` classified it as `npm publish`; the subcommand
|
|
60
|
+
must now be the first non-flag token. Fail-closed overmatch is still overmatch —
|
|
61
|
+
a gate that fights an ordinary script daily is a gate that gets removed.
|
|
62
|
+
|
|
63
|
+
**The build gate now fires on the flow this plugin ships.** It matched
|
|
64
|
+
`state == "build"` while the shipped `pipeline.example.json` names its build
|
|
65
|
+
stage `dev` — the canonical config, copied verbatim per its own note, disarmed
|
|
66
|
+
the hook it ships beside. The declaration path now matches `build` or `dev`,
|
|
67
|
+
mirroring the hook's own ledger fallback, and the fixture loads the example
|
|
68
|
+
unmodified so the two cannot drift apart silently again.
|
|
69
|
+
|
|
70
|
+
**The example no longer arms a loop its own note calls off, and no longer
|
|
71
|
+
teaches the retro deadlock.** `run.loop.mode` was `dynamic` under a `_run_note`
|
|
72
|
+
saying "shipped explicitly OFF" — every project copying the example verbatim
|
|
73
|
+
armed the loop. It is `off` now, with queue, arm and command kept as
|
|
74
|
+
documentation of the shape. And stage 10's check ordered "prune before you add …
|
|
75
|
+
then stamp", which is the deadlock the retro doctrine records: the
|
|
76
|
+
cold-retirement trigger reads the stamp, so a prune placed ahead of it can never
|
|
77
|
+
run on real data. The check now orders stamp → prune → entry and carries the
|
|
78
|
+
sixty-day cold clause the doctrine already had.
|
|
79
|
+
|
|
80
|
+
**Three stage-0 surfaces stopped telling the run to read the uncapped log in
|
|
81
|
+
full.** `templates/retro.md`, `references/knowledge-sources.md` and
|
|
82
|
+
`templates/brief.md` each instructed reading the retro's Recent log end to end —
|
|
83
|
+
contradicting the retro's own header, measured at 74% of the file with nothing
|
|
84
|
+
capping it. All three now say what the command surface already said: standing
|
|
85
|
+
instructions and run stamps are read in full because they are bounded by
|
|
86
|
+
construction; the Recent log and the archive are queried by the task's nouns.
|
|
87
|
+
|
|
88
|
+
**Also in this release, previously on `main` with no section here:**
|
|
89
|
+
`references/prioritisation.md` — the impact ladder that dominates (a gate, not a
|
|
90
|
+
factor: confidence × ease orders within a rung and never across one) with the
|
|
91
|
+
operator above it, and `references/stages.md`'s note on what a stage that
|
|
92
|
+
produces user-facing text owes. Both shipped commits now share a heading with
|
|
93
|
+
the version that carries them.
|
|
94
|
+
|
|
95
|
+
**And this file itself is repaired:** the `# Changelog` H1 sat below five
|
|
96
|
+
version sections with an unheaded block attached — the split half of v1.78.0's
|
|
97
|
+
own entry, orphaned when the section was inserted above the title instead of
|
|
98
|
+
below it. The H1 leads the file again and the orphan is back in its section.
|
|
99
|
+
|
|
100
|
+
Guards: 413 → **417**. Four new plants, one per gate fix — a blind gate reverted
|
|
101
|
+
to skip, the two-pass scan collapsed to first-match, the npm overmatch restored,
|
|
102
|
+
the build-state match narrowed back — each watched failing against the fixtures
|
|
103
|
+
before it shipped. `test/negatives.py`'s floor moved with the count, in this
|
|
104
|
+
change.
|
|
105
|
+
|
|
106
|
+
## v1.78.4 — the channel that sends the installs, on npm too
|
|
107
|
+
|
|
108
|
+
- The `skills.sh` badge and the canonical `homepage` reached GitHub in the previous cycle and stopped
|
|
109
|
+
there: npm serves the README and the metadata from the last **publish**, so the package
|
|
110
|
+
page still showed a badge-less README and a homepage pointing at GitHub.
|
|
111
|
+
This release carries both across.
|
|
112
|
+
- No behaviour changes. Cut because a change that lands on `main` and never publishes is a
|
|
113
|
+
change the package's own readers cannot see.
|
|
114
|
+
|
|
1
115
|
## v1.78.3 — the shared seam is explicit
|
|
2
116
|
|
|
3
117
|
Both shared validators now state `diverges: none`, completing the umbrella
|
|
@@ -37,8 +151,6 @@ with rows still open prints the attribution and drops the request. Asking to be
|
|
|
37
151
|
endorsed while something is still open is asking to be judged on manner rather
|
|
38
152
|
than on evidence.
|
|
39
153
|
|
|
40
|
-
# Changelog
|
|
41
|
-
|
|
42
154
|
**The flow diagram now reads top to bottom, and cannot trip GitHub's renderer.** It
|
|
43
155
|
was reported failing with *"Could not find a suitable point for the given
|
|
44
156
|
distance"* — the error mermaid's edge routing raises on long curved edges, which is
|
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://github.com/ssheleg/task-pipeline/actions/workflows/validate.yml)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://skills.sshlg.me/skills/task-pipeline/)
|
|
7
|
+
[](https://skills.sh/ssheleg/task-pipeline)
|
|
7
8
|
|
|
8
9
|
**Carry a substantial repository change from a locked brief through gated delivery and evidence-backed completion.**
|
|
9
10
|
|
|
@@ -192,6 +193,7 @@ until it is installed.
|
|
|
192
193
|
| run-wide reporting | [`progress.md`](plugins/task-pipeline/skills/task-pipeline/references/progress.md) — the header block at task start and the one-line iteration close: the stage rail computed from the project's own `pipeline.json`, every number borrowed from the gate that measured it |
|
|
193
194
|
| run-wide pacing | [`continuity.md`](plugins/task-pipeline/skills/task-pipeline/references/continuity.md) — the loop mode (`run.loop`, default off, never collapses a manual gate) and the context budget (fires on a harness signal, never on an estimate) |
|
|
194
195
|
| the board between runs | [`backlog.md`](plugins/task-pipeline/skills/task-pipeline/references/backlog.md) — the work-list that outlives a run: seeded at stage 0 or picked up, priority **computed** from three stated inputs rather than assigned, and the seam where the carry-over ledger's `backlog` pointer finally resolves |
|
|
196
|
+
| what to do next | [`prioritisation.md`](plugins/task-pipeline/skills/task-pipeline/references/prioritisation.md) — an impact ladder that dominates, confidence × ease inside the rung, researchers dispatched **before** scoring, and the operator's word overriding all of it |
|
|
195
197
|
| what a human confirmed | [`verification.md`](plugins/task-pipeline/skills/task-pipeline/references/verification.md) — one row per shipped REQ and the one column a machine may not fill: the date somebody **looked**, or `never`. No floor, no target — `never` is a fact, and a count driven down is a column that has started lying |
|
|
196
198
|
| how much is unconfirmed | [`exposure.md`](plugins/task-pipeline/skills/task-pipeline/references/exposure.md) — the unconfirmed count as a **vector with its components named**, never a percentage: `P(defect)` is not computable from these inputs and a single score invites a threshold, which is a target on `never`. Plus the check-list, oldest first, and the `/task-pipeline checkup` mode that prints it with no task in flight |
|
|
197
199
|
| 0 + 9 + any settled decision | [`documentation.md`](plugins/task-pipeline/skills/task-pipeline/references/documentation.md) — the inventory, registers and ids, SSOT, the Doc Loop, supersede semantics, the propagation matrix, intent vs as-built |
|
|
@@ -585,7 +587,7 @@ must say so.
|
|
|
585
587
|
### Held to Anthropic's own Skill authoring guidance
|
|
586
588
|
|
|
587
589
|
Audited against the four Agent Skills pages. Most of it already held — `name`
|
|
588
|
-
13/64 chars, `description` inside 1024, `SKILL.md` 334/500 lines, all
|
|
590
|
+
13/64 chars, `description` inside 1024, `SKILL.md` 334/500 lines, all 37 references
|
|
589
591
|
linked **directly** from `SKILL.md`, 436 KB against a 30 MB ceiling. What did not,
|
|
590
592
|
now does:
|
|
591
593
|
|
package/SKILL-CARD.md
CHANGED
|
@@ -12,7 +12,7 @@ harmless.
|
|
|
12
12
|
|---|---|
|
|
13
13
|
| **Purpose** | Runs a substantial task through ten gated delivery stages — intake grill, docs study, brainstorm, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs+registers, acceptance — refusing to advance until each gate passes |
|
|
14
14
|
| **Owner** | ssheleg ([github.com/ssheleg/task-pipeline](https://github.com/ssheleg/task-pipeline)) |
|
|
15
|
-
| **Version** | 1.
|
|
15
|
+
| **Version** | 1.79.1 |
|
|
16
16
|
| **Surface** | Claude Code (filesystem skill + plugin) and the vercel `skills` CLI. **Not** uploaded to the Skills API; custom Skills do not sync across surfaces |
|
|
17
17
|
| **Dependencies** | None required. Optional: `context7` (MCP), `figma` (MCP), super-ux, agent-sync, graphify, obsidian-wiki, and **one of two browser channels** — `playwright` (CLI or MCP) or `chrome-devtools` (MCP); either satisfies the browser step and neither is required. Every stage's doctrine ships in-repo; the one conditional requirement is super-ux for the stage-3 UX track on a user-facing task |
|
|
18
18
|
| **Evaluation status** | Suite authored, 5 categories. One recorded run, **self-observed by the author**; **zero blind runs on zero of three models** — the split, and the numbers, live in [`evals/RESULTS.md`](evals/RESULTS.md) and are computed by `evals/run.py` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-pipeline-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.79.1",
|
|
4
4
|
"description": "Full-cycle delivery pipeline for coding agents: a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine ships inside the skill — no companion plugin required. This package is the installer CLI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"task-pipeline": "bin/task-pipeline.js"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"CODE_OF_CONDUCT.md"
|
|
33
33
|
],
|
|
34
34
|
"repository": "github:ssheleg/task-pipeline",
|
|
35
|
-
"homepage": "https://
|
|
35
|
+
"homepage": "https://skills.sshlg.me/skills/task-pipeline/",
|
|
36
36
|
"bugs": "https://github.com/ssheleg/task-pipeline/issues",
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"author": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "task-pipeline",
|
|
3
3
|
"displayName": "Task Pipeline",
|
|
4
4
|
"description": "Runs a substantial task through a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine is built into the skill — no companion plugin required — with typed auto/manual gates, a frozen requirement spine that closes with evidence, a work board and a verification ledger that outlive a run, an exposure line naming what shipped unconfirmed, a progress rail computed from the project's own config, a loop guard whose review ceiling measures rather than stops, and stage-3 tracks for what a product does, how it sounds and how it looks. Two modes need no task: `checkup` (what is unverified) and `setup` (audit existing docs). Retro insights can publish upstream as issues, opt-in and redacted.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.79.1",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
8
8
|
"url": "https://x.com/sshlg93"
|
|
@@ -64,7 +64,12 @@ def build_stage_id():
|
|
|
64
64
|
try:
|
|
65
65
|
cfg = json.load(open(os.path.join(project, "pipeline.json"), encoding="utf-8"))
|
|
66
66
|
for s in cfg.get("stages") or []:
|
|
67
|
-
|
|
67
|
+
# `build` OR `dev` — mirroring this hook's own ledger fallback
|
|
68
|
+
# (`build|dev`) below. Matching only `build` meant the shipped
|
|
69
|
+
# pipeline.example.json, whose build stage is `state: "dev"`, never
|
|
70
|
+
# armed this gate: the canonical config disarmed the hook it ships
|
|
71
|
+
# beside.
|
|
72
|
+
if isinstance(s, dict) and s.get("state") in ("build", "dev"):
|
|
68
73
|
return str(s.get("id"))
|
|
69
74
|
except Exception:
|
|
70
75
|
pass
|
|
@@ -49,23 +49,25 @@ try:
|
|
|
49
49
|
except Exception:
|
|
50
50
|
raise SystemExit(0)
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
# EVERY stage that declares a command, not the first. A first-match scan meant
|
|
53
|
+
# a lint stage declared before the tests stage was the only one ever observed,
|
|
54
|
+
# so the real suite's runs left no trace for the release gate to corroborate —
|
|
55
|
+
# the same first-match break the release gate's own stage scan shipped with,
|
|
56
|
+
# fixed together (sweep the class, not the instance).
|
|
57
|
+
declared = [(str(s.get("id")), (s.get("gate") or {})["command"].strip())
|
|
58
|
+
for s in cfg.get("stages") or []
|
|
59
|
+
if isinstance(s, dict) and (s.get("gate") or {}).get("command")]
|
|
60
|
+
if not declared:
|
|
58
61
|
raise SystemExit(0)
|
|
59
62
|
|
|
60
|
-
stage_id, declared = target
|
|
61
|
-
|
|
62
63
|
# Compared on the normalised command line, not on a substring: `echo "npm test"`
|
|
63
64
|
# and `npm test --watch` are not the project's gate, and treating them as one puts
|
|
64
65
|
# a fabricated observation in the ledger the release gate trusts.
|
|
65
66
|
def norm(s):
|
|
66
67
|
return " ".join(s.split())
|
|
67
68
|
|
|
68
|
-
if norm(cmd)
|
|
69
|
+
matches = [(sid, d) for sid, d in declared if norm(cmd) == norm(d)]
|
|
70
|
+
if not matches:
|
|
69
71
|
raise SystemExit(0)
|
|
70
72
|
|
|
71
73
|
# PostToolUse fires on success; PostToolUseFailure carries the error. Both are
|
|
@@ -78,11 +80,13 @@ else:
|
|
|
78
80
|
code = 1 if failed else 0
|
|
79
81
|
|
|
80
82
|
stamp = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
81
|
-
line = 'gate: %s — command "%s" — exit %d — %s\n' % (stage_id, norm(declared), code, stamp)
|
|
82
83
|
|
|
83
84
|
# Append-only, like every other line in this file. A ledger that is rewritten is a
|
|
84
|
-
# ledger whose history can be edited to say the suite passed.
|
|
85
|
+
# ledger whose history can be edited to say the suite passed. One line per stage
|
|
86
|
+
# that declared this exact command: two stages declaring the same command is two
|
|
87
|
+
# observations, because each stage's gate is corroborated separately.
|
|
85
88
|
with open(ledger, "a", encoding="utf-8") as fh:
|
|
86
|
-
|
|
89
|
+
for stage_id, decl in matches:
|
|
90
|
+
fh.write('gate: %s — command "%s" — exit %d — %s\n' % (stage_id, norm(decl), code, stamp))
|
|
87
91
|
PY
|
|
88
92
|
exit 0
|
|
@@ -54,13 +54,36 @@ decide() {
|
|
|
54
54
|
# JSON from there. Watched failing — the gate allowed every release, silently,
|
|
55
55
|
# because `sys.stdin.read()` came back empty and an empty payload is a skip.
|
|
56
56
|
HOOK_INPUT="$input" python3 - "$ledger" "$project" <<'PY'
|
|
57
|
-
import json, shlex, sys, os, re
|
|
57
|
+
import datetime, json, shlex, sys, os, re
|
|
58
58
|
|
|
59
59
|
ledger, project = sys.argv[1], sys.argv[2]
|
|
60
60
|
raw = os.environ.get("HOOK_INPUT", "")
|
|
61
61
|
try:
|
|
62
62
|
data = json.loads(raw)
|
|
63
|
-
|
|
63
|
+
if not isinstance(data, dict):
|
|
64
|
+
raise ValueError("payload is not a JSON object")
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
# An empty or unreadable payload is the gate NOT SEEING — it says nothing
|
|
67
|
+
# about whether a release is happening. With no run in flight that is
|
|
68
|
+
# nobody's business, so stay silent. With a run in flight, a silent skip
|
|
69
|
+
# here is the fail-open this hook exists to close: watched happening —
|
|
70
|
+
# `printf '' | release-gate.sh` exited 0 with no output, and the gate
|
|
71
|
+
# allowed every release for as long as its stdin plumbing was broken. A
|
|
72
|
+
# component that never receives its input is indistinguishable from
|
|
73
|
+
# approval, so it fails CLOSED, and the blindness is written into the
|
|
74
|
+
# ledger (append-only, the lifecycle grammar) so the run can see the gate
|
|
75
|
+
# went blind even after the refusal scrolls away.
|
|
76
|
+
if os.path.exists(ledger):
|
|
77
|
+
why = ("empty payload" if not raw.strip()
|
|
78
|
+
else "unparseable payload (%s)" % type(exc).__name__)
|
|
79
|
+
try:
|
|
80
|
+
stamp = (datetime.datetime.now(datetime.timezone.utc)
|
|
81
|
+
.replace(microsecond=0).isoformat().replace("+00:00", "Z"))
|
|
82
|
+
with open(ledger, "a", encoding="utf-8") as fh:
|
|
83
|
+
fh.write("event: gate-blind — %s — %s\n" % (why, stamp))
|
|
84
|
+
except Exception:
|
|
85
|
+
pass # recording failed; the block below still stands
|
|
86
|
+
print("blind\t%s" % why); raise SystemExit(0)
|
|
64
87
|
print("skip"); raise SystemExit(0)
|
|
65
88
|
|
|
66
89
|
cmd = ((data.get("tool_input") or {}).get("command") or "")
|
|
@@ -99,8 +122,22 @@ def outward(tokens):
|
|
|
99
122
|
return "git push <tag>"
|
|
100
123
|
if t.endswith("gh") and rest[:2] == ["release", "create"]:
|
|
101
124
|
return "gh release create"
|
|
102
|
-
if t.endswith("npm")
|
|
103
|
-
|
|
125
|
+
if t.endswith("npm"):
|
|
126
|
+
# `publish` must be npm's SUBCOMMAND — the first non-flag token after
|
|
127
|
+
# npm. `"publish" in rest` matched anywhere in the argument list, so a
|
|
128
|
+
# project's own `npm run publish` script was gated as the registry
|
|
129
|
+
# act; fail-closed overmatch is still overmatch, and a gate that
|
|
130
|
+
# fights an ordinary script daily is a gate that gets removed. Known
|
|
131
|
+
# narrowness, stated rather than discovered: a flag whose value is a
|
|
132
|
+
# separate token (`npm --loglevel silent publish`) hides the
|
|
133
|
+
# subcommand from this scan; the `--flag=value` spelling is read
|
|
134
|
+
# correctly.
|
|
135
|
+
for a in rest:
|
|
136
|
+
if a.startswith("-"):
|
|
137
|
+
continue
|
|
138
|
+
if a == "publish":
|
|
139
|
+
return "npm publish"
|
|
140
|
+
break
|
|
104
141
|
return None
|
|
105
142
|
|
|
106
143
|
|
|
@@ -122,16 +159,25 @@ stage_lines = [l.strip() for l in text.splitlines() if l.strip().startswith("sta
|
|
|
122
159
|
|
|
123
160
|
|
|
124
161
|
def declared_test_stage():
|
|
125
|
-
"""The tests stage, from the project's own flow. `(id, command)` or None.
|
|
162
|
+
"""The tests stage, from the project's own flow. `(id, command)` or None.
|
|
163
|
+
|
|
164
|
+
TWO PASSES, and the order is the fix. One pass took the first stage that was
|
|
165
|
+
declared `tests` OR merely carried a gate command — so a lint stage declared
|
|
166
|
+
before the tests stage became "the tests stage", and its green observation
|
|
167
|
+
released a tag with the suite never run. A stage the project DECLARED as
|
|
168
|
+
tests outranks any stage that happens to carry a command; the command-bearing
|
|
169
|
+
fallback remains for flows that declare no `tests` state at all."""
|
|
126
170
|
try:
|
|
127
171
|
cfg = json.load(open(os.path.join(project, "pipeline.json"), encoding="utf-8"))
|
|
128
172
|
except Exception:
|
|
129
173
|
return None
|
|
130
|
-
for s in cfg.get("stages") or []
|
|
131
|
-
|
|
132
|
-
|
|
174
|
+
stages = [s for s in cfg.get("stages") or [] if isinstance(s, dict)]
|
|
175
|
+
for s in stages:
|
|
176
|
+
if s.get("state") == "tests":
|
|
177
|
+
return (str(s.get("id")), (s.get("gate") or {}).get("command"))
|
|
178
|
+
for s in stages:
|
|
133
179
|
gate = s.get("gate") or {}
|
|
134
|
-
if
|
|
180
|
+
if gate.get("command"):
|
|
135
181
|
return (str(s.get("id")), gate.get("command"))
|
|
136
182
|
return None
|
|
137
183
|
|
|
@@ -207,6 +253,21 @@ why=$(printf '%s' "$verdict" | cut -f3)
|
|
|
207
253
|
|
|
208
254
|
case "$state" in
|
|
209
255
|
skip|ok) exit 0 ;;
|
|
256
|
+
blind)
|
|
257
|
+
# act holds the reason here — the payload never carried a command to name.
|
|
258
|
+
cat >&2 <<EOF
|
|
259
|
+
task-pipeline: this project has a run in flight ($ledger) and the release gate
|
|
260
|
+
received an unreadable hook payload ($act), so it cannot tell whether this
|
|
261
|
+
command is an outward act. A gate that cannot see fails CLOSED — a blind gate
|
|
262
|
+
that waves things through is indistinguishable from approval, and that is the
|
|
263
|
+
exact shape it once shipped with.
|
|
264
|
+
|
|
265
|
+
An \`event: gate-blind\` line was appended to the ledger. Check the hook wiring
|
|
266
|
+
(stdin must carry the PreToolUse JSON), then re-run the command. To work without
|
|
267
|
+
the pipeline, remove the ledger ($ledger) or say «без пайплайна» and take the
|
|
268
|
+
route by hand.
|
|
269
|
+
EOF
|
|
270
|
+
exit 2 ;;
|
|
210
271
|
block)
|
|
211
272
|
cat >&2 <<EOF
|
|
212
273
|
task-pipeline: \`$act\` is an outward, irreversible act and the tests gate has not
|
|
@@ -62,8 +62,38 @@ way to teach an agent to route around the rule where it actually protects someth
|
|
|
62
62
|
pass through here: do it directly and **say out loud** that the claim is unbacked, rather
|
|
63
63
|
than presenting an estimate as a measurement (canon 8).
|
|
64
64
|
|
|
65
|
+
## Whom the canons serve
|
|
66
|
+
|
|
67
|
+
**The operator's intent is the point. Evidence is how it survives contact with reality —
|
|
68
|
+
not a licence to refuse it.**
|
|
69
|
+
|
|
70
|
+
Everything above exists so the operator gets the result they actually wanted and nothing
|
|
71
|
+
breaks quietly on the way. It does not exist to decide *what they are allowed to say*. A
|
|
72
|
+
skill that reads canon 8 as "I may not write an unverified claim" and refuses the work has
|
|
73
|
+
inverted its own purpose: it protected a rule and lost the person the rule was for.
|
|
74
|
+
|
|
75
|
+
The line, and it is not the same line:
|
|
76
|
+
|
|
77
|
+
| The operator may | The operator may not, and this is refusal ground |
|
|
78
|
+
|---|---|
|
|
79
|
+
| assert something not yet true — a landing page describing the product they are building, a roadmap, a pitch | make a **measurement** say something it did not: a test that passed, a benchmark, a count, a citation |
|
|
80
|
+
| ship an unbacked claim knowingly, after being told once | have an estimate **presented as** a measurement (canon 8), which is the one thing no intent authorises |
|
|
81
|
+
|
|
82
|
+
**So the sequence on an unproven claim is: say it once, label it, do the work.** Not a
|
|
83
|
+
negotiation, not a second warning, and never a silent refusal dressed as a question. If
|
|
84
|
+
the operator confirms they know, the claim ships with a marker naming it as forward-looking
|
|
85
|
+
— that marker is the whole of what this skill owes here, and the task gets done.
|
|
86
|
+
|
|
87
|
+
**Then do the harder half.** Following intent is the floor, not the service. The service is
|
|
88
|
+
making that intent *better* — more structural, more predictable, easier to maintain and
|
|
89
|
+
extend than the operator asked for — while still being the thing they asked for. An agent
|
|
90
|
+
that only obeys is a slower keyboard.
|
|
91
|
+
|
|
65
92
|
## The one test
|
|
66
93
|
|
|
67
94
|
Before a document ships, read it for the sentence that would embarrass you if someone
|
|
68
|
-
asked *"how do you know?"* —
|
|
69
|
-
|
|
95
|
+
asked *"how do you know?"* — then do one of **three** things: give that sentence its
|
|
96
|
+
address, delete it, or **mark it as an unbacked claim the operator chose to make and ship
|
|
97
|
+
it**. The third option is not a loophole; it is the reason the other two are worth
|
|
98
|
+
anything. A rule with no way to proceed under it becomes a rule people route around, and
|
|
99
|
+
then nothing carries an address.
|
|
@@ -73,7 +73,7 @@ gate stops until it is installed.
|
|
|
73
73
|
| run-wide · what the run **leaves running and leaves behind** — every gate, and stage 10 | `references/residue.md` |
|
|
74
74
|
| run-wide · what the run **prints about itself** — the rail, the iteration line | `references/progress.md` |
|
|
75
75
|
| run-wide · how a run keeps going (the loop mode + the context budget) | `references/continuity.md` |
|
|
76
|
-
| run-wide · the work-list **between** runs, and
|
|
76
|
+
| run-wide · the work-list **between** runs, and the order it comes off | `references/backlog.md` + `references/prioritisation.md` |
|
|
77
77
|
| run-wide · whether a **human** ever confirmed what shipped, and when | `references/verification.md` |
|
|
78
78
|
| run-wide · how much unconfirmed work has piled up, and what to look at first | `references/exposure.md` |
|
|
79
79
|
| any stage · Where each artifact belongs | `references/artifacts.md` |
|
|
@@ -167,14 +167,14 @@
|
|
|
167
167
|
],
|
|
168
168
|
"gate": {
|
|
169
169
|
"type": "manual",
|
|
170
|
-
"check": "Close the circle. FIRST the LADDER WALK (references/audit.md), because the REQ table can only find what was named and lost — a comparison needs two sides and an absence has one: walk each REQ bottom-up through its rungs (decision -> spec section -> contract AND its failure behavior -> plan task -> change -> executed test -> surface/docs), check the seam at each step, order findings BY SEAM not by file, and turn every absence into a new REQ row with its check BEFORE the table is written; findings belonging to a lower layer go back to that layer (spec -> stage 3, plan -> stage 4); record the pass's two counts (new findings vs findings caused by this run's own fixes) so the next pass can tell whether the axis is exhausted. THEN the coverage table: every REQ has a status (verified / partial / deferred / dropped) — none unknown; every verified carries evidence (a passing test name, file:line, a command and its output, or a scenario ID) — 'done' without evidence is downgraded to partial, not upgraded, and a green from a check nobody has watched fail against a planted defect is not evidence at all; every partial names what is missing and where it is tracked; every deferred/dropped has the operator's agreement and, for deferred, a tracker entry; no carry-over row is left unresolved and the ledger's counts are printed beside this verdict, so 'green' never reads as 'verified'; EVERY REPOSITORY IS CLOSED, THE PARENT INCLUDED — a submodule is finished only when its parent points at it, so 'git submodule status' shows no line starting with '+' and every repo is clean and pushed ('git -C <repo> status --porcelain' and 'git -C <repo> log @{u}..HEAD' both empty), because a parent records a submodule as a pointer to one commit and moving the submodule does not move the pointer: neither repo looks wrong alone and the disagreement survives every check that runs inside one; and the operator answers the closing question — here is what you asked for, here is what shipped, here is what is deferred, what is missing? — and signs off. LAST ACT, THE RETROSPECTIVE (references/retrospective.md, written to docs/evidence/retro.md — one file per project, not per run, because every gate in this flow is good at THIS run and blind across runs):
|
|
170
|
+
"check": "Close the circle. FIRST the LADDER WALK (references/audit.md), because the REQ table can only find what was named and lost — a comparison needs two sides and an absence has one: walk each REQ bottom-up through its rungs (decision -> spec section -> contract AND its failure behavior -> plan task -> change -> executed test -> surface/docs), check the seam at each step, order findings BY SEAM not by file, and turn every absence into a new REQ row with its check BEFORE the table is written; findings belonging to a lower layer go back to that layer (spec -> stage 3, plan -> stage 4); record the pass's two counts (new findings vs findings caused by this run's own fixes) so the next pass can tell whether the axis is exhausted. THEN the coverage table: every REQ has a status (verified / partial / deferred / dropped) — none unknown; every verified carries evidence (a passing test name, file:line, a command and its output, or a scenario ID) — 'done' without evidence is downgraded to partial, not upgraded, and a green from a check nobody has watched fail against a planted defect is not evidence at all; every partial names what is missing and where it is tracked; every deferred/dropped has the operator's agreement and, for deferred, a tracker entry; no carry-over row is left unresolved and the ledger's counts are printed beside this verdict, so 'green' never reads as 'verified'; EVERY REPOSITORY IS CLOSED, THE PARENT INCLUDED — a submodule is finished only when its parent points at it, so 'git submodule status' shows no line starting with '+' and every repo is clean and pushed ('git -C <repo> status --porcelain' and 'git -C <repo> log @{u}..HEAD' both empty), because a parent records a submodule as a pointer to one commit and moving the submodule does not move the pointer: neither repo looks wrong alone and the disagreement survives every check that runs inside one; and the operator answers the closing question — here is what you asked for, here is what shipped, here is what is deferred, what is missing? — and signs off. LAST ACT, THE RETROSPECTIVE (references/retrospective.md, written to docs/evidence/retro.md — one file per project, not per run, because every gate in this flow is good at THIS run and blind across runs): STAMP THE RUN FIRST (date, topic, verdict, counts) — the order is load-bearing, not style: the cold-retirement trigger reads the stamp this stage writes, so a prune placed ahead of the stamp can never run on real data; THEN PRUNE — every standing instruction checked against its retirement triggers (it became a check; every path/command/stage it names is gone; it has not fired in the last five run stamps, or in the last sixty days), the list held to its hard cap of ten (at eleven the oldest never-fired row goes — 'they all matter' is the state in which the list stopped being read), and EVERY DELETION LOGGED as one line, never silent; THEN, only if the run diverged, write the entry — symptom with evidence, the stage it surfaced at, the stage that OWNED it, the root cause ('the agent was careless' is not one), the fix by grade (mechanical check > standing instruction with its retire-when written at birth > a note that expires in two runs), and the check that catches it the first time from now on. A retro left empty after a messy run is the failure this file exists to stop, and the retro counts are printed beside this gate's verdict like the carry-over ledger's, so a list that quietly grew back is visible where it happened. EVERY LESSON CARRIES ITS COMMIT: each standing instruction has the SHA that introduced it and the SHA of the run in which it last fired, each log entry and each retirement carries one, the run stamp carries the run's own — a file:line rots at the next edit while 'git show <sha>' reconstructs the whole incident two months later — and every SHA must resolve, which the documentation gate checks with 'git rev-parse --verify'. ROTATION: entries older than the last five run stamps MOVE into docs/evidence/retro/YYYY-QN.md, which is append-only and QUERIED rather than read, so the in-force file stays short enough to be read in full and pruning costs no knowledge. AND THE GATE ITSELF IS PROVEN: every check this close-out leans on — the documentation gate included — has been seen failing once against a planted defect, with the probe recorded, and its ratchet counts are printed beside this verdict (references/gates.md). THE HAND-BACK IS WRITTEN — the request quoted as GIVEN, progress against it, what was solved with evidence, what surfaced unasked, every waiting decision ASKED here with options, and the ambiguity count computed from the four registers; zero prints as zero."
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
],
|
|
174
|
-
"_run_note": "Run-wide pacing. Shipped explicitly OFF so this example DEMONSTRATES the default rather than relying on its absence — see references/continuity.md. Set mode to 'interval' and a project stops being asked whether to run item-by-item; it still stops at every manual gate. There is no context-budget field here on purpose: the threshold is not measurable, so it is unconditional doctrine, not config.",
|
|
174
|
+
"_run_note": "Run-wide pacing. Shipped explicitly OFF so this example DEMONSTRATES the default rather than relying on its absence — see references/continuity.md. (It shipped saying this while mode was 'dynamic' — an example whose note and config disagree arms a loop in every project that copies it verbatim, which is the opposite of demonstrating the default.) The queue, arm and command keys stay filled in as documentation of the shape a project switches on; only mode arms anything. Set mode to 'interval' or 'dynamic' and a project stops being asked whether to run item-by-item; it still stops at every manual gate. There is no context-budget field here on purpose: the threshold is not measurable, so it is unconditional doctrine, not config.",
|
|
175
175
|
"run": {
|
|
176
176
|
"loop": {
|
|
177
|
-
"mode": "
|
|
177
|
+
"mode": "off",
|
|
178
178
|
"queue": "module-map",
|
|
179
179
|
"arm": "after-decomposition",
|
|
180
180
|
"command": "/loop"
|
|
@@ -122,6 +122,12 @@ Two consequences worth stating, because both are the point:
|
|
|
122
122
|
`sev` and `blast` are judgement, and they are written down *as* judgement: two small
|
|
123
123
|
integers a reader can disagree with, rather than a ranking they can only accept.
|
|
124
124
|
|
|
125
|
+
**This column ranks findings against each other.** When findings and product work compete
|
|
126
|
+
in one list — which is what happens the moment somebody asks *what next* — the order comes
|
|
127
|
+
from [`prioritisation.md`](prioritisation.md): an impact ladder that dominates, then
|
|
128
|
+
confidence × ease inside the rung. `sev × blast` is evidence for the rung there, not a
|
|
129
|
+
substitute for it.
|
|
130
|
+
|
|
125
131
|
## The shape is not fixed, so nothing may assume it
|
|
126
132
|
|
|
127
133
|
This template ships one shape. Projects change it, and they are right to — the family
|
|
@@ -50,7 +50,7 @@ makes the grill's answers *checkable* instead of merely confident.
|
|
|
50
50
|
| 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* |
|
|
51
51
|
| 5 | **Product/UX docs** | `docs/ux/` (super-ux chain), `README`, runbooks | user-facing behavior that is already specified |
|
|
52
52
|
| 6 | **Pipeline history** | `<artifacts>/specs/`, `plans/`, past `-carryover.md` | what a previous run of this pipeline decided or deferred |
|
|
53
|
-
| 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)
|
|
53
|
+
| 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 bounded by construction; the *Recent log* is **queried** by the task's nouns, like the archive — nothing caps it |
|
|
54
54
|
| 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 |
|
|
55
55
|
| 8 | **The knowledge wiki** | see below | distilled cross-project knowledge, prior sessions, why decisions were made |
|
|
56
56
|
| 9 | **Other doc repos the project names** | a docs repo URL or submodule in `CLAUDE.md`/`README`, a sibling checkout, a `docs/` monorepo package | specs, contracts and runbooks that live outside this repo |
|
|
@@ -57,6 +57,7 @@ a row pointing outside the bundle is the defect this file exists to catch.
|
|
|
57
57
|
| **The run mode** — item-by-item pacing, default off, what it never collapses | `references/continuity.md` |
|
|
58
58
|
| **The context budget** — the evidence rule and what a flush actually updates | `references/continuity.md` |
|
|
59
59
|
| **The board** — the work-list between runs, its computed priority, and the ledger seam it resolves | `references/backlog.md` |
|
|
60
|
+
| **The order** — the impact ladder, confidence and ease, research before scoring, and where the human goes | `references/prioritisation.md` |
|
|
60
61
|
| **The verification ledger** — what shipped, and whether a human ever confirmed it | `references/verification.md` |
|
|
61
62
|
| **Exposure** — the unconfirmed count as a named vector, never a probability, and the `checkup` mode | `references/exposure.md` |
|
|
62
63
|
| The retro: prune, cap, commits, archive | `references/retrospective.md` |
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Prioritisation — what to do next, and why that and not the other thing
|
|
2
|
+
|
|
3
|
+
**Load this when** the task was not named, when the next row comes off the backlog, when
|
|
4
|
+
the operator asks what to work on, or when a run is about to spend a day on something and
|
|
5
|
+
nobody has checked that it is the most valuable day available.
|
|
6
|
+
|
|
7
|
+
`backlog.md` owns the **board** — what a row is, where it lives, how it closes. This owns
|
|
8
|
+
the **order** the rows come off it.
|
|
9
|
+
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- The default, and its one exception
|
|
13
|
+
- 1. The impact ladder
|
|
14
|
+
- 2. Confidence and Ease
|
|
15
|
+
- 3. The order, and why it is not a product
|
|
16
|
+
- 4. Research before scoring, not after
|
|
17
|
+
- 5. Where the human goes
|
|
18
|
+
- 6. Two models in one family, and why both are right
|
|
19
|
+
|
|
20
|
+
## The default, and its one exception
|
|
21
|
+
|
|
22
|
+
**Every run assumes a backlog exists and that this task competes with it.** A task arriving
|
|
23
|
+
with no stated priority is not priority-free; it is unranked, which is a different and
|
|
24
|
+
worse thing.
|
|
25
|
+
|
|
26
|
+
**The exception is the operator, and it is absolute.** When they name the task, that is the
|
|
27
|
+
task. The most this file authorises is **one line** — *"`BL-14` scores higher: a checkout
|
|
28
|
+
error affecting paying users. Say the word and I switch; otherwise starting yours now"* —
|
|
29
|
+
and then starting theirs. Not a second ask, not a hedge, not a silent substitution. An
|
|
30
|
+
operator who knows what they are doing is the most reliable prioritiser in the system, and
|
|
31
|
+
an agent that argues with them twice has made itself expensive to use.
|
|
32
|
+
|
|
33
|
+
## 1. The impact ladder
|
|
34
|
+
|
|
35
|
+
Impact is the rung, not a feeling. The ladder exists so a bug and a feature can be compared
|
|
36
|
+
at all — without it, "impact" is scored per-item by whoever is looking, and every item is
|
|
37
|
+
somebody's priority.
|
|
38
|
+
|
|
39
|
+
| Rung | What it means | Test |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| **I5** | the product does not work for someone **right now** | data loss, an outage, a broken purchase, a blocked signup |
|
|
42
|
+
| **I4** | a product or business metric moves | revenue, activation, retention, traffic, conversion |
|
|
43
|
+
| **I3** | it works, and the experience degrades | crash-free rate, error rate, latency, a rough edge people hit |
|
|
44
|
+
| **I2** | our own speed | debt, tooling, tests, anything that changes how fast the next thing ships |
|
|
45
|
+
| **I1** | polish | nobody's metric moves; it is better and that is all |
|
|
46
|
+
|
|
47
|
+
Two rules that carry most of the value:
|
|
48
|
+
|
|
49
|
+
- **An unmeasured claim of I4 is I3 until it is measured.** "This will lift conversion" is
|
|
50
|
+
a hypothesis; the rung is what the evidence supports, and the gap goes in Confidence.
|
|
51
|
+
- **Blocking is not a rung, it is a multiplier on someone else.** A task nobody can proceed
|
|
52
|
+
past inherits the highest rung it blocks. State whose work it blocks, or it is not
|
|
53
|
+
blocking — it is just old.
|
|
54
|
+
|
|
55
|
+
## 2. Confidence and Ease
|
|
56
|
+
|
|
57
|
+
Both are 1–3, both are judgement, and both are written down **as** judgement — small
|
|
58
|
+
integers a reader can disagree with, in the row, next to the rung.
|
|
59
|
+
|
|
60
|
+
**Confidence** — how sure are we that doing this produces the effect claimed?
|
|
61
|
+
|
|
62
|
+
| | |
|
|
63
|
+
|---|---|
|
|
64
|
+
| **C3** | measured, or the mechanism is obvious and the change is local |
|
|
65
|
+
| **C2** | reasoned from something real — a trace, a ticket, one user |
|
|
66
|
+
| **C1** | a guess we believe. Fine to hold, not fine to hide |
|
|
67
|
+
|
|
68
|
+
**Ease** — how cheap is it, including the parts nobody counts?
|
|
69
|
+
|
|
70
|
+
| | |
|
|
71
|
+
|---|---|
|
|
72
|
+
| **E3** | within a run, one surface, reversible |
|
|
73
|
+
| **E2** | a few surfaces, or one that needs coordination |
|
|
74
|
+
| **E1** | crosses repositories, needs a migration or a credential, or cannot be undone cheaply |
|
|
75
|
+
|
|
76
|
+
Ease counts the **whole** cost: the migration, the review, the rollback, the second
|
|
77
|
+
repository. An E3 that turns out to need a credential from a person who is asleep was an E1
|
|
78
|
+
and the estimate was the defect.
|
|
79
|
+
|
|
80
|
+
## 3. The order, and why it is not a product
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
sort by I descending the ladder dominates
|
|
84
|
+
then by C × E descending 1..9 within the rung
|
|
85
|
+
then by age descending the tie-break, oldest first
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Classic ICE multiplies I × C × E, and this deliberately does not.** Multiplying lets a
|
|
89
|
+
trivial certain easy win outrank a hard uncertain critical one — `I1×C3×E3 = 9` beats
|
|
90
|
+
`I5×C1×E1 = 5` — and shipping the 9 while the 5 waits is precisely the failure this file
|
|
91
|
+
exists to prevent. It is also the failure that feels most productive from inside: a
|
|
92
|
+
stream of small completed things while the thing that matters sits.
|
|
93
|
+
|
|
94
|
+
So the ladder is a **gate**, not a factor. `C × E` orders within a rung and never across
|
|
95
|
+
one.
|
|
96
|
+
|
|
97
|
+
**What age may and may not do.** It breaks ties, and past **30 days** the row is reported
|
|
98
|
+
as one the queue has been lying about — surfaced in the run's output, not silently
|
|
99
|
+
promoted. Age never lifts a row across a rung: a polish item that has waited a year is
|
|
100
|
+
still polish, and letting it climb is how a queue ends up sorted by patience.
|
|
101
|
+
|
|
102
|
+
**`prio` in `backlog.md` is not replaced.** That formula (`sev × blast + age_bonus`) ranks
|
|
103
|
+
*findings against each other* and stays the board's own column. This file's ordering is
|
|
104
|
+
what runs when findings and product work compete in **one** list — the finding's rung comes
|
|
105
|
+
from the ladder above, and `sev × blast` is evidence for it, not a substitute.
|
|
106
|
+
|
|
107
|
+
## 4. Research before scoring, not after
|
|
108
|
+
|
|
109
|
+
**A row is scored from what it touches, never from its title.** "Fix the login redirect"
|
|
110
|
+
is E3 by its name and E1 once you find it crosses an auth library, a cookie domain and a
|
|
111
|
+
cached edge rule. Scoring first and discovering second produces an order that was wrong
|
|
112
|
+
before the first task started.
|
|
113
|
+
|
|
114
|
+
So, before an order is emitted:
|
|
115
|
+
|
|
116
|
+
1. **Fan out one researcher per candidate row** — same rules as `build.md` §4.2: fresh
|
|
117
|
+
context each, given the row and nothing else, no shared working tree because they write
|
|
118
|
+
nothing.
|
|
119
|
+
2. **Each returns four things and no opinion**: what it touches (`file:line`, services,
|
|
120
|
+
external systems), what it depends on and what depends on it, what is already
|
|
121
|
+
*measured* about the claimed effect, and what would have to be true for it to be wrong.
|
|
122
|
+
3. **Score after they return.** The rung comes from the evidence, `C` from what the
|
|
123
|
+
researcher could and could not confirm, `E` from the surfaces they found.
|
|
124
|
+
4. **Emit the ordered list once**, with the three numbers and one line of why per row.
|
|
125
|
+
|
|
126
|
+
A researcher that comes back with "seems fine, medium effort" has returned nothing. The
|
|
127
|
+
four fields are the contract; anything else is a subagent that spent context to guess.
|
|
128
|
+
|
|
129
|
+
**Do not fan out to score two rows.** The dispatch costs more than reading them.
|
|
130
|
+
|
|
131
|
+
## 5. Where the human goes
|
|
132
|
+
|
|
133
|
+
The operator is a **scarce, high-value input**, and the failure mode is spending them one
|
|
134
|
+
question at a time.
|
|
135
|
+
|
|
136
|
+
- **Ask at the start, or in a batch at a boundary. Never after each task.** If the whole
|
|
137
|
+
queue needs one decision, ask it before the queue starts.
|
|
138
|
+
- **A blocked row does not stop the run.** When something needs a person: first satisfy
|
|
139
|
+
yourself that you are actually right and it is actually blocked — most "I need input"
|
|
140
|
+
is an unread file — then write the question **into the row**, mark it `needs-operator`,
|
|
141
|
+
and take the next row that is not blocked by the same answer.
|
|
142
|
+
- **The question in the row is answerable without context.** What was tried, what is
|
|
143
|
+
needed, and what happens under each answer. A question that requires re-reading the run
|
|
144
|
+
to understand is a question that waits another day.
|
|
145
|
+
- **Batch the answers back.** The operator returns to a list of questions with their
|
|
146
|
+
consequences, not to a transcript.
|
|
147
|
+
|
|
148
|
+
The measure of this section: **how much of the queue moved while nobody was watching**, and
|
|
149
|
+
whether the things that stopped genuinely could not proceed.
|
|
150
|
+
|
|
151
|
+
## 6. Two models in one family, and why both are right
|
|
152
|
+
|
|
153
|
+
`seo-aeo-audit` triages on impact, irreversibility, uncertainty and coordination, and states
|
|
154
|
+
that **effort is recorded and never ranks**.
|
|
155
|
+
This file makes Ease rank. That is not a contradiction and the difference is the queue:
|
|
156
|
+
|
|
157
|
+
| | an **audit** | a **backlog** |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| the list is | everything that is wrong | everything that could be done |
|
|
160
|
+
| letting effort rank means | a blocker gets skipped for being hard, and the audit lies by omission | the queue self-selects toward value per day |
|
|
161
|
+
| so effort | is recorded and never ranks | ranks, inside the rung |
|
|
162
|
+
|
|
163
|
+
An audit must be complete before it is ordered; a backlog is ordered because it will never
|
|
164
|
+
be complete. Reach for the wrong one and the symptom is recognisable: an audit that
|
|
165
|
+
quietly dropped its hardest finding, or a backlog where nothing small ever ships.
|
|
@@ -66,6 +66,7 @@ never that the work was skipped quietly.
|
|
|
66
66
|
- Cross-cutting — the Doc Loop
|
|
67
67
|
- Cross-cutting — the loop guard
|
|
68
68
|
- Cross-cutting — the audit
|
|
69
|
+
- A stage that produces text a user will read
|
|
69
70
|
|
|
70
71
|
## 0 — Intake grill — MANDATORY
|
|
71
72
|
- **Freedom: medium** — the interview adapts to the answers; its two phases and their order do not ([`gates.md`](gates.md) → *Axis C*).
|
|
@@ -860,3 +861,22 @@ nothing.
|
|
|
860
861
|
- **Whatever can't be fixed now becomes a ratchet** — a named, counted set that may
|
|
861
862
|
only shrink, printed beside every gate verdict, so "green" never reads as
|
|
862
863
|
"verified".
|
|
864
|
+
|
|
865
|
+
## A stage that produces text a user will read
|
|
866
|
+
|
|
867
|
+
The framework has no opinion on which skills run where — `pipeline.json` is the contract —
|
|
868
|
+
but two facts about copy are worth stating once, because a stage author rediscovers them
|
|
869
|
+
expensively.
|
|
870
|
+
|
|
871
|
+
**Route it through the copy skill, not through the stage's own prose.** A stage that writes
|
|
872
|
+
interface strings, a landing page or a changelog entry for users is producing brand surface;
|
|
873
|
+
the registers, terminology and canonical facts that constrain it live in `docs/brand/`, and
|
|
874
|
+
a stage writing directly does not read them.
|
|
875
|
+
|
|
876
|
+
**The humanization pass is decided once, in the brand pack, not per run.**
|
|
877
|
+
`docs/brand/voice.md` carries an optional `Humanization pass:` field; absent means nobody
|
|
878
|
+
has been asked, and the copy skill asks once and records the answer. A pipeline that asks
|
|
879
|
+
every run has turned a settled decision into a prompt, and a pipeline that gates on a
|
|
880
|
+
marker count has turned a writing-quality signal into a verdict — the false positives fall
|
|
881
|
+
hardest on people writing in a second language, and nothing here fails a stage for them.
|
|
882
|
+
`npx sshlg-skills humanizers` lists what is installed.
|
|
@@ -1042,7 +1042,7 @@ def cmd_producer(graph, args):
|
|
|
1042
1042
|
def cmd_doctrine(graph, args):
|
|
1043
1043
|
"""Which doctrine this run actually read — B-061.
|
|
1044
1044
|
|
|
1045
|
-
The bundle is
|
|
1045
|
+
The bundle is 37 reference files. A run reads some subset and nothing recorded which,
|
|
1046
1046
|
so **a skipped file and a read one were indistinguishable** — the class every guard in
|
|
1047
1047
|
this repository exists to catch, left standing over the doctrine itself.
|
|
1048
1048
|
|
|
@@ -59,9 +59,10 @@ source is a recorded decision, an unquoted one is an undetected divergence.
|
|
|
59
59
|
- **Knowledge wiki:** installed / not installed
|
|
60
60
|
([obsidian-wiki](https://github.com/ar9av/obsidian-wiki); recommended, never a gate)
|
|
61
61
|
- **Retro, in force:** `docs/evidence/retro.md` — none / N standing instructions
|
|
62
|
-
(read **in full**, together with the run stamps
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
(read **in full**, together with the run stamps — both bounded by construction;
|
|
63
|
+
the recent log is **queried** by this task's nouns, never read end to end; list
|
|
64
|
+
which instructions bind this run, and stamp each as it fires **with the commit**
|
|
65
|
+
— that stamp is the only evidence behind stage 10's cold-retirement rule)
|
|
65
66
|
- **Retro archive:** `docs/evidence/retro/` — **queried** by this task's nouns;
|
|
66
67
|
what it returned: … (or `nothing`)
|
|
67
68
|
- **Code graph:** built / installed-not-built / not installed
|
|
@@ -6,10 +6,12 @@ The order is load-bearing: the cold-retirement trigger reads the stamp this stag
|
|
|
6
6
|
writes, so a prune ahead of it can never run on real data (`learned.md` rule 21).
|
|
7
7
|
Doctrine: `references/retrospective.md`.
|
|
8
8
|
|
|
9
|
-
**What stage 0 reads in full:** *Standing instructions
|
|
10
|
-
|
|
11
|
-
negotiable. The **
|
|
12
|
-
the task's nouns and never
|
|
9
|
+
**What stage 0 reads in full:** *Standing instructions* and *Run stamps* — both
|
|
10
|
+
are bounded by construction (ten rows, one line per run), which is why the cap is
|
|
11
|
+
not negotiable. The **Recent log** and the **archive**
|
|
12
|
+
(`docs/evidence/retro/YYYY-QN.md`) are *queried* by the task's nouns and never
|
|
13
|
+
read end to end: nothing caps the log's length, and an uncapped section read in
|
|
14
|
+
full is the volume that stops the ten binding rows being read.
|
|
13
15
|
|
|
14
16
|
## Standing instructions (max 10 — in force right now)
|
|
15
17
|
|
|
@@ -20,7 +20,7 @@ Run: `<topic>` · started `<YYYY-MM-DD>` · module map: `<path or "none">`
|
|
|
20
20
|
|
|
21
21
|
## `read:` — which doctrine this run actually opened
|
|
22
22
|
|
|
23
|
-
The bundle is
|
|
23
|
+
The bundle is 37 reference files and nothing recorded which of them a run read, so **a
|
|
24
24
|
skipped file and a read one were indistinguishable** — the class every guard in this
|
|
25
25
|
pipeline exists to catch, left standing over the doctrine itself.
|
|
26
26
|
|
|
@@ -48,7 +48,7 @@ same claim one level down.
|
|
|
48
48
|
|---|---|---|
|
|
49
49
|
| `unmeasured — no run ledger` | there is no ledger | nothing to read from |
|
|
50
50
|
| `unmeasured — the ledger carries no read: lines` | the hook is absent, **or** the run opened no doctrine | two opposite facts, and the ledger cannot separate them, so neither is claimed |
|
|
51
|
-
| `N of
|
|
51
|
+
| `N of 37 reference files read — unattested`, then each unread one | the hook is installed and fired | the count alone says there is a gap, not where — and `unattested` says the ledger cannot name who wrote the lines |
|
|
52
52
|
|
|
53
53
|
**It is a disclosure: no floor, no direction, never a target.** A run that needs four files
|
|
54
54
|
and reads four is not worse than one that reads thirty — and the moment the number becomes
|