T-F61F-pluggable-claim-resolver-interface-for-task-ensure-ready
Status: closed/done · Impact: high · Complexity: medium
Seven proposed task-ensure-ready (or task-work relevance-check) extensions all share the same shape:
each adds one new disqualifier — paths, universal quantifiers, CLI flags, “X shipped via PR #N”
claims, cited schema fields, named consuming skills, hardcoded shell-grep drift — implemented as
inline prose in implementation-ready.md + a one-off check in task-ensure-ready/SKILL.md.
Shipping them as 7 separate inline disqualifiers means duplicated body-parsing logic, no shared test
scaffold, no uniform finding output, and a SKILL.md that grows unboundedly. This task lands a
pluggable claim-resolver interface so each claim kind becomes a small co-located resolver module
with a uniform contract; the SKILL.md becomes a one-line iteration; future claim kinds add a single
file.
The 7 originating drafts are already superseded by this task (each was closed closed/superseded on
2026-05-28 with a completion_note pointing here).
| Location | Role today |
|---|---|
plugin/skills/task-ensure-ready/SKILL.md | Implements all current disqualifier checks inline (path-exists, symbol-exists, placeholder-scan, malformed touchpoint rows). Adding a new disqualifier today requires editing this file + implementation-ready.md + the eval harness. |
plugin/lib/model/entities/task/implementation-ready.md | The contract doc the skill consults. Each new disqualifier kind is a new prose bullet here. |
plugin/skills/task-ensure-ready/tests/ensure_ready.test.ts | Per-case fixture harness; each disqualifier kind gets case(s) here. |
plugin/skills/task-ensure-ready/parse_touchpoints.ts | Co-located parser for ## Today and ## Files to touch tables. Emits structured JSON via import.meta.main CLI. Exemplar of the target shape — a small focused module with a clear contract. |
plugin/skills/task-ensure-ready/scan_placeholders.ts | Co-located scanner for <...> placeholders in body sections. Emits structured JSON lines per match. Second exemplar. |
plugin/lib/ | Established home for shared, importable TypeScript packages (lease, prose_match, entity_naming, model); each is a directory with an index.ts barrel + tests/. No claims package exists under the task entity today. |
Proposed
Section titled “Proposed”A new plugin/lib/model/entities/task/claims/ TypeScript package defines a small interface in
types.ts: a ClaimResolver interface with a
resolve(taskBody: string, frontmatter: Record<string, unknown>, projectRoot: string): Finding[]
method (plus a name: string), and a Finding type carrying line: number | null,
severity: 'disqualifier' | 'warning', message: string, and resolver: string. An index.ts
barrel exports a typed RESOLVERS: ClaimResolver[] registry — each module is imported and added
explicitly, so the registry is type-checked at compile time (no dynamic filesystem scan, in keeping
with the rest of plugin/lib/).
Each claim kind ships as a module under plugin/lib/model/entities/task/claims/:
paths.ts— fuzzy-locates cited paths whose basename matches but parent dir doesn’t (subsumes the fuzzy-locate-cited-paths draft).quantifiers.ts— flags ACs containingevery/each/all/siblingreferencing an unpinned class (subsumes the vacuous-universal-quantifier draft).cli_flags.ts— verifies back-ticked CLI flags cited in Today against the cited.tsscripts’ argument parsing (commander.option(...)/ argv handling) (subsumes the task-work relevance CLI-flag draft).shipped_claims.ts— verifies “shipped via PR #N” statements against GitHub PR merge state (subsumes the shipped-claims draft).schema_fields.ts— verifies cited schema field names against the actualplugin/lib/model/entities/<type>/schema.json(subsumes the cited-schema-fields draft).consuming_skills.ts— verifies “consuming skills” lists againstplugin/skills/*/SKILL.md(subsumes the named-consuming-skills draft).shell_drift.ts— flags hardcodedgrep <literal>recipes in Today/Approach when a resolver pattern could replace them (subsumes the spec-shell-drift draft).
task-ensure-ready/SKILL.md Step 3 reduces to invoking the sdlc task check-claims op (via the
unified CLI) that imports the RESOLVERS registry, runs each resolver, collects findings, and emits
a single JSON object on stdout. The skill aggregates the findings into its existing pass/fail
evaluation alongside the structural checks (touchpoint parsing, placeholder scan).
The implementation strategy lands the interface plus two initial resolvers (paths.ts and
quantifiers.ts) as canonical examples in one PR. Each subsequent resolver lands as a tiny
follow-on PR: one new module file, one line added to the RESOLVERS registry in index.ts, fixture
cases under claims/tests/. The 7 originating drafts are already closed/superseded; concrete
resolvers can ship as small follow-on tasks or as freelance one-file commits.
Approach
Section titled “Approach”- Write
types.ts: theFindingtype +ClaimResolverinterface. Match the JSON output shape of the existing parse-helpers (parse_touchpoints.ts/scan_placeholders.ts) so the SKILL.md/script boundary stays clean. - Create
index.tsas the typed registry barrel: import each resolver module and collect them into an exportedRESOLVERS: ClaimResolver[]. (Explicit typed registry over filesystem discovery — it compile-checks every entry and matches theindex.ts-barrel convention every otherplugin/lib/package already uses; adding a resolver is one import + one array entry.) - Implement
paths.tsas the first canonical resolver. It covers the most concrete drafted task and exercises the full interface (body parsing + filesystem queries vianode:fs+ finding emission). - Implement
quantifiers.tsas the second canonical resolver. Purely text-shape (no filesystem queries); proves the interface accommodates resolvers that don’t touch the codebase. - Wire the runner as
plugin/lib/model/entities/task/ops/check-claims.ts— adefineOpmodule the registry walk discovers, surfacingsdlc task check-claims; importsRESOLVERS, runs each against the task file, emits aggregate JSON. (Initially landed as a co-locatedrun_resolvers.tsscript; relocated onto the op substrate during PR review.) - Update
task-ensure-ready/SKILL.mdStep 3 to invokesdlc task check-claims <task-file>alongside the existing checks; aggregate the JSON findings into the existing pass/fail evaluation. - Update
plugin/lib/model/entities/task/implementation-ready.md— instead of N inline disqualifier bullets, one paragraph referencing the resolver list atplugin/lib/model/entities/task/claims/and naming each resolver’s contract. - Add
bun:testcoverage:plugin/lib/model/entities/task/claims/tests/paths.test.tsandquantifiers.test.tsexercise each resolver’s pass/fail cases; a runner test lives underplugin/skills/task-ensure-ready/tests/. All ride the existingbun testquality gate. - The 7 originating drafts are already closed
closed/supersededwithcompletion_notepointing here — no further action; concrete-resolver follow-ons reference them as needed.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/model/entities/task/claims/types.ts | new | ClaimResolver interface + Finding type. |
plugin/lib/model/entities/task/claims/index.ts | new | Barrel exporting the typed RESOLVERS: ClaimResolver[] registry. |
plugin/lib/model/entities/task/claims/paths.ts | new | First canonical resolver (fuzzy-locate cited paths). |
plugin/lib/model/entities/task/claims/quantifiers.ts | new | Second canonical resolver (vacuous universal-quantifier ACs). |
plugin/lib/model/entities/task/claims/tests/paths.test.ts | new | bun:test fixture suite for the paths resolver. |
plugin/lib/model/entities/task/claims/tests/quantifiers.test.ts | new | bun:test fixture suite for the quantifiers resolver. |
plugin/lib/model/entities/task/ops/check-claims.ts | new | task check-claims op: imports RESOLVERS, runs each, emits aggregate JSON via the registry adapter. |
plugin/skills/task-ensure-ready/SKILL.md | modify | Step 3 reduces to invoking sdlc task check-claims; remove the inline path/symbol/placeholder duplications the resolvers cover. |
plugin/lib/model/entities/task/implementation-ready.md | modify | Replace inline per-disqualifier bullets with a single section pointing at plugin/lib/model/entities/task/claims/ and naming each resolver’s contract. |
plugin/skills/task-ensure-ready/tests/ensure_ready.test.ts | modify | Add cases that exercise the full runner end-to-end against fixture tasks. |
Acceptance criteria
Section titled “Acceptance criteria”-
AC-1:
plugin/lib/model/entities/task/claims/exists with aClaimResolverinterface +Findingtype (types.ts), a typedRESOLVERSregistry (index.ts), and at least two concrete resolvers (paths.ts,quantifiers.ts).bunx tsc --noEmitis clean. -
AC-2:
plugin/cli/sdlc task check-claims <task-file>runs every registered resolver and emits one JSON object on stdout in the shape{ "<resolver>": [{ "line": <int|null>, "severity": "disqualifier"|"warning", "message": "<str>" }, ...], ... } -
AC-3: A fixture task whose body cites a path with a matching basename but wrong parent directory produces a
pathsfinding with severitydisqualifier. -
AC-4: A fixture task whose AC contains a universal quantifier without enumerating the universal set or naming a query produces a
quantifiersfinding with severitydisqualifier. -
AC-5: A well-formed fixture task produces zero disqualifier findings; both resolvers exit cleanly.
bun test plugin/lib/model/entities/task/claims/passes. -
AC-6:
plugin/lib/model/entities/task/implementation-ready.mdno longer duplicates per-disqualifier prose for the kinds the resolvers cover; the canonical list lives in code underplugin/lib/model/entities/task/claims/. -
AC-7: The 7 superseded originating drafts (listed in Discovery context) are
closed/supersededwith acompletion_notepointing to this task or its concrete-resolver follow-ons. (Already satisfied on main as of 2026-05-28; re-confirm none regressed.)
Out of scope
Section titled “Out of scope”- Implementing all 7 resolvers in one PR. Land the interface + 2 canonical resolvers; ship the others as separate small PRs (each is a single module + one registry line).
- Resolvers for entity types other than tasks. Epics, milestones, backlog items use the same skill but the resolver shape is task-shaped today; extension is a separate concern.
- A “warning-only” tier separate from
disqualifier. TheFindingtype carriesseverityso the room exists, but the SKILL.md’s pass/fail evaluation only acts ondisqualifierseverity in this task — warning-handling lands when a resolver actually emits one. - LLM-driven resolvers. Every resolver here is deterministic TypeScript. If a claim kind needs LLM judgment (e.g. “is this AC subjective?”), that’s a sub-agent invocation in the SKILL.md, not a resolver.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned 2026-05-28 from a consolidation audit. Seven prior drafts proposed inline disqualifiers in
implementation-ready.md + checks in task-ensure-ready/SKILL.md. The repeated shape made the
abstraction obvious. Co-located helper exemplars (parse_touchpoints.ts, scan_placeholders.ts)
already use the “JSON-emitting module called from SKILL.md” pattern; the resolver interface
generalizes that one notch.
Re-specced to TypeScript on 2026-06-03: the original spec proposed Python modules (dataclass /
Protocol / __init__.py / test_*.py), authored before the en-masse Python→TypeScript migration
(D-0006-typescript-substrate / M-0002) moved the substrate. The interface, resolver list,
and strategy are unchanged; only the implementation shape is now TS (types.ts interface,
index.ts registry barrel, bun:test). The original in-progress attempt against the Python shape
was abandoned (no PR).
The superseded drafts:
- T-V8K4-task-ensure-ready-fuzzy-locates-cited-paths
- T-PCJB-task-ensure-ready-flags-vacuous-universal-acs
- T-DV8J-task-work-relevance-verifies-cli-flag-claims
- T-780G-task-ensure-ready-checks-shipped-claims
- T-ZJXU-task-ensure-ready-verifies-cited-schema-fields
- T-BKRP-task-ensure-ready-verifies-named-consuming-skills
- T-XSI8-task-define-flags-spec-shell-drift
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-06-04. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
bunx tsc --noEmitclean; newplugin/lib/model/entities/task/claims/package (types.ts,index.ts,paths.ts,quantifiers.ts) compiles. - AC-2: agent-manual — ran
plugin/cli/sdlc task check-claims <task-file>; emits{ "paths": [...], "quantifiers": [...] }with every registered resolver as a key, exit 0. - AC-3: agent-manual — fixture citing
plugin/skills/task-ensure-ready/paths.ts(basename matches the realplugin/lib/model/entities/task/claims/paths.ts, wrong parent) produced apathsfinding, severitydisqualifier. - AC-4: agent-manual — fixture AC “every consuming skill is updated” produced a
quantifiersfinding, severitydisqualifier; a sibling AC citing a back-ticked command was correctly not flagged. - AC-5: auto —
bun test plugin/lib/model/entities/task/claims/passes (20 pass, 0 fail); a well-formed fixture yields zero disqualifiers. - AC-6: auto —
plugin/lib/model/entities/task/implementation-ready.mdnow carries one “Pluggable claim-resolver disqualifiers” section pointing atplugin/lib/model/entities/task/claims/instead of per-disqualifier prose; resolver module docstrings are the authoritative contract. - AC-7: auto — all 7 originating drafts confirmed
closed/supersededwith acompletion_note(none regressed).
What worked
Section titled “What worked”- The resume detector / lease bookkeeping held cleanly: the prior run’s two implementation commits
were intact, the lease was still
workingand owned by this host (unexpired — heartbeat had ticked it forward before the run died), so resume was a verification-and-ship pass with no rework. - Baseline-gated quality checks subtracted 354 pre-existing findings and reported
OK 5/5with zero new drift, so the gate did not force triage of unrelated corpus drift.
Friction and automation gaps
Section titled “Friction and automation gaps”run_quality_checks.ts --diff-against-baselinedefaults--baseline-dirto the worktree’s.sdlc/quality-baselines/, but Step 3a’squality_baseline.ts capture(run from the main repo) writes to the main repo’s dir — the first gate invocation failed withbaseline not founduntil--baseline-dir <main-repo>/.sdlc/quality-baselineswas passed explicitly. The two scripts should resolve the same default baseline dir (anchor both on the superproject root, or have the gate fall back to the main-repo dir when the worktree dir misses). → T-44OO-plugin-scripts-self-discover-project-root
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-44OO-plugin-scripts-self-discover-project-root — linked existing (fourth post-mortem to
surface the same baseline-dir silent-fallback friction; T-44OO is the active tracker whose
Goal/Approach name the
git rev-parse --git-common-dirself-discovery fix forrun_quality_checks.ts+quality_baseline.ts).