| name | pm-organize |
| description | Use this skill when the user says "organize this issue", "place this issue", "where does this belong", "set status and project", "assign this issue", "file it under the right workstream", "/pm-organize", or asks how to route a workable issue into the workflow. Takes a workable issue (one that already has a type + exec-mode from pm-triage) and PLACES it — sets status, project/milestone, parent, assignee, and workstream — with tiered write-safety (auto-safe for status + existing-workstream; reversible for project/milestone/parent; always-confirm for assignee reassignment + new-workstream + new-project). Consumes the team roster via rankAssignees for assignee recommendations and the conventions |
| version | 0.44.4 |
PM Organize
Placement is the second half of intake: pm-triage makes an issue workable (type + exec-mode label); pm-organize makes it placed (status + location). An issue that has been triaged but not placed is typed, labeled, and structurally sound — but it has no workflow status, no project home, no parent, and no assigned owner. pm-organize closes that gap.
Usage
/pm-organize [<issue-id>]
<issue-id> (issue key e.g. FUNC-12, optional) — the issue to place; omit to work the set of triaged-but-unplaced issues.
Writes are tiered: auto-safe changes (status, existing-workstream label) are applied on one confirmation; reversible changes (project/milestone/parent) are shown and applied on confirm; always-confirm changes (assignee reassignment, new-workstream creation, new-project creation) are surfaced as proposals and require an explicit separate OK.
When to use this
- An issue has been triaged (typed + labeled by pm-triage) but is still unplaced — no status, no project home, no assigned owner.
- The operator says "where does this issue belong", "assign FUNC-12", "place this in the right project", "/pm-organize".
When not to use it. If the issue is already placed (has a status + project + assignee that reflect current intent), running pm-organize produces noise. If the issue lacks a type tag or exec-mode label (is still raw / untriaged), run pm-triage first — pm-organize assumes the issue is workable. If you want only to change a type tag or execution mode, that is pm-triage, not this skill.
The five dimensions and write-safety tiers
| Dimension | Tier | What it means |
|---|
| Status | auto-safe | Set or advance the workflow status. Applied in default mode. (Linear only — GitHub returns TRACKER_UNSUPPORTED for --status; surface as a manual step on GitHub.) |
| Existing-workstream label | auto-safe | Add a workstream label that already exists in the tracker. Applied in default mode. |
| Project | reversible | Move the issue into a project. Shown always; applied on confirm or in autonomous mode. |
| Milestone | reversible | Set or change the milestone. Shown always; applied on confirm or in autonomous mode. |
| Parent | reversible | Link to a parent issue. Shown always; applied on confirm or in autonomous mode. |
| Assignee reassignment | always-confirm | Change who owns the issue. Never auto-applied — always a separate explicit confirmation. |
| New-workstream creation | always-confirm | Apply a workstream label that does not yet exist. Never auto — requires the operator to confirm the new label should be created. |
| New-project creation | always-confirm | Assign to a project that does not yet exist. Never auto — route to pm-projects to create it first. |
These tiers map directly to placement-resolver.tierFor:
tierFor('status') → 'auto-safe'
tierFor('workstream-existing') → 'auto-safe'
tierFor('project') → 'reversible'
tierFor('milestone') → 'reversible'
tierFor('parent') → 'reversible'
tierFor('assignee') → 'always-confirm'
tierFor('workstream-new') → 'always-confirm'
tierFor('project-new') → 'always-confirm'
Workflow
Steps 1-4 are read-only and run without confirmation. Step 5 is the only write, and it is always confirmation-gated.
Step 1 — Fetch the issue fresh
Read from the tracker — not the cache. Cache state can lag (stale labels, outdated status) and placement decisions hinge on current state.
- Linear:
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-cache.js" verify-issue --slug <slug> --id <ID> — reads the single issue fresh from Linear and returns it under the fresh key (alongside cached and a divergence array). Read placement state from fresh, not cached.
- GitHub:
gh issue view <NUM> --repo <org/repo> --json assignees,labels,milestone,projectCards,state,title,body,parentIssue.
If the tracker is unreachable, stop with a transport error.
Step 2 — Load conventions and parse the workstream scheme
Read <repo>/.claude/pm/conventions.md. Extract the ## Workstreams section body and parse it:
const { extractSections } = require(process.env.CLAUDE_PLUGIN_ROOT + '/hooks/lib/markdown-utils');
const { parseWorkstreamScheme } = require(process.env.CLAUDE_PLUGIN_ROOT + '/hooks/lib/placement-resolver');
const sections = extractSections(conventionsMarkdown);
const scheme = parseWorkstreamScheme(sections['Workstreams'] || '');
If the conventions file is absent or has no ## Workstreams section, scheme.mode is 'none' — the workstream dimension is disabled; omit it from the proposal.
Step 3 — Compute placement candidates deterministically
Use the resolver and roster libraries — do not invent heuristics inline:
Workstream:
const { currentWorkstream, availableWorkstreams } = require('.../placement-resolver');
const ws = currentWorkstream(issue.labels, scheme);
const candidates = availableWorkstreams(corpusLabels, scheme);
Project candidates:
const { candidateProjects } = require('.../placement-resolver');
const projects = ;
const cands = candidateProjects(issue, projects);
If no portfolio data is available, the project dimension degrades gracefully — note "recommend /pm-setup --portfolio" in the proposal.
Parent candidates: candidate parents are other open issues in the same project or team. Select the most plausible (e.g., epics or initiatives that share the workstream or title keywords). No resolver function is needed — use judgment.
Assignee candidates:
const r = require('.../team-roster');
const cs = require('.../cache-store');
const curated = r.parseTeamMd(fs.existsSync(teamMdPath) ? fs.readFileSync(teamMdPath, 'utf8') : '');
const cache = cs.readTeam(slug) || { members: [] };
const { roster } = r.mergeRoster(curated, cache.members);
const ranked = r.rankAssignees({ title: issue.title, labels: issue.labels, workstream: ws }, roster);
Step 4 — Build the placement proposal
Combine all dimensions into the proposal format shown below. Mark each dimension with its tier from tierFor. See Placement proposal — output format.
Step 5 — Apply
Show the proposal. On the operator's confirmation:
- Apply the auto-safe tier via
pm-issue.js update (status + existing-workstream label).
- In autonomous mode, also apply the reversible tier (project/milestone/parent) in the same or a follow-up update call.
- Never auto-apply assignee reassignment, new-workstream creation, or new-project creation — surface those as proposals and require an explicit separate confirmation.
For a single issue, one confirmation covers the auto-safe tier; the reversible tier is a second confirm; always-confirm dims are each their own explicit OK. For a batch, assemble all proposals into one summary and confirm once per tier class.
Applying placement
The exact commands to apply each dimension:
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-issue.js" update --id <ID> --status "<state name>"
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-issue.js" update --id <ID> --add-label "<ws-label>"
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-issue.js" update --id <ID> --project "<name>" --milestone "<name>" --parent "<parent-id>"
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-issue.js" update --id <ID> --assignee "<email>"
Combine flags in one update call where the tier allows (e.g., status + existing-workstream in one call; project + milestone + parent in one call). Always-confirm dimensions are a separate, explicit confirmation — never bundled with auto-safe or reversible writes.
All Linear writes MUST go through pm-issue.js update. Never call linear issue update directly — its --label flag is replace-semantics and silently drops any label not explicitly listed (NTH-511). The wrapper fetches existing labels, merges, and writes the union back.
Placement proposal — output format
Use this shape, scaled down when a section is empty:
# Placement proposal — <ID> · <title>
| Dimension | Proposed | Tier | Why |
|---|---|---|---|
| Status | <state name> | auto-safe | <reason — e.g. "issue is typed + labeled; ready to move to In Progress"> |
| Workstream | <ws-label or "new: ws:foo"> | auto-safe / always-confirm | <reason> |
| Project | <project name or "—"> | reversible | <reason — e.g. "matches team ENG + title keywords"> |
| Milestone | <milestone name or "—"> | reversible | <reason> |
| Parent | <parent-id or "—"> | reversible | <reason> |
| Assignee | <@handle (score)> | always-confirm | <top reasons from rankAssignees — e.g. "owns ws:billing, strong in payments"> |
## Apply now (auto-safe)
Applying adds `<state>` status and `<ws-label>` label to <ID>. Confirm to proceed.
## Needs your explicit OK
- **Assignee → @<handle>:** <rationale from rankAssignees>
- **New workstream `<label>`:** <why this is a new workstream and not an existing one>
- **Project → <name>:** <why — and note if it needs /pm-projects to create it first>
The proposal is honest about confidence — an inferred project match says so; a workstream in 'none' mode is omitted entirely. Operators should be able to trust a clean proposal and scrutinize a hedged one.
What this skill does NOT do
- Does not make an issue workable — type tag + exec-mode label is pm-triage, not this skill. Run pm-triage first if the issue is still raw.
- Does not create or close issues — that is pm-issues.
- Does not reassign, create a new workstream, or create a new project without explicit confirmation — those three always require an explicit separate OK.
- Does not set status on GitHub —
--status returns TRACKER_UNSUPPORTED for GitHub issues; status is surfaced as a manual step in that case.
- Does not rewrite the issue body — structural gaps are reported; closing them is a pm-issues edit.
- Does not apply anything without confirmation — the analysis is free; every write is confirmation-gated.
Where pm-organize sits
pm-triage → pm-organize → pm-issues / pm-projects
(classify · (status · project/milestone · (body edits · create project
type + hitl/afk) parent · assignee · workstream) if needed)
pm-triage answers "is this issue workable?"; pm-organize answers "where does it belong?". When pm-organize surfaces an always-confirm proposal (e.g., the issue needs a new project), that is the cue for /pm-projects to create it first.