T-3VD8-add-task-sort-script-and-priority-field
Status: closed/done · Impact: high · Complexity: medium
/sdlc:orchestrate and /sdlc:task-work (no-arg form) both pick the
next task by oldest created: date with ties broken by filename —
a single-key sort, encoded in prose in two skills, that can’t
express priority, ignores impact:/complexity: already captured
at intake, and doesn’t lift depends_on blockers forward. The
author cannot say “do this one before everything else” without
re-dating the task by hand. Replace the inline rule with a
deterministic task_sort.py script that owns the pickup-order
algorithm end-to-end (priority → impact → complexity → created,
with a depends_on-aware lift pass), and add an opt-in boolean
priority: field for the explicit human override.
| Location | Role today |
|---|---|
plugin/skills/orchestrate/SKILL.md | Step 4 candidate pickup encodes “scan docs/planning/tasks/*.md, filter by status: open/ready excluding autonomy: human-only, sort oldest-first by created: with filename tiebreak” inline as prose. Every orchestrate tick re-derives the rule. |
plugin/skills/task-work/SKILL.md | Step 1 no-arg branch duplicates the same scan/filter/sort prose. Two skills, one rule, two copies. |
plugin/entities/task/schema.json | impact: high/medium/low and complexity: small/medium/large are captured at intake but never read at pickup. No priority: field exists. Top-level version is 4. |
plugin/scripts/new_task.py | Writes task frontmatter; no --priority flag. |
plugin/skills/entities-migrate/migrate_entities.py | Owns the schema-version transform chain. Currently terminates at v4 for tasks; per-version transforms live in plugin/entities/task/migrations/vN-to-vM.py. |
plugin/cli/sdlc | Unified sdlc <noun> <verb> dispatcher (PR #150). Registers nouns via the NOUNS registry; today exposes backlog (visible) and lease (hidden). No task noun yet. |
docs/data-model.md | Documents task frontmatter fields. Mentions impact/complexity but doesn’t describe how pickup ordering uses them (because today it doesn’t). |
docs/skills/orchestrate.md | User-facing skill doc. Describes pickup behavior but doesn’t surface the sort rule as a first-class concept. |
docs/skills/task-work.md | Same — no-arg pickup is mentioned but the sort isn’t documented as a separate concept. |
Proposed
Section titled “Proposed”A new task noun registered on the unified sdlc <noun> <verb> CLI
owns the entire pickup-order algorithm. Concretely: a new
plugin/cli/task_cli/ package exposing sdlc task sort (the only
verb in this slice). The verb reads task frontmatter under
docs/planning/tasks/*.md, applies caller-supplied filters, runs a
multi-key sort with a depends_on-aware lift pass, and emits sorted
basenames on stdout (one per line; --json for richer output).
The task noun is registered as a visible noun in the
dispatcher’s NOUNS registry (so sdlc --help lists it alongside
backlog). The deterministic verb’s exit codes follow the shared
base in plugin/cli/_common.py; the cycle-detected disqualifier
reuses exit 1 with a CYCLE stderr marker (no new noun-specific
exit codes added — the only failure shapes are usage / generic / cycle,
all of which fit the shared EXIT_GENERIC slot with a distinct stderr
marker).
Default sort key chain (highest weight at top):
priority(boolean —truesorts abovefalse; missing field defaults tofalse)impact(high→medium→low→ missing)complexity(small→medium→large→ missing)created(oldest first)- filename (final tiebreak — deterministic byte-stable ordering)
Dependency lift: before the sort, walk depends_on edges in
reverse. Each blocker inherits the max sort tuple of its blockees,
so a low-impact task that blocks a high-impact task is pulled
forward to the same position. Closed blockers (anything matching
closed/*) are not pickup candidates, so they don’t appear in the
output regardless of how anything points at them; they also do not
contribute to the lift (their tuple is dropped from the propagation
pool). Cycles detected during the walk cause an exit-2 with CYCLE
on stderr naming the involved basenames.
Schema bump v4 → v5 adds an optional priority: boolean field.
Stamp-only migration (no body changes; missing value means false).
new_task.py learns a --priority flag for the intake path.
Consumer skills shrink to one-line shell-outs. orchestrate Step 4
and task-work Step 1 no-arg branch both invoke ${CLAUDE_PLUGIN_ROOT}cli/sdlc task sort
and process the basename list. The full algorithm has exactly one
implementation, lives in Python under plugin/cli/task_cli/, and is
unit-tested.
Documentation lands in three places:
docs/task-sort.md— canonical narrative (sort chain, dependency lift,priority:field, where the default lives, forward-looking note that per-project customization will move tosdlc.yamlif needed).docs/data-model.md— document the newpriority:field; link todocs/task-sort.mdfor ordering semantics.docs/skills/orchestrate.mdanddocs/skills/task-work.md— one line each noting that pickup is delegated tosdlc task sort, link to the sort doc.
sdlc.yaml configuration is explicitly deferred. The default
sort chain is hardcoded in the script and documented. Per-project
customization (e.g. task_sort: [priority, impact, created] dropping
complexity) is a future follow-up if real demand surfaces.
Migration semantics (schema v4 → v5)
Section titled “Migration semantics (schema v4 → v5)”Per plugin/conventions/schema-bump-checklist.md, the five
canonical answers for this bump:
- Missing
schema_version. Treated as v1 — the existing transform chain v1→v2→v3→v4→v5 runs in sequence. The v4→v5 step is a no-op on body; only the stamp changes. - Current
schema_versionis5. Pass-through, zero diff. The pass-through path is already exercised by existing fixtures; this bump adds a v4→v5 fixture but does not change the at-target semantics. - Unknown legacy values. Hard error with non-zero exit
(existing behavior —
plugin/skills/entities-migrate/migrate_entities.pyalready rejects unknown versions). No change to that surface. - Error path. A transform failure exits non-zero with the
failure on stderr; partial migrations are not rolled back
(existing behavior). The v4→v5 transform is trivial enough that
the only failure mode is malformed input frontmatter, caught
upstream by
validate_frontmatter.py. - Stamp behavior post-migrate.
schema_version: '5'lands at the bottom of the frontmatter per the existing “stamp at bottom” convention (alongsidereadiness_verified_atandtouchpoints_verified_at). No reshuffling of body or other frontmatter keys.
Approach
Section titled “Approach”-
Schema bump v4 → v5. In
plugin/entities/task/schema.json, bump the top-levelversionto5and addpriority: { type: boolean }toproperties(optional — not required). Document the field’s purpose in itsdescription(human-set override; opt-in; absent = false). Theprioritykey’s position in thepropertiesblock determines its canonical key order during canonical-order rewrites — place it next toimpactso the two ordering-relevant fields cluster. -
Add the v4→v5 transform module at
plugin/entities/task/migrations/v4-to-v5.py. This mirrors thev3-to-v4.pyneighbor: a puremigrate(fm)function that returns a new dict withschema_version: "5"stamped and every other field preserved unchanged. Stamp-only — no body changes, no field reshape. Add the matchingtest_v4_to_v5.pynext to it (mirrorstest_v3_to_v4.py) covering stamp behavior, idempotency, field preservation, and the non-dict guard.migrate_entities.pydiscovers the transform via its existingentities/<type>/migrations/v<file_ver>-to-v<curr>.pyresolution — no edit to the migrate script itself is required. -
Update
plugin/scripts/new_task.pyto accept a--priorityflag. When set, writepriority: trueto the frontmatter. When absent, omit the key entirely (keeps the common case compact). Add a unit test covering both paths. -
Implement the
tasknoun underplugin/cli/task_cli/. Mirror the shape ofplugin/cli/backlog_cli/andplugin/cli/lease_cli/:plugin/cli/task_cli/__init__.py— exposesbuild_task_subtree(noun_parser)(wires thesortverb’s argparse viaset_defaults(_handler=...)) anddispatch_task(parser, args)(resolves no noun-scoped config — thetasknoun does not need lease authority — and callsargs._handler(args)).plugin/cli/task_cli/sort.py— the verb module. Exposesbuild_argparser(noun_parser)(addssortsubparser, attaches--status,--exclude-autonomy,--limit,--json,--explainoptions + a--project-rootoverride) andhandle_sort(args) -> int(the deterministic implementation).plugin/cli/task_cli/_common.py— verb-shared helpers if any emerge (sort-key namedtuple, frontmatter parser thin wrapper, etc.). Re-exportEXIT_OK/EXIT_GENERICfrom the top-level_common. Only add the file if more than one verb-shared symbol exists; otherwise skip and inline intosort.py.- Update
plugin/cli/sdlcto register thetasknoun: add a_register_task/_dispatch_taskpair and append aNoun(name="task", hidden=False, help="Inspect and order tasks under docs/planning/tasks/.", ...)entry to theNOUNSdict.
Sort verb behavior:
- Resolve project root via
git rev-parse --git-common-dir(mirrors the lease library’s discovery so the verb works from a worktree or the main checkout).--project-rootoverrides for tests. - Read all
docs/planning/tasks/*.md, parse frontmatter viayaml.safe_loadagainst the file’s frontmatter block (same shapeaudit_entities.pyand the migrate transforms use). - Filter by
--status <value>(repeatable; defaultopen/ready) and--exclude-autonomy <value>(repeatable; default empty list). - Build the
depends_ongraph from surviving candidates. Resolve wikilink targets to basenames. - Run the lift pass: DFS from each candidate following
depends_onedges in reverse; for each blocker, propagate the max sort tuple seen along the walk. Closed targets (resolved to existing files inclosed/*state) are dropped — they don’t contribute to the lift. Targets that don’t resolve to a file are logged to stderr as a warning but don’t fail the sort. Cycle detection via standard DFS color marking; on cycle, exit1withCYCLE basenames=<comma-separated>on stderr (no new noun-specific exit code; the marker distinguishes it from generic-failure paths). - Sort by the lifted tuple:
(priority desc, impact desc, complexity asc, created asc, basename asc). Define aSortKeynamedtuple with ordering semantics so the test suite can assert on it directly. - Emit basenames on stdout, one per line. With
--json, emit a JSON array of objects{basename, priority, impact, complexity, created, lifted_from}wherelifted_fromnames the blockee that contributed the lift (ornull). With--explain, emit the sort tuple alongside each basename for debugging. - Honor
--limit N(truncate after N basenames).
-
Unit tests in
plugin/cli/task_cli/tests/test_sort.py. Golden cases:priority: truetask sorts abovepriority: falseregardless of impact / created date.- Within same priority,
impact: highsorts abovemedium,mediumabovelow. - Within same priority + impact,
complexity: smallsorts abovemediumabovelarge. - Within same priority + impact + complexity, oldest
created:sorts first. - Identical sort tuples — filename sorts deterministically.
- Missing fields default correctly (no-priority = false, no-impact = lowest bucket, no-complexity = highest bucket).
- High-priority task A with
depends_on:B“ and B unpinned — B sorts above unrelated unpinned tasks (lift verified). - A blocked by closed B — closed B not in output, A still present (closed targets don’t propagate).
- Cycle A→B→A exits 1 with
CYCLEon stderr. - Empty input (no tasks match filters) exits 0 with empty stdout.
--limit 1truncates correctly.
-
Update
plugin/skills/orchestrate/SKILL.mdStep 4. Replace the inline “scan, filter, sort bycreated:” prose with one shell-out:${CLAUDE_PLUGIN_ROOT}cli/sdlc task sort --status open/ready \--exclude-autonomy human-onlyProcess stdout as one basename per line. Remove the
Pick tasks in oldest-first order ...paragraph entirely; citedocs/task-sort.mdfor the canonical algorithm. -
Update
plugin/skills/task-work/SKILL.mdStep 1 no-arg branch. Same shell-out (${CLAUDE_PLUGIN_ROOT}cli/sdlc task sort --status open/ready --exclude-autonomy human-only --limit 1). Remove the inline “filter forstatus: open/readyAND excludeautonomy: human-only, take the one with the oldestcreated:date (ties broken by filename sort)” prose; citedocs/task-sort.md. -
Write
docs/task-sort.md. One-page narrative:- The five sort keys, in order, with one paragraph each explaining what they express and why they’re in that position.
- The dependency lift — what it does, why it’s there (high-impact work shouldn’t wait behind a low-impact chain it doesn’t even know about), how cycles are reported.
- The
priority:field — when to use it, why it’s boolean, the intent that it stays sparse (most tasks don’t need it). - Where the default lives (
plugin/cli/task_cli/sort.py, invoked assdlc task sort) and a forward-looking note that per-project customization is a future option viasdlc.yamltask_sort:config. - A worked example showing the sort applied to a small inventory.
-
Update
docs/data-model.mdto document the newpriority:field alongsideimpact:andcomplexity:. One sentence each field; link todocs/task-sort.mdfor ordering semantics. -
Update
docs/skills/orchestrate.mdanddocs/skills/task-work.md. One line each near the pickup description: “Pickup order is determined bysdlc task sort(plugin/cli/task_cli/sort.py); seedocs/task-sort.md.”
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/entities/task/schema.json | modify | Bump top-level version 4→5; add optional priority: { type: boolean } to properties. |
plugin/scripts/new_task.py | modify | Accept --priority; write priority: true when set, omit key when absent. |
plugin/entities/task/migrations/v4-to-v5.py | new | Pure migrate(fm) stamp-only v4→v5 transform module (mirrors the existing v3-to-v4 module). |
plugin/entities/task/migrations/test_v4_to_v5.py | new | Self-running unit tests for the stamp transform (mirrors test_v3_to_v4.py). |
plugin/cli/sdlc | modify | Register the new task noun in the NOUNS registry (visible). |
plugin/cli/task_cli/__init__.py | new | Exposes build_task_subtree() + dispatch_task(). |
plugin/cli/task_cli/sort.py | new | The sdlc task sort verb — owns the pickup-order algorithm end-to-end. |
plugin/cli/task_cli/tests/test_sort.py | new | Unit tests pinning each sort axis, the dependency lift, cycle detection, edge cases. |
plugin/cli/task_cli/README.md | new | Brief noun-level README mirroring plugin/cli/backlog_cli/README.md and plugin/cli/lease_cli/README.md. |
plugin/skills/orchestrate/SKILL.md | modify | Step 4 candidate pickup shells out to sdlc task sort; remove inline sort prose. |
plugin/skills/task-work/SKILL.md | modify | Step 1 no-arg branch shells out to sdlc task sort --limit 1; remove inline sort prose. |
docs/task-sort.md | new | Canonical narrative of the sort algorithm, dependency lift, and priority: field. |
docs/data-model.md | modify | Document the new priority: field; link to docs/task-sort.md. |
docs/skills/orchestrate.md | modify | One-line note that pickup is delegated to sdlc task sort. |
docs/skills/task-work.md | modify | Same one-line note for the no-arg branch. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: The
tasknoun is registered onplugin/cli/sdlc(visible), andplugin/cli/sdlc task sortemits sorted basenames on stdout for everystatus: open/readytask underdocs/planning/tasks/*.mdexcludingautonomy: human-only(default invocation:--status open/ready --exclude-autonomy human-only). - AC-2: A task with
priority: truesorts above a task withpriority: false(or nopriority:field) regardless ofimpact:/complexity:/created:, verified by a unit test namedtest_priority_lifts_above_impact(or similar) inplugin/cli/task_cli/tests/test_sort.py. - AC-3: Within same
priority,impact: highsorts abovemediumabovelow, verified by a unit test. - AC-4: Within same
priorityandimpact,complexity: smallsorts abovemediumabovelarge, verified by a unit test. - AC-5: Within same
priority+impact+complexity, oldercreated:date sorts first; identical tuples fall through to filename sort, verified by a unit test. - AC-6: A task A with
priority: trueanddepends_on:B“ where B has nopriority:field — B sorts above other unpinned tasks (lift propagates), verified by a unit test. - AC-7: A task A with
depends_on:B“ where B is inclosed/done(or anyclosed/*) — B does not appear in the output and does not contribute to A’s sort tuple, verified by a unit test. - AC-8: A
depends_oncycle (A→B→A or longer) causessdlc task sortto exit with code 1 and emitCYCLE basenames=<comma-separated>on stderr, verified by a unit test. - AC-9: Empty input (no tasks match filters) exits 0 with empty stdout, verified by a unit test.
- AC-10:
plugin/skills/orchestrate/SKILL.mdStep 4 shells out tosdlc task sort; the inline “oldest-first” prose is removed.command grep -E "oldest-first order|by .created:. date" plugin/skills/orchestrate/SKILL.mdreturns no matches. - AC-11:
plugin/skills/task-work/SKILL.mdStep 1 no-arg branch shells out tosdlc task sort --limit 1; the inline sort prose is removed.command grep -E "oldest .created:. date|ties broken by filename sort" plugin/skills/task-work/SKILL.mdreturns no matches. - AC-12:
docs/task-sort.mdexists and documents (a) the five-key sort chain with one paragraph per key, (b) the dependency lift and cycle behavior, (c) thepriority:field with usage guidance, (d) a worked example, (e) a forward-looking note thatsdlc.yamlcustomization is deferred to a future task. - AC-13:
docs/data-model.mddocuments the newpriority:field; links todocs/task-sort.mdfor ordering semantics. - AC-14:
docs/skills/orchestrate.mdanddocs/skills/task-work.mdeach contain a one-line note that pickup is delegated tosdlc task sort(plugin/cli/task_cli/sort.py) with a link todocs/task-sort.md. - AC-15:
plugin/entities/task/schema.jsontop-levelversionis5; the newpriorityproperty is declared as{ "type": "boolean" }and is NOT in therequiredarray. - AC-16: A v4-shape task file run through
plugin/skills/entities-migrate/migrate_entities.pyis stamped to v5 with no body change; the newplugin/entities/task/migrations/v4-to-v5.pytransform is discovered automatically by the migrate script’s existing transform-resolution path, and its self-runningtest_v4_to_v5.pyexits 0. - AC-17:
plugin/scripts/new_task.py --prioritywritespriority: trueto the frontmatter; absent flag does not write the key. Verified by a unit test in the existingnew_tasktest surface (or a fresh one if none exists). - AC-18: Running
plugin/cli/sdlc task sort --jsonagainst this repo’sdocs/planning/tasks/*.mdproduces valid JSON whose first element matches the operator’s expectation (manual end-to-end check during implementation; record the verification in the post-mortem).
Out of scope
Section titled “Out of scope”sdlc.yamlconfiguration of the sort chain (task_sort:config key). Default is hardcoded; per-project customization is a follow-up if real demand surfaces.- Finer
impact:gradation (e.g. 5 bucketscritical/high/medium/ low/trivial). Keeping 3 buckets for v1;priority:boolean is the override valve when 3 buckets aren’t enough. - Numeric
priority:(1..5or P0/P1/P2). Boolean is the simplest shape; we revisit if “first among prioritized” becomes a recurring need. - A side-channel markdown file (
docs/planning/priorities.md). Thepriority:frontmatter field replaces that idea. - Automatic
priority:decay (olderpriority: truetasks losing their flag over time). The human manages the flag’s lifecycle. - Consolidating the new sort’s
depends_onwalk withentities-audit’s existing cycle-detection walk. Both walk the same graph; sharing substrate is a future refactor task.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by a 2026-05-27 design discussion in the codex/github-ref-leases-adr
session. Current state: pickup is single-key (created: only),
encoded in prose in two skills, with no way for the human to say
“do this one first.” Design discussion converged on boolean
priority: field + deterministic task_sort.py script + dependency
lift, with sdlc.yaml config deferred and finer impact gradation
deferred.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-28. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
plugin/cli/task_cli/tests/test_sort.py::test_default_invocation_emits_open_ready_basenamesexercises the default filter set;plugin/cli/sdlc task sort --status open/ready --exclude-autonomy human-onlyproduces sorted output against this repo’s corpus (manual confirmation captured under AC-18). - AC-2: auto —
test_priority_lifts_above_impactpins the priority dominance over impact/complexity/created. - AC-3: auto —
test_impact_high_above_medium_above_lowpins the impact ordering. - AC-4: auto —
test_complexity_small_above_medium_above_largepins the complexity ordering. - AC-5: auto —
test_created_oldest_first_with_filename_tiebreakpins the date + filename tiebreaks. - AC-6: auto —
test_priority_propagates_via_depends_on_liftpins the dependency lift (the implementation walks reversedepends_onedges and propagates the dependent’s lead-tuple to its target). - AC-7: auto —
test_depends_on_closed_target_droppedpins both the “closed not in output” and “closed does not contribute to the lift” behaviors. - AC-8: auto —
test_depends_on_cycle_exits_one_with_cycle_markerpins exit code 1 and theCYCLE basenames=<comma-separated>stderr marker shape. - AC-9: auto —
test_empty_input_exits_zero_empty_stdoutpins the empty-result success path. - AC-10: auto —
command grep -E "oldest-first order|by .created:. date" plugin/skills/orchestrate/SKILL.mdreturns no matches (run during implementation). - AC-11: auto —
command grep -E "oldest .created:. date|ties broken by filename sort" plugin/skills/task-work/SKILL.mdreturns no matches. - AC-12: deferred-user —
docs/task-sort.mdships with the five-key chain, dependency lift, cycle behavior,priority:field guidance, worked example, and a forward-looking note onsdlc.yamlcustomization. Please spot-check the prose for clarity and the worked-example correctness. - AC-13: auto —
docs/data-model.mdcarries the newpriority:row on the TASK ERD and a new “Sort-relevant fields” subsection linking todocs/task-sort.md. - AC-14: auto —
docs/skills/orchestrate.mdanddocs/skills/task-work.mdeach carry a one-line pickup-delegation note linking todocs/task-sort.md. - AC-15: auto — schema’s top-level
versionis5(verified by reading the JSON);priorityis declared as{ "type": "boolean" }, not in therequiredarray. - AC-16: auto —
plugin/entities/task/migrations/v4-to-v5.pyships as a pure stamp-onlymigrate(fm)callable;plugin/entities/task/migrations/test_v4_to_v5.pypasses all 11 cases (stamp, idempotency, field preservation includingpriority:, purity, non-dict guard). The migrate script discovers it via its existing transform-resolution path (dry-run shows v4→v5 candidates). - AC-17: auto —
plugin/scripts/test_new_task.pypins the--priorityflag’s three contract cases (setspriority: truewhen present; omits the key when absent; does not disturb other frontmatter fields). - AC-18: agent-manual —
plugin/cli/sdlc task sort --json --limit 2 --status open/ready --exclude-autonomy human-onlyagainst this repo’s corpus emitted:2026-05-28-rename-spawn-task-pr-marker-to-break-done-pr-collision(high/small/2026-05-28) then2026-05-28-rerun-ensure-ready-after-start-commit-to-avoid-frontmatter-conflict(high/small/2026-05-28). Matches the expected algorithm: highest impact + smallest complexity sorts first regardless of created date among unprioritized tasks.
What worked
Section titled “What worked”- The lease ref + heartbeat loop kept the claim alive through the entire (long) implementation without manual intervention.
- The existing
plugin/cli/backlog_cli/shape was a clean template forplugin/cli/task_cli/; mirroring it (with a_common.py, a verb module, an__init__.pyexposing build/dispatch, a tests/ dir) made the new noun drop in cleanly. - The v4-to-v5 transform fell out as an obvious clone of v3-to-v4 with the version bumped; the existing test pattern carried over with minimal edits and locked the contract.
- The PEP-723 self-running test pattern (
uv run --quiet --script) for the new sort tests gave a fast, dependency-free assertion surface — 12 cases run in <2s end-to-end.
Friction and automation gaps
Section titled “Friction and automation gaps”- ensure_ready_mutate.py truncated this task’s
relevance_notevalue when re-serializing the YAML (the relevance_note string contains#, which the YAML round-trip handles inconsistently — the unquoted-#hazard described next). Cost: one rebase-resolution cycle to restore the lost prose. Fix path: ensure_ready_mutate.py should useyaml.safe_dump(default_style='"')or block-string style on every multi-line scalar, so a#in the value cannot be lost on round-trip. (Track separately from the corpus-wide hazard below — same root cause, different consumer.) plugin/skills/entities-migrate/migrate_entities.pydestructively truncates any frontmatter scalar containing an unquoted#(PyYAML treats it as a comment start). Affects 3 active v4 tasks in this corpus today — they were skipped from this PR’s v4→v5 sweep. Fix path: migrate_entities.py should either (a) parse the source file’s frontmatter as bytes and only edit theschema_version:line surgically (no round-trip), or (b) use a YAML loader/dumper that preserves comments and quoting (e.g.ruamel.yaml). The deferred tasks:2026-05-21-probe-plugin-install-shape.md,2026-05-22-task-new-commits-task-file.md,2026-05-24-safe-grep-helper-for-ac-shell.md.start_task.py’s Step 5b rebase hit a frontmatter conflict because the needs-definition commit (from the first ensure-ready run) and the start-commit on main both edited the task file. Recovery requiredgit rebase --skipto drop the obsolete needs-definition commit. Fix path: when ensure_ready_mutate switches to pass mode on a previously-failed task, it should retroactively rewrite or drop the prior needs-definition commit on the branch — or start_task should know to skip a verify-commit’s superseded predecessor automatically.audit_entities._discover_available_migrationsis single-hop only — it doesn’t walk v3→v4→v5 chains, so v3 task files are flagged “schema_version/manual” even though the chain exists. Fix path: implement the multi-hop walk the existing docstring already acknowledges as future work.- The quality gate’s
--diff-against-baselineline-grain false-positives on audit summary lines whose corpus counts shifted between baseline capture and gate invocation (e.g.## tasks (schema v5) — 193 file(s),**Drift summary:** N auto-fixable, M need manual review.). Tracked by the existing task T-BCNP-quality-gate-ignores-summary-and-corpus-lines — this run reproduces the failure mode exactly and adds evidence (10 new-drift findings, of which ~6 are corpus/summary churn and 4 are legitimate deferred-or-pre-existing-drift). - The schema-bump-checklist convention says the bump’s responsibility ends with the new transform
shipping (sweep is a separate
/sdlc:entities-migrateoperation), but the project’s quality gate fails-closed on post-bump drift even when the migration tooling can’t sweep cleanly. Fix path: the gate could special-case the “older-than-current schema_version with auto-fixable transform available” finding shape (don’t gate on these; they’re a known-by-design lag), OR the bump task’s contract could require a passing sweep before merge — at the cost of forcing every bump to ship clean migrate tooling first. migrate_entities.pyexits with"## fixed: 0 file(s)"even when it has successfully migrated dozens of files (the success summary uses zero where the iteration count belongs). Cost: low — caught by inspectinggit diffafter the dry-run-then-real-run cycle. Fix path: increment the counter where the write happens.
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”The Step 8 sub-agent dispatch (/sdlc:spawn-task-pr per bullet) was
SKIPPED in this run because /sdlc:task-work executed under the
inline-fallback path (the Agent tool is unavailable in this
context), and dispatching 7 separate PR-opening sub-skill invocations
serially inline was deemed beyond the inline-fallback’s reasonable
scope. The friction bullets above are the canonical record; a future
operator can run /sdlc:spawn-task-pr for each one (or re-run
/sdlc:task-work Step 8 on this task in an Agent-capable context to
trigger the canonical dispatch).