.conductor/state.json and PROJECT.md whether or not a tracker is configured.
Instruction-layer philosophy
PM is an instruction layer, not an integration layer. This is a deliberate architectural choice with a practical consequence: it works with any tracker regardless of how you connect to it. The engine never opens a network connection to an external system. It never calls a Jira API, fires a Linear mutation, or runsgh issue create itself. Instead, it writes rules into your CLAUDE.md and surfaces sync obligations in the session briefing. You — the interactive agent — act on those instructions using whatever tooling your project has: a Jira or Linear MCP server, the GitHub CLI, an Atlassian REST connector, a Python library. The tracker doesn’t need special PM support. Any system works.
Configuring a tracker
1
Run /pm:tracker
PM scans your project for tracker signals — connected MCP servers (
mcp__jira__*, mcp__linear__*, GitHub issue tools), issue-key conventions in commit or branch history (e.g. PROJ-123, #142), or explicit statements in CLAUDE.md or README that “we track work in X”. Hosting a repo on GitHub is NOT a signal by itself — PM only infers tracker intent from real evidence that work is actively managed there.2
Confirm the system and project key
PM presents what it detected and asks you to confirm or correct. You specify the
system (e.g. jira, linear, github-issues), projectKey (e.g. PROJ), instance, and mechanism (e.g. mcp or cli). If you don’t want to connect a tracker, declining changes nothing — PM continues tracking everything locally.3
Map PM lifecycle statuses to semantic tracker targets
Define
statusIntent — a mapping from PM’s lifecycle statuses to semantic descriptions of where a tracker issue should land. These are semantic targets, not literal workflow transition names. Your agent resolves the actual transition using its own knowledge of the tracker’s workflow.4
PM records the tracker block and updates CLAUDE.md
PM calls Re-running
set-tracker under the hood to write the tracker configuration into .conductor/state.json and refreshes the managed rules block in CLAUDE.md with whichever sync section the tracker’s direction calls for. The TRACKER SYNC line appears in the session briefing from this point on.For reference, the underlying set-tracker command uses --intent flags (repeatable — one <status>:<target> per entry):set-tracker merges — only the flags you pass change. It also refreshes the CLAUDE.md rules block automatically.Tracker configuration shape
A fully configured tracker block in.conductor/state.json looks like this:
statusIntent maps PM’s lifecycle to a semantic target — for example, "active": "in-progress" — never a literal tracker workflow transition name like "Start Progress". Your agent resolves the real transition when it executes the sync instruction.
Direction decides what PM emits — not the vendor
A secondary tracker is pinned toinward; any other value is refused. And direction alone does not turn a section on — an inward section is emitted only when the tracker also names a scope to read (a repo for github-issues, a repo or --project for anything else), because PM may not emit a command line with an unfilled placeholder.
Outward mirroring
Where a tracker’s direction includesoutward, the CLAUDE.md rules block tells your agent to:
- Create a tracker issue for any epic that lacks an
externalId, then record the returned key:update-epic <id> --external-id <KEY> --external-url <url> - Transition the linked issue toward the
statusIntentsemantic target on each status change - Link parent and child tracker issues for parent epics that have children
TRACKER SYNC line in the session briefing lists every active-work epic that still needs an issue created — it only reports honestly computable drift (epics missing externalId). It never fabricates transition state the engine cannot actually see.
An epic that has been mirrored looks like this:
Inward pull, worked through on GitHub Issues
Any tracker whose direction includesinward pulls open items in as untriaged epics. github-issues is not special here; it is simply the one system whose CLI PM can name concretely, so it makes the clearest example. Every other system receives the same steps phrased as “list open items in <system> (<scope>) with your own tooling”.
Configure it with a --repo:
/pm:sync, your agent runs:
gh issue list --repo <repo> --state open --json number,title,url,labels- For each issue, check whether an epic already carries that item’s
externalUrl— if so, skip it. Re-running sync must never create a duplicate epic for the same issue. - Register new issues with the recipe the rules block emits —
add-epic --id <derived> --status untriaged --external-id <number> --external-url <url> --external-updated-at <iso> --lane <lane> --title "<issue-title>" --priority P2— unless the issue carries aP0/P1/P2/P3label, in which case use that priority instead.
--id is derived (<system>-<scope>-<number>), so the same item yields the same epic id in every repo and every session and a re-run is refused as a duplicate rather than landing under a slug the agent invented from the title. Its <lane> comes from lane routing (suggest-lane) rather than a hardcoded claude-code — the lane decides whether the work leaves any spec, plan, or gate record, so hardcoding it decided that silently for every mirrored item.
Freshness and the refresh gate
A tracker-linked epic carriesexternalUpdatedAt — the tracker’s own updated timestamp as of the last time your agent actually read that item. Seeing it in a list response is not reading it; advancing the watermark from a listing would erase the drift the watermark exists to find.
Before an epic becomes the active piece of work, its source of truth is re-read and the obligation is cleared with record-tracker-refresh <id> --verdict unchanged|material-change --external-updated-at <iso>. The gate keys on provenance — does this epic have an external origin — never on direction. An issue filed by a third party and an epic born from a local spec have different sources of truth in the same repo on the same day.
Primary + secondary trackers
A repo has exactly one primary tracker — everything above — plus, optionally, one or more secondary trackers. Secondary trackers cover a different case: your real dev tracker is Jira, but you also want to watch a GitHub repo for inbound issues, for example from outside contributors, or from another internal repo publishing cross-project notifications (a service filing a GitHub issue in a downstream repo to flag a breaking change), without Jira losing its primary spot. A secondary tracker gets exactly two behaviors, both narrower than primary:- Inward pull — open issues become untriaged epics, the same shape as the inward pull above, deduped by
externalUrl(globally unique) rather than bareexternalId(only unique within one tracker/repo — two secondary trackers can each have an issue numbered#42without colliding). - Completion status writeback — when an epic sourced from a secondary tracker reaches
archived, your agent closes the linked issue there too.
--system plus --repo or --project. Re-running set-tracker --role secondary with a matching system + repo/project merges into the existing entry instead of adding a duplicate. --remove against a key with no match exits non-zero and leaves secondaryTrackers unchanged.
Once configured, the CLAUDE.md rules block gains one “Secondary tracker sync” section per entry, in addition to — never instead of — the primary tracker’s own “External tracker sync” or “GitHub Issues” section above.
Resyncing after completion
Where at least one configured tracker has an emittable inward procedure — its direction includesinward and it names a scope to read — the CLAUDE.md rules block also gains a “Sync after completing tracker-linked work” section. After your agent closes or transitions a tracker-linked issue as part of completing an epic, it re-syncs with your tracker(s) (/pm:sync) right away — it’s already doing tracker I/O for that epic, so this is the cheapest moment to also pull in anything new that appeared while it was heads-down. The instruction is phrased tracker-count-agnostic — “re-sync with your tracker(s)” — so it reads correctly whether a repo has one tracker configured or several (e.g. Jira primary + GitHub Issues secondary).
The session briefing mirrors this with a one-line, non-blocking nudge on the same condition. This is a reminder only — PM never runs a sync itself, and there’s no lastSyncedAt timestamp behind it; the agent simply decides whether it’s worth the round trip that session.
Changed in 0.27.0. Both used to fire more widely — the reminder appeared in every rules block that had a tracker at all, citing “the writeback steps above” that the same block never emitted, and the briefing’s nudge appeared whenever any tracker existed. An outward-only repo now gets neither.
What the TRACKER SYNC briefing line means
The briefing’sTRACKER SYNC section lists only epics where the engine can honestly compute drift — specifically, active-work epics that have no externalId recorded. It never guesses at transition state, and it never reports an issue as “needing a status update” based on anything the engine cannot directly see in .conductor/state.json.
Status transition sync is your responsibility at the moment of each status change. The engine cannot observe the tracker’s state from the outside.
Hosting a repository on GitHub is NOT a tracker signal. PM only configures tracker awareness when there is real evidence you are actively managing work in an issue tracker — a connected MCP server, issue-key conventions in commit history, or an explicit statement in
CLAUDE.md or README. If the evidence isn’t there, PM will not suggest a tracker configuration.
