task-pipeline-skill 1.63.0 → 1.65.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 +70 -0
- package/SKILL-CARD.md +1 -1
- package/package.json +1 -1
- package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/backlog.md +34 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/backlog.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/exposure.sh +128 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.65.0 — a decision is not debt
|
|
4
|
+
|
|
5
|
+
**`open` is work not done, `dropped` is an idea abandoned, and neither fits a deliberate
|
|
6
|
+
*no*.** Putting a decision in `open` has a cost that only appears later: **it accrues age
|
|
7
|
+
exactly like debt and eventually outranks real work.**
|
|
8
|
+
|
|
9
|
+
Measured on 2026-08-16. Two rows in the family umbrella recorded decisions on 2026-08-06 —
|
|
10
|
+
one waiving a UX chain in favour of fixtures, one keeping a skill an audit because another
|
|
11
|
+
router carries the design-time rule. Both sat `open`. The moment the age term started being
|
|
12
|
+
computed they reached the **top** of that board at 2.67 each, and the next run spent itself
|
|
13
|
+
re-deriving two decisions that were correct when made and are still correct.
|
|
14
|
+
|
|
15
|
+
So `waived` is a state: not counted open, no priority (`—`), and it must name what would
|
|
16
|
+
bring it back. **The `revisit:` clause is mandatory and gated** — a waiver with no trigger
|
|
17
|
+
is a row nobody will reconsider, and the trigger has to be something a later run can
|
|
18
|
+
measure. *"The command surface grows past what the fixtures describe"* is checkable; *"if
|
|
19
|
+
it becomes a problem"* is not.
|
|
20
|
+
|
|
21
|
+
The doctrine also requires the condition to be **re-derived when the row is touched**, like
|
|
22
|
+
any other claim. Both umbrella rows were re-measured before being waived — 8 CLI commands
|
|
23
|
+
with 0 uncovered by fixtures, and a router that still states the rule — because a waiver
|
|
24
|
+
resting on a condition nobody has checked is the same expired claim in a quieter voice.
|
|
25
|
+
|
|
26
|
+
Guards: 349 → **351**. Both new plants watched rejecting their defect: a waived row that
|
|
27
|
+
keeps its priority, and a waiver that names no trigger.
|
|
28
|
+
|
|
29
|
+
## v1.64.0 — the exposure line reported a clean bill on ledgers it could not read
|
|
30
|
+
|
|
31
|
+
**The exposure command reported a clean bill on ledgers it could not read.** Shipped two
|
|
32
|
+
releases ago keyed on POSITION — `NF >= 7`, status in field 7. Run against the family
|
|
33
|
+
umbrella's four-column ledger it found **four rows out of 298**, because those four happen
|
|
34
|
+
to contain a `|` inside inline code and so crossed the field count by accident, and from
|
|
35
|
+
them printed:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
exposure: 0 unverified · never checked · 125 releases carry one
|
|
39
|
+
every shipped row carries a human confirmation
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The most reassuring sentence available, derived from punctuation, in the one tool whose
|
|
43
|
+
stated purpose is to stop silent greens. Its own two halves contradict each other and the
|
|
44
|
+
fixtures did not notice.
|
|
45
|
+
|
|
46
|
+
**The status column is now found by name, per section, and the order matters.**
|
|
47
|
+
`sheleg-design` carries both `Last verified` (a date and the thing watched) and `Status`
|
|
48
|
+
(which holds `**green**`); preferring `status` read the gate instead of the person and
|
|
49
|
+
reported 174 confirmations. Preference is `Human`, then `Last verified`, then
|
|
50
|
+
`Status`/`State`. **`Verified by`, `Confirmed` and `How it is checked` are deliberately not
|
|
51
|
+
status names** — five members hold shell commands under those headers, and a column of
|
|
52
|
+
commands read as a column of statuses is how `python3 test/validate.py` became a status
|
|
53
|
+
nobody could parse.
|
|
54
|
+
|
|
55
|
+
Three further corrections, each of which was hiding a real row:
|
|
56
|
+
|
|
57
|
+
- **Bold is stripped before matching.** These ledgers write `**never**`, not `never`.
|
|
58
|
+
- **A shrug is not a clean bill.** A status that is neither a date nor a known word gets
|
|
59
|
+
its own count and its own list, and no clean bill prints over it.
|
|
60
|
+
- **Only a `Human` column licenses the word "human".** The umbrella's ledger defines
|
|
61
|
+
`verified` as *a person **or** a command*, so a confirmation drawn from `Status` now says
|
|
62
|
+
which column it came from.
|
|
63
|
+
|
|
64
|
+
Measured across the family afterwards: `task-pipeline` **126 unverified**, `sheleg-design`
|
|
65
|
+
1, and `seo-aeo-audit`, `super-ux` and `agent-sync` **dormant — no status column at all**,
|
|
66
|
+
which is the truth their shapes support and not a zero.
|
|
67
|
+
|
|
68
|
+
Fixtures 14 → **18**, the four new ones named for the exact silent green.
|
|
69
|
+
|
|
70
|
+
Guards: 349 → **349**. No new plant: every one of these is a behaviour of the seeded
|
|
71
|
+
script, which the fixtures exercise directly by running it.
|
|
72
|
+
|
|
3
73
|
## v1.63.0 — the row you are about to work is a claim, and it has a date
|
|
4
74
|
|
|
5
75
|
**A board row states the world at its `Source` date and says nothing about now.** Three
|
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.65.0 |
|
|
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.65.0",
|
|
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"
|
|
@@ -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.65.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
8
8
|
"url": "https://x.com/sshlg93"
|
|
@@ -20,6 +20,7 @@ exactly one seam, and that seam already existed as a dangling pointer.
|
|
|
20
20
|
- Seeded or picked up
|
|
21
21
|
- A row that says work exists names where it lives
|
|
22
22
|
- Priority is computed, not assigned
|
|
23
|
+
- A decision is not debt — `waived`
|
|
23
24
|
- What a loop iteration does with it
|
|
24
25
|
- Rationalizations
|
|
25
26
|
|
|
@@ -120,6 +121,39 @@ Two consequences worth stating, because both are the point:
|
|
|
120
121
|
`sev` and `blast` are judgement, and they are written down *as* judgement: two small
|
|
121
122
|
integers a reader can disagree with, rather than a ranking they can only accept.
|
|
122
123
|
|
|
124
|
+
## A decision is not debt — `waived`
|
|
125
|
+
|
|
126
|
+
`open` is work not done. `dropped` is an idea abandoned. Neither fits a deliberate **no**
|
|
127
|
+
that could be revisited, and putting one in `open` has a cost that only shows up later:
|
|
128
|
+
**a decision accrues age exactly like debt, and eventually outranks real work.**
|
|
129
|
+
|
|
130
|
+
Measured in this family on 2026-08-16. Two rows recorded deliberate decisions on
|
|
131
|
+
2026-08-06 — one waiving a UX chain in favour of fixtures, one keeping a skill an audit
|
|
132
|
+
because another router carries the design-time rule. They sat `open`. The moment the age
|
|
133
|
+
term started being computed they reached the **top** of the board at 2.67 each, and the
|
|
134
|
+
next run spent itself re-deriving two decisions that were correct when made and are still
|
|
135
|
+
correct.
|
|
136
|
+
|
|
137
|
+
So:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
| B-NNN | … | … | 1 | 7 | 3 | — | waived — revisit: <the condition, and how to measure it> |
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- **Not counted open.** It is not queued work and must not appear as any.
|
|
144
|
+
- **No priority.** The cell reads `—`; ranking a decision puts it above things to do.
|
|
145
|
+
- **`revisit:` is mandatory.** A waiver with no trigger is a row nobody will reconsider,
|
|
146
|
+
and the trigger must be something a later run can **measure** — *"the command surface
|
|
147
|
+
grows past what the fixtures describe"* is checkable; *"if it becomes a problem"* is not.
|
|
148
|
+
- **Still disclosed every run.** A waiver that becomes invisible is how a decision outlives
|
|
149
|
+
the reason for it. It is printed beside the verdict, never counted in it.
|
|
150
|
+
|
|
151
|
+
**And the revisit condition is re-derived when the row is touched**, like any other claim
|
|
152
|
+
([`knowledge-sources.md`](knowledge-sources.md)). Both of the rows above were re-measured
|
|
153
|
+
before being waived — 8 commands with 0 uncovered by fixtures, and a router that still
|
|
154
|
+
states the rule — because a waiver resting on a condition nobody has checked since 2026 is
|
|
155
|
+
the same expired claim in a quieter voice.
|
|
156
|
+
|
|
123
157
|
## What a loop iteration does with it
|
|
124
158
|
|
|
125
159
|
At the **top** of an iteration, one command: count the open rows and read the top few.
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
| B-002 | XLSX export path | `2026-07-28-export` / 5 Dev | M | 1 | 2 | 12 | 2 | open | LIN-483 |
|
|
16
16
|
| B-003 | REQ-007 bulk export | `2026-07-28-export` / 2 Brainstorm | L | 1 | 1 | 12 | 1 | dropped | operator 2026-07-28 |
|
|
17
17
|
| B-004 | Retry/backoff on the upload path | `2026-07-28-export` / 5 Dev | M | 2 | 2 | 4 | **4** | parked — feat/upload-retry | — |
|
|
18
|
+
| B-005 | Per-tenant rate limits | `2026-07-28-export` / 2 Brainstorm | L | 1 | 1 | 9 | — | waived — revisit: a tenant exceeds 1k req/min in `docs/metrics.md` | — |
|
|
18
19
|
|
|
19
20
|
## Columns
|
|
20
21
|
|
|
@@ -47,43 +47,122 @@ fi
|
|
|
47
47
|
TMP=$(mktemp -d 2>/dev/null || mktemp -d -t exposure)
|
|
48
48
|
trap 'rm -rf "$TMP"' EXIT
|
|
49
49
|
|
|
50
|
-
# ---------- the ledger's rows ----------
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
50
|
+
# ---------- the ledger's rows, resolved BY HEADER ----------
|
|
51
|
+
# The first draft keyed on position — `NF >= 7`, status in field 7 — and on a ledger with
|
|
52
|
+
# four columns it did not report a shape mismatch. It found FOUR rows out of 298, because
|
|
53
|
+
# those four happened to contain a `|` inside inline code and so crossed the field count by
|
|
54
|
+
# accident, then printed **"0 unverified · every shipped row carries a human confirmation"**.
|
|
55
|
+
# The most reassuring sentence available, derived from punctuation. In a tool whose whole
|
|
56
|
+
# purpose is to stop silent greens.
|
|
57
|
+
#
|
|
58
|
+
# So the status column is found by NAME, per section, and a ledger with no such column is
|
|
59
|
+
# DORMANT rather than clean. Three shapes exist in this family already — `Human`, `Status`,
|
|
60
|
+
# `Watched` — and a fourth will arrive without asking.
|
|
54
61
|
awk -F'|' '
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
function trim(s) { gsub(/^[ \t]+|[ \t]+$/, "", s); return s }
|
|
63
|
+
function lower(s) { return tolower(s) }
|
|
64
|
+
# A header is a row whose first cell names the id column. Everything after it, until the
|
|
65
|
+
# next header, is read with THAT header shape.
|
|
66
|
+
{
|
|
67
|
+
if (NF < 3) next
|
|
68
|
+
c1 = lower(trim($2))
|
|
69
|
+
if (c1 == "req" || c1 == "id" || c1 == "#") {
|
|
70
|
+
nf = NF; scol = 0; sname = ""
|
|
71
|
+
for (i = 2; i < NF; i++) {
|
|
72
|
+
n = lower(trim($i))
|
|
73
|
+
# `human` is the specific one and wins outright; the others are accepted so a
|
|
74
|
+
# differently-shaped ledger is read rather than declared unreadable.
|
|
75
|
+
# ORDER MATTERS, and getting it wrong reads the gate instead of the person.
|
|
76
|
+
# `sheleg-design`'"'"'s ledger carries BOTH `Last verified` (a date and what was
|
|
77
|
+
# watched) and `Status` (which holds `**green**`); preferring `status` reported
|
|
78
|
+
# 174 confirmed rows from the column that says the suite passed.
|
|
79
|
+
if (n == "human") { scol = i; sname = n; break }
|
|
80
|
+
if (n == "last verified" && sname != "human") { scol = i; sname = n }
|
|
81
|
+
if (scol == 0 && (n == "status" || n == "state")) { scol = i; sname = n }
|
|
82
|
+
# `Verified by`, `Confirmed` and `How it is checked` name the EVIDENCE, not the
|
|
83
|
+
# state — five members hold shell commands there. A column of commands read as a
|
|
84
|
+
# column of statuses is how `python3 test/validate.py` became an unreadable status.
|
|
85
|
+
}
|
|
86
|
+
next
|
|
87
|
+
}
|
|
88
|
+
if (nf == 0) next # rows before any header
|
|
89
|
+
if (trim($2) ~ /^[-: ]+$/) next # the |---| separator
|
|
90
|
+
if (NF != nf) next # a row of another shape
|
|
91
|
+
if (scol == 0) { headerless++; next } # counted, so the caller can say so
|
|
92
|
+
print trim($2) "\t" trim($(scol)) "\t" trim($3) "\t" (nf > 5 ? trim($5) : "")
|
|
93
|
+
}
|
|
94
|
+
END { print "##HEADERLESS " headerless+0 > "/dev/stderr"
|
|
95
|
+
print "##COLUMN " (sname == "" ? "-" : sname) > "/dev/stderr" }
|
|
96
|
+
' "$LEDGER" > "$TMP/rows" 2>"$TMP/meta"
|
|
97
|
+
|
|
63
98
|
ROWS=$(grep -c '' "$TMP/rows" 2>/dev/null) || ROWS=0
|
|
99
|
+
HEADERLESS=$(sed -n 's/^##HEADERLESS //p' "$TMP/meta" 2>/dev/null)
|
|
100
|
+
HEADERLESS=${HEADERLESS:-0}
|
|
101
|
+
SCOL=$(sed -n 's/^##COLUMN //p' "$TMP/meta" 2>/dev/null)
|
|
102
|
+
SCOL=${SCOL:--}
|
|
103
|
+
|
|
64
104
|
if [ "$ROWS" -eq 0 ]; then
|
|
105
|
+
if [ "$HEADERLESS" -gt 0 ]; then
|
|
106
|
+
# THE CASE THAT WAS SILENTLY GREEN. Rows exist and none can be read, which is the one
|
|
107
|
+
# answer that must never be spelled "0 unverified".
|
|
108
|
+
echo "dormant: exposure — $LEDGER has $HEADERLESS row(s) but no column named Human,"
|
|
109
|
+
echo " Last verified, Status or State. Columns like Verified by or How it is"
|
|
110
|
+
echo " checked name the EVIDENCE, not the state, and are deliberately not read."
|
|
111
|
+
echo " Nothing here can say whether anybody looked, so this reports no number."
|
|
112
|
+
exit 0
|
|
113
|
+
fi
|
|
65
114
|
echo "dormant: exposure — $LEDGER has no REQ rows yet"
|
|
66
115
|
exit 0
|
|
67
116
|
fi
|
|
117
|
+
if [ "$HEADERLESS" -gt 0 ]; then
|
|
118
|
+
echo "note: $HEADERLESS row(s) sit under a header with no status column and are not counted"
|
|
119
|
+
fi
|
|
68
120
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
121
|
+
# Unconfirmed is a small closed vocabulary, and the empty cell is in it: a blank status is
|
|
122
|
+
# the commonest way a row means "nobody has said", and reading it as confirmed inverts the
|
|
123
|
+
# file's purpose.
|
|
124
|
+
# NORMALISED first: these ledgers write `**never**`, not `never`. Bold hid three real
|
|
125
|
+
# unverified rows in one member and seven in another, and both reported zero.
|
|
126
|
+
awk -F'\t' '{ s=tolower($2); gsub(/[*`]/, "", s); gsub(/^[ \t]+|[ \t]+$/, "", s);
|
|
127
|
+
if (s=="never" || s=="" || s=="-" || s=="no" || s=="unverified" ||
|
|
128
|
+
s=="pending" || s=="none") print }' \
|
|
73
129
|
"$TMP/rows" > "$TMP/unverified"
|
|
74
130
|
UNVERIFIED=$(grep -c '' "$TMP/unverified" 2>/dev/null) || UNVERIFIED=0
|
|
75
131
|
|
|
132
|
+
# A THIRD BUCKET, because two are not enough. A status that is neither a known
|
|
133
|
+
# unconfirmed word nor a date is UNREADABLE — `last tuesday`, `soon`, `ask Ben`. Counting
|
|
134
|
+
# it as confirmed is how a shrug becomes a clean bill, which the fixture for this line
|
|
135
|
+
# caught the moment it was written.
|
|
136
|
+
awk -F'\t' '{ s=tolower($2); gsub(/[*`]/, "", s); gsub(/^[ \t]+|[ \t]+$/, "", s);
|
|
137
|
+
if (s=="never" || s=="" || s=="-" || s=="no" || s=="unverified" ||
|
|
138
|
+
s=="pending" || s=="none") next;
|
|
139
|
+
if ($2 ~ /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/) next;
|
|
140
|
+
if (s=="pass" || s=="yes" || s=="verified" || s=="ok" || s=="observed" ||
|
|
141
|
+
s=="confirmed" || s=="green") next;
|
|
142
|
+
print }' "$TMP/rows" > "$TMP/unreadable"
|
|
143
|
+
UNREADABLE=$(grep -c '' "$TMP/unreadable" 2>/dev/null) || UNREADABLE=0
|
|
144
|
+
|
|
76
145
|
# ---------- since: a date, or the literal `never checked` ----------
|
|
77
146
|
# ZERO WOULD BE A LIE IN THE DANGEROUS DIRECTION. "0 days" reads as *checked today*,
|
|
78
147
|
# which is the exact opposite of "nobody has ever looked", so the two cases print
|
|
79
148
|
# different words rather than different numbers.
|
|
80
|
-
awk -F'
|
|
81
|
-
|
|
149
|
+
awk -F'\t' '{ if ($2 ~ /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/) {
|
|
150
|
+
match($2, /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/);
|
|
151
|
+
print substr($2, RSTART, RLENGTH) } }' \
|
|
82
152
|
"$TMP/rows" | sort > "$TMP/dates"
|
|
83
153
|
NEWEST=$(tail -1 "$TMP/dates" 2>/dev/null)
|
|
84
154
|
|
|
85
155
|
if [ -z "$NEWEST" ]; then
|
|
86
|
-
|
|
156
|
+
# THREE FACTS, NOT TWO. "nobody has ever looked", "this shape records no dates" and
|
|
157
|
+
# "somebody wrote something nobody can read" are different, and the first draft printed
|
|
158
|
+
# the middle one for the last — a clean bill two lines under a shrug.
|
|
159
|
+
if [ "$UNREADABLE" -gt 0 ]; then
|
|
160
|
+
SINCE="never checked ($UNREADABLE status(es) this script cannot read)"
|
|
161
|
+
elif [ "$UNVERIFIED" -eq 0 ]; then
|
|
162
|
+
SINCE="no confirmation dates recorded"
|
|
163
|
+
else
|
|
164
|
+
SINCE="never checked"
|
|
165
|
+
fi
|
|
87
166
|
else
|
|
88
167
|
# Portable day arithmetic: BSD date and GNU date disagree on every flag that matters,
|
|
89
168
|
# so ask python, which both platforms have and which will not silently return today.
|
|
@@ -97,10 +176,16 @@ print((datetime.date.today() - d).days)
|
|
|
97
176
|
PY
|
|
98
177
|
)
|
|
99
178
|
if [ -z "${DAYS:-}" ]; then
|
|
100
|
-
echo "FAIL: exposure — $LEDGER has a
|
|
179
|
+
echo "FAIL: exposure — $LEDGER has a confirmation date this script cannot parse: $NEWEST"
|
|
101
180
|
exit 1
|
|
102
181
|
fi
|
|
103
|
-
|
|
182
|
+
# The same discipline as the clean bill below: only a `Human` column licenses the word
|
|
183
|
+
# "human". Any other column records that something looked, not that somebody did.
|
|
184
|
+
if [ "$SCOL" = "human" ]; then
|
|
185
|
+
SINCE="$DAYS days since the last human confirmation"
|
|
186
|
+
else
|
|
187
|
+
SINCE="$DAYS days since the last \`$SCOL\` entry"
|
|
188
|
+
fi
|
|
104
189
|
fi
|
|
105
190
|
|
|
106
191
|
# ---------- releases: what has gone out on top of it ----------
|
|
@@ -134,18 +219,35 @@ esac
|
|
|
134
219
|
echo "$LINE"
|
|
135
220
|
|
|
136
221
|
# ---------- the check-list: a number without it says there is a problem, not where ----------
|
|
222
|
+
# A SHRUG IS NOT A CLEAN BILL. A status that is neither a date nor a known word means
|
|
223
|
+
# somebody wrote something and nobody can act on it; printing "every shipped row is
|
|
224
|
+
# confirmed" over that is the failure this whole file exists to prevent.
|
|
225
|
+
if [ "$UNREADABLE" -gt 0 ]; then
|
|
226
|
+
echo " $UNREADABLE row(s) carry a status that is neither a date nor a known word,"
|
|
227
|
+
echo " so no clean bill is printed over them:"
|
|
228
|
+
cut -f1,2 "$TMP/unreadable" | head -"$LIST_MAX" | sed 's/^/ /'
|
|
229
|
+
if [ "$UNVERIFIED" -eq 0 ]; then exit 0; fi
|
|
230
|
+
fi
|
|
231
|
+
|
|
137
232
|
if [ "$UNVERIFIED" -eq 0 ]; then
|
|
138
|
-
|
|
233
|
+
# NAME THE COLUMN. `Human` means a person; `Status`, `State` and `Watched` do not
|
|
234
|
+
# distinguish a person from a command — the umbrella's own ledger says `verified` means
|
|
235
|
+
# "a person **or a command** looked", so claiming a human confirmation from that column
|
|
236
|
+
# is a stronger sentence than the data supports.
|
|
237
|
+
if [ "$SCOL" = "human" ]; then
|
|
238
|
+
echo " every shipped row carries a human confirmation"
|
|
239
|
+
else
|
|
240
|
+
echo " every shipped row is confirmed in its \`$SCOL\` column — which does not"
|
|
241
|
+
echo " separate a person from a command; only a \`Human\` column does that"
|
|
242
|
+
fi
|
|
139
243
|
exit 0
|
|
140
244
|
fi
|
|
141
245
|
|
|
142
246
|
# Oldest first by `Shipped in`, which is the only ordering this repository can defend.
|
|
143
247
|
# Version sort, so v1.9.0 precedes v1.10.0 — a lexical sort puts the newer one first and
|
|
144
248
|
# hands the operator the wrong end of the list.
|
|
145
|
-
awk -F'
|
|
146
|
-
|
|
147
|
-
gsub(/^[ \t]+|[ \t]+$/, "", ship);
|
|
148
|
-
gsub(/^[ \t]+|[ \t]+$/, "", what);
|
|
249
|
+
awk -F'\t' '{ req=$1; what=$3; ship=$4;
|
|
250
|
+
if (ship == "") ship = "—";
|
|
149
251
|
# Truncated on a WORD boundary, never mid-character: awk counts bytes
|
|
150
252
|
# here, so `substr(what, 1, 56)` cut a Cyrillic letter in half and printed
|
|
151
253
|
# a replacement glyph. Appending whole words cannot land inside one.
|