TLDR: "Any function that re-detects the tag invites race conditions. Resolve it in the CLI/MCP layer, thread { projectRoot, tag } everywhere else, and keep IO dumb."
"Boundary resolves, context carries, IO obeys."
| Status | Command | Notes | 
|---|---|---|
| ✅ | add-dependency | Correctly resolves and passes tag. | 
| ✅ | add-subtask | Correctly resolves and passes tag. | 
| ❌ | add-task | Passes raw options.tag; can be undefined. | 
| ✅ | analyze-complexity | Correctly resolves and passes tag. | 
| ❌ | clear-subtasks | Passes unresolved tag value. | 
| ❌ | expand/expand --all | Passes unresolved tag value. | 
| ✅ | fix-dependencies | Correctly resolves and passes tag. | 
| ✅ | list | Correctly resolves and passes tag. | 
| ❌ | move | Passes unresolved tag value. | 
| ❌ | next | Passes unresolved tag value. | 
| ✅ | parse-prd | Correctly resolves and passes tag. | 
| ✅ | remove-dependency | Correctly resolves and passes tag. | 
| ✅ | remove-subtask | Correctly resolves and passes tag. | 
| ❌ | remove-task | Passes unresolved tag value. | 
| ❌ | research | Passes unresolved tag value. | 
| ✅ | set-status | Correctly resolves and passes tag. | 
| ❌ | show | Passes unresolved tag value. | 
| ✅ | update | Correctly resolves and passes tag. | 
| ✅ | update-subtask | Correctly resolves and passes tag. | 
| ✅ | update-task | Correctly resolves and passes tag. | 
| ✅ | validate-dependencies | Correctly resolves and passes tag. | 
| ❌ | generate | Fails to resolve or pass the tag. | 
| ❌ | sync-readme | No --tag flag; fails to pass tag. | 
| N/A | complexity-report,init,models,lang,migrate | No file I/O or not applicable to tag contexts. | 
| N/A | tags,add-tag,delete-tag,use-tag, etc. | Tag administration tools operate outside a single tag context. | 
| Status | Tool file | Notes | 
|---|---|---|
| ✅ | set-task-status.js | Correctly passes args.tagto the direct function. | 
| ✅ | update.js | Correctly passes args.tagto the direct function. | 
| ✅ | fix-dependencies.js | Correctly passes args.tagto the direct function. | 
| ❌ | validate-dependencies.js | Fails to resolve or pass the tag. | 
| ✅ | remove-task.js | Correctly passes args.tagto the direct function. | 
| ❌ | add-dependency.js | Fails to resolve or pass the tag. | 
| ❌ | add-subtask.js | Fails to resolve or pass the tag. | 
| ❌ | add-task.js | Fails to resolve or pass the tag. | 
| ❌ | analyze.js | Fails to resolve or pass the tag. | 
| ❌ | clear-subtasks.js | Fails to resolve or pass the tag. | 
| ❌ | expand-all.js | Fails to resolve or pass the tag. | 
| ❌ | expand-task.js | Fails to resolve or pass the tag. | 
| ❌ | generate.js | Fails to resolve or pass the tag. | 
| ❌ | get-task.js | Fails to resolve or pass the tag. | 
| ❌ | get-tasks.js | Fails to resolve or pass the tag. | 
| ❌ | move-task.js | Fails to resolve or pass the tag. | 
| ❌ | next-task.js | Fails to resolve or pass the tag. | 
| ❌ | parse-prd.js | Fails to resolve or pass the tag. | 
| ❌ | remove-dependency.js | Fails to resolve or pass the tag. | 
| ❌ | remove-subtask.js | Fails to resolve or pass the tag. | 
| ❌ | research.js | Fails to resolve or pass the tag. | 
| ❌ | update-subtask.js | Fails to resolve or pass the tag. | 
| ❌ | update-task.js | Fails to resolve or pass the tag. | 
| N/A | Tag-admin tools ( add-tag.js, etc.) | Operate across all tags by design, no single tag context needed. | 
| N/A | Other tools ( models.js, etc.) | No file I/O or not applicable to tag contexts. | 
| Status | Direct-function file | Notes | 
|---|---|---|
| ❌ | add-dependency.js | Does NOT forward any tag/context object to core addDependency. | 
| ✅ | fix-dependencies.js | Correct – forwards { projectRoot, tag }in context. | 
| ❌ | generate-task-files.js | Calls core generateTaskFileswith no tag/context. | 
| ✅ | list-tags.js | Correctly operates across all tags by design (no single-tag need). | 
| ❌ | list-tasks.js | Passes literal nullfor tag – needs explicit tag forwarding. | 
| ❌ | parse-prd.js | Builds context without tag; tag not forwarded. | 
| ❌ | remove-dependency.js | Does NOT supply tag in context. | 
| ❌ | show-task.js | Uses readJSON(... projectRoot)only – tag omitted. | 
| ✅ | add-subtask.js | Forwards { projectRoot, tag }correctly. | 
| ❌ | add-task.js | Fails to forward tag to core addTask. | 
| ❌ | analyze-task-complexity.js | Fails to forward tag to core analysis. | 
| ✅ | clear-subtasks.js | Passes { projectRoot, tag }to coreclearSubtasks. | 
| ❌ | complexity-report.js | Report path resolution ignores tag. | 
| ❌ | expand-all-tasks.js | Does NOT pass tag in context. | 
| ✅ | expand-task.js | Forwards tag inside context to core expandTask. | 
| ✅ | move-task.js | Forwards tag via context to core moveTask. | 
| ❌ | next-task.js | Tag not forwarded to findNextTask. | 
| ❌ | remove-subtask.js | No tag/context passed. | 
| ✅ | remove-task.js | Forwards tag in context to core removeTask. | 
| ❌ | research.js | Tag not forwarded to core performResearch. | 
| ✅ | set-task-status.js | Passes tag as separate arg to core setTaskStatus. | 
| ❌ | update-subtask-by-id.js | Tag absent in context to core updateSubtaskById. | 
| ❌ | update-task-by-id.js | Tag absent in context to core updateTaskById. | 
| ✅ | update-tasks.js | Context { projectRoot, tag }forwarded to coreupdateTasks. | 
| N/A | cache-stats.js | No file I/O; not applicable. | 
| N/A | initialize-project.js | No file I/O; not applicable. | 
| N/A | models.js | No file I/O; not applicable. | 
| N/A | rules.js | No file I/O; not applicable. | 
| N/A | response-language.js | No file I/O; not applicable. | 
| Status | Module file | Notes | 
|---|---|---|
| ✅ | dependency-manager.js | Passes context.tagfrom a dedicated context object | 
| task-manager/add-subtask.js | Contains internal fallback logic ( getCurrentTag) | |
| task-manager/analyze-task-complexity.js | Relies on readJSONfallback; does not enforce tag from caller | |
| task-manager/expand-all-tasks.js | Contains internal fallback logic ( getCurrentTag) | |
| task-manager/expand-task.js | Relies on readJSONfallback; does not enforce tag from caller | |
| task-manager/clear-subtasks.js | Relies on readJSONfallback; does not enforce tag from caller | |
| task-manager/list-tasks.js | Relies on readJSONfallback; does not enforce tag from caller | |
| task-manager/update-task-by-id.js | Contains internal fallback logic ( getCurrentTag) | |
| task-manager/update-subtask-by-id.js | Contains internal fallback logic ( getCurrentTag) | |
| task-manager/update-tasks.js | Contains internal fallback logic ( getCurrentTag) | |
| task-manager/remove-task.js | First readJSONomitstag; write has tag | |
| task-manager/research.js | First readJSONomitstag; later calls correct | |
| ❌ | task-manager/add-task.js | no tag in initial read / write – defaults to master | 
| ❌ | task-manager/set-task-status.js | read & write omit tag | 
| ❌ | task-manager/move-task.js | read & write omit tag | 
| ❌ | task-manager/generate-task-files.js | readJSONomits tag | 
| ❌ | utils/contextGatherer.js | readJSONomits tag | 
| ❌ | sync-readme.js | listTaskscall omitstag | 
| N/A | task-manager/tag-management.js | operates across all tags by design (reads raw, un-scoped data) | 
Pros
- Deterministic state – the tag is chosen exactly once; every layer gets the same value.
- Race-condition killer – no hidden fallback logic means concurrent invocations can't silently overwrite each other.
- Debuggable – when something is wrong you inspect the boundary, not every helper down-stream.
- Unit-test friendly – helpers accept explicit ctx; no guessing inside mocks.
- Stupid-fast IO – readJSON/writeJSONno longer need extra fs calls to "discover" a tag.
- Safe refactors – changing how/where the tag is stored only touches the resolver.
- Better error surfacing – missing tag triggers an immediate, obvious failure.
Cons
- Boiler-plate propagation – { projectRoot, tag }must be threaded through every call chain.
- Legacy breakage – older code/tests that omitted a tag explode until patched.
- Higher cognitive load – quick scripts must remember to resolve & pass ctx.
- Large diff surface – ripping out fallbacks touches many files.
runWithCtx({ projectRoot, tag }, () => coreFn(...))
const { projectRoot, tag } = getCtx(); // inside helpers / IOWhy I considered it
– removes parameter noise; deep helpers stay untouched.
Why I rejected it
- Hidden dependency – functions rely on ambient state; harder to reason about in isolation.
- Testing overhead – every unit test must wrap calls in runWithCtx, orgetCtx()is undefined.
- Async-hook limitations – spawning worker threads or non-hook-aware libraries loses the context.
The explicit-context approach stays transparent, predictable and cross-runtime.
Even if every caller tries to pass the tag, keeping a "smart" fallback inside the IO layer would re-introduce the bugs I am purging.
| Problem | What happens in a CLI / MCP world | 
|---|---|
| Two sources of truth | Boundary resolves feature-foo, butreadJSONlooks atstate.jsonand decidesmaster– the file winds up in the wrong context. | 
| Race conditions | In the long-running MCP server two simultaneous requests hit different tags; the one that happens to update state.jsonlast wins, corrupting the other request's write. | 
| Hidden coupling | Helpers must defensively double-check that the IO-inferred tag equals the ctx tag → more code, more tests. | 
| Harder tests | Unit tests would need to fake both context and the config/state files so that the fallback picks the "right" tag. | 
| Extra I/O | Every call rereads config/state to "detect" the tag – unnecessary disk churn. | 
Therefore IO obeys: it accepts the explicit tag and does no further guessing. This keeps behaviour deterministic across both the one-shot CLI process and the multi-request MCP server.
| Step | Responsibility | 
|---|---|
| 1 | Boundary layer (CLI command handler or MCP tool) calls resolveEffectiveTag({ projectRoot, explicitTag }). | 
| 2 | The resolved tagplusprojectRootare stored in actxobject and forwarded to direct-functions & core logic. | 
| 3 | readJSON&writeJSONrequire an explicittag; they no longer guess. | 
| 4 | Legacy code can use a thin shim ( @deprecated) that still calls the resolver. | 
add-task, clear-subtasks, expand, move, next, remove-task, research, show, generate, sync-readme
add-dependency, add-subtask, add-task, analyze, clear-subtasks, expand-all, expand-task, generate, get-task, get-tasks, move-task, next-task, parse-prd, remove-dependency, remove-subtask, research, update-subtask, update-task, validate-dependencies
add-dependency, add-subtask, add-task, analyze-task-complexity, clear-subtasks, complexity-report, expand-all-tasks, expand-task, generate-task-files, list-tasks, move-task, next-task, remove-subtask, research, update-subtask-by-id, update-task-by-id, update-tasks
This involves removing || getCurrentTag(projectRoot) || 'master' fallbacks from all the ⚠️ files listed in section 3.4 and ensuring their callers (from CLI, MCP tools, and direct functions) always provide an explicit tag.
All tests must now prove that the boundary-layer contract is honoured:
- Always pass a tag – every helper, core function, direct-function, CLI handler or MCP tool invoked from tests gets tag: 'master'(or another explicit tag).
- Mock updates
- readJSON(path, tag)– second param is mandatory and asserted.
- writeJSON(path, data, tag)– third param is mandatory and asserted.
 
- CLI / shell tests – append --tag masterto every command.
- Fixture helpers – sample blobs now include a top-level tagkey ({ …, tag: 'master' }).
- Negative guard – add one test that omits the tag and expects the new "tag required" error.
Standard resolver snippet to drop into each boundary layer:
const projectRoot = findProjectRoot(); // CLI helper
const resolvedTag = options.tag || getCurrentTag(projectRoot) || "master";
await coreFn(...args, { projectRoot, tag: resolvedTag, session });Boundary tools (MCP) do the same but read args.projectRoot instead of findProjectRoot.
- Once all boxes are green, make readJSON/writeJSONthrow iftagis missing – enforcing the contract.
- Update tests to supply { tag }where necessary.
- Remove legacy fallback path in a follow-up release.
Last updated: 2025-07-07