Skip to content

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).

LocationRole today
plugin/skills/task-ensure-ready/SKILL.mdImplements 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.mdThe contract doc the skill consults. Each new disqualifier kind is a new prose bullet here.
plugin/skills/task-ensure-ready/tests/ensure_ready.test.tsPer-case fixture harness; each disqualifier kind gets case(s) here.
plugin/skills/task-ensure-ready/parse_touchpoints.tsCo-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.tsCo-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.

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 containing every/each/all/sibling referencing an unpinned class (subsumes the vacuous-universal-quantifier draft).
  • cli_flags.ts — verifies back-ticked CLI flags cited in Today against the cited .ts scripts’ 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 actual plugin/lib/model/entities/<type>/schema.json (subsumes the cited-schema-fields draft).
  • consuming_skills.ts — verifies “consuming skills” lists against plugin/skills/*/SKILL.md (subsumes the named-consuming-skills draft).
  • shell_drift.ts — flags hardcoded grep <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.

  1. Write types.ts: the Finding type + ClaimResolver interface. Match the JSON output shape of the existing parse-helpers (parse_touchpoints.ts / scan_placeholders.ts) so the SKILL.md/script boundary stays clean.
  2. Create index.ts as the typed registry barrel: import each resolver module and collect them into an exported RESOLVERS: ClaimResolver[]. (Explicit typed registry over filesystem discovery — it compile-checks every entry and matches the index.ts-barrel convention every other plugin/lib/ package already uses; adding a resolver is one import + one array entry.)
  3. Implement paths.ts as the first canonical resolver. It covers the most concrete drafted task and exercises the full interface (body parsing + filesystem queries via node:fs + finding emission).
  4. Implement quantifiers.ts as the second canonical resolver. Purely text-shape (no filesystem queries); proves the interface accommodates resolvers that don’t touch the codebase.
  5. Wire the runner as plugin/lib/model/entities/task/ops/check-claims.ts — a defineOp module the registry walk discovers, surfacing sdlc task check-claims; imports RESOLVERS, runs each against the task file, emits aggregate JSON. (Initially landed as a co-located run_resolvers.ts script; relocated onto the op substrate during PR review.)
  6. Update task-ensure-ready/SKILL.md Step 3 to invoke sdlc task check-claims <task-file> alongside the existing checks; aggregate the JSON findings into the existing pass/fail evaluation.
  7. Update plugin/lib/model/entities/task/implementation-ready.md — instead of N inline disqualifier bullets, one paragraph referencing the resolver list at plugin/lib/model/entities/task/claims/ and naming each resolver’s contract.
  8. Add bun:test coverage: plugin/lib/model/entities/task/claims/tests/paths.test.ts and quantifiers.test.ts exercise each resolver’s pass/fail cases; a runner test lives under plugin/skills/task-ensure-ready/tests/. All ride the existing bun test quality gate.
  9. The 7 originating drafts are already closed closed/superseded with completion_note pointing here — no further action; concrete-resolver follow-ons reference them as needed.
LocationKindChange
plugin/lib/model/entities/task/claims/types.tsnewClaimResolver interface + Finding type.
plugin/lib/model/entities/task/claims/index.tsnewBarrel exporting the typed RESOLVERS: ClaimResolver[] registry.
plugin/lib/model/entities/task/claims/paths.tsnewFirst canonical resolver (fuzzy-locate cited paths).
plugin/lib/model/entities/task/claims/quantifiers.tsnewSecond canonical resolver (vacuous universal-quantifier ACs).
plugin/lib/model/entities/task/claims/tests/paths.test.tsnewbun:test fixture suite for the paths resolver.
plugin/lib/model/entities/task/claims/tests/quantifiers.test.tsnewbun:test fixture suite for the quantifiers resolver.
plugin/lib/model/entities/task/ops/check-claims.tsnewtask check-claims op: imports RESOLVERS, runs each, emits aggregate JSON via the registry adapter.
plugin/skills/task-ensure-ready/SKILL.mdmodifyStep 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.mdmodifyReplace 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.tsmodifyAdd cases that exercise the full runner end-to-end against fixture tasks.
  • AC-1: plugin/lib/model/entities/task/claims/ exists with a ClaimResolver interface + Finding type (types.ts), a typed RESOLVERS registry (index.ts), and at least two concrete resolvers (paths.ts, quantifiers.ts). bunx tsc --noEmit is 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 paths finding with severity disqualifier.

  • AC-4: A fixture task whose AC contains a universal quantifier without enumerating the universal set or naming a query produces a quantifiers finding with severity disqualifier.

  • 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.md no longer duplicates per-disqualifier prose for the kinds the resolvers cover; the canonical list lives in code under plugin/lib/model/entities/task/claims/.

  • AC-7: The 7 superseded originating drafts (listed in Discovery context) are closed/superseded with a completion_note pointing to this task or its concrete-resolver follow-ons. (Already satisfied on main as of 2026-05-28; re-confirm none regressed.)

  • 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. The Finding type carries severity so the room exists, but the SKILL.md’s pass/fail evaluation only acts on disqualifier severity 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.
  • none

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:

Captured by /sdlc:task-work on 2026-06-04. PR: pending.

  • AC-1: auto — bunx tsc --noEmit clean; new plugin/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 real plugin/lib/model/entities/task/claims/paths.ts, wrong parent) produced a paths finding, severity disqualifier.
  • AC-4: agent-manual — fixture AC “every consuming skill is updated” produced a quantifiers finding, severity disqualifier; 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.md now carries one “Pluggable claim-resolver disqualifiers” section pointing at plugin/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/superseded with a completion_note (none regressed).
  • The resume detector / lease bookkeeping held cleanly: the prior run’s two implementation commits were intact, the lease was still working and 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/5 with zero new drift, so the gate did not force triage of unrelated corpus drift.
  • run_quality_checks.ts --diff-against-baseline defaults --baseline-dir to the worktree’s .sdlc/quality-baselines/, but Step 3a’s quality_baseline.ts capture (run from the main repo) writes to the main repo’s dir — the first gate invocation failed with baseline not found until --baseline-dir <main-repo>/.sdlc/quality-baselines was 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
  • 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-dir self-discovery fix for run_quality_checks.ts + quality_baseline.ts).

← Back to Tasks