Read docs/test-readiness/test-readiness-plan.md and queue its flat T-NNN task list as task-store tasks, one task per row, with dependency edges (predecessor/fan-out) and per-task HITL classification. Requires test-roadmap to have run first. Replaces the former GitHub-issue publish skill's dashboard with a task-store queue.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add app-vitals/shipwright --skill test-fix --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Test Fix?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/app-vitals-test-fix)More formats (shields.io, HTML) on the badges page.
---
name: test-fix
description: Read docs/test-readiness/test-readiness-plan.md and queue its flat T-NNN task list as task-store tasks, one task per row, with dependency edges (predecessor/fan-out) and per-task HITL classification. Requires test-roadmap to have run first. Replaces the former GitHub-issue publish skill's dashboard with a task-store queue.
---
# Test Fix
Read the latest `docs/test-readiness/test-readiness-plan.md` and queue its flat task list as
task-store tasks — one task per `T-NNN` row. Dependency edges (predecessor links) are written
as task-store `dependencies` arrays, so task-store's own `ready:true` query computes readiness
directly. Findings are never turned into direct PRs;
they always become task-store tasks that `dev-task` (or a human, for HITL tasks) picks up
later.
This is the **Phase 5** replacement for the former GitHub-issue publish skill.
**Prerequisites:** Run `/test-roadmap` first to produce `test-readiness-plan.md`.
> **Task store setup:** This skill pushes tasks to the Shipwright task store. If
> `SHIPWRIGHT_TASK_STORE_URL` or `SHIPWRIGHT_TASK_STORE_TOKEN` is missing, invoke
> `/shipwright:task-store` for setup instructions.
---
## Setup: Parse Arguments
Before starting, check for flags:
- `--dry-run` — print what would be queued (including hitl classification) without querying
the task store for dedup and without writing anything.
> **Note:** Queueing is the only mode for the regular flow. There is no PR mode and no
> `--queue` flag — every regular run queues tasks. `--dry-run` shows a preview and stops
> without touching the task store.
---
## Step 1: Verify test-readiness-plan.md Exists
1. Look for `docs/test-readiness/test-readiness-plan.md` in the project root.
2. If it does not exist, print:
```
No docs/test-readiness/test-readiness-plan.md found. Run /test-roadmap first to generate it.
```
Then stop.
3. Read the plan.
---
## Step 2: Parse the Task List
1. Parse the `## 5. Task list` section's flat rows, format `T-NNN | M# | files | layer |
bucket | outcome | verify` (per `test-roadmap/SKILL.md` section 5).
2. Parse any **audit-decision-row** tables (`## 5. Task list` → "Audit task decision rows",
also rendered per-task in `issue.md.tmpl`'s "Audit decisions" section) attached to tasks
marked `[audit: N items]` in the expected-outcome column. Keep each table's rows verbatim —
they're needed for Step 5's description field and for the M5 HITL rule (rule c below).
3. Parse `depends_on` / predecessor links: any explicit `depends_on:` annotation on a row, and
the Repo Configuration tasks' pairing (`depends_on` the paired workflow task, per
`repo-config/SKILL.md`'s pairing rule).
4. If the task list is empty or malformed (rows don't match the `T-NNN | M# | ...` shape),
print:
```
No parseable T-NNN task rows found in test-readiness-plan.md. Nothing to queue.
```
Then stop.
5. Run each surviving `T-NNN` row through the pre-filing verification checklist —
`references/pre-filing-verification.md` (relative to the plugin root) — before it proceeds
any further to Step 3 (repo detection), Step 4 (dedup), or Step 5 (task creation). This
re-verifies the row against the current repo state (`test-readiness-plan.md` is a snapshot
that may already be stale by the time this skill runs) and catches task ID / branch
collisions early. Treat `references/pre-filing-verification.md` as canonical for how to
apply the checklist. Per its four checks:
- Drop rows whose target files (the `files` column, plus any fixture/test paths the row
implies) no longer exist as described, or whose testing gap has already been closed by a
later commit on main since `test-readiness-plan.md` was generated (Checklist Items 1–2) —
do not build a task for them. Skip silently, matching this skill's own convention (there
is no separate ledger section here beyond the dedup/summary output Step 4/Step 7 already
produce).
- **Route rows whose target-file or fixture existence can't be confirmed by a literal
check to HITL, rather than assuming they're safe to drop or safe to file clean**
(Checklist Item 3). This is the exact failure mode that caused task T-130's false
negative: a grep-based existence check missed a fixture that was referenced via
`path.join()` construction rather than a literal import or string match, so the row was
treated as a confirmed pass when the fixture's real status couldn't actually be
determined that way. When a row's target file or fixture is referenced indirectly —
constructed paths, dynamic dispatch, reflection-based lookups, or anything else a literal
grep/import check can't definitively resolve — do not treat it as either a confirmed pass
(file exists, skip) or a confirmed absence (file missing, safe to file); route it to HITL
instead. This feeds directly into the `hitl` computation in Step 5.2 as an explicit path
to `hitl: true`.
- Checklist Item 4 (task ID / branch collisions) is satisfied by this skill's own Step 3 /
Step 4 dedup check (repo-slug-scoped task IDs, dedup query); no separate action is needed
here beyond noting the overlap.
This runs once, here in Step 2, so both the `--dry-run` preview (Step 6) and the real queue
path (Step 7) operate on the same already-verified row set.
6. **Anti-gaming check — reject an uncited line-coverage row.** A `bucket`/`layer` row whose
only justification is raising `line_coverage_pct` (a **line-coverage** task, per
`test-roadmap/SKILL.md`'s sequencing rule in its own Process step 5) must cite, in its
`outcome` column or paired description text, the specific feature or genuine uncovered
error path it maps to — e.g. a named feature from `test-inventory.md`, or a specific
uncovered error path/branch (an untested `catch` block, an untested validation branch). This
is a structural check only: it confirms a citation is present, it does not verify the
citation is true. If a line-coverage row has no such citation — assertion-free filler added
just to move the aggregate line-coverage number, e.g. testing a trivial getter with no real
behavior at stake — **reject it: skip filing the row**, the same silent-skip convention used
for the Checklist Items 1–2 drops above. Print: `Skipping {T-NNN} — line-coverage task has no
cited feature/error-path mapping (possible coverage gaming)`. A line-coverage row that cites
a genuine uncovered error path (e.g. "closes the untested retry-exhaustion catch block in
`paymentsGatewayClient.ts`") is not gaming and files normally — this check targets
citation-less filler, not every line-coverage-bucket row.
---
## Step 3: Detect Target Repo
Detect the current repo from git: run `git remote get-url origin` and strip the
`https://github.com/` (or `git@github.com:`, stripping the `.git` suffix) prefix to get the
`org/repo` value — e.g. `app-vitals/shipwright`. This is the `repo` value used both to scope
the dedup queries in Step 4 and, unchanged, as the task JSON's `repo` field in Step 5 —
compute it once here and reuse it there. (Same derivation as entropy-fix's Step 6q.1.)
Derive `repo-slug` from it too: the last path segment, lowercased — e.g.
`app-vitals/shipwright` → `shipwright`. This slug is appended to every task ID (Step 5) so
the same `T-NNN` scanned in two different repos never collides — the same class of bug
previously fixed for entropy-fix's task IDs.
---
## Step 4: Dedup Check
Skip this step entirely if `--dry-run` was passed (Step 6 handles the dry-run preview
instead — no dedup query is made).
Run (URL-encode the detected repo, e.g. `app-vitals%2Fshipwright`). `GET /tasks` defaults to
`limit=50` and truncates silently unless the caller raises it — pass an explicit high
`limit=1000` on both queries:
```bash
curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
"$SHIPWRIGHT_TASK_STORE_URL/tasks?status=pending&repo={url-encoded-repo}&limit=1000" | jq .
curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
"$SHIPWRIGHT_TASK_STORE_URL/tasks?status=in_progress&repo={url-encoded-repo}&limit=1000" | jq .
```
The `&repo=` filter scopes dedup to tasks for the repo currently being scanned — without it,
a T-NNN active for one repo would incorrectly block or interfere with dedup for a different
repo.
**Pagination guard — do not silently proceed with a partial result set.** `limit=1000`
covers the common case, but a repo can still have more than 1000 active tasks. After each
call, check the response's `total` field against the number of tasks actually returned
(`.tasks | length`): if `total <= tasks.length`, the page is complete. If `total >
tasks.length`, the first `limit=1000` page did not cover every active task — re-run the same
query with `&offset=1000` (then `&offset=2000`, and so on) and merge the `.tasks` arrays
until `tasks.length` (summed across pages) `>= total`. Only treat the dedup query as complete
once every page has been fetched this way; never build the "already active" set (below) from
a page that fails this check.
Parse both `.tasks` arrays. From the combined results, collect tasks where:
- `source == "test-fix"`, AND
- `title` starts with `"Test readiness:"`
Extract the already-active `T-NNN` IDs from the task `id` field (format:
`test-{t-nnn}-{repo-slug}` — e.g. `test-t-104-shipwright` maps back to `T-104`). Build a set
of "already active" T-NNN IDs.
For each row from Step 2: if its `T-NNN` is in the "already active" set, skip it. Print:
`Skipping {T-NNN} — task already active`.
### 4.1 Fuzzy Near-Duplicate Check
The exact-ID check above only catches literal `T-NNN` reuse, scoped to `source ==
"test-fix"` tasks whose title starts with `"Test readiness:"`. It cannot catch a
differently-numbered task that semantically duplicates existing active work — e.g. a
finding that gets re-scoped and re-queued under a new `T-NNN` after the original active task
was created by a different source or with a different title convention. That gap is exactly
what let one duplicate issue slip past dedup once the original had no task-store footprint
matching the narrow `source`/prefix filter.
To close it, run a second, wider comparison — no new API calls, no `--repo=`-scope change:
reuse the **same combined `.tasks` array** already fetched above (the `status=pending` and
`status=in_progress` results, both `&repo=`-scoped), but this time **do not** filter by
`source == "test-fix"` or by the `"Test readiness:"` title prefix. Consider literally every
active (pending + in_progress) task title for the repo, regardless of source or naming
convention.
For each row surviving the exact-ID check (i.e. not already skipped), compute its would-be
title exactly as Step 5 will build it — `"Test readiness: {outcome} (T-NNN)"` — and compare it
against every title in that unfiltered active-task set using **normalized word-overlap
(Jaccard similarity)**:
1. Lowercase both titles and strip punctuation.
2. Split each into a set of words (tokens).
3. `similarity = |intersection| / |union|` of the two word sets.
Flag any pair where `similarity >= 0.6`. This threshold is a deliberate middle ground:
- **Too strict (e.g. 0.9)** would miss genuine rewordings — the entire point of this check is
to catch titles that got re-scoped or rephrased under a different `T-NNN`, which by
definition won't be a near-exact string match.
- **Too loose (e.g. 0.3)** would flag unrelated tasks that merely share generic vocabulary
("test", "fix", "add"), producing enough noise that humans learn to ignore the flag.
- **0.6** is biased toward not missing a real duplicate, which is the safer failure mode —
consistent with the "flag, don't skip" philosophy below.
**Worked example.** Suppose an existing active task (any source) is titled:
```
Test readiness: add integration coverage for auth middleware (T-041)
```
And a new candidate row from Step 2 would build the title:
```
Test readiness: add integration coverage for the auth middleware (T-058)
```
Lowercase, strip punctuation, and tokenize (dropping the parenthetical `T-NNN`, which is
excluded from the token comparison since it's expected to differ):
- Title A words: `{test, readiness, add, integration, coverage, for, auth, middleware}` (8)
- Title B words: `{test, readiness, add, integration, coverage, for, the, auth, middleware}` (9)
- Intersection: `{test, readiness, add, integration, coverage, for, auth, middleware}` (8)
- Union: `{test, readiness, add, integration, coverage, for, the, auth, middleware}` (9)
- `similarity = 8 / 9 ≈ 0.89`
`0.89 >= 0.6`, so this pair is flagged — even though `T-041` and `T-058` are different IDs and
the exact-ID check alone would have missed this duplicate entirely.
**This never causes a skip.** A fuzzy match is not removed from the Step 5 build list and is
never excluded from the Step 7.1 bulk POST — it still gets created. Fuzzy-match false
positives are common (shared vocabulary, generic verbs like "fix"/"add"/"update"), and
silently dropping genuinely new work because of a coincidental title overlap is a worse
failure mode than one extra line a human has to glance at. This mirrors the same philosophy
already applied to the `hitl` field (Step 5.2): no auto-decision on a fuzzy or judgment-based
signal where the cost of a wrong auto-decision is asymmetric — surface it for a human instead.
For each flagged pair, print:
`Flagging {T-NNN} — {similarity}% title-similar to active task {other-id}: "{other-title}" — not skipped, review after queueing`
---
## Step 4.5: Cross-Check test-readiness-decisions.md (Once More Before Filing Any Task)
Even though a matching decision may already have shaped an earlier `test-inventory` pass's
classification, the registry can change between when `test-readiness-plan.md` was generated
and when this skill runs — a human may have reviewed a prior ambiguous item and added a new
decision entry since. Never trust the plan's row as still current on this point; re-check live.
This mirrors `consolidation-fix/SKILL.md`'s Step 3 ("Cross-Check consolidation-decisions.md
(Once More Before Queueing)") exactly, applied to this skill's registry instead.
This check runs against **every** row surviving Step 4 — not just rows headed toward a
rule-(d)/HITL classification in Step 5.2. A registry decision can resolve a row that would
otherwise be filed as a perfectly ordinary autonomous task just as easily as it can resolve an
ambiguous one; the check is unconditional, upstream of the `hitl` computation.
1. Check for `.claude/shipwright/test-readiness-decisions.md` in the project root.
2. **If it does not exist**, treat this as "no decisions configured" — the same graceful no-op
`consolidation-fix`'s Step 3 uses for a missing `consolidation-decisions.md`. Print: "No
test-readiness-decisions.md found — no decisions configured." and continue to Step 5 with
every surviving row unaffected. This is expected and not an error.
3. **If it exists**, load and parse it the same generic, defensive way described in
`test-inventory/SKILL.md`'s Step 0: read each `###` entry for its **Item**, **Decision**,
**Rationale**, and **Revisit** fields where present, without hardcoding assumptions about the
registry's exact heading/field layout beyond those four fields — skip any entry you can't
confidently interpret rather than failing the whole load.
4. For each row surviving Step 4, compare its target file(s)/outcome against every decision
entry's **Item** field. This is a judgment-based match, not exact string equality — read
both descriptions and decide whether they describe the same ambiguous item, the same
comparison style `consolidation-fix`'s Step 3 uses for its own registry.
- If a decision entry clearly matches and its **Revisit** condition has **not** been met,
**skip filing this row** — do not build a task-store task for it in Step 5. Print:
`Skipping {T-NNN} — resolved per test-readiness-decisions.md ("{entry name}")`. Record the
row as **suppressed** for the Step 7.2 summary.
- If a decision entry matches but its **Revisit** condition has been met (a stated date has
passed, a stated occurrence/call-site threshold has now been exceeded, or a stated
triggering event has occurred), the row is no longer resolved by that entry — continue
filing it normally through Step 5.
- If no decision entry matches, continue filing the row normally through Step 5.
5. If a registry entry's recorded layer/convention no longer applies to the current codebase
(e.g. the call sites it names have since diverged, or the test layer it names has been
removed from the repo's conventions), treat the match as if the revisit condition had been
met — do not apply a stale resolution; log this explicitly (`{T-NNN} — registry entry
"{entry name}" no longer applies, filing normally`) and continue filing the row normally.
This is prompt-content instruction, not executable logic: there is no crash path here, only
"fall through to filing normally and say why."
6. Every row skipped in this step is excluded from Step 5's build list and from the Step 7.1
bulk POST — same effect as a Step 4 dedup skip, different reason (resolved, not
already-active).
---
## Step 5: Build Task JSON
Skip this entire step if `--dry-run` was passed — Step 6 performs the equivalent
classification in-memory for the preview instead, without writing anything.
For each remaining `T-NNN` row (not skipped in Step 4 or suppressed in Step 4.5), build a task
object. Reuse the `repo`
and `repo-slug` values detected in Step 3 — do not re-derive them:
```json
{
"id": "test-{t-nnn}-{repo-slug}",
"title": "Test readiness: {outcome} (T-NNN)",
"source": "test-fix",
"repo": "<repo, as detected in Step 3>",
"branch": "feat/test-{t-nnn-lower}-{slug}",
"layer": "<test-layer from the row: unit | integration | smoke | e2e | infra>",
"priority": "<critical | high | medium — see 5.1>",
"type": "test-readiness",
"status": "pending",
"hitl": <true | false — computed per 5.2>,
"dependencies": ["test-{predecessor-t-nnn}-{repo-slug}", "..."],
"acceptanceCriteria": ["...", "Verification command `{verify}` passes"],
"description": "<see 5.3>"
}
```
`{t-nnn}`: lowercase the row's task ID (e.g. `T-104` → `t-104`). `{t-nnn-lower}`: same value,
used in the branch name.
`slug` (for the branch name): lowercase, hyphens, max 5 words from the row's outcome.
**`layer` field note:** this is the row's **test-layer** value (`unit` / `integration` /
`smoke` / `e2e` / `infra`) — deliberately *not* the API/Frontend/Database/Shared/
Background/CLI convention `entropy-fix`/`error-fix`/`plan-session` use for their `layer`
field. Task-store's `layer` column is free text (`layer String?` in
`task-store/prisma/schema.prisma` — no enum constraint), so repurposing it here for
test-layer is safe and is more useful to `dev-task` and downstream tooling than a generic
bucket would be. Do not invent a second field for this — `layer` carries it.
### 5.1 Compute the `priority` Field
`priority` is the row's criticality tier, derived primarily from milestone:
- **M2 rows** (Critical-path coverage) → `critical`
- **M4 rows** (High-tier coverage) → `high`
- **M1 / M3 / M5 rows** → `medium`, **unless** the row carries its own explicit tier
annotation (e.g. an inline `criticality: high` note or an audit-decision-row's own
criterion naming a tier) — in that case use the row's own annotation instead.
This milestone-to-tier default for M1/M3/M5 is a deliberate, documented default — M1
(infrastructure), M3 (canary plumbing), and M5 (cleanup) tasks are usually not
criticality-ranked the way M2/M4 inventory items are, so `medium` is the safe default rather
than leaving the field ambiguous. Criticality tiers themselves come from
`test-inventory/SKILL.md` Step 4 (`critical` / `high` / `medium`) — the same three-tier
vocabulary, reused here for consistency with the rest of the pipeline.
### 5.2 Compute the `hitl` Field
The `hitl` boolean is a **per-task judgment call**, same philosophy as entropy-fix's Step
6q.4 and error-fix's Step 6 — no numeric or count-based backstop ever forces the
classification. Default `hitl: false`, except:
- **(a) CI workflow secret scan** — if the task creates or modifies a CI workflow file (e.g.
a M1 "establish runner + CI pipeline shape" task touching `.github/workflows/*.yml`) and
doing so would need a new `${{ secrets.* }}` reference: extract every `${{ secrets.* }}`
reference the task's change would introduce, and check whether each secret name already
appears in another workflow file in the repo (`grep -r '\${{ *secrets\.' .github/workflows/`).
Any secret that is net-new — not referenced anywhere else in the repo — means a human must
provision it. Set `hitl: true` and list the new secret name(s) in the description (same
procedure as `plan-session.md`'s Step 5.5 "CI workflow secret scan" paragraph).
- **(b) Branch protection changes** — if the task modifies branch protection settings (the
paired branch-protection tasks from `repo-config/SKILL.md`'s pairing rule, e.g. "Enable
branch protection on `main` requiring the new CI jobs"), set `hitl: true` — this is a
GitHub Settings action, not a code change an agent can make.
- **(c) M5 "delete (redundant)" audit tasks with an untested canonical owner** — if this is a
M5 deletion task and its audit-decision-row table flags the canonical owner (the test that
supersedes the one being deleted) as itself untested or not yet landed, set `hitl: true`.
This mirrors `test-roadmap`'s own Open Risks guidance: don't delete a "redundant" test
before its replacement exists — that call needs a human to confirm the replacement is
actually in place and adequate.
- **(d) Ambiguous fix approach** — a general "is the fix approach obvious" judgment call,
modeled on `consolidation-fix/SKILL.md`'s Step 7 per-finding `hitl` classification. The
question here isn't "which canonical shape to converge on" (consolidation-fix's framing) —
it's "is there an obvious, single way to write this test that mirrors existing precedent in
the codebase, or is the testing approach itself ambiguous":
- `hitl: false` continues to apply (no change to the existing default) when there's a clear
existing test file/pattern nearby to mirror — the row's `files` column names a target test
file adjacent to (or extending) an existing suite with the same layer/suffix convention,
the fixture/mocking strategy already has precedent elsewhere in the codebase, and there's
no real ambiguity about how the test should be structured.
- `hitl: true` (new rule d) applies when **any** of the following hold:
- **No existing precedent to mirror** — the row requires a genuinely novel test-fixture
strategy (e.g. the first integration test against a new external dependency, or a test
layer/suffix that doesn't yet exist anywhere in the repo for this component) and
reasonable engineers could disagree about how to structure it.
- **The row's scope crosses component/service boundaries** in a way that makes an
isolated, obvious test hard to write — e.g. the row's `files` column spans multiple
packages/services with no existing shared test-fixture convention between them.
- **The row is itself an audit/judgment row** whose own audit-decision-row table (parsed
in Step 2) reflects unresolved ambiguity about what "correct" behavior even is — distinct
from rule (c)'s narrower untested-canonical-owner case, this is about the row's own
decision criterion being unclear, not about a deletion target.
- Rule (d) is evaluated **in addition to** rules (a)–(c) — it does not replace them, and the
same "no default lean, judge each row on its own facts, no numeric backstop" philosophy
that governs (a)–(c) and this whole section applies to (d) too.
- **Carry-forward from Step 2's pre-filing check:** an unconfirmable indirect-reference row
from Step 2's pre-filing verification (Checklist Item 3 — the T-130 false-negative
pattern) is itself a path to `hitl: true` here. It is not a fifth numbered rule
the way (a)–(d) are — it's carried forward from Step 2's per-row verification — but the
`hitl` value it produces lands in this same field, so treat it as already-decided input
alongside (a)–(d) rather than re-litigating it.
Judge (a)/(b)/(c)/(d) against the row's own files/outcome/audit table — there is no default
lean either way beyond `false`; these four rules (plus the Step 2 pre-filing carry-forward) are
the only paths to `true`.
**Worked example.**
Row 1 — `hitl: false`:
```
T-071 | M4 | src/lib/formatCurrency.ts | unit | high-tier | add unit coverage for formatCurrency edge cases | bun test src/lib/formatCurrency.unit.test.ts
```
`src/lib/formatCurrency.unit.test.ts` already exists with 10 sibling unit tests covering other
pure functions in the same file, following the same `describe`/`it` structure and no external
fixtures. This row simply extends that suite with more cases for the same function. Clear
existing pattern to mirror, no boundary crossing, no audit ambiguity → `hitl: false`.
Row 2 — `hitl: true` under rule (d):
```
T-088 | M2 | src/clients/paymentsGatewayClient.ts | integration | critical-path | add integration coverage for the new paymentsGatewayClient | bun test src/clients/paymentsGatewayClient.integration.test.ts
```
This is the first integration test against a brand-new external payments gateway dependency —
there is no existing `*.integration.test.ts` fixture/recording convention anywhere in the repo
for this kind of third-party client (the repo's other integration tests all cover internal
services with an established recorded-fixture pattern that doesn't obviously transfer to an
external payment API's auth/retry semantics). No existing precedent to mirror, reasonable
engineers could disagree on the fixture strategy → `hitl: true` per rule (d), even though
neither rule (a), (b), nor (c) applies.
### 5.3 Build the `description` Field
The `description` field must give `dev-task` (or the HITL executor) enough context to fix
without re-reading `test-readiness-plan.md`. It mirrors `issue.md.tmpl`'s body content minus
the GitHub-specific "Closing checklist" section — `acceptanceCriteria` is the task-store
equivalent of that checklist:
```
Milestone: {milestone name, e.g. "M2 — Critical-path coverage"}
Layer: {test-layer} | Bucket: {bucket} | Criticality: {priority}
Expected outcome: {outcome}
Files to touch:
{files_to_touch}
{If this is an audit task — include the audit-decision-rows table verbatim:}
Audit decisions:
| Item | Decision | Criterion |
|---|---|---|
{...rows, verbatim from Step 2's parse...}
Context:
- Inventory entry: docs/test-readiness/test-inventory.md#{anchor}
- Blueprint section: docs/test-readiness/test-system.md#{anchor}
- Migration bucket: docs/test-readiness/test-migration.md#{anchor}
Verification command: {verify}
Task: {T-NNN} | HITL: {hitl}
```
Resolve the three context anchors the same way the former GitHub-issue publish skill's Step
5.4 resolved context links — by matching the row's task ID / outcome against the corresponding
section headers in each artifact file. If an anchor can't be confidently resolved, omit that
line rather than guessing a broken link.
### 5.4 Compute `dependencies`
`dependencies` is an array of task-store IDs (`test-{t-nnn}-{repo-slug}` form) — one entry
per predecessor `T-NNN` this row's `depends_on` / pairing-rule reference names (parsed in
Step 2.3). Map each predecessor `T-NNN` to its task-store ID using the same
`{t-nnn}-{repo-slug}` derivation used for this task's own `id`.
This is what makes task-store's own `ready:true` query correctly compute readiness for these
tasks — it fully replaces the old `ready` / `blocked` label toggling the former GitHub-issue
publish skill's `--refresh` flag used to do. **Do not implement any separate ready/blocked
computation in this skill** — no local label state, no re-evaluation step. A task is ready
exactly when the task store says it is, by walking `dependencies`.
If a row has no predecessors, `dependencies` is an empty array.
---
## Step 6: Dry-Run Output (if --dry-run)
If `--dry-run` was passed, run Steps 1–3 and 2's parsing as normal, but skip Step 4 (dedup)
entirely — do not query the task store. This includes the Step 4.1 fuzzy near-duplicate
check: it reuses Step 4's same two task-store queries, and `--dry-run`'s whole contract is
"no task-store queries made" (see Setup: Parse Arguments). Extending the fuzzy check into
dry-run would require querying the task store, which would break that contract — so dry-run
previews never surface fuzzy-duplicate flags; they only appear on a real (non-dry-run) run's
Step 4/7.2 output. Step 4.5 (registry cross-check) is a local file read, not a task-store
query, so it **does** still run in `--dry-run` mode — a preview should reflect suppressed rows
the same way a real run would, printing `Would skip {T-NNN} — resolved per
test-readiness-decisions.md ("{entry name}")` for each match instead of including it below. For
each parsed row surviving Step 4.5, compute `priority` (5.1), `hitl`
(5.2), and `dependencies` (5.4) in-memory, then print a preview:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TEST FIX — DRY RUN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Would queue {N} tasks:
1. test-{t-nnn}-{repo-slug}
Outcome: {outcome} (T-NNN, M#)
Layer: {test-layer} | Priority: {priority}
Depends on: {dependency task IDs, or "none"}
HITL: {true|false} (classification per Step 5.2{a|b|c} or default)
2. ...
No tasks written to task store. No dedup query made.
Re-run without --dry-run to queue tasks.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
Stop after printing.
---
## Step 7: Write, Queue, and Summarize
Skip this step if `--dry-run` was passed (handled in Step 6).
### 7.1 Write and Append
1. Write all task objects built in Step 5 to `/tmp/test-fix-tasks-{unix-timestamp}.json` as a
JSON array.
2. Run:
```bash
curl -sf -X POST \
-H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
-H "Content-Type: application/json" \
"$SHIPWRIGHT_TASK_STORE_URL/tasks/bulk" \
--data-binary @/tmp/test-fix-tasks-{unix-timestamp}.json | jq .
```
3. Delete the temp file after appending.
### 7.2 Print Summary
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TEST FIX — QUEUED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
QUEUED {N} tasks ({A} autonomous, {H} HITL)
SKIPPED {K} tasks (already active)
FLAGGED {F} tasks (possible near-duplicate, not skipped)
SUPPRESSED {S} - resolved per test-readiness-decisions.md
Tasks queued:
test-{t-nnn}-{repo-slug} — {outcome} [layer: {test-layer}, priority: {priority}, hitl: {true|false}]
...
{If any skipped:}
Skipped (already active):
{T-NNN} — task already in queue or in progress
{If any flagged:}
Flagged for review (possible near-duplicate, not skipped):
{T-NNN} — {similarity}% title-similar to active task {other-id}: "{other-title}"
{If any suppressed:}
Suppressed (resolved per test-readiness-decisions.md):
{T-NNN} — {entry name}
Run /shipwright:dev-task to execute autonomous tasks. HITL tasks are picked up via
/shipwright:hitl.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
Stop after printing — this is the sole final output for the regular flow.
---
## Error Handling
- **`test-readiness-plan.md` missing** (regular flow): handled in Step 1 — print the message
and stop.
- **No parseable task rows** (regular flow): handled in Step 2 — print the message and stop.
- **Task-store dedup query fails** (Step 4): log the failure and stop. Do not queue tasks
without a dedup pass, or you risk duplicate tasks for an already-active `T-NNN`.
- **Bulk append fails** (`/tasks/bulk` non-2xx, Step 7.1): log the response body and
stop. Do not retry blindly; re-running the regular flow is idempotent because the dedup
check (Step 4) skips already-queued rows.
- **`test-readiness-decisions.md` fails to parse** (Step 4.5): skip only the entries that can't
be confidently interpreted (same defensive-parse contract as `test-inventory/SKILL.md`'s Step
0) — never fail the whole run over one malformed entry. A missing file is not a failure at
all; it's the documented no-op path.
---
## Constraints (Do Not Violate)
- **Queue only** — this skill never opens PRs and never leaves the base branch. It only
writes tasks to the task store; the actual fix lands later via `dev-task` or
`/shipwright:hitl`.
- **No separate ready/blocked computation** — task-store's `dependencies` array and its own
`ready:true` query are the sole readiness mechanism (Step 5.4). This skill never writes,
toggles, or reads a local `ready` / `blocked` label the way the former GitHub-issue publish
skill's `--refresh` flag did; that mechanism is fully replaced, not duplicated.
- **Dedup before queueing** — never skip Step 4 in the regular flow (outside `--dry-run`,
which explicitly skips it by design and queues nothing for real).
- **Cross-check test-readiness-decisions.md live, every run, for every row** — never trust
that a row's classification is still unresolved by the registry; a human may have added a
new decision entry since `test-readiness-plan.md` was generated (Step 4.5). This check is
unconditional across all rows, not scoped to rule-(d)/HITL rows only.
- **Fuzzy near-duplicate check (Step 4.1) never skips** — it only ever flags for human review
(printed in Step 4's output and again in Step 7.2's summary). Only the exact-ID check can
remove a row from the Step 5 build list; a fuzzy title match is never, on its own, a reason
to withhold a task from the bulk POST.
- **No numeric/count backstop on `hitl`** — the only paths to `hitl: true` are the four
explicit rules in Step 5.2 (CI workflow secrets, branch protection, untested M5 deletion
owner, ambiguous fix approach) plus the Step 2 pre-filing-verification carry-forward
(unconfirmable indirect-reference rows). No file-count, line-count, or milestone-number
threshold ever forces the classification on its own.
- **Repo-scoped task IDs** — every task ID carries the `{repo-slug}` suffix (Step 3) so
the same `T-NNN` scanned in two different repos never collides.
- **`test-readiness-plan.md` is not modified here** — a queued task only means a fix is
scheduled. The plan document is Phase 4's output and is not checked off, annotated, or
rewritten by this skill.
- **No interactive confirmation** — matches entropy-fix/error-fix: queue-only skills don't
gate on user confirmation for regular-flow task-store writes, since they're internal and
correctable.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!