T-TZQ7-sdlc-working-dir-configurable-via-sdlc-yaml
Status: closed/obsoleted · Impact: medium · Complexity: medium
The SDLC plugin currently writes its runtime state (PR cursors, orchestrator
log, task worktrees) under .claude/, which is conceptually Claude Code’s
own directory for settings, hooks, and session state. Conflating “Claude
Code’s stuff” with “SDLC plugin’s stuff” hurts ownership clarity and
forces narrow per-path permission allowlists in .claude/settings.json for
files the plugin owns end-to-end. This task introduces an explicit
configurable working directory for SDLC runtime artifacts so projects can
adopt a cleaner default (.sdlc/) while preserving the option to point
elsewhere.
Three kinds of SDLC plugin runtime state live under .claude/:
.claude/pr-cursors/<N>.json— per-PR “last seen comment at” cursors written by/sdlc:pr-checkStep 4 (seeplugin/skills/pr-check/SKILL.md:170-203)..claude/orchestrator-log.md— append-only digest log written one line per tick by/sdlc:orchestrateStep 5 (seeplugin/skills/orchestrate/SKILL.md”## 5. Digest”)..claude/worktrees/<basename>/— git worktrees created by/sdlc:task-workStep 4 and torn down by/sdlc:task-close-out. Referenced byplugin/scripts/count_inflight_tasks.pyand byplugin/skills/orchestrate/SKILL.mdwhen dispatching resolution sub-agents.
Each of those paths is a literal string in the skill prose. There is no
configuration knob, no shared “where does SDLC runtime state live” helper,
and no provisioning step in /sdlc:setup. The .claude/* blanket
gitignore covers them, but every write requires a narrow allowlist entry
in .claude/settings.local.json because the harness defaults to cautious
on .claude/ writes.
Proposed
Section titled “Proposed”sdlc.yaml gains a top-level working_dir: key (exact key name TBD —
see “Open questions” in Approach) whose value is a project-root-relative
path under which all SDLC runtime artifacts live. Default value:
.sdlc/ (the clean break from .claude/). Each skill that today writes
to a .claude/<thing> path reads the configured working_dir and writes
to <working_dir>/<thing> instead. /sdlc:setup provisions the
directory on first run and adds it to .gitignore if not already
covered. A migration step (one-shot script or part of /sdlc:setup)
moves any existing .claude/pr-cursors/, .claude/orchestrator-log.md,
and .claude/worktrees/ content to the new location, including
git worktree move for active worktrees.
Approach
Section titled “Approach”Open questions to resolve before implementation:
- Key name.
working_dir,runtime_dir,state_dir,sdlc_working_dir? Leanworking_dirsince it lives under thesdlc.yamlnamespace already. - Default value.
.sdlc/(clean break, conceptually correct) vs.claude/(backwards-compatible, no migration needed for existing projects). Recommend.sdlc/— the migration is a one-shot cost, and the conceptual separation pays back every day after. - Migration scope. Active worktrees (
git worktree move) are the riskiest piece. Do we migrate live, require a drained queue, or support a hybrid (new worktrees under new dir, old ones stay)? - Per-skill or per-project. Is this one global directory for all
SDLC runtime, or per-skill subdirs configurable independently?
Recommend one directory with conventional subpaths
(
<working_dir>/pr-cursors/, etc.) — simpler.
Implementation steps:
-
Decide the open questions above (likely via a short
/sdlc:discuss-phase-style interaction or PR-review feedback). -
Document the
working_dir:key inplugin/conventions/sdlc-yaml.mdwith default, semantics, and the conventional subpaths. -
Add a shared loader helper (e.g.
plugin/scripts/resolve_working_dir.py) that reads sdlc.yaml, applies the default, returns the absolute path. Callers shell out to it rather than re-parsing YAML. -
Update
plugin/skills/pr-check/SKILL.mdStep 4 to use<working_dir>/pr-cursors/(default.sdlc/pr-cursors/). -
Update
plugin/skills/orchestrate/SKILL.mdStep 5 to use<working_dir>/orchestrator-log.md. -
Update
plugin/skills/task-work/SKILL.md,plugin/skills/task-close-out/SKILL.md, andplugin/scripts/count_inflight_tasks.pyto use<working_dir>/worktrees/. -
Update
/sdlc:setup(plugin/scripts/setup_planning.pyand theskill prose) to create the working_dir and add it to
.gitignore. -
Write a migration script (
plugin/scripts/migrate_working_dir.py?)that detects existing
.claude/pr-cursors/,.claude/orchestrator-log.md,.claude/worktrees/and moves them to the configuredworking_dir, usinggit worktree movefor worktrees. -
Run the migration on this project (sdlc-plugin itself) and verify
no in-flight task-work runs break.
-
Add the new permission allowlist entries
(
Write(<working_dir>/pr-cursors/*), etc.) to the project’s.claude/settings.local.jsonand document the pattern for downstream consumers.
Files to touch
Section titled “Files to touch”plugin/conventions/sdlc-yaml.md— document theworking_dir:key, default, conventional subpaths.plugin/scripts/resolve_working_dir.py(new) — shared loader that resolves<project-root>/sdlc.yaml’s working_dir to an absolute path.plugin/skills/pr-check/SKILL.md— replace literal.claude/pr-cursors/with<working_dir>/pr-cursors/.plugin/skills/orchestrate/SKILL.md— replace literal.claude/orchestrator-log.mdwith<working_dir>/orchestrator-log.md.plugin/skills/task-work/SKILL.md— replace literal.claude/worktrees/with<working_dir>/worktrees/.plugin/skills/task-close-out/SKILL.md— same replacement.plugin/scripts/count_inflight_tasks.py— same replacement.plugin/skills/project-cleanup/(any helper that scans worktrees) — same replacement.plugin/scripts/setup_planning.py— create<working_dir>and add to project’s.gitignoreif absent.plugin/scripts/migrate_working_dir.py(new) — one-shot migration for existing.claude/-based projects.sdlc.yaml(this project) — add explicitworking_dir: .sdlcentry..claude/settings.local.json— add allowlist entries for the new.sdlc/write paths (and eventually drop the.claude/ones)..gitignore— ensure.sdlc/is covered (or whatever the configured default is).
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
sdlc.yamlaccepts aworking_dir:key; the documented default is applied when the key is absent or the file is missing. - AC-2: After running this task’s migration on a clean checkout of
this project, no
.claude/pr-cursors/,.claude/orchestrator-log.md, or.claude/worktrees/paths remain; equivalents exist under the configuredworking_dir. - AC-3:
/sdlc:orchestrateruns a full tick successfully against the new working_dir layout (writes the digest line, dispatches resolution sub-agents with worktree paths derived from the new config). - AC-4:
/sdlc:setupon a fresh project creates<working_dir>/and adds it to the project’s.gitignore. - AC-5:
plugin/conventions/sdlc-yaml.mddocuments the new key including default, semantics, and conventional subpaths.
Out of scope
Section titled “Out of scope”<Things adjacent to this task that are deliberately NOT being addressed here. Useful for keeping PR review focused and for future tasks to point back to. Always required: if scope is obvious and nothing is excluded, leave a single ”- none” bullet so the explicit signal is “scope considered, nothing to exclude.”>
- none
Dependencies
Section titled “Dependencies”<Other tasks, branches, infra changes, or external decisions this task
waits on. For hard “B cannot start until A closes” dependencies on
other tasks or epics, also record them in the frontmatter
depends_on: array (strict wikilink shape, e.g.
2026-04-12-other-task or E0042) — the audit walks that
graph for cycle detection. This prose section is the human-readable
narrative; depends_on: is the machine-readable canonical list.
Leave a single ”- none” bullet if there are none.>
- <dependency or “none”>
Discovery context
Section titled “Discovery context”Surfaced 2026-05-21 during a working session with the orchestrator:
permission prompts for .claude/pr-cursors/ and
.claude/orchestrator-log.md writes (the harness defaults to cautious
on .claude/ writes) prompted a discussion about whether the SDLC
plugin should own its own runtime directory. The immediate permission
prompt issue was patched separately with narrow allowlist entries; this
task captures the broader reorganization that makes those allowlist
entries cleaner and the ownership distinction explicit.