Skip to content

T-K7FR-task-work-worktree-init-language-agnostic

Status: closed/done · Impact: high · Complexity: small

Auto-generated from a /sdlc:task-work post-mortem in rust-path-opener (2026-05-19-obsidian-vault-id-uri). Review and promote to open/ready before picking up.

/sdlc:task-work Step 4’s “Initialize the worktree” block assumes the host project is a JS-flavored repo with pnpm, lefthook, and mise — it tells the operator to run mise trust && just setup-worktree. On a pure-Rust project (or anything without just setup-worktree), that command doesn’t exist and the operator either skips it without confidence or has to invent a justification. Detect what the project actually needs and run that, or document clearly that the step is JS-only and skippable for other stacks.

  • Step 4 of plugin/skills/task-work/SKILL.md includes a “do not skip” block titled “Initialize the worktree (mandatory — do not skip)”. It calls mise trust && just setup-worktree, which in turn runs pnpm run project:init:worktree.
  • The instructions explain why (lefthook + commitlint armed via commit-msg hook), grounded in a JS-toolchain assumption.
  • On sksizer/rust-path-opener (pure-Rust, no pnpm, no lefthook, no mise config, no setup-worktree recipe in justfile), Step 4 has no applicable action — the operator skipped it without consequence because the project doesn’t use the JS-flavored commit-msg hook the step is protecting.
  • The skill has no language/toolchain detection, so the JS-specific block runs as mandatory text regardless of project type.

Step 4’s init block becomes conditional on detected project signals. Examples:

  • package.json + pnpm-lock.yaml + lefthook.yml present → run mise trust && just setup-worktree.
  • Cargo.toml present and no package.json → no-op (or run a Rust-equivalent hook if one is defined).
  • Mixed or unknown → fall back to a generic “verify your project’s worktree-init recipe ran” check that consults a per-project manifest.

Alternative: split the JS init into a separate sub-skill (/sdlc:worktree-init-js) that /sdlc:task-work invokes only when the JS signals are present, and document the contract for other toolchains.

  1. Add a project-type detector helper (or extend an existing one) in plugin/lib/ that reports {js, rust, mixed, unknown} based on manifest files at the project root.
  2. Update Step 4 to branch on that signal. For rust, skip the init block silently with a one-line log. For js, run the existing pnpm/lefthook commands. For mixed, warn and run JS init.
  3. Optionally, allow projects to override via a .claude/sdlc/worktree-init.sh hook that any toolchain can define.
  4. Update Step 4’s doc text to describe the conditional behavior so operators understand why the init may or may not run.
  • plugin/skills/task-work/SKILL.md — rewrite the Step 4 init block.
  • Possibly plugin/lib/project_type.py (new) or similar — language detection helper.
  • Possibly plugin/skills/worktree-init-js/SKILL.md (new) if the init logic gets factored out.
  • AC-1: Running /sdlc:task-work in a pure-Rust project does not prompt the operator to run a non-existent just setup-worktree recipe.
  • AC-2: Running /sdlc:task-work in a JS project still arms lefthook before the first feature-branch commit (i.e. the current behavior is preserved).
  • AC-3: Step 4’s text in SKILL.md reflects the new conditional behavior and lists the manifest signals that trigger each branch.
  • Inventing Rust-side equivalents of lefthook / commitlint. If a Rust project doesn’t have a commit-msg hook, the skill assumes none is needed.
  • Toolchain detection for languages beyond JS and Rust. Add others when a real project needs them.
  • none
  • Surfaced 2026-05-20 by /sdlc:task-work running 2026-05-19-obsidian-vault-id-uri in sksizer/rust-path-opener. Step 4’s mandatory init block had no applicable action on that pure-Rust project; the operator skipped it without consequence because the JS commit-msg hook it’s protecting doesn’t exist there.

Captured by /sdlc:task-work on 2026-05-20. PR: pending.

  • AC-1: agent-manual — created /tmp/rust_demo with no sdlc.yaml, confirmed the Step 4 procedure short-circuits (no executor invocation, no prompt for a non-existent recipe). Then with an empty sdlc.yaml, ran the executor with --key worktree_init --allow-empty and observed exit=0 with a stderr warning only — no just setup-worktree attempt.
  • AC-2: agent-manual — populated worktree_init: ["echo mise trust simulated", "echo just setup-worktree simulated"] and confirmed the executor ran both verbs in declared order with the existing run_quality_checks.py machinery (OK 2/2). The shape is identical to the prior hardcoded recipe — a JS project that ports its two verbs into sdlc.yaml’s worktree_init: keeps the same pre-commit behaviour.
  • AC-3: auto — Step 4’s rewritten text in plugin/skills/task-work/SKILL.md enumerates three project shapes (JS-flavoured with declared verbs, non-JS with absent/empty key, no-sdlc.yaml projects) and the corresponding behaviour for each; the project-context bullet at the top of the SKILL also lists the worktree_init: slot alongside quality_checks:.
  • Reusing run_quality_checks.py as the executor instead of writing a new one — the --key NAME flag was a one-line addition and the whole shape (line/json/log modes, --allow-empty, project-root handling) carried over for free.
  • Reading plugin/conventions/sdlc-yaml.md first surfaced the worktree_init: slot was already RESERVED with the exact shape intended — implementation became “promote reserved to live” rather than “design from scratch.”
  • plugin/skills/CLAUDE.md’s “co-locate first, promote when shared” rule pointed at the right thing immediately: don’t make a new helper script when an existing shared one extends cleanly.
  • The hardcoded just setup-worktree survived in this project’s own task-work SKILL.md long after the project itself stopped having a justfile — the skill couldn’t self-detect that its own Step 4 was already broken against the very codebase hosting it. A pre-flight check on the live skill against the cwd’s manifests (a “does this Step 4 actually apply here?” lint) would have caught it months earlier. Open follow-up: a skill-runtime drift audit that probes each skill’s hardcoded commands against the current project’s manifests. -> T-E69Y-skill-md-runtime-drift-audit
  • The Step 4 doc previously declared itself “mandatory — do not skip” while being silently inapplicable on this project. The agent (correctly) skipped it, but the contract said otherwise. Skill prose that gates execution on a hardcoded command should carry a “applies-when” predicate that the skill or a linter can evaluate. Open follow-up: add an applies_when: shape to the invariants.yaml format so mandatory-step claims are checkable. -> 2026-05-20-skill-step-applies-when-predicate
  • run_quality_checks.py and the --key extension share no test fixtures, just live behavior. The script has no unit tests; this change relied on ad-hoc /tmp smoke-runs. Open follow-up: introduce a plugin/scripts/tests/test_run_quality_checks.py fixture suite (config-found, config-missing, key-missing, key-empty, key-non-list, key-with-verbs, --allow-empty permutations) so future flag additions have a regression net. -> T-SYM2-run-quality-checks-test-fixtures
  • 2026-05-20-skill-step-applies-when-predicate — created. New task: declare applies_when: predicates for conditional skill steps so the runtime can self-skip and prose stops claiming unconditional mandatoriness.
  • T-SYM2-run-quality-checks-test-fixtures — created. New task: cover run_quality_checks.py’s config / key / exit-code matrix with a regression fixture suite.
  • T-E69Y-skill-md-runtime-drift-audit — linked existing. Already-drafted auditor for SKILL.md vs. script --help; covers this run’s “skill couldn’t detect its own broken Step 4” gap.

← Back to Tasks