T-UBJK-co-locate-skill-specific-scripts
Status: closed/done · Impact: medium · Complexity: small
Today plugin/scripts/ holds a mix of “shared across multiple skills”
and “used by exactly one skill” scripts, with no signal at the
filesystem layer to distinguish them. Move single-skill scripts into
their owning skill’s directory (plugin/skills/<skill>/<script>.py)
and keep plugin/scripts/ for the actually-shared ones. Codify the
rule as a principle (“co-locate first, promote when shared”) so future
scripts land in the right place by default.
Audit of current callers:
| Script | Skills invoking it | Current home | Target home |
|---|---|---|---|
setup_planning.py | setup (only) | plugin/scripts/ | plugin/skills/setup/ |
new_epic.py | epic-new (only) | plugin/scripts/ | plugin/skills/epic-new/ |
migrate_entities.py | entities-migrate (only) | plugin/scripts/ | plugin/skills/entities-migrate/ |
audit_entities.py | entities-audit, setup, entities-migrate (subprocess) | plugin/scripts/ | stays shared — 3 callers |
new_task.py | task-new, backlog-triage, review-todos | plugin/scripts/ | stays shared — 3 callers |
new_milestone.py | milestone-new, milestones-from-file, backlog-triage | plugin/scripts/ | stays shared — 3 callers |
new_backlog.py | review-todos, backlog-triage | plugin/scripts/ | stays shared — 2 callers |
Three scripts (setup_planning.py, new_epic.py, migrate_entities.py)
have exactly one caller and belong with their skill. The other four
are genuinely shared and stay in plugin/scripts/.
PRINCIPLES.md and plugin/skills/CLAUDE.md say nothing about where
scripts should live; the implicit “drop it in plugin/scripts/” pattern
is a default by inertia.
Proposed
Section titled “Proposed”setup_planning.py,new_epic.py,migrate_entities.pylive next to their owning skill.- Every
${CLAUDE_PLUGIN_ROOT}scripts/<x>.pyreference in skill prose, in test runners, and in cross-script defaults (e.g.migrate_entities.py’s--audit-scriptdefault) is updated to the new path. PRINCIPLES.mdcarries a “Co-locate first, promote when shared” engineering principle.plugin/skills/CLAUDE.mdcarries the operational form of the same rule: single-skill scripts live inplugin/skills/<name>/, multi-skill scripts inplugin/scripts/, “promotion” is an explicit refactor when a second caller appears.- All existing eval suites still pass.
/sdlc:entities-auditand/sdlc:entities-migratecontinue to behave identically (the--audit-scriptdefault just resolves differently).
Approach
Section titled “Approach”-
Confirm the audit. Grep
plugin/skills/*/SKILL.mdandplugin/scripts/*.pyfor invocations of each script in the audit table. Flag any caller this task doesn’t already list. Adjust the target-home column if the actual call graph differs from the table. -
Add the principle to
PRINCIPLES.md. A short section: scripts start co-located with their skill; promotion to a shared home is an explicit refactor triggered by a second skill needing the script. The principle exists so the default behavior pushes toward co-location, with promotion as a deliberate event. -
Add the operational convention to
plugin/skills/CLAUDE.md. Mirror the principle in the skill-authoring context: when adding a new script, ask “is any other skill plausibly going to call this in the next few iterations?” — if not, put it inplugin/skills/<your-skill>/. ReferencePRINCIPLES.mdfor the why. -
Move
setup_planning.py→plugin/skills/setup/setup_planning.py. Updateplugin/skills/setup/SKILL.mdto invoke the new path.git mvto preserve history. Run setup against a tmp project root to verify. -
Move
new_epic.py→plugin/skills/epic-new/new_epic.py. Updateplugin/skills/epic-new/SKILL.md. Verify by creating an epic against a tmp project root. -
Move
migrate_entities.py→plugin/skills/entities-migrate/migrate_entities.py. Updateplugin/skills/entities-migrate/SKILL.md. Update the script’s owndefault_audit_scriptresolution — it currently uses(here / "audit_entities.py"), which after the move points at the wrong location; change to(here / ".." / ".." / "scripts" / "audit_entities.py")or similar. Update the migrate eval runner’sSCRIPTpath. -
Test every touched skill. Run
plugin/skills/entities-audit/tests/run_evals.py,plugin/skills/entities-migrate/tests/run_evals.py, and an end-to-end smoke (setup_planning.pyagainst a fresh tmp dir thenaudit_entities.pyagainst it). All must pass. -
Grep for stale path references.
grep -rEn 'plugin/scripts/(setup_planning|new_epic|migrate_entities)\.py'across the whole repo (skipping.gitandnode_modules) should return no live references — only docs that intentionally describe the migration (this task, post-mortems, etc.).
Files to touch
Section titled “Files to touch”PRINCIPLES.md— add “Co-locate first, promote when shared” principle.plugin/skills/CLAUDE.md— add operational convention referencing the principle.plugin/scripts/setup_planning.py→plugin/skills/setup/setup_planning.py(move).plugin/scripts/new_epic.py→plugin/skills/epic-new/new_epic.py(move).plugin/scripts/migrate_entities.py→plugin/skills/entities-migrate/migrate_entities.py(move).plugin/skills/setup/SKILL.md— update script-path reference.plugin/skills/epic-new/SKILL.md— update script-path reference.plugin/skills/entities-migrate/SKILL.md— update script-path reference.plugin/skills/entities-migrate/migrate_entities.py— update internaldefault_audit_scriptresolution after the move.plugin/skills/entities-migrate/tests/run_evals.py— updateSCRIPTpath.- Any other SKILL.md / doc that grep turns up referencing the moved scripts.
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
PRINCIPLES.mdcontains a “Co-locate first, promote when shared” section that names what triggers promotion (a second skill needing the script). - AC-2:
plugin/skills/CLAUDE.mdcontains a section that operationalizes the principle for skill authors and points atPRINCIPLES.md. - AC-3:
ls plugin/scripts/does NOT containsetup_planning.py,new_epic.py, ormigrate_entities.py.ls plugin/skills/setup/,plugin/skills/epic-new/, andplugin/skills/entities-migrate/contain the moved scripts respectively. - AC-4:
grep -rEn 'plugin/scripts/(setup_planning|new_epic|migrate_entities)\.py' --include='*.md' --include='*.py'outside this task file returns no live references. - AC-5:
plugin/skills/entities-migrate/tests/run_evals.py8/8 andplugin/skills/entities-audit/tests/run_evals.py8/8 still pass. - AC-6: End-to-end smoke — fresh tmp project root →
plugin/skills/setup/setup_planning.py→plugin/scripts/audit_entities.pyreports no drift. (The audit invocation does NOT need to change becauseaudit_entities.pystays inplugin/scripts/.) - AC-7:
git log --follow plugin/skills/setup/setup_planning.pyshows the history of the file from before the move (i.e.,git mvwas used, not delete + add).
Out of scope
Section titled “Out of scope”- Re-shaping
plugin/conventions/(commit-messages, branch-naming) — those are durable cross-skill conventions and stay where they are. - Moving
audit_entities.py,new_task.py,new_milestone.py,new_backlog.py— they’re shared by 2+ skills and stay inplugin/scripts/. - Renaming
plugin/scripts/to something more explicit likeplugin/scripts/shared/. The implicit “this dir = shared” works once the principle is documented; renaming is cosmetic churn. - The validator at
plugin/validators/validate_frontmatter.py— it’s already in its own directory and used by every skill that touches an entity; not in scope here.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Raised during the SDLC:Mini session on 2026-05-19 while reviewing
the T-XPG7-validator-skill-examples-no-pipe-tail follow-up
task. The conversation about wrapping the validator (rejected) led
to a broader observation: plugin/scripts/ mixes single-skill and
shared scripts with no signal to tell them apart. Establishes the
co-location principle so future scripts default to the right place.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-20. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”-
AC-1: auto — verified by
command grep "Co-locate" PRINCIPLES.mdin the relevance check; principle landed in an earlier pass and is already on main. -
AC-2: auto — verified by
command grep "Co-locate" plugin/skills/CLAUDE.mdin the relevance check; operational convention already on main. -
AC-3: auto —
ls plugin/scripts/confirms the three scripts are gone;ls plugin/skills/{setup,epic-new,entities-migrate}/confirms each lands next to its owning skill. -
AC-4: agent-manual —
command grep -rEn 'plugin/scripts/(setup_planning|new_epic|migrate_entities)\.py' --include='*.md' --include='*.py'returns zero results in
plugin/andsite/. Hits remain insidedocs/planning/tasks/*.md— these are historical task documents recounting the original creation/extension of those scripts at their oldplugin/scripts/home, not live references. The task’s relevance_note already carved out “docs that intentionally describe the migration.” -
AC-5: auto —
${CLAUDE_PLUGIN_ROOT}scripts/run_quality_checks.py --config sdlc.yaml --linereportedOK 5/5, which covers bothentities-audit/tests/run_evals.pyandentities-migrate/tests/run_evals.py. -
AC-6: agent-manual — ran
plugin/skills/setup/setup_planning.py --project-root <tmp>(created 4 entity dirs +sdlc.yaml), thenplugin/scripts/audit_entities.py --project-root <tmp>(exit=0, “No drift.”). -
AC-7: auto —
git log --follow --oneline plugin/skills/setup/setup_planning.pyshows the pre-move history (cc4b2f4 feat(setup): scaffold top-level sdlc.yaml during /sdlc:setup, etc.), confirminggit mvpreserved provenance.
What worked
Section titled “What worked”- Pre-task relevance check immediately confirmed AC-1 / AC-2 were already satisfied on main, so the
scope reduced cleanly to the mechanical move. The task body’s
relevance_notehad already telegraphed this. git mvfor all three scripts + targeted edits to relative-path resolution kept the surface area small (14 files changed, mostly one-line updates).- The
default_audit_scriptresolution inmigrate_entities.pywas called out explicitly in the task body’s Approach step 6 and in the orchestrator’s brief, so the cross-script default was easy to fix without surprises.
Friction and automation gaps
Section titled “Friction and automation gaps”site/scripts/regen.mjsonly walksplugin/scripts/for script reference pages, so the three moved scripts had their auto-generated reference pages deleted with no replacement. The site reference now under-covers those scripts. Gap: regen.mjs should also walkplugin/skills/<skill>/*.pyso co-located scripts keep their reference pages. This is the natural follow-up of the co-location principle landing. → T-5PLQ-regen-walks-skill-co-located-scriptssite/scripts/regen.mjsalso surfaced unrelated drift on this run (newly-createdfind-quality-checks.md,run_quality_checks.md,detect_quality_runners.mdreference pages plus four updates to skill reference pages) that wasn’t caused by this task. Had to manuallygit restorethose to keep the PR scope tight. Gap: the orchestrator should not runregen.mjsfor unrelated scope reasons during a mechanical-refactor task, or/dev-update-docsshould be a separate scheduled pass so per-task PRs don’t end up coupled to ambient regen drift. → T-ETML-decouple-regen-from-task-work-prs- The historical
docs/planning/tasks/*.mdreferences toplugin/scripts/<moved>.pyremain. They’re frozen historical artifacts (each task documents the world as it was when written), but a literal reading of AC-4’s grep counts them. Gap: AC wording could be tightened to “no live references in plugin/ or site/” so the historical-artifact carve-out is explicit and machine-checkable. (skipped — meta/doc-only) - The orchestrator’s brief mentioned “Step 9’s contamination rebase will need to drop the
chore(tasks): start ...commit from main between your branch-point and your start-commit if it lands first.” That parallel session’s commit was already on main when this run started (it landed before this task-work fired). I’ll need to confirm the rebase is clean againstorigin/mainat Step 9; gap: a small helper that prints “your feat branch ancestry vs origin/main” in one line would make the contamination-rebase decision a one-liner instead of eyeballinggit log. → T-WKQD-task-work-ancestry-helper-one-liner - The
Skilltool was denied at Step 5a so I had to inline the ensure-ready logic (read implementation-ready.md, evaluate, stamp, validate, commit). The skill is supposed to be invoked as a sub-call fromtask-work. Gap: when invoked via/sdlc:orchestrate, the nestedSkilltool isn’t authorized — the dispatch contract should either grantSkillto sub-agents or task-work’s documentation should call out the manual fallback explicitly. → T-QJY7-orchestrate-grants-skill-tool-to-subagents
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-5PLQ-regen-walks-skill-co-located-scripts — created; extend regen.mjs to walk skill-co-located scripts.
- T-ETML-decouple-regen-from-task-work-prs — created; keep ambient site-regen drift out of per-task PRs.
- T-WKQD-task-work-ancestry-helper-one-liner — created; deterministic contamination check for Step 9.
- T-QJY7-orchestrate-grants-skill-tool-to-subagents — created; let dispatched task-work runs invoke nested skills.