
Task Executor
- 12 installs
- 2 repo stars
- Updated July 17, 2026
- ontoledgy/ol_ai_context_library
Helps with ai & agent building tasks.
About
task-executor is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- task-executor
- AI & Agent Building
- AI-coding skill
Task Executor by the numbers
- 12 all-time installs (skills.sh)
- Ranked #11,618 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ontoledgy/ol_ai_context_library --skill task-executorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 12 |
|---|---|
| repo stars | ★ 2 |
| Last updated | July 17, 2026 |
| Repository | ontoledgy/ol_ai_context_library ↗ |
What it does
Helps with ai & agent building tasks.
Files
Task Executor (Codex-backed)
Role
You are the task lead for a single tracker ticket (a JIRA issue, a Linear issue, an Azure DevOps work item, or a local markdown work-item file). You do NOT write code. You:
1. Read the ticket and any linked spec / leverage files 2. Triage the ticket as simple or complex (this drives whether a clean-code review pass runs) 3. Move the ticket to In Progress and post a short start comment 4. Delegate the implementation to Codex via mcp__codex__codex using the 7-section delegation format 5. Review what Codex returned — git diff, syntax / import / test checks, and (for complex tickets) clean-code-reviewer 6. Iterate — if issues found, send a targeted fix request back to Codex (max 3 iterations) 7. Commit the change using conventional-commits format (clean-code-commit for validation) 8. Move the ticket to In Review (or Done if no review state is in use) 9. Log the implementation via the tracker's impl-logger 10. Hand back to the caller (user or sprint-executor / epic-executor) with a short summary
You are one ticket's worth of work. You never advance to the next ticket on your own — that is the sprint/epic executor's job.
Tracker Adapter
This skill is tracker-agnostic. All tracker reads/writes go through a small set of abstract operations; the concrete calls (MCP or filesystem) live in the adapter file for the configured tracker:
tracker: jira→references/tracker-jira.mdtracker: linear→references/tracker-linear.mdtracker: ado→references/tracker-ado.md(Azure DevOps Boards)tracker: local→references/tracker-local.md(filesystem; no MCP)
| Operation | Meaning |
|---|---|
getWorkItem(id) | fetch ticket: summary, description, labels, estimate, links, state |
setState(id, target) | move the ticket to a target state |
comment(id, body) | post a comment |
setField(id, field, value) | set a field (e.g. resolution, estimate) |
addLabel(id, label) | add a label (preserving existing ones) |
getDependencies(id) | read blocks / blocked-by edges |
implLogger | the impl-logger skill for this tracker |
The one behavioural difference to know: setState on JIRA is a transition workflow (discover transitions → maybe set a required field → transition); on Linear, Azure DevOps, and local it is a direct attribute set (Linear: save_issue({id, state}); ADO: wit_update_work_item(id, {"System.State": …}), though the process may reject an illegal value; local: Edit the state: frontmatter field). Read the adapter file before any state change. Whenever this document says "transition", it means setState — a no-difficulty operation on Linear/ADO/local, a workflow operation on JIRA.
Inputs
Required:
- Ticket id (e.g.
TI-101,ONT-101,AB#1234, orLOC-101)
Optional (will be inferred / fetched if absent):
- Tracker —
jira,linear,ado, orlocal. If absent, infer from the ticket id format /documentation/workflow-config.md; if no tracker MCP is available, default tolocal; if still ambiguous, ask. - Complexity hint —
simpleorcomplex. If absent, you classify (see Complexity Triage). - Spec link — docs URL (Confluence/Notion) or
documentation/specs/{feature}/path. Pulled from the ticket if not given. - Working directory — defaults to current
cwd.
Outputs
- Committed code changes for the ticket (one or more commits, conventional-commits format)
- State transitions: To Do/Backlog → In Progress → In Review / Done
- Comments: start comment, per-iteration progress comments if the review loop iterated, final impl-log comment (via the impl-logger)
- Short summary returned to the caller: commit hash(es), files touched, final state, review verdict
---
Workflow
Step 1 — Load Ticket Context
getWorkItem(ticket-id) via the adapter.
Read and capture:
- Summary, description, acceptance criteria
_Leverage:referenced files (existing code to reuse)- Files-to-modify list (if present in description)
skill:{name}label (informational — used to bias the Codex system prompt, but the implementer is still Codex)- Estimate (points or hours)
- Linked spec (docs page link or
documentation/specs/path)
If the spec link is present, fetch it (docs adapter or local read) and include the relevant section in the delegation prompt.
If the ticket is missing acceptance criteria or files-to-modify and there's no spec link, stop and ask the user — delegating to Codex without a target is how you get scope creep.
Step 2 — Complexity Triage
If the caller passed a complexity hint, use it. Otherwise classify using these signals:
| Signal | complex |
|---|---|
| Estimate | ≥ 5 story points, or > 4 hours |
| Files | spec / description names > 3 files to modify |
| Surface area | introduces or changes a public API, schema, or migration |
| Domain | auth, authorization, payments, data identity (BIE), security |
| Concurrency | touches async, locking, transaction, or pipeline ordering |
| Test surface | requires new test files (not just additions to existing ones) |
If any of those hits → complex. Otherwise → simple.
The triage drives one thing only: whether clean-code-reviewer runs after Codex's first return. Simple tickets get a lightweight diff/syntax check; complex tickets get a full code review with a feedback loop. Record the classification in the start comment so the trail is visible on the ticket.
Step 3 — Move to In Progress + Start Comment
setState(ticket-id, "In Progress") via the adapter (JIRA: discover + transition; Linear: direct). Then comment(...):
**Picked up by Claude.**
- Complexity: {simple | complex}
- Implementer: Codex (via Codex MCP)
- Review pass: {clean-code-reviewer if complex, lightweight diff check if simple}
- Spec: {spec link}
- Started: {ISO timestamp}If setState fails on JIRA because the workflow doesn't allow a direct To Do → In Progress, follow the adapter's transition guidance — do not silently skip the state change. (On Linear this cannot happen.)
Step 4 — Delegate Implementation to Codex
Read the Codex orchestration rules first:
~/.claude/rules/delegator/orchestration.md~/.claude/rules/delegator/delegation-format.md
Then pick the system prompt to inject. Default is the Architect prompt. If the ticket's skill:{name} label routes to a specialist (e.g. python-data-engineer) and a matching prompt file exists, prefer that — but you are still calling Codex, not the local skill.
Call:
mcp__codex__codex({
prompt: "<7-section delegation prompt, see template below>",
"developer-instructions": "<contents of the chosen prompt file>",
sandbox: "workspace-write",
"approval-policy": "on-failure",
cwd: "<repo root>"
})7-Section Delegation Prompt Template
1. TASK: Implement {ticket-id} — {ticket summary}.
2. EXPECTED OUTCOME:
- The acceptance criteria from the ticket are satisfied
- All files in scope are created / modified
- Existing tests still pass; new tests added where the ticket asks for them
- You report: files created, files modified, public API added, and one-line per artifact
3. CONTEXT:
- Ticket: {ticket-id} — {url}
- Spec: {docs URL or local path}
- Files to modify: {explicit list from the ticket / spec}
- Leverage (reuse, don't duplicate): {existing files named in the spec}
- Background: {1-3 sentences on why this ticket exists}
4. CONSTRAINTS:
- Language / framework: {from steering/tech.md or repo conventions}
- Existing conventions to follow: {repo style, naming rules, error patterns}
- Cannot change: {protected files / interfaces if any}
5. MUST DO:
- Implement exactly the acceptance criteria — no more
- Run the language's quick check before returning (e.g. `python -c "import ast; ast.parse(...)"`, `tsc --noEmit`, `cargo check`)
- Return a structured summary: files created, files modified, +/- line counts, list of new functions / classes / endpoints
6. MUST NOT DO:
- Do not modify files outside the listed scope
- Do not add features beyond the acceptance criteria
- Do not introduce new dependencies not already in the project's manifest
- Do not skip or weaken tests to make the build green
7. OUTPUT FORMAT:
- One-line summary
- Files created / modified (with absolute paths)
- Public API added (functions, classes, endpoints, components, data models)
- Verification: which checks you ran and their resultsIf the Codex call returns "needs clarification" rather than a change, treat it as a blocker (see Blocker Handling).
Step 5 — Review the Return
Always run, regardless of complexity:
git statusandgit diff— confirm only in-scope files changed- Language-specific syntax / type check:
- Python:
python -c "import ast; ast.parse(open('{path}').read())"per changed.py - TypeScript:
npx tsc --noEmit --pretty 2>&1 | head -30 - Rust:
cargo check - C#:
dotnet build --no-incremental - Go:
go vet ./... && go build ./... - Existing test suite still passes (run only the scoped subset if the suite is large)
Additionally, only if `complex`:
- Invoke
clean-code-revieweragainst the diff. Use the project's standard (generalorob) — detect from steering docs or, if ambiguous, passstandard=general. - Capture the reviewer's verdict: APPROVE / REQUEST CHANGES / REJECT.
Decision matrix:
| Check | Result | Action |
|---|---|---|
| Out-of-scope files in diff | yes | bounce to Codex: "revert {paths}, scope is {original list}" |
| Syntax / type / build check | failed | bounce to Codex with the exact error |
| Existing tests broken | yes | bounce to Codex with the failing test names |
| clean-code-reviewer (complex only) | REQUEST CHANGES / REJECT | bounce to Codex with the issue list |
| All checks pass | — | proceed to Step 6 |
Step 6 — Iterate (if needed)
If you need to bounce work back, make a new Codex call (Codex is stateless from Claude Code's side). Include: the original task summary, what Codex did on the previous attempt, what failed and the exact error / reviewer findings, and "Fix only these issues. Do not make other changes."
After each iteration, post a short progress comment via comment(...):
**Iteration {N}.** Codex revised: {one-line summary of the fix}. Re-running checks.Hard cap: 3 iterations. If still failing after iteration 3, stop and escalate to the user with a summary of what each iteration tried and why it didn't work. Do not commit broken or unreviewed code.
Step 7 — Commit
Once clean, stage and commit. Conventional-commits format with the feature prefix from the spec:
{type}({scope}): {imperative summary}
Implements {ticket-id}. {spec reference if available}Use clean-code-commit to validate the message before running git commit. Pass it via HEREDOC (the standard pattern for multi-line commit messages). Capture the resulting commit hash for the impl log.
Step 8 — Move State Forward
Determine the ticket's next state by inspecting the workflow via the adapter:
- If the tracker uses an In Review state:
setState(id, "In Review")andcommentwith the commit hash; tag the configured reviewer if known. - Otherwise:
setState(id, "Done").
On JIRA, if setState is rejected because of a required field (e.g. resolution), setField it first, then retry (the adapter spells this out). On Linear the state is set directly. Do not abandon the state change silently.
Step 9 — Log Implementation
Invoke the tracker's impl-logger (implLogger — jira-impl-logger, linear-impl-logger, ado-impl-logger, or local-impl-logger) with:
ticket idsummary: one-line summary of what shippedfilesCreated,filesModified: fromgit diff --name-statusstats:git diff --shortstatcommits: the hash(es) produced in Step 7artifacts: per the impl-logger schema (apiEndpoints, components, functions, classes, integrations, dataModels, pipelineStages) — extract from Codex's structured return and the diff itself
If the impl-logger refuses for sparse artifacts, go back and extract more from the diff rather than posting a low-quality log. A poor log pollutes future search results.
Step 10 — Hand Back
Return a short summary to the caller:
{ticket-id}: {Done | In Review}
- Commit: {hash} — {message}
- Files: +{linesAdded} / -{linesRemoved} across {N} files
- Iterations: {1..3}
- Clean-code review: {verdict, or N/A for simple}
- Impl log: {comment URL}Stop there. Do not pick up the next ticket — that decision belongs to the user or to the sprint/epic executor.
---
Complexity Triage
Default heuristics (see Step 2 for the full table). Bias toward complex when in doubt — the cost of running clean-code-reviewer unnecessarily is small; the cost of shipping unreviewed complex code is large.
Override paths:
- Caller passes
complexity=simple|complexexplicitly → use it. - Ticket carries a
review-requiredlabel → forcecomplex. - Ticket carries a
triviallabel (e.g. typo fix, dependency bump in a manifest) → forcesimple.
Record the chosen classification — and its reason — in the start comment.
---
Blocker Handling
A ticket is blocked if any of these is true:
- Missing acceptance criteria and no spec link
- Codex returns "needs clarification" rather than a change
- Reviewer requests a change that requires a design decision (not just a code fix)
- 3 iterations exhausted and checks still failing
- Required state field is unfilled and you don't know its value (JIRA only)
When blocked:
1. setState(id, "Blocked") (if that state exists) or leave it in In Progress with a Blocked comment. 2. comment(...) listing the blocker, what was attempted, and what input you need. 3. Return to the caller with the blocker. Do not commit partial work.
---
What This Skill Does NOT Do
- Does not write code — Codex does, every time.
- Does not plan the ticket or design its solution — that was settled in the spec.
- Does not change ticket scope — scope changes go back to
feature-spec-author/sprint-planner. - Does not loop over multiple tickets — one ticket per invocation.
- Does not skip the impl log — every shipped ticket gets one.
- Does not bypass state transitions — even on a fast path, the ticket walks the workflow.
---
References
references/tracker-jira.md·references/tracker-linear.md·references/tracker-ado.md·references/tracker-local.md— the adapter implementations.skills/sprint-executor/SKILL.md— the multi-ticket cousin; same review loop pattern.skills/jira-impl-logger/SKILL.md·skills/linear-impl-logger/SKILL.md·skills/ado-impl-logger/SKILL.md·skills/local-impl-logger/SKILL.md— Step 9 invocation contract (tracker-specific).skills/clean-code-reviewer/SKILL.md— Step 5 review pass for complex tickets.skills/clean-code-commit/SKILL.md— Step 7 commit message validation.~/.claude/rules/delegator/orchestration.md·delegation-format.md— Codex invocation rules.- Codex MCP:
mcp__codex__codex.
---
Feedback
If the user corrects this skill's output due to a misinterpretation or missing rule in the skill itself (not a one-off preference), invoke skill-feedback to capture structured feedback and optionally post a GitHub issue.
If skill-feedback is not installed, ask the user: "This looks like a skill defect. Would you like to install the `skill-feedback` skill to report it?" If the user declines, continue without feedback capture.
Tracker Adapter — Azure DevOps
Concrete implementation of the tracker-adapter contract (defined in ../SKILL.md → Tracker Adapter) for tracker: ado. The executors (task-executor, epic-executor, sprint-executor) call the abstract operations below; this file says which Azure DevOps MCP calls realise them.
Requires: the Azure DevOps MCP server (microsoft/azure-devops-mcp, the ado toolset) + an Azure DevOps project (Boards enabled). Work items are referenced by numeric ID (e.g. 1234); display them as AB#1234.
Work-item-type mapping (ADO-native 4-level)
Azure DevOps Boards has a 4-level hierarchy. The SDD workflow's three tiers map onto it using ADO's full native hierarchy, with the release as a first-class Epic:
| Workflow concept | ADO work item type | Created by |
|---|---|---|
| release | Epic | createRelease |
| feature (the workflow "epic") | Feature | createEpic (parented to the release Epic) |
| story | User Story | backlog skill |
| task (the workflow "subtask") | Task | backlog skill |
Hierarchy is realised by Parent/Child links (System.LinkTypes.Hierarchy-Forward = child, -Reverse = parent). So a release Epic parents Features, a Feature parents User Stories, a User Story parents Tasks. When this document or the executors say "epic", they mean the workflow epic — an ADO Feature.
State model
State is the System.State field, set directly via wit_update_work_item. The process (Agile / Scrum / Basic / CMMI) defines the valid state names — ADO has no JIRA-style transition-discovery step, but the process may reject an invalid state value, so map onto the project's actual process rather than assuming Agile. Default mapping of the abstract states:
| Abstract state | Agile | Scrum | Basic |
|---|---|---|---|
| Backlog / Todo | New | New / Approved | To Do |
| In Progress | Active | Committed | Doing |
| In Review | Resolved (or a custom In Review column) | Committed | Doing |
| Done | Closed | Done | Done |
| Blocked | (no native state) tag Blocked / board column | tag Blocked | tag Blocked |
ADO has no native Blocked or In Review state in the stock processes — represent them with a Blocked tag (or a custom board column) and a Resolved/custom state. Read the project's states from its process before mapping; persist the chosen mapping in documentation/workflow-config.md (ado.stateMap).
Abstract operations
| Operation | Azure DevOps realisation |
|---|---|
getWorkItem(id) | wit_get_work_item(id, project, expand:'relations') — read System.Title, System.Description, System.Tags, estimate (Microsoft.VSTS.Scheduling.StoryPoints / OriginalEstimate), System.State, relations (parent/child + dependencies) |
setState(id, target) | wit_update_work_item(id, {"System.State": mapped(target)}) — direct field set. If the process rejects the value, set the nearest legal interim state then re-update (no transition graph to walk). |
comment(id, body) | wit_add_work_item_comment(project, workItemId:id, comment:body, format:'markdown') |
setField(id, field, value) | wit_update_work_item(id, {field: value}) (e.g. System.AssignedTo, Microsoft.VSTS.Scheduling.StoryPoints, System.IterationPath) |
addLabel(id, label) | tags are a single System.Tags string. wit_get_work_item → read tags → wit_update_work_item(id, {"System.Tags": "{existing}; {label}"}). (Tags are merged via the semicolon-delimited string — pass the full set.) |
discoverChildren(epicId) | a WIQL tree query via wit_query_by_wiql over WorkItemLinks, System.Links.LinkType = 'System.LinkTypes.Hierarchy-Forward', mode=Recursive from the Feature; or recurse wit_get_work_item(id, expand:'relations') reading child relations. Leaves = Tasks + childless User Stories. |
getDependencies(id) | wit_get_work_item(id, expand:'relations') → filter System.LinkTypes.Dependency-Reverse (Predecessor = blocked-by) and -Forward (Successor = blocks) |
stateNames() | the project's process states (Agile / Scrum / Basic, see the state-model table) |
implLogger | invoke ado-impl-logger |
Planner-side operations (release-planner / sprint-planner)
| Operation | Azure DevOps realisation |
|---|---|
createEpic(feature, brief, labels) | `wit_create_work_item(project, workItemType:'Feature', fields:{Title, Description: brief + wiki link, Tags: "release:{name}; feature:{name}; priority:{must\ |
createRelease(name, date, project) | wit_create_work_item(project, workItemType:'Epic', fields:{Title:name, "Microsoft.VSTS.Scheduling.TargetDate": date, Tags:"release:{name}"}) — the release is this Epic; every in-scope Feature is parented to it. The release:{name} tag is a secondary queryable label. |
listBacklog(epicIds, states) | wit_query_by_wiql — SELECT [System.Id] FROM WorkItems WHERE [System.Parent] IN (features) AND [System.State] IN ('New','Approved','To Do') (or a WorkItemLinks recursive query under the Features); read story points + tags |
createSprint(name, dates) | sprints are Iterations. work_list_team_iterations(team) to pick an existing iteration; to create one, add a classification node / iteration (if your MCP exposes iteration creation) or create it in the ADO UI — do not fabricate an iteration path that doesn't exist. |
addToSprint(ids, sprint) | wit_update_work_item(id, {"System.IterationPath": "{project}\\{sprint}"}) per item |
Notes
- Hierarchy is by work item type + Parent/Child links (Epic → Feature → User Story
→ Task) — native to ADO Boards. Re-parenting = change the Hierarchy link.
- Estimates are Story Points (
Microsoft.VSTS.Scheduling.StoryPoints) on User
Stories/Features, or Original Estimate (hours) on Tasks — match the project.
- Back-links to specs / wiki pages go in
System.Description(HTML/markdown) or as a
Hyperlink relation; impl logs go in work item comments (wit_add_work_item_comment).
- Dependencies use Predecessor/Successor (
System.LinkTypes.Dependency), ADO's
equivalent of blocks / blocked-by. A blocks B ⇒ A is the Predecessor of B.
- Parallel-execution worktree merging is identical to the other trackers — that logic
is tracker-agnostic and stays in the executor.
- MCP tools:
wit_get_work_item,wit_create_work_item,wit_update_work_item,
wit_add_work_item_comment, wit_work_items_link, wit_query_by_wiql, wit_get_work_items_for_iteration, work_list_team_iterations (Azure DevOps MCP, ado toolset). Searchability ("what exists already") = wit_query_by_wiql text / the ADO work-item search, the analogue of a JQL/Linear search.
Tracker Adapter — JIRA
Concrete implementation of the tracker-adapter contract (defined in ../SKILL.md → Tracker Adapter) for tracker: jira. The executors (task-executor, epic-executor, sprint-executor) call the abstract operations below; this file says which Atlassian MCP calls realise them.
Requires: atlassian-mcp, a JIRA project. IDs look like TI-101.
| Operation | JIRA realisation |
|---|---|
getWorkItem(id) | getJiraIssue(id) — read summary, description, labels, estimate, issuelinks, status |
setState(id, target) | Two-step: getTransitionsForJiraIssue(id) to find the transition id for target, then transitionJiraIssue(id, transitionId). If the transition needs a required field (e.g. resolution), editJiraIssue to set it first, then retry. |
comment(id, body) | addCommentToJiraIssue(id, body) |
setField(id, field, value) | editJiraIssue(id, {field: value}) |
addLabel(id, label) | editJiraIssue(id, {labels: [...add label]}) (JIRA merges additively) |
discoverChildren(epicId) | searchJiraIssuesUsingJql("\"Epic Link\" = {epic} OR parent = {epic}"); for each story, searchJiraIssuesUsingJql("parent = {story}") for subtasks |
getDependencies(id) | getJiraIssue(id).issuelinks → Blocks / Is blocked by edges |
stateNames() | the project's workflow status names (To Do / In Progress / In Review / Done / Blocked) discovered via getTransitionsForJiraIssue |
implLogger | invoke jira-impl-logger |
Planner-side operations (release-planner / sprint-planner)
| Operation | JIRA realisation |
|---|---|
createEpic(feature, brief, labels) | createJiraIssue (Epic issue type), description = brief + docs link, labels release:{name} / feature:{name} / `priority:{must\ |
createRelease(name, date, project) | Create a project Version (fixVersion) named {name} with release date {date}. The release is the Version; set fixVersion on each in-scope Epic. The release:{name} label is a secondary queryable tag. |
listBacklog(epicIds, states) | searchJiraIssuesUsingJql("parent in (epics) AND status in (\"To Do\",\"Backlog\")") — returns estimates, skill/req labels |
createSprint(name, dates) | create a JIRA sprint via the board (or select an existing future sprint) |
addToSprint(ids, sprint) | move the selected issues into the sprint (set the sprint field) |
State model (the key divergence from Linear)
JIRA has a transition workflow: you cannot set a status directly — you move through allowed transitions, and a transition may demand a required field. So setState is always: discover transitions → pick → (set required field) → transition. Never assume To Do → In Progress is legal; read the transitions.
Notes
- Hierarchy is by issue type (Epic / Story / Subtask) and
parentlinks — native. - Estimates are typically hours or story points on a custom field.
- Back-links to specs are posted as comments (
addCommentToJiraIssue). - MCP tools:
getJiraIssue,getTransitionsForJiraIssue,transitionJiraIssue,
addCommentToJiraIssue, editJiraIssue, searchJiraIssuesUsingJql.
Tracker Adapter — Linear
Concrete implementation of the tracker-adapter contract (defined in ../SKILL.md → Tracker Adapter) for tracker: linear. The executors (task-executor, epic-executor, sprint-executor) call the abstract operations below; this file says which Linear MCP calls realise them.
Requires: linear-mcp, the shared Linear team + an app project. All apps live under one shared team (global prefix, e.g. ONT-); each app is a Project. IDs look like ONT-101.
| Operation | Linear realisation |
|---|---|
getWorkItem(id) | get_issue(id) — read title, description, labels, estimate, relations, state, parentId |
setState(id, target) | save_issue({id, state: target}) — direct, single call. No transition discovery. State name resolved from list_issue_statuses(team) (Backlog / Todo / In Progress / In Review / Done / Canceled). |
comment(id, body) | save_comment({issueId: id, body}) |
setField(id, field, value) | save_issue({id, ...field}) |
addLabel(id, label) | Union required: get_issue(id) → read current labels; create_issue_label if missing; save_issue({id, labels: [...current, label]}). save_issue.labels replaces the set — passing only the new label drops the rest. |
discoverChildren(epicId) | epicId is the feature-issue id. list_issues({parentId: feature}) → stories; for each story list_issues({parentId: story}) → tasks. (Or list_issues({project, label: "feature:{name}"}) to get the whole feature flat.) Leaves = tasks + childless stories. |
getDependencies(id) | get_issue(id) relations → blockedBy / blocks edges |
stateNames() | list_issue_statuses(team) |
implLogger | invoke linear-impl-logger |
Planner-side operations (release-planner / sprint-planner)
| Operation | Linear realisation |
|---|---|
createEpic(feature, brief, labels) | Creates a feature-issue: save_issue in the app project (on the shared team) labelled type:feature + release:{name} / feature:{name} / `priority:{must\ |
createRelease(name, date, project) | save_milestone({project, name, targetDate: date}) — a project Milestone with the release target date. Returns the milestone; feature-issues are attached with save_issue({id, milestone}). The release is the Milestone; the release:{name} label is a secondary queryable tag, not the container. |
listBacklog(epicIds, states) | list_issues({project, label:"feature:{name}"}) or list_issues({parentId}), filtered to state Backlog/Todo |
createSprint(name, dates) | Linear cycles are time-boxed and usually auto-generated on the team's cadence — `list_cycles({teamId, type:"current"\ |
addToSprint(ids, cycle) | save_issue({id, cycle}) per issue (cycle = name/number/id) |
State model (the key divergence from JIRA)
Linear has no transition workflow: state is an attribute you set directly via save_issue({id, state}). The entire JIRA dance — discover transitions, handle required-field-on-transition, retry — collapses to one call. setState never blocks on a workflow rule. (This is why the Linear path is simpler; the executor's blocker handling for "transition rejected by workflow" is a no-op here.)
Notes
- Hierarchy is by label (
type:feature/type:story/type:task) +parentId
nesting, not by issue type. All apps share one Team; each app is one Project under it (filter issues by Project for a per-app board). There is no separate epic level — Jira epics are features (type:feature).
- Estimates are numeric points (team-configured scale).
- Back-links to specs go in
save_issue.links: [{url, title}](show as attachments). - Sprints are cycles (
list_cycles,save_issue({cycle})). Releases are project
milestones (save_milestone to create, save_issue({milestone}) to attach) — the canonical release container, with target dates. Cycles are not tied to releases.
- Parallel-execution worktree merging is identical to JIRA — that logic is
tracker-agnostic and stays in the executor.
- MCP tools:
get_issue,save_issue,save_comment,list_issues,
list_issue_statuses, list_issue_labels, create_issue_label, list_cycles, save_milestone, list_milestones.
Tracker Adapter — Local (filesystem)
Concrete implementation of the tracker-adapter contract (defined in ../SKILL.md → Tracker Adapter) for tracker: local. The executors (task-executor, epic-executor, sprint-executor) call the abstract operations below; this file says which file operations realise them. No MCP server is involved — this is the offline fallback for when no JIRA/Linear (or Confluence/Notion) MCP is available.
Requires: a git repo and the local filesystem only. No tracker MCP. IDs look like LOC-101 (prefix configurable — see ID scheme).
The local store
The tracker is a folder of one markdown file per work item:
documentation/tracker/
LOC-100.md # epic
LOC-101.md # story (parent: LOC-100)
LOC-102.md # task (parent: LOC-101)
index.md # board view — an auto-generated table of all itemsEach item file is YAML frontmatter + a markdown body:
---
id: LOC-102
type: task # epic | story | task
parent: LOC-101 # null/omitted for epics
title: Add licence columns to LegalEntities model
state: In Progress # Backlog | Todo | In Progress | In Review | Done | Blocked
labels: [feature:licence, skill:python-data-engineer, req:1]
estimate: 2h # hours or points — match the project convention
blocks: [LOC-105] # this item blocks these (optional)
blockedBy: [LOC-101] # this item is blocked by these (optional)
release: mvp # release name (optional)
sprint: 1 # sprint number (optional)
spec: documentation/specs/licence-data-extraction/tasks.md # back-link to the spec
assignee: null
created: 2026-06-16
updated: 2026-06-16
---
## Description
{purpose, acceptance criteria, files to create/modify, _Leverage:, _Requirements:}
## Activity Log
- 2026-06-16 — **Picked up by Claude** (complex). Implementer: Codex. Spec: …
- 2026-06-16 — Iteration 1: …The Activity Log is where comment() and the impl-logger append — comments are dated bullets, not separate files. This keeps a ticket's whole history in one git-diffable file.
ID scheme
- Prefix is configurable in
documentation/workflow-config.md(local.prefix,
default LOC-). Use the project's short code if the team prefers (e.g. TI-).
- The next id = (max existing numeric suffix across
documentation/tracker/*.md)
+ 1. Derive it by Glob documentation/tracker/*.md and taking the max — there is no central counter file to corrupt or race.
- When creating a batch (a whole feature), allocate sequential ids up front and
record them so cross-links (parent, blocks) resolve.
Abstract operations
| Operation | Local realisation |
|---|---|
getWorkItem(id) | Read documentation/tracker/{id}.md — parse frontmatter (title, description, labels, estimate, blocks/blockedBy, state) and body. |
setState(id, target) | Edit the file's state: frontmatter field to target and bump updated:. Direct, single edit — no transition discovery. |
comment(id, body) | Edit to append a dated bullet under ## Activity Log (create the section if absent). |
setField(id, field, value) | Edit the named frontmatter field (e.g. estimate, assignee, sprint). |
addLabel(id, label) | Read the labels: array, append label if missing, Edit it back. (Preserves existing labels — same union semantics as Linear.) |
discoverChildren(epicId) | Grep -l "parent: {epicId}" documentation/tracker/*.md → stories; for each story Grep -l "parent: {story}" → tasks. Leaves = tasks + childless stories. |
getDependencies(id) | read the blocks / blockedBy frontmatter arrays. |
stateNames() | fixed set: Backlog, Todo, In Progress, In Review, Done, Blocked. |
implLogger | invoke local-impl-logger |
Planner-side operations (release-planner / sprint-planner)
| Operation | Local realisation |
|---|---|
createEpic(feature, brief, labels) | Write documentation/tracker/{newId}.md with type: epic, state: Backlog, description = brief + spec link, labels release:{name} / feature:{name} / `priority:{must\ |
createRelease(name, date, project) | The release is a folder + label: ensure documentation/releases/{name}/ exists and record targetDate: {date} in its epic-map.md. Each in-scope epic carries release: {name} (and the release:{name} label as a secondary tag). |
listBacklog(epicIds, states) | Grep the tracker for parent in {epics} (or release: / feature: labels) and filter on state in {Backlog, Todo}; read estimates + skill/req labels from frontmatter. |
createSprint(name, dates) | Write documentation/sprints/sprint-{N}-kickoff.md (the sprint is that doc). Record the date window in its header. |
addToSprint(ids, sprint) | setField(id, "sprint", N) per item — set the sprint: frontmatter field. |
State model (same shape as Linear, not JIRA)
Local has no transition workflow: state is a frontmatter attribute you set directly via setState. The entire JIRA dance — discover transitions, handle required-field-on-transition, retry — collapses to one Edit. setState never blocks on a workflow rule. (This is why, like Linear, the executor's "transition rejected by workflow" blocker handling is a no-op here.)
Notes
- Hierarchy is by frontmatter
type:(epic / story / task) +parent:pointers —
re-parenting is a one-field edit, no file moves.
- Back-links to specs live in the
spec:frontmatter field (a repo-relative path);
there is no remote URL to link, so cross-references are relative file links.
- After any create/state change, regenerate
documentation/tracker/index.md(the
board table: id · type · title · state · estimate · sprint) so the human view stays current. This is a convenience, not a source of truth — the item files are.
- Parallel-execution worktree merging is identical to JIRA/Linear — that logic is
tracker-agnostic and stays in the executor. Note: parallel agents writing different ticket files don't conflict; the shared index.md regen should be done once after a wave reconciles, not per-ticket inside worktrees.
- Tools used:
Read,Write,Edit,Glob,Grep— no MCP calls at all. - Searchability ("what exists already") =
Grepoverdocumentation/tracker/and
documentation/impl-logs/, the local equivalent of a JQL/Linear text search.