Skip to content

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.

  1. Add gh issue support to @sksizer/easy-gh (packages/ts/easy-gh/): verbs.ts gains argv builders + parsers for issue list (--label, --json number,title,body,labels,state), issue view and issue edit --add-label/--remove-label; sync.ts gains Gh.issueList()/issueView() (queries) and Gh.setIssueLabel() (a mutation that removes any existing sdlc:stage/* label and adds the target one in a single issue edit call, so an issue is never briefly unlabeled or briefly holding two stage labels).
  2. Task schema v7 -> v8 (solutions/ontological/lib/model/entities/task/schema.ts): add issue: z.string().trim().min(1).optional() (the linked GitHub issue reference, e.g. #1234 or the full URL — pick one canonical shape and validate it with a regex, documented in the field’s .describe()). Add solutions/ontological/lib/model/entities/task/migrations/v7-to-v8.ts mirroring v5-to-v6.ts’s stampOnlyMigration('8') shape (no body transform — the field is optional and additive).
  3. 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 whose issue field matches; ensureLinkedTask(issue, ctx) returns the existing match or creates a new task (via the existing sdlc task create op, seeded with the issue’s title as the task title and the issue’s body folded into the task’s Goal) and sets its issue field via sdlc task update (schema-validated write, not a hand-rolled frontmatter edit). This is what the issues loop’s define-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.
  4. Define the status<->stage mapping table (as code, e.g. a const STATUS_TO_STAGES: Record<TaskStatus, ChainStageName[]> in lib/services/issues/mapping.ts, importing ChainStageName from lib/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 single in-progress status spans every chain stage between ready and close-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, so in-progress can only rule out a mismatch, not pin one exact stage)
    • in-progress/blocked -> same set as in-progress
    • every closed/* status -> [close-out]
  5. Add solutions/ontological/lib/services/issues/ops/sync.ts (sdlc issues sync): for every task with an issue field set, read the issue’s current sdlc:stage/<x> label (live via ctx.gh, or a fixture in tests — see AC-3) and the task’s status; 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: sync never calls a label-mutation or task-update verb itself — no --fix flag yet (see Out of scope). Output: one line per conflict plus a summary count; exit non-zero when any conflict is found (mirroring the apply --check convention), 0 when clean.
  6. 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 real docs/planning/tasks/), lib/services/issues/mapping.test.ts (table shape / every TaskStatus value covered — a missing entry should be a type error, not a runtime gap), and lib/services/issues/ops/sync.test.ts (a fake ctx.gh returning fixture issue/label data + a fixture task corpus; covers a clean pair, a conflicting pair, and the exit-code contract).
AreaNote
packages/ts/easy-gh/verbs.tsNew issue list/issue view/issue edit argv builders + parsers
packages/ts/easy-gh/sync.tsNew Gh.issueList/issueView/setIssueLabel methods
solutions/ontological/lib/model/entities/task/schema.tsNew optional issue field, schema v8
solutions/ontological/lib/model/entities/task/migrations/v7-to-v8.tsNew stamp-only migration
solutions/ontological/lib/services/issues/link.tsNew: find-or-create the linked task
solutions/ontological/lib/services/issues/mapping.tsNew: status<->stage mapping table
solutions/ontological/lib/services/issues/ops/sync.tsNew: sdlc issues sync, conflict reporting
  • AC-1: ensureLinkedTask() given an issue with no existing linked task creates one with issue set to that issue’s reference; given an issue whose reference already matches an existing task’s issue field, returns that task without creating a duplicate.
  • AC-2: sdlc issues sync reports a conflict (non-zero exit) when a linked pair’s task status maps to a stage set that does not contain the issue’s actual sdlc:stage/<x> label, and reports clean (exit 0) when every linked pair is consistent.
  • AC-3: sync’s tests exercise it against a fixture ctx.gh and a fixture task corpus only — no real gh call and no read/write under the real docs/planning/tasks/.
  • AC-4: sync makes no mutating call (no label edit, no task update) in this phase — verified by asserting the fake ctx.gh records zero mutation-verb invocations across all sync tests.
  • AC-5: Every TaskStatus enum value has an entry in STATUS_TO_STAGES (a test iterating the enum against the table, not a hand-eyeballed list).
  • AC-6: bun test over both solutions/ontological/lib/services/issues and solutions/ontological/lib/model/entities/task passes, including the new v7-to-v8 migration test.
  • sync --fix / automatically relabeling or rewriting a task to resolve a conflict (D-0019 Option B — deferred until real conflict data exists).
  • The issues loop’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 status to state on the task schema (that is Phase 10’s hard cutover, not this phase’s).
  • solutions/ontological/lib/config/chain.ts (T-8JJE) for ChainStageName/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.

← Back to Tasks