T-3YQO-markdown-stays-the-source-of-truth-issue-to-task-linking
State: closed/done · Impact: medium · Complexity: large
An issue entering define gets a linked task entity (issue:), and
sdlc issues sync reports a conflict when the issue’s sdlc:stage/<x>
label disagrees with the linked task’s status. Design (including the
status<->stage mapping table and the “task is authoritative,
report-only in this phase” call): D-0019-sdlc-chain-stages-and-labels.
Approach
Section titled “Approach”- Add
gh issuesupport to@sksizer/easy-gh(packages/ts/easy-gh/):verbs.tsgains argv builders + parsers forissue list(--label,--json number,title,body,labels,state),issue viewandissue edit --add-label/--remove-label;sync.tsgainsGh.issueList()/issueView()(queries) andGh.setIssueLabel()(a mutation that removes any existingsdlc:stage/*label and adds the target one in a singleissue editcall, so an issue is never briefly unlabeled or briefly holding two stage labels). - Task schema
v7->v8(solutions/ontological/lib/model/entities/task/schema.ts): addissue: z.string().trim().min(1).optional()(the linked GitHub issue reference, e.g.#1234or the full URL — pick one canonical shape and validate it with a regex, documented in the field’s.describe()). Addsolutions/ontological/lib/model/entities/task/migrations/v7-to-v8.tsmirroringv5-to-v6.ts’sstampOnlyMigration('8')shape (no body transform — the field is optional and additive). - Add
solutions/ontological/lib/services/issues/link.ts:findLinkedTask(issueRef)scans the task corpus (via the existing entity-read layer,lib/model/entities/task/read.ts/lib/model/read.ts— never a hand-rolled frontmatter parse) for a task whoseissuefield matches;ensureLinkedTask(issue, ctx)returns the existing match or creates a new task (via the existingsdlc task createop, seeded with the issue’s title as the task title and the issue’s body folded into the task’s Goal) and sets itsissuefield viasdlc task update(schema-validated write, not a hand-rolled frontmatter edit). This is what theissuesloop’sdefine-stage dispatch (T-0AKS) calls; also exercise it directly in this task’s own tests so linking doesn’t depend on the loop existing yet. - Define the status<->stage mapping table (as code, e.g. a
const STATUS_TO_STAGES: Record<TaskStatus, ChainStageName[]>inlib/services/issues/mapping.ts, importingChainStageNamefromlib/config/chain.ts):planning/draft,planning/backlog,planning/proposed->[triage]planning/needs-definition->[define]open/ready->[ready]in-progress->[implement, check, judge, review, respond, merge](the task contract’s singlein-progressstatus spans every chain stage betweenreadyandclose-out; the mapping is a SET because the task file alone cannot say which of those the task is currently in — the issue’s own label is the more precise signal for that sub-position, soin-progresscan only rule out a mismatch, not pin one exact stage)in-progress/blocked-> same set asin-progress- every
closed/*status ->[close-out]
- Add
solutions/ontological/lib/services/issues/ops/sync.ts(sdlc issues sync): for every task with anissuefield set, read the issue’s currentsdlc:stage/<x>label (live viactx.gh, or a fixture in tests — see AC-3) and the task’sstatus; look up the expected stage set from step 4; if the label’s stage is not in that set, record a conflict{ task, issue, taskStatus, expectedStages, actualStage }. Report-only in this phase:syncnever calls a label-mutation or task-update verb itself — no--fixflag yet (see Out of scope). Output: one line per conflict plus a summary count; exit non-zero when any conflict is found (mirroring theapply --checkconvention), 0 when clean. - Tests:
lib/services/issues/link.test.ts(create-new and reuse-existing paths, using a temp task corpus the way other entity tests do — never the realdocs/planning/tasks/),lib/services/issues/mapping.test.ts(table shape / everyTaskStatusvalue covered — a missing entry should be a type error, not a runtime gap), andlib/services/issues/ops/sync.test.ts(a fakectx.ghreturning fixture issue/label data + a fixture task corpus; covers a clean pair, a conflicting pair, and the exit-code contract).
| Area | Note |
|---|---|
packages/ts/easy-gh/verbs.ts | New issue list/issue view/issue edit argv builders + parsers |
packages/ts/easy-gh/sync.ts | New Gh.issueList/issueView/setIssueLabel methods |
solutions/ontological/lib/model/entities/task/schema.ts | New optional issue field, schema v8 |
solutions/ontological/lib/model/entities/task/migrations/v7-to-v8.ts | New stamp-only migration |
solutions/ontological/lib/services/issues/link.ts | New: find-or-create the linked task |
solutions/ontological/lib/services/issues/mapping.ts | New: status<->stage mapping table |
solutions/ontological/lib/services/issues/ops/sync.ts | New: sdlc issues sync, conflict reporting |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
ensureLinkedTask()given an issue with no existing linked task creates one withissueset to that issue’s reference; given an issue whose reference already matches an existing task’sissuefield, returns that task without creating a duplicate. - AC-2:
sdlc issues syncreports a conflict (non-zero exit) when a linked pair’s taskstatusmaps to a stage set that does not contain the issue’s actualsdlc:stage/<x>label, and reports clean (exit 0) when every linked pair is consistent. - AC-3:
sync’s tests exercise it against a fixturectx.ghand a fixture task corpus only — no realghcall and no read/write under the realdocs/planning/tasks/. - AC-4:
syncmakes no mutating call (no label edit, no task update) in this phase — verified by asserting the fakectx.ghrecords zero mutation-verb invocations across all sync tests. - AC-5: Every
TaskStatusenum value has an entry inSTATUS_TO_STAGES(a test iterating the enum against the table, not a hand-eyeballed list). - AC-6:
bun testover bothsolutions/ontological/lib/services/issuesandsolutions/ontological/lib/model/entities/taskpasses, including the newv7-to-v8migration test.
Out of scope
Section titled “Out of scope”sync --fix/ automatically relabeling or rewriting a task to resolve a conflict (D-0019 Option B — deferred until real conflict data exists).- The
issuesloop’s per-tick dispatch and label-advance mechanics (T-0AKS) — this task provides the linking and sync primitives the loop calls, not the loop itself. - Renaming
statustostateon the task schema (that is Phase 10’s hard cutover, not this phase’s).
Dependencies
Section titled “Dependencies”solutions/ontological/lib/config/chain.ts(T-8JJE) forChainStageName/CHAIN_STAGE_NAMES. If T-8JJE has not landed yet when this task starts, take a short-lived local copy and switch to the import once it lands.