task-pipeline-skill 1.63.0 → 1.64.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 CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.64.0 — the exposure line reported a clean bill on ledgers it could not read
4
+
5
+ **The exposure command reported a clean bill on ledgers it could not read.** Shipped two
6
+ releases ago keyed on POSITION — `NF >= 7`, status in field 7. Run against the family
7
+ umbrella's four-column ledger it found **four rows out of 298**, because those four happen
8
+ to contain a `|` inside inline code and so crossed the field count by accident, and from
9
+ them printed:
10
+
11
+ ```
12
+ exposure: 0 unverified · never checked · 125 releases carry one
13
+ every shipped row carries a human confirmation
14
+ ```
15
+
16
+ The most reassuring sentence available, derived from punctuation, in the one tool whose
17
+ stated purpose is to stop silent greens. Its own two halves contradict each other and the
18
+ fixtures did not notice.
19
+
20
+ **The status column is now found by name, per section, and the order matters.**
21
+ `sheleg-design` carries both `Last verified` (a date and the thing watched) and `Status`
22
+ (which holds `**green**`); preferring `status` read the gate instead of the person and
23
+ reported 174 confirmations. Preference is `Human`, then `Last verified`, then
24
+ `Status`/`State`. **`Verified by`, `Confirmed` and `How it is checked` are deliberately not
25
+ status names** — five members hold shell commands under those headers, and a column of
26
+ commands read as a column of statuses is how `python3 test/validate.py` became a status
27
+ nobody could parse.
28
+
29
+ Three further corrections, each of which was hiding a real row:
30
+
31
+ - **Bold is stripped before matching.** These ledgers write `**never**`, not `never`.
32
+ - **A shrug is not a clean bill.** A status that is neither a date nor a known word gets
33
+ its own count and its own list, and no clean bill prints over it.
34
+ - **Only a `Human` column licenses the word "human".** The umbrella's ledger defines
35
+ `verified` as *a person **or** a command*, so a confirmation drawn from `Status` now says
36
+ which column it came from.
37
+
38
+ Measured across the family afterwards: `task-pipeline` **126 unverified**, `sheleg-design`
39
+ 1, and `seo-aeo-audit`, `super-ux` and `agent-sync` **dormant — no status column at all**,
40
+ which is the truth their shapes support and not a zero.
41
+
42
+ Fixtures 14 → **18**, the four new ones named for the exact silent green.
43
+
44
+ Guards: 349 → **349**. No new plant: every one of these is a behaviour of the seeded
45
+ script, which the fixtures exercise directly by running it.
46
+
3
47
  ## v1.63.0 — the row you are about to work is a claim, and it has a date
4
48
 
5
49
  **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.63.0 |
15
+ | **Version** | 1.64.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.63.0",
3
+ "version": "1.64.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.63.0",
5
+ "version": "1.64.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -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
- # A row is a table line whose first cell is a REQ id. The header and the |---| separator
52
- # are excluded by that shape rather than by counting lines, because a ledger grows
53
- # sections and a line offset stops being true on the first one added.
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
- NF >= 7 && $2 ~ /^[[:space:]]*[A-Za-z][A-Za-z0-9-]*-?[0-9]*[[:space:]]*$/ &&
56
- $2 !~ /^[[:space:]]*REQ[[:space:]]*$/ { print }
57
- ' "$LEDGER" > "$TMP/rows" 2>/dev/null
58
-
59
- # `$(grep -c … || echo 0)` prints TWO zeroes when there are no matches: grep prints its
60
- # 0 and exits 1, so the fallback runs too and the variable becomes "0\n0" — which then
61
- # fails `[ "$ROWS" -eq 0 ]` with *integer expression expected* and takes the rest of the
62
- # script with it. The fallback belongs OUTSIDE the substitution.
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
- # Column order is the template's: REQ | What | Run | Shipped in | Auto | Human | Note.
70
- # With awk -F'|' on a leading-pipe line those are $2..$8.
71
-
72
- awk -F'|' '{ h=$7; gsub(/^[ \t]+|[ \t]+$/, "", h); if (h == "never") print }' \
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'|' '{ h=$7; gsub(/^[ \t]+|[ \t]+$/, "", h);
81
- if (h ~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) print h }' \
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
- SINCE="never checked"
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 Human date this script cannot parse: $NEWEST"
179
+ echo "FAIL: exposure — $LEDGER has a confirmation date this script cannot parse: $NEWEST"
101
180
  exit 1
102
181
  fi
103
- SINCE="$DAYS days since the last human confirmation"
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
- echo " every shipped row carries a human confirmation"
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'|' '{ req=$2; ship=$5; what=$3;
146
- gsub(/^[ \t]+|[ \t]+$/, "", req);
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.