Problem
parse_prd pollutes whatever TaskMaster tag is currently active (in practice master). When an effort runs parse-prd and the active tag already holds tasks from a prior effort (e.g. 6 done /inevitable tasks under master), the new PRD's tasks are silently appended alongside them — no isolation, no warning, no per-PRD boundary. Each PRD should get its own dedicated tag so its task graph is isolated from every other effort.
Current Behavior
The parse step lives in skill prose, not in any Python tool of this plugin:
skills/generate/SKILL.md "Step 4: Parse tasks via TaskMaster" (lines 189–206) instructs the agent to call, in priority order:
mcp__task-master-ai__parse_prd(input=".taskmaster/docs/prd.md", numTasks=<n>) (line 199)
mcp__plugin_atlas-go_go__tm_parse_prd(input_path=..., num_tasks=<n>) (line 201)
- CLI
task-master parse-prd --input ... --num-tasks <n> (line 203)
- All three invocations are tagless.
task-master therefore writes into whatever tag is set as currentTag in .taskmaster/state.json (master in the observed run). A repo-wide grep across skills/ for tag-creation/selection language (add-tag, use-tag, --tag, "dedicated tag", "per-PRD", "isolate", "fresh tag") returns ZERO matches — the skill never creates, selects, or warns about a tag before parsing.
- Step 6 expansion (
skills/generate/SKILL.md lines 232–311) shells out to task-master expand --all, which expands every task in the active tag (same tag-blindness). The coverage-verify snippet (lines 287–310) hard-codes the master tag.
mcp-server/pipeline.py preflight() (lines 179–234) already reads state.json + tasks.json, computes per-tag tag_counts (line 190), resolves current_tag via _current_tag() (lines 158–164, falling back to currentTag then "master"), and can emit recommended_action="select_taskmaster_tag" with a recommended_tag (lines 211–213). But the parse rule (lines 205–206: elif prd_exists and tasks_count == 0: rec = "parse_prd") only checks the current tag's count being 0 — it never flags that master already holds unrelated tasks before recommending a parse that will append into it.
skills/go/SKILL.md (lines 22–31) routes purely on current_phase and discards recommended_action/current_tag/recommended_tag/tag_counts entirely.
mcp-server/server.py registers 18 tools (verified @mcp.tool() count = 18); none is parse_prd, tm_parse_prd, expand_tasks, add_tag, or use_tag — confirming the parse/tag behavior is owned by external task-master-ai, not this codebase.
mcp-server/taskmaster.py is the only TaskMaster CLI wrapper and does only init (no tag operations).
Note on the two root causes (independent): A separate observed parse_prd exit-1 failure is the claude-code nested-spawn problem (a child claude -p spawn dies inside a nested Claude Code session). That is real but orthogonal — the tag-pollution bug manifests on any successful parse, because the parse invocation carries no tag and writes to the active currentTag. This issue fixes the pollution; the spawn failure is tracked separately.
Expected Behavior
Each PRD's tasks are parsed into a dedicated, fresh, per-PRD tag, leaving every other tag (especially master and prior efforts) physically untouched. Concretely:
- Before parsing, the generate flow derives a deterministic slugified per-PRD tag (e.g.
site-funnel, inevitable), creates it (idempotent), and selects it.
parse_prd/parse-prd writes into that fresh tag. Because the tag is empty, no --append is needed and master is never modified.
expand --all and the coverage-verify snippet operate on that same dedicated tag (not hard-coded master).
preflight() detects a non-empty/foreign active tag and recommends a fresh dedicated tag instead of silently recommending a parse into a polluted master; the orchestrator consumes that recommendation.
Files to Touch
skills/generate/SKILL.md — PRIMARY. In "Step 4: Parse tasks via TaskMaster" (lines 189–206), insert a create+select dedicated-tag step before line 199; thread tag=<slug> into the parse invocations at lines 199/201/203. In Step 6 (lines 232–311), run use-tag <slug> before expand --all, and repoint the master-hard-coded coverage-verify snippet (lines 287–310) at <slug>.
mcp-server/pipeline.py — SECONDARY. In preflight() make the parse_prd recommendation (lines 205–206) tag-aware: detect a non-empty/foreign active tag (current_counts["total"] > 0) and return a fresh dedicated recommended_tag (or a warn flag) instead of silently recommending parse into master.
skills/go/SKILL.md — CONTEXT. Flow (lines 22–31) routes only on current_phase; if preflight starts recommending a dedicated tag, go (or generate) must actually consume recommended_action/recommended_tag, which is dropped today.
mcp-server/taskmaster.py — OPTIONAL. If tag selection should be a first-class engine tool, add thin add_tag/use_tag wrappers here mirroring init_taskmaster's subprocess pattern, then register them in mcp-server/server.py.
Researched Fix Approaches
1. [Recommended] — Parse directly into a fresh per-PRD tag (tag=<slug> + auto-create) (confidence: 92%)
- Library/Config:
task-master-ai (claude-task-master). MCP parse_prd param tag ("Tag context to operate on"); CLI flag --tag=<name>. Tag mgmt: MCP add_tag/use_tag or CLI task-master add-tag <name> / use-tag <name>.
- Pattern: Derive a deterministic slug from the PRD filename/effort title (e.g.
site-funnel, inevitable). Sequence: (1) add-tag <slug> (idempotent — treat "already exists" as success); (2) use-tag <slug> (sets state.json.currentTag so list/expand/next operate in-tag); (3) parse into it; (4) expand --all in it.
- Why: Verified against the live MCP tool schema and TaskMaster's command-reference —
parse_prd accepts a first-class tag parameter, and add-tag/use-tag are the documented commands for isolated parallel contexts. A fresh tag is empty, so parse writes into {<slug>:{tasks:[...],metadata:{...}}} and prior tasks under master are physically untouched. Change site matches the code map exactly (insert before skills/generate/SKILL.md line 199; thread tag into 199/201/203 and the Step 6 expand+verify).
- Risk: (1) The
claude-code nested-spawn parse_prd exit-1 is orthogonal and still kills parse inside a nested Claude Code session — tag isolation does not fix the spawn death. (2) parse-prd defaults to overwriting the target tag's task list (the --append flag exists specifically to add instead of overwrite, per claude-task-master issue #207). Guard: if the target slug tag is already non-empty, suffix the slug or require explicit append/force. (3) expand --all has no documented --tag flag — use-tag <slug> FIRST, then expand --all operates on currentTag.
- Implementation hint:
# MCP
mcp__task-master-ai__add_tag(name="site-funnel", description="Site funnel PRD", projectRoot="<abs>")
mcp__task-master-ai__use_tag(name="site-funnel", projectRoot="<abs>")
mcp__task-master-ai__parse_prd(input=".taskmaster/docs/prd.md", numTasks=<n>, tag="site-funnel", projectRoot="<abs>")
# CLI
task-master add-tag site-funnel --description="Site funnel PRD"
task-master use-tag site-funnel
task-master parse-prd --input .taskmaster/docs/prd.md --num-tasks <n> --tag=site-funnel
task-master expand --all
2. [Alternative] — Preflight guard: detect a polluted/foreign active tag, recommend parse_into_fresh_tag (confidence: 80%)
- Library/Config: No external API — pure Python in
mcp-server/pipeline.py (already imports state.json + tasks.json, computes tag_counts/_current_tag/_recommended_pending_tag). Pairs with Approach 1's calls in the skill.
- Pattern: Stop steering parse into a non-empty
master. Before the elif prd_exists and tasks_count == 0 branch (lines 205–206), add: when prd_exists and the active tag is non-empty and all-done/foreign, return recommended_action="parse_into_fresh_tag" with recommended_tag=<slugified PRD name> instead of parse_prd (which appends/overwrites master) or select_taskmaster_tag (which only points at an EXISTING pending tag). Then teach skills/go (or generate) to consume recommended_tag.
- Why:
preflight() already has ~90% of the machinery (per-tag counts, current_tag resolution, a select_taskmaster_tag branch, a recommended_tag return field). This is the deterministic, testable home for "detect a non-empty/foreign tag before appending," making isolation a property of the orchestrator rather than relying on the LLM to remember.
- Risk: Inert unless a consumer is wired —
go/generate currently drop recommended_action/recommended_tag, so without a matching skill edit it is dead code. Foreign-vs-same-PRD detection is heuristic; safest rule is "active tag non-empty AND (name == 'master' OR all tasks done) ⇒ recommend fresh tag." Over-eager detection could spuriously fork tags on a legitimate resume.
- Implementation hint:
# in preflight(), before the `elif prd_exists and tasks_count == 0` branch:
polluted = current_counts["total"] > 0 and current_counts["pending"] == 0
if prd_exists and polluted:
rec = "parse_into_fresh_tag"
recommended_tag = _slug_from_prd() # deterministic slug from PRD filename/title
# return recommended_tag alongside existing fields
3. [Fallback] — Thin MCP/CLI tag wrapper in taskmaster.py (add_tag/use_tag/ensure_prd_tag) exposed via server.py (confidence: 68%)
- Library/Config: Wraps
task-master add-tag <name> [--description=...] and task-master use-tag <name> in mcp-server/taskmaster.py (the only existing TM CLI wrapper, currently init-only). Register the new tools in mcp-server/server.py.
- Pattern: Add
add_tag(name, description, project_root) and use_tag(name, project_root) plus a convenience ensure_prd_tag(prd_path) that slugifies+creates+selects, mirroring init_taskmaster's _build_env/_find_binary subprocess pattern. The generate skill then calls one deterministic engine tool before parse instead of three-way priority-ordered prose the agent can skip (grep proved the prose path is empirically skippable — 0 hits).
- Why: Makes per-PRD isolation a guaranteed idempotent step owned by code, not an instruction the model may ignore. Centralizes slug derivation and the "tag already non-empty?" guard in one tested place. Bonus:
add-tag/use-tag invoke no AI model (pure local tasks.json/state.json mutations), so they run fine even inside the nested Claude Code session that breaks parse_prd.
- Risk: Most invasive (new tools +
server.py registration + tests) for a problem Approach 1 solves with a few skill-line edits; duplicates task-master's own CLI surface. Lower confidence only because it is heavier than needed, not because the API is uncertain.
- Implementation hint:
# taskmaster.py — mirror init_taskmaster's subprocess + _build_env path
def add_tag(name, description=None, *, project_root):
args = ["add-tag", name]
if description: args.append(f"--description={description}")
# run via same subprocess+_build_env path; treat 'already exists' stderr as success
def use_tag(name, *, project_root):
# run ["use-tag", name]
# then register both in server.py next to init_taskmaster
Reference
task-master-ai (claude-task-master) ships a first-class "Tagged Task Lists" system precisely for this isolation problem. The canonical per-feature workflow in its own tutorial/command-reference is: task-master add-tag <feature> --description=... (or add-tag --from-branch to name the tag after the git branch) → task-master use-tag <feature> → parse/expand within that tag; optionally delete-tag <feature> after merge. Tags give complete isolation: every operation (list/show/add/expand/next) acts only on the active tag (state.json.currentTag), and tasks.json is grouped as {<tag>:{tasks:[...],metadata:{...}}}. Critically, parse-prd defaults to overwriting the target tag's task list — the --append flag exists specifically to add instead of overwrite (GitHub issue #207) — so parsing into a FRESH empty tag is the clean pattern and needs no --append. The MCP parse_prd tool exposes a tag parameter alongside append/force/numTasks/input/output/research/projectRoot, and add_tag/use_tag are dedicated MCP tools (params: name, description, copyFromCurrent, copyFromTag, fromBranch, projectRoot). The Atlas engine currently bypasses all of this: its generate skill calls parse tagless, so TM writes into whatever currentTag is set (master).
Sources:
Acceptance Criteria
Complexity: M
Trust Level: HINT (not specification)
The researched approaches above are starting points. Before implementing:
- Verify the library/config exists as stated (e.g.
task-master models, confirm parse_prd's tag param and add-tag/use-tag via task-master --help or the MCP schema; read .taskmaster/config.json).
- Check that imports/keys match reality (slug derivation source,
state.json currentTag semantics, tasks.json tag-grouped shape).
- Try the recommended approach — if it works in 1-2 attempts, use it.
- If it fails, do NOT keep retrying — research why, explore alternatives.
- The acceptance criteria are the real spec, not the approach.
Problem
parse_prdpollutes whatever TaskMaster tag is currently active (in practicemaster). When an effort runsparse-prdand the active tag already holds tasks from a prior effort (e.g. 6 done/inevitabletasks undermaster), the new PRD's tasks are silently appended alongside them — no isolation, no warning, no per-PRD boundary. Each PRD should get its own dedicated tag so its task graph is isolated from every other effort.Current Behavior
The parse step lives in skill prose, not in any Python tool of this plugin:
skills/generate/SKILL.md"Step 4: Parse tasks via TaskMaster" (lines 189–206) instructs the agent to call, in priority order:mcp__task-master-ai__parse_prd(input=".taskmaster/docs/prd.md", numTasks=<n>)(line 199)mcp__plugin_atlas-go_go__tm_parse_prd(input_path=..., num_tasks=<n>)(line 201)task-master parse-prd --input ... --num-tasks <n>(line 203)task-mastertherefore writes into whatever tag is set ascurrentTagin.taskmaster/state.json(masterin the observed run). A repo-wide grep acrossskills/for tag-creation/selection language (add-tag,use-tag,--tag, "dedicated tag", "per-PRD", "isolate", "fresh tag") returns ZERO matches — the skill never creates, selects, or warns about a tag before parsing.skills/generate/SKILL.mdlines 232–311) shells out totask-master expand --all, which expands every task in the active tag (same tag-blindness). The coverage-verify snippet (lines 287–310) hard-codes themastertag.mcp-server/pipeline.pypreflight()(lines 179–234) already readsstate.json+tasks.json, computes per-tagtag_counts(line 190), resolvescurrent_tagvia_current_tag()(lines 158–164, falling back tocurrentTagthen"master"), and can emitrecommended_action="select_taskmaster_tag"with arecommended_tag(lines 211–213). But the parse rule (lines 205–206:elif prd_exists and tasks_count == 0: rec = "parse_prd") only checks the current tag's count being 0 — it never flags thatmasteralready holds unrelated tasks before recommending a parse that will append into it.skills/go/SKILL.md(lines 22–31) routes purely oncurrent_phaseand discardsrecommended_action/current_tag/recommended_tag/tag_countsentirely.mcp-server/server.pyregisters 18 tools (verified@mcp.tool()count = 18); none isparse_prd,tm_parse_prd,expand_tasks,add_tag, oruse_tag— confirming the parse/tag behavior is owned by externaltask-master-ai, not this codebase.mcp-server/taskmaster.pyis the only TaskMaster CLI wrapper and does onlyinit(no tag operations).Note on the two root causes (independent): A separate observed
parse_prdexit-1 failure is theclaude-codenested-spawn problem (a childclaude -pspawn dies inside a nested Claude Code session). That is real but orthogonal — the tag-pollution bug manifests on any successful parse, because the parse invocation carries no tag and writes to the activecurrentTag. This issue fixes the pollution; the spawn failure is tracked separately.Expected Behavior
Each PRD's tasks are parsed into a dedicated, fresh, per-PRD tag, leaving every other tag (especially
masterand prior efforts) physically untouched. Concretely:site-funnel,inevitable), creates it (idempotent), and selects it.parse_prd/parse-prdwrites into that fresh tag. Because the tag is empty, no--appendis needed andmasteris never modified.expand --alland the coverage-verify snippet operate on that same dedicated tag (not hard-codedmaster).preflight()detects a non-empty/foreign active tag and recommends a fresh dedicated tag instead of silently recommending a parse into a pollutedmaster; the orchestrator consumes that recommendation.Files to Touch
skills/generate/SKILL.md— PRIMARY. In "Step 4: Parse tasks via TaskMaster" (lines 189–206), insert a create+select dedicated-tag step before line 199; threadtag=<slug>into the parse invocations at lines 199/201/203. In Step 6 (lines 232–311), runuse-tag <slug>beforeexpand --all, and repoint the master-hard-coded coverage-verify snippet (lines 287–310) at<slug>.mcp-server/pipeline.py— SECONDARY. Inpreflight()make theparse_prdrecommendation (lines 205–206) tag-aware: detect a non-empty/foreign active tag (current_counts["total"] > 0) and return a fresh dedicatedrecommended_tag(or a warn flag) instead of silently recommending parse intomaster.skills/go/SKILL.md— CONTEXT. Flow (lines 22–31) routes only oncurrent_phase; if preflight starts recommending a dedicated tag,go(orgenerate) must actually consumerecommended_action/recommended_tag, which is dropped today.mcp-server/taskmaster.py— OPTIONAL. If tag selection should be a first-class engine tool, add thinadd_tag/use_tagwrappers here mirroringinit_taskmaster's subprocess pattern, then register them inmcp-server/server.py.Researched Fix Approaches
1. [Recommended] — Parse directly into a fresh per-PRD tag (
tag=<slug>+ auto-create) (confidence: 92%)task-master-ai(claude-task-master). MCPparse_prdparamtag("Tag context to operate on"); CLI flag--tag=<name>. Tag mgmt: MCPadd_tag/use_tagor CLItask-master add-tag <name>/use-tag <name>.site-funnel,inevitable). Sequence: (1)add-tag <slug>(idempotent — treat "already exists" as success); (2)use-tag <slug>(setsstate.json.currentTagso list/expand/next operate in-tag); (3) parse into it; (4)expand --allin it.parse_prdaccepts a first-classtagparameter, andadd-tag/use-tagare the documented commands for isolated parallel contexts. A fresh tag is empty, so parse writes into{<slug>:{tasks:[...],metadata:{...}}}and prior tasks undermasterare physically untouched. Change site matches the code map exactly (insert beforeskills/generate/SKILL.mdline 199; threadtaginto 199/201/203 and the Step 6 expand+verify).claude-codenested-spawnparse_prdexit-1 is orthogonal and still kills parse inside a nested Claude Code session — tag isolation does not fix the spawn death. (2)parse-prddefaults to overwriting the target tag's task list (the--appendflag exists specifically to add instead of overwrite, per claude-task-master issue #207). Guard: if the target slug tag is already non-empty, suffix the slug or require explicitappend/force. (3)expand --allhas no documented--tagflag —use-tag <slug>FIRST, thenexpand --alloperates oncurrentTag.2. [Alternative] — Preflight guard: detect a polluted/foreign active tag, recommend
parse_into_fresh_tag(confidence: 80%)mcp-server/pipeline.py(already importsstate.json+tasks.json, computestag_counts/_current_tag/_recommended_pending_tag). Pairs with Approach 1's calls in the skill.master. Before theelif prd_exists and tasks_count == 0branch (lines 205–206), add: whenprd_existsand the active tag is non-empty and all-done/foreign, returnrecommended_action="parse_into_fresh_tag"withrecommended_tag=<slugified PRD name>instead ofparse_prd(which appends/overwritesmaster) orselect_taskmaster_tag(which only points at an EXISTING pending tag). Then teachskills/go(orgenerate) to consumerecommended_tag.preflight()already has ~90% of the machinery (per-tag counts,current_tagresolution, aselect_taskmaster_tagbranch, arecommended_tagreturn field). This is the deterministic, testable home for "detect a non-empty/foreign tag before appending," making isolation a property of the orchestrator rather than relying on the LLM to remember.go/generatecurrently droprecommended_action/recommended_tag, so without a matching skill edit it is dead code. Foreign-vs-same-PRD detection is heuristic; safest rule is "active tag non-empty AND (name == 'master'OR all tasks done) ⇒ recommend fresh tag." Over-eager detection could spuriously fork tags on a legitimate resume.3. [Fallback] — Thin MCP/CLI tag wrapper in
taskmaster.py(add_tag/use_tag/ensure_prd_tag) exposed viaserver.py(confidence: 68%)task-master add-tag <name> [--description=...]andtask-master use-tag <name>inmcp-server/taskmaster.py(the only existing TM CLI wrapper, currentlyinit-only). Register the new tools inmcp-server/server.py.add_tag(name, description, project_root)anduse_tag(name, project_root)plus a convenienceensure_prd_tag(prd_path)that slugifies+creates+selects, mirroringinit_taskmaster's_build_env/_find_binarysubprocess pattern. The generate skill then calls one deterministic engine tool before parse instead of three-way priority-ordered prose the agent can skip (grep proved the prose path is empirically skippable — 0 hits).add-tag/use-taginvoke no AI model (pure localtasks.json/state.jsonmutations), so they run fine even inside the nested Claude Code session that breaksparse_prd.server.pyregistration + tests) for a problem Approach 1 solves with a few skill-line edits; duplicates task-master's own CLI surface. Lower confidence only because it is heavier than needed, not because the API is uncertain.Reference
task-master-ai(claude-task-master) ships a first-class "Tagged Task Lists" system precisely for this isolation problem. The canonical per-feature workflow in its own tutorial/command-reference is:task-master add-tag <feature> --description=...(oradd-tag --from-branchto name the tag after the git branch) →task-master use-tag <feature>→ parse/expand within that tag; optionallydelete-tag <feature>after merge. Tags give complete isolation: every operation (list/show/add/expand/next) acts only on the active tag (state.json.currentTag), andtasks.jsonis grouped as{<tag>:{tasks:[...],metadata:{...}}}. Critically,parse-prddefaults to overwriting the target tag's task list — the--appendflag exists specifically to add instead of overwrite (GitHub issue #207) — so parsing into a FRESH empty tag is the clean pattern and needs no--append. The MCPparse_prdtool exposes atagparameter alongsideappend/force/numTasks/input/output/research/projectRoot, andadd_tag/use_tagare dedicated MCP tools (params:name,description,copyFromCurrent,copyFromTag,fromBranch,projectRoot). The Atlas engine currently bypasses all of this: its generate skill calls parse tagless, so TM writes into whatevercurrentTagis set (master).Sources:
Acceptance Criteria
skills/generate/SKILL.mdStep 4 contains a create+select dedicated-tag step (add-tag <slug>/use-tag <slug>or the MCPadd_tag/use_tagequivalents) BEFORE the first parse invocation; a repo-wide grep acrossskills/foradd-tag|use-tag|--tag|tag=now returns ≥1 match (currently 0).skills/generate/SKILL.md(lines ~199/201/203) carry an explicit per-PRD tag (--tag=<slug>for CLI,tag="<slug>"for MCP)..taskmaster/state.jsonwithcurrentTag="master"and atasks.jsonwheremasterholds N done tasks, running the generate parse flow leaves themastertag's task array unchanged (N tasks, byte-identical) and writes the new PRD's tasks under a separate<slug>key intasks.json.<slug>is already non-empty, the flow either suffixes the slug or uses explicit append/force rather than silently overwriting).skills/generate/SKILL.mdtargets the dedicated<slug>tag, not a hard-codedmaster.mcp-server/pipeline.pypreflight()returnsrecommended_action != "parse_prd"(e.g."parse_into_fresh_tag"with a populatedrecommended_tag) when fed astate.json{currentTag:"master"}+tasks.json{master:{tasks:[6 done]}}and a PRD present; a unit test in the plugin's test suite asserts this.parse_prdsucceeds and writes tasks to a fresh tag when invoked from inside a Claude Code / MCP session (i.e. the chosen approach is demonstrated end-to-end producing tasks under<slug>and an unmodifiedmaster); if theclaude-codenested-spawn exit-1 blocks this, it is documented as the orthogonal failure and a non-model path —add-tag/use-tagselection — is shown to still run in-session.skills/go/SKILL.md(orskills/generate/SKILL.md) consumesrecommended_action/recommended_tagrather than discarding it — verified by tracing the recommendation to an actualadd-tag/use-tag/parse call.Complexity: M
Trust Level: HINT (not specification)
The researched approaches above are starting points. Before implementing:
task-master models, confirmparse_prd'stagparam andadd-tag/use-tagviatask-master --helpor the MCP schema; read.taskmaster/config.json).state.jsoncurrentTagsemantics,tasks.jsontag-grouped shape).