Craft-Tasks — Post-Design Work-Decomposition Pipeline
SkillDocs & knowledgeUse when a design is already settled (intent, approach, invariants, and boundary decided — typically after deep-interview) and it must be broken into concrete implementation tasks filed as shareable child tickets in the PM tool (Linear sub-issue / Notion / Jira) for the team to track. Triggers inclu
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Craft-Tasks — Post-Design Work-Decomposition Pipeline skill
What this skill tells your AI
The instructions your AI receives, as published by toongri/oh-my-toong-playground in skills/craft-tasks/SKILL.md and read by ahel’s review.
Turns a settled design (the WHAT and the approach are decided) into a set of concrete implementation tasks, materialized as shareable child tickets in the PM tool for the team to track. This is HOW-decomposition — the deliberate inverse of craft-issue's WHAT-slicing.
Where this sits (the chain)
craft-issue deep-interview craft-tasks prometheus ultragoal
(WHAT/story) → (design + intent) → (HOW → task tickets) → (plan per task) → (execute)
the core THIS SKILL optional·usual
craft-tasks is the output-materializer of the design core: deep-interview settles a unit's design; craft-tasks projects that design into trackable work.
When to use / when NOT
- Use when: the design of one unit is settled and you need trackable task tickets in the PM tool.
- NOT when the requirement/WHAT is still open, or no design exists yet → that is craft-issue (it produces the requirement unit; it must run before this).
- NOT when you only need an AI-execution plan, not team-facing tickets → that is prometheus.
REQUIRED BACKGROUND: the design contract and PM-tool write machinery this skill reuses live in craft-issue (../craft-issue/references/issue-craft.md). This skill owns decomposition and task maintenance. Issue/parent handling belongs to craft-issue; only shared PM mechanics are reused for task writes.
Precondition Gate — is the design actually settled?
Do not decompose, and do not invent the missing design, if any of these is still open for the input unit:
- Intent — what outcome the unit delivers and why.
- Approach — the decided way to build it (the ADR-level choices).
- Invariants — the rules that must always hold.
- Boundary — which services/layers/components the unit touches.
If any is open, route back: an open intent, approach, invariant, or boundary → deep-interview; an unclear underlying WHAT (the requirement itself) → craft-issue. Decomposing an unsettled design manufactures HOW that nobody decided — the exact "one giant plan that needs constant mid-course correction" failure this pipeline exists to prevent.
The Inversion — craft-tasks rules are the OPPOSITE of craft-issue
An agent reaching for craft-issue on a post-design job will value-slice into requirement units and fold the real work items away (observed baseline failure). On a settled design the rules invert. ("WHAT-unit" and "work item" below name roles, not any PM-tool level — a team may call them story/task, issue/sub-issue, or anything else.) Hold the right column:
| Axis | craft-issue (WHAT — requirement unit) | craft-tasks (HOW — work item) |
|---|---|---|
| Cut by | user/business value | implementation step / component |
| Layer/platform split (BE / RN / device / DB) | FORBIDDEN — worthless-until-integrated anti-pattern | EXPECTED and correct — each layer is a real, separately-mergeable unit |
| Implementation step (schema, guard, calc, wiring) | FOLD into the requirement unit | MATERIALIZE as its own work-item ticket |
| Produces | the requirement units (design open) | the work items of a settled unit (see the state rule below) |
| Ordering | mostly independent | explicit blocked-by chains are normal — work items are sequenced |
The point of the inversion: craft-issue folds implementation steps because they have no stand-alone user value. craft-tasks materializes them because a trackable, assignable, separately-mergeable unit of work is exactly the team artifact being asked for. Folding here destroys the deliverable.
What routes a unit here is a STATE, not a level or label
The one thing that puts a unit in craft-tasks' hands is that its design is settled — not what the PM tool labels it (project, epic, story, issue, ticket — the label varies by team and does not matter). Vocabulary is per-team; role and state are what decide.
- Decompose the settled unit into its work items — the next-finer trackable child tickets your PM tool offers (in Linear, Sub-issues; the label is irrelevant). Create them under the settled unit.
- Do not decompose a unit whose design is still open — route it back (see the Precondition Gate).
- Recursion, not fixed levels, is the "fractal": if a work item is itself too large to implement as one unit, it re-enters the loop —
deep-interviewsettles ITS design, thencraft-tasksbreaks it down further. There is no level-name to get right; there is only "is this unit's design settled, and does it still need breaking into trackable work items?"
The same ticket is craft-issue's subject while its design is open, and craft-tasks' subject once the design is settled. It flows through both as its state advances — it does not change level.
Scope Fidelity — decompose only what the design contains
Materialize a task only for work the settled design actually names. Do not invent scope: no QA task, no analytics/telemetry task, no notification task, no hardening task unless the design names it. (Baseline agents invented a QA ticket and a notification ticket the design never mentioned — that silently expands the committed work.)
- A genuinely-implied-but-unstated piece (e.g., "this requires a push the design didn't mention") is surfaced as a flagged question to the design owner, recorded as
TBD — needs confirmation, and left OUT of the created task set until confirmed — never silently filed as a task. - Telemetry/analytics/tests that ride inside a component's own work fold into that component's task's done-check; they do not become separate tasks unless the design schedules them separately.
Granularity Contract — where one task begins and ends
One task = the smallest unit of work that a single developer can implement, review, and merge on its own (≈ one PR) and that has a defined done-check. Stop splitting below that.
- This is not file-count or LOC. A task may touch several files; it is one task if it is one coherent, separately-mergeable change with one done-check.
- Two steps that can only be reviewed and merged together are one task, not two.
- A step with no independent done-check (it cannot be verified until a sibling lands) folds into the sibling — unless the design deliberately sequences it as its own deliverable with a
blocked-bylink.
This contract is what makes two runs land on the same grain instead of one cutting 3 tasks and another cutting 9.
Task Title & Body Shape
Required reader template: read presentation.md before drafting and apply its reader check to the exact outgoing body. The reader is an implementing/reviewing engineer without the design conversation. Every 변경 대상 entry includes the component's role in this change beside its name/location; the three-section body and native relation rules below remain authoritative.
Title — name the change, not its position
A task title names the concrete change this task makes, in the team's working language (Korean by default) — component/layer plus the action, specific enough to tell apart from its siblings without opening the body (e.g. [모바일] 프로그램 상세: 미장착 슬롯 흐림 처리 복원).
- No decomposition ordinals. Never append
(item N),(task 3),#2, or any index from your internal task list — that number is a scratchpad artifact, meaningless to whoever reads the board. - Match sibling ticket titles for any layer/platform prefix; do not invent a new prefix scheme on the spot.
Body — reader-facing prose only
New task bodies carry exactly these three sections, in the team's working language. Existing task updates maintain these sections while preserving contributor records (see Task maintenance):
- 목적 — what this task delivers toward the settled design (one or two sentences; cite the design decision it implements).
- 변경 대상 — the component / layer / files this touches. Observational, evidence-backed (from the design's boundary map). This IS allowed here — unlike craft-issue, a task legitimately states HOW.
- 완료 조건 (DoD) — verifiable done-checks, each with a verification method (test / query / manual step). Same observable-AC bar as craft-issue's rubric (
../craft-issue/references/issue-craft.md§2).
Everything else about a task is expressed through the PM tool's native fields, not body prose:
- Dependencies → native relation field, never body prose. A sequenced task's predecessor is set through the PM tool's own relation (Linear
blockedBy/blocks), which the team sees on the ticket and filters on. Never write a## 의존section or a "blocked by X" sentence in the body — a hard dependency described only in prose is invisible to the board. No hard dependency → no relation to set and nothing to write. - Design anchor + parent link → native parent relation. Each child inherits the verified design association through
parentId; put no raw anchor string, per-child anchor label, or "부모 X의 설계 확정 코멘트 참조" boilerplate in its body.
Use the shared design context resolved through craft-issue. Parent definitions and shared-context placement belong to craft-issue.
Example — a settled parent design and one child ticket
- 부모 설계(확정) —
sync.yaml의skills.items를 시작점으로 삼아 각SKILL.md의Skill(...)참조를 재귀적으로 해석하고, 중복을 제거한 스킬 의존성 폐쇄만 대상 플랫폼의 스킬 디렉터리에 배포한다. 누락·순환 참조는 동기화를 실패시키며 폐쇄 밖의 스킬은 건드리지 않는다. 경계는tools/sync.ts,tools/sync.test.ts, 플랫폼별 스킬 배포 경로다. - 자식 티켓 제목 —
sync: 스킬 의존성 폐쇄 수집 단계 추가(only the change, without ordinals or(item N))- 목적 — 확정된 부모 설계에 따라
skills.items와 각SKILL.md의 참조를 재귀 수집해 플랫폼별 배포 단계가 동일한 폐쇄 집합을 사용하게 한다. - 변경 대상 —
tools/sync.ts의skills.items해석·배포 대상 수집 로직과tools/sync.test.ts의 중복·누락·순환 참조 테스트. - 완료 조건 (DoD) —
skills.items: [craft-tasks]에서 시작해 참조된 스킬을 중복 없이 배포 대상에 포함하고 폐쇄 밖의 스킬은 포함하지 않는다(검증:bun test tools/sync.test.ts). 누락·순환 참조는 부분 배포 없이 명시적 오류로 실패한다(검증:bun test tools/sync.test.ts).
- 목적 — 확정된 부모 설계에 따라
(The body contains only the three sections above. Do not write dependencies, anchors, or parent links in the body — with no hard dependency, leave the relation field unset too; the child inherits its verified design association through parentId.)
Write Tail — reuse only applicable craft-issue Stage 6 mechanics
For task writes, reuse the plain-language/humanizer pass, abstract relation/label/write mechanics, and runtime binding. Task maintenance below owns the task update policy.
Tasks have no automated task reviewer: WHAT-only slicing and the mandatory issue-reviewer Checklist Review Gate do not apply to task bodies. craft-issue runs its own workflow for delegated issue/parent work, including its applicable review gates.
Design-anchor gate
The handoff carries one immutable shared metadata value in designAnchor:
designAnchor: "design-anchor: deep-interview:<state.interview_id>".
Accept only the exact canonical value design-anchor: deep-interview:<state.interview_id>, where <state.interview_id> is the non-empty identifier persisted in the settled spec's state.interview_id. Reject a missing or invalid anchor — including an anchor derived from a title, slug, timestamp, or hash — before any child-tree/create operation.
Parent-resolution gate
REQUIRED SUB-SKILL: Use craft-issue whenever an issue or parent needs handling, including finding, creating, supplementing, or updating it. Invoke the repository canonical chained skill literally with Skill(skill: "craft-issue"). Carry this handoff to it:
parentId: "<known parent ID or URL, when available>"
designAnchor: "design-anchor: deep-interview:<state.interview_id>"
settledContext: "<settled design context, inline or canonical external URL>"
Include parentId when known, preserve the exact designAnchor, and pass the settled design context. craft-issue owns the handling policy and record shape; use its current instructions.
Before reading the child tree, re-read the returned parentId and verify that it identifies one parent associated with the exact designAnchor and accessible settled context. An already verified handoff needs no redundant parent write. Any ambiguity, mismatch, failure, or interruption stops child processing; return the issue/parent handling to craft-issue. Every child uses that verified parentId.
The local spec path is input-only. Outgoing bodies, comments, and delegated write context use portable inline evidence or a canonical external URL; never $OMT_DIR, a machine-local path, or file://. When no external design URL exists, pass the settled context inline.
Existing-child / duplicate gate
After the parent-resolution gate, and before any child create, read the verified parent's current child tree and use the organized-tree pattern: validate → update → gap-fill.
Immutable child identity
Every child task has a non-empty opaque immutable taskKey, distinct from designAnchor, parentId, title, purpose, changed target, slug, timestamp, and hash. For a new genuine gap, generate the key once before creation from fresh opaque identity material; never derive it from mutable fields or shared identities, and never regenerate it during update or recovery. A new gap also gets a fresh opaque createIntentId distinct from taskKey; never derive or regenerate taskKey/createIntentId from childId, title, purpose, changed target, slug, timestamp, hash, parentId, anchor, list order, or body similarity.
The task plan/handoff carries these per-child fields:
taskKey: "<existing immutable key for a known task>"
childId: "<verified PM child ID when known>"
taskKey: the existing immutable key for a known task; it is required when updating an existing child. childId: the verified PM child ID when known; it is optional when the task key is available. New gaps may omit taskKey only until craft-tasks generates it. The handoff may also carry taskIdentities, the prior result collection of { taskKey, childId }, so a later maintenance run can preserve keys even when the caller does not know every child ID.
Persist each generated key through the existing create_comment mechanism as one durable, portable, append-only identity comment, separate from reader-facing body sections and change comments. Use this canonical shape exactly:
<!-- Task identity
taskKey: <opaque immutable task key>
-->
Never put identity metadata in body prose or machine-local paths in comments/handoffs. A missing or mismatched identity comment is not a successful create/update.
Durable create-intent protocol
The bundled script is the runtime contract. Invoke it through ${CLAUDE_SKILL_DIR}; its journal is session-scoped local orchestration state, not a PM field, comment, or idempotency primitive. Do not invent a PM field or idempotency primitive: there is no invented PM field or idempotency primitive. The exact command names are list --pending, list --reconciliation, create-prepare, create-child, create-complete, manual-reconciliation, manual-reconciliation-missing, quarantine-journal, receipt-ack, update-prepare, update-mutation-written, update-complete, and get. The create commands are create-prepare, create-child, and create-complete; the manual stop commands are manual-reconciliation, manual-reconciliation-missing, and quarantine-journal. list --pending is read-only and returns deterministic JSON entries for every intent that still needs a receipt acknowledgment across $OMT_DIR/task-write-journal-<safeSessionId>.json, including sourceSessionId, intentId, kind, state, parentId, designAnchor, and childId when present; terminal complete and manual-reconciliation-required intents remain listed, and create entries also expose taskKey. Malformed matching journals are returned as explicit error entries with sourceSessionId.
The commands get, create-child, create-complete, update-mutation-written, update-complete, manual-reconciliation, manual-reconciliation-missing, quarantine-journal, and receipt-ack accept the optional --source-session <sessionId> argument after the command's normal positional arguments. create-prepare, update-prepare, and list reject --source-session; prepare always creates in the current session, and list scans all sessions. For example:
bun "${CLAUDE_SKILL_DIR}/scripts/task-write-journal.ts" list --pending
bun "${CLAUDE_SKILL_DIR}/scripts/task-write-journal.ts" get <intentId> --source-session <sourceSessionId>
Without --source-session, these commands use the current session exactly as before. With it, the session ID must be safe and the command reads or writes only that source journal; there is no automatic fallback across sessions. A quarantined source session is sealed: prepare commands cannot write to it, and recovery must use a new session. Recovery never infers or replaces a child identity.
For every genuine gap, generate the opaque taskKey and prepare the exact proposed creation payload that will be sent to save_issue. create-prepare requires nested creationPayload containing the exact fields for the selected PM binding; when Linear is selected, use title, description, and optional blockedBy as an optional array of predecessor task IDs (omit blockedBy when there is no dependency). Other selected PM bindings retain their native creation fields. This journal write happens before save_issue, so call create-prepare and wait for its JSON result:
printf '%s\n' '{"parentId":"<verified parent ID>","designAnchor":"design-anchor: deep-interview:<state.interview_id>","creationPayload":{"title":"<exact task title>","description":"<exact task description>","blockedBy":[<optional predecessor task IDs>]}}' \
| bun "${CLAUDE_SKILL_DIR}/scripts/task-write-journal.ts" create-prepare
The caller supplies the exact proposed PM-native fields for the selected binding inside creationPayload; when Linear is selected, these are title, description, and optional blockedBy. The journal strips orchestration-only designAnchor and any caller-supplied identityComment from that nested payload, verifies the parentId, and createPrepare injects the verified parentId, generates the opaque taskKey, stores and returns the resulting creationPayload, and keeps identityComment separate from creationPayload. If the proposed payload already contains a different parentId, create-prepare rejects it. Verify that parentId is injected in the returned creationPayload. Pass the returned creationPayload unchanged to save_issue. Use the returned identityComment as the comment body passed separately to create_comment; never add it to the save_issue payload. Do not supply an arbitrary identity comment or replace the returned one. Keep identity metadata out of the reader-facing body; do not put identity metadata in the reader-facing body. Use the returned createIntentId to call save_issue only after the journal result has been persisted with state prepared.
After save_issue returns a child, verify its parentId and exact designAnchor. Only after that verified result, call create-child with the returned childId, the verified parentId, and the exact anchor. This changes the journal to state child-created:
printf '%s\n' '{"childId":"<verified child ID>","parentId":"<verified parent ID>","designAnchor":"design-anchor: deep-interview:<state.interview_id>"}' \
| bun "${CLAUDE_SKILL_DIR}/scripts/task-write-journal.ts" create-child <createIntentId>
Then pass the returned identityComment unchanged to create_comment as a separate comment write. Re-read the child and its exact PM-native creation fields, plus the canonical identity comment. The caller projects the exact re-read create fields into nested creationPayload, including the injected verified parentId. The create-complete input is the verified association plus that nested creationPayload and a separate identityComment. Call create-complete only when all required re-reads pass with those values; this marks the intent complete, so mark the intent complete only after those re-reads.
printf '%s\n' '{"childId":"<verified child ID>","parentId":"<verified parent ID>","designAnchor":"design-anchor: deep-interview:<state.interview_id>","creationPayload":{"title":"<re-read title>","description":"<re-read description>","blockedBy":[<re-read predecessor task IDs, when any>],"parentId":"<verified parent ID>"},"identityComment":"<re-read canonical identity comment>"}' \
| bun "${CLAUDE_SKILL_DIR}/scripts/task-write-journal.ts" create-complete <createIntentId>
create-complete receives the verified association, the nested exact creationPayload projected from the PM re-read, and a separate identityComment. The completion creationPayload includes the injected verified parentId. The identityComment is a separate verification field. It first verifies that the stored identityComment is exactly the canonical comment derived from the intent's taskKey, then deep-compares the whole creationPayload with the stored creationPayload and separately deep-compares the re-read identityComment with the stored identity comment. An arbitrary or mismatched identity comment cannot complete the intent. If the create_comment response is lost, re-read the canonical identity comment before create-complete; specifically, re-read the exact canonical identity comment before create-complete. If the exact comment is already present and valid, never writes a duplicate: use the existing verified child and complete the journal without duplicating the comment after the required re-reads. If no verified childId/result exists, call manual-reconciliation with a nonblank reason and stop. Do not infer a child from title, description, time, or tree position, and do not create a replacement. Replaying create-complete with the identical verified association, exact creationPayload, and exact identityComment is idempotent and returns the already-complete intent.
On create-intent recovery, use only a verified intent-to-child association. When the create intent records a childId, verify its parent and exact anchor, then retry only missing identity/comment writes. If the intent has no childId/result, use a documented PM idempotency/client-request lookup only when that PM primitive actually exists; otherwise surface manual-reconciliation-required and stop. An unreadable source stops safely without a replacement child. Never create a replacement for an uncertain partial child.
Terminal complete and manual-reconciliation-required intents remain durable in the journal and continue to appear in list --pending until an explicit receipt-ack. The caller must durably retain the taskIdentities result from every create/update or recovery, then acknowledge the exact receipt: use the exact parentId and designAnchor, plus the create taskKey and exact optional childId (or the update childId). receipt-ack alone compacts/unlinks the acknowledged intent; it does not perform PM writes. Task journals and recovery artifacts are excluded from generic SESSION_ARTIFACT_PREFIXES TTL deletion; their recognized names are recognition-only to the state-liveness unclassified-file classifier. Preserve them for explicit recovery.
The lock claim owner is fully initialized before atomic publication; retry transient empty release. A stale empty legacy lock may be reclaimed. Preserve malformed or live owner locks and fail acquisition boundedly when they remain held.
Durable update-intent protocol
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 25
- Forks
- 1
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
craft-tasks- Source
- github.com/toongri/oh-my-toong-playground