Tasks: Skills-Only Installer Overhaul
Format: [ID] [P?] [Story] Descriptionā
- [P]: Can run in parallel with other [P] tasks in the same phase (different files, no inter-task dependencies)
- [Story]: User story (US1 = "one install, every agent" P1; US2 = "helpers ship as real Skills" P1; US3 = "sane uninstall across both scopes" P2)
Phase 1: Foundational ā Registry & Rendering Core (US1, US2)ā
Purpose: Refactor the data model and rendering pipeline so every downstream consumer can be flipped to the new shape in lockstep. Phase 1 leaves the build temporarily broken (tests reference old types) ā Phases 2ā4 finish before anything ships.
-
T001 [US1] Rewrite
ui/src/app/api/skills/live-skills/agents.ts:- Drop
defaultLayout,format,ext,isFragment,skillsPaths,AgentLayout,AgentFormat,layoutsAvailableFor,pathsForLayout - Define
UNIVERSAL_USER_PATHS = ["~/.claude/skills/{name}/SKILL.md", "~/.agents/skills/{name}/SKILL.md"]andUNIVERSAL_PROJECT_PATHS = [".claude/skills/{name}/SKILL.md", ".agents/skills/{name}/SKILL.md"] - Reduce
AgentSpecto{ id, label, installPaths: Partial<Record<AgentScope, readonly string[]>>, argRef: '$ARGUMENTS' | '$1', launchGuide: string, docsUrl?: string } - Reduce
AGENTSto 5 entries:claude,cursor,codex,gemini,opencode. Removecontinue,specify. Codex and Gemini keepargRef: '$1'; the other three use'$ARGUMENTS' - Rewrite
renderForAgentto always emit aSKILL.mdbody withname:+description:frontmatter (anddisable-model-invocation+allowed-toolswhen the source template already declares them) - Update the exported
RenderResulttype to dropfile_extension,format,is_fragment,layout,layout_requested,layout_fallback,layouts_available.install_pathsis nowPartial<Record<AgentScope, readonly string[]>>.install_pathis the first entry of the resolved scope's array (display only). - Per FR-001 / FR-002 / FR-003 / FR-004 / FR-005 / FR-006
- Drop
-
T002 [US1] Rewrite
ui/src/app/api/skills/_lib/template-route.ts:- Drop the
layoutfield fromRenderInputs - Stop importing
AgentLayout/layoutsAvailableFor - Remove
layout,layout_requested,layout_fallback,layouts_available,file_extension,format,is_fragmentfrom the response JSON - Map
agent.installPathsdirectly toinstall_pathsin the response - Per FR-006
- Drop the
-
T003 [US1] Rewrite
ui/src/app/api/skills/install.sh/route.tsā Part A (registry/render plumbing):- Drop the
layoutquery parameter handling (silently ignore if present, per FR-007) - Drop the per-agent format
switch(markdown-frontmatter / markdown-plain / gemini-toml / continue-json-fragment) ā every agent now goes through the singleSKILL.mdrenderer - Replace single-target install path resolution with multi-target: enumerate
agent.installPaths[scope]and emit onemkdir -p+cat > ⦠<< 'EOF'block per path - Per FR-001 / FR-007 / FR-009 ($ARGUMENTS ā $1 substitution for codex/gemini)
- Drop the
-
T004 [US1]
install.sh/route.tsā Part B (~/.claude/settings.jsonpatch trim):- Remove the two emitted
Bash(uv run ~/.config/caipe/caipe-skills.py*)andBash(python3 ~/.config/caipe/caipe-skills.py*)entries frompermissions.allow - Keep the SessionStart hook entry under
hooks.SessionStartuntouched - Per FR-010
- Remove the two emitted
-
T005 [US3]
install.sh/route.tsā Part C (manifest entries widen topaths: []):- Change the JSON written to
~/.config/caipe/installed.jsonand./.caipe/installed.jsonfrom{ "name": "...", "path": "..." }to{ "name": "...", "paths": ["...", "..."] } - Manifest read path: treat a legacy
pathfield as a one-elementpathsarray - Per FR-012
- Change the JSON written to
-
T006 [US3]
install.sh/route.tsā Part D (buildUninstallScriptwalks both manifests):- When invoked without
scope=, walk~/.config/caipe/installed.jsonfirst (independent y/N/a/q loop), then./.caipe/installed.json(independent y/N/a/q loop) - For each entry, iterate
paths[]removing each file, thenrmdirthe empty parent skill directory in each tree - Handle legacy
pathshape transparently - Per FR-013 / FR-014
- When invoked without
-
T007 [US2]
install.sh/route.tsā Part E (extend--upgradelegacy cleanup):- For each of the 5 agents, remove the legacy commands-layout artifacts at both user and project scope:
- Claude:
~/.claude/commands/<name>.md+.claude/commands/<name>.md - Cursor:
~/.cursor/commands/<name>.md+.cursor/commands/<name>.md - Codex:
~/.codex/prompts/<name>.md+.codex/prompts/<name>.md - Gemini:
~/.gemini/commands/<name>.toml+.gemini/commands/<name>.toml - opencode:
~/.config/opencode/command/<name>.md+.opencode/command/<name>.md
- Claude:
- Also strip the two
Bash(...caipe-skills.py*)allowlist entries from~/.claude/settings.jsonif a prior install added them - Per FR-011
- For each of the 5 agents, remove the legacy commands-layout artifacts at both user and project scope:
Checkpoint: After Phase 1 the API routes are coherent under the new shape. Tests are still on old shape and will fail until Phase 4. Helpers and UI are still on old shape and will be flipped in Phases 2 and 3.
Phase 2: Helper Templates (US2)ā
Purpose: Update the two CAIPE-authored helper templates so the new install layout treats them as proper Skills with auto-approved tool access.
-
T008 [P] [US2] Update
charts/ai-platform-engineering/data/skills/live-skills.md:- Add
disable-model-invocation: trueto frontmatter - Add
allowed-tools:array with bothBash(uv run ~/.config/caipe/caipe-skills.py*)andBash(python3 ~/.config/caipe/caipe-skills.py*) - Per FR-008
- Add
-
T009 [P] [US2] Update
charts/ai-platform-engineering/data/skills/update-skills.md:- Same frontmatter additions as T008
- Per FR-008
Checkpoint: After Phase 2 the helper templates are valid SKILL.md files that will install with the right pre-approval bytes.
Phase 3: UI Simplification (US1, US3)ā
Purpose: Remove the layout toggle and demote project-scope to "Advanced" so the default user flow is one install, one click.
- T010 [US1] Update
ui/src/components/skills/TrySkillsGateway.tsx:- Remove the entire "Skills layout" toggle (radio group, helper text, label)
- Move project-scope selection inside an
<details>(or equivalent collapse component) labeled "Advanced: project scope" - When project scope is selected, render a
.gitignorereminder block listing.caipe/,.claude/,.agents/ - Update the path preview block to render all paths in
install_paths[scope]as a vertical list (currently shows one) - Refresh the curl one-liner preview to drop
&layout=ā¦(it never appears in the new flow) - Per FR-015
Checkpoint: After Phase 3 the UI matches the new default-user-scope, multi-target reality.
Phase 4: Test Rewrites (US1, US2, US3)ā
Purpose: Bring the four affected Jest test files into alignment with the new shape so make caipe-ui-tests passes.
-
T011 [P] [US1] Rewrite
ui/src/app/api/skills/live-skills/__tests__/agents.test.ts:- Remove all
defaultLayout,format,ext,isFragment,layoutsAvailableFor,pathsForLayoutassertions - Assert the registry has exactly the 5 expected entries
- Assert each agent's
installPaths.userandinstallPaths.projectcontain the two universal paths - Assert
renderForAgentalways returns aSKILL.mdbody withname:+description:frontmatter - Assert codex/gemini get
$1substitution; others keep$ARGUMENTS
- Remove all
-
T012 [P] [US1] Rewrite
ui/src/app/api/skills/install.sh/__tests__/route.test.ts:- Drop
?layout=ā¦from every test URL - Add assertions that the emitted bash writes to both target paths per skill (grep for both path patterns in the script body)
- Add an assertion that an incoming
?layout=commandsis silently accepted (no 400) and the emitted script still writes the new layout
- Drop
-
T013 [P] [US3] Rewrite
ui/src/app/api/skills/install.sh/__tests__/route.uninstall.test.ts:- Update fixture manifests to use the new
paths: []shape - Add a test for legacy
path:shape (assert it is read transparently and rewritten aspaths:) - Add a test for the no-
scope=invocation that asserts the emitted script walks both manifests in order and prompts independently
- Update fixture manifests to use the new
-
T014 [P] [US3] Rewrite
ui/src/app/api/skills/install.sh/__tests__/route.uninstall.smoke.test.ts:- Update bash-syntax smoke test to match the new uninstall script structure (multi-manifest walk, paths[] iteration, parent-dir rmdir)
-
T015 [P] [US1] Update
ui/src/components/skills/__tests__/TrySkillsGateway.uninstall.test.tsx:- Drop assertions that reference the layout toggle DOM
- Add assertions that project-scope selector is hidden until the Advanced disclosure is opened
- Add assertion that the path preview lists 2 paths for user scope
Checkpoint: After Phase 4, make caipe-ui-tests passes end-to-end.
Phase 5: Verification & Commitā
-
T016 Run the full UI test suite from the repo root:
make caipe-ui-testsInvestigate and fix any regressions. The expectation is zero failures; if a non-target test breaks, that is a real regression to fix before commit.
-
T017 Manual smoke against a locally running UI (owner: human reviewer ā automated suite + bug-fix commit
996fb2a6cover the regression that prompted this; this task is the optional final hand-validation before merge):curl -fsSL 'http://localhost:3000/api/skills/install.sh?scope=user' | bash
ls ~/.claude/skills/ ~/.agents/skills/ # both populated
curl -fsSL 'http://localhost:3000/api/skills/install.sh?scope=user' | bash
# ā second run should be a no-op (zero new bytes); manifest unchanged
curl -fsSL 'http://localhost:3000/api/skills/install.sh?mode=uninstall' | bash
# ā no scope= ā walks both manifests, prompts y/N/a/q per skill -
T018 Conventional Commits + DCO. One commit per coherent slice on
fix/skills-ai-generate-use-dynamic-agents:refactor(skills): collapse agent registry to skills-only universal layout(T001 + T002)refactor(skills): rewrite install.sh for multi-target writes and dual-manifest uninstall(T003āT007)feat(skills): helper templates ship as real SKILL.md with allowed-tools(T008āT009)refactor(ui/skills): drop layout toggle, demote project scope to Advanced(T010)test(skills): rewrite installer + UI tests for skills-only layout(T011āT015)
Use
git commit -sfor every commit (DCO).
Phase 6: Multi-source crawl + path filtering (US4) ā added 2026-05-04ā
Purpose: Bring GitLab to feature parity with GitHub on the per-skill ad-hoc importer, add a path-prefix filter to both the hub crawler and the per-skill importer so admins can target specific subdirectories of large monorepos, and fix the GitLab subgroup URL truncation bug. All work continues on fix/skills-ai-generate-use-dynamic-agents.
Phase 6a: Schema + crawler core (US4)ā
-
T019 [US4] Extend
SkillHubDoc(inui/src/lib/hub-crawl.ts) with optionalinclude_paths?: readonly string[]. UpdatecrawlGitHubRepo(owner, repo, token, includePaths?)andcrawlGitLabRepo(projectPath, token, includePaths?)to accept the new arg. WhenincludePathsis non-empty, filterskillMdPathsto entries whose path starts with one of the prefixes (after enforcing a trailing/on each prefix). LeavebelongsToNestedSkilland the ancillary-collection loop untouched. Per FR-021. -
T020 [US4] Update
_crawlAndCache(same file) to readhub.include_pathsand forward it to whichever crawler matcheshub.type. Per FR-021.
Phase 6b: Hub admin API (US4)ā
-
T021 [US4] Update
POST /api/skill-hubs(ui/src/app/api/skill-hubs/route.ts):- Accept optional
include_paths: string[]in the request body - Validate: each entry MUST match
/^[A-Za-z0-9._\-/]+$/(no.., no leading/); trim; drop empties; dedupe; append a trailing/if absent; cap at 20 entries - Persist the normalized array into the new doc; absent or empty stays absent (so existing docs are untouched)
- Widen the URL normalizer: when the URL host is
gitlab.com(or matches the configuredGITLAB_API_URLhost), keep every path segment after the host (preserves subgroups). The existing two-segment truncation MUST stay forgithub.comonly. - Per FR-020, FR-022.
- Accept optional
-
T022 [US4] Update
PATCH /api/skill-hubs/[id](ui/src/app/api/skill-hubs/[id]/route.ts):- Accept the same
include_pathsfield with the same validation/normalization helper extracted from T021 (share the function ā do not duplicate) - Apply the same widened GitLab subgroup normalizer
- Per FR-020, FR-022.
- Accept the same
Phase 6c: Per-skill importer (US4)ā
-
T023 [US4] Create
ui/src/app/api/skills/import/route.ts(the new source-agnostic endpoint):POSTbody:{ source: "github" | "gitlab", repo: string, paths: string[], credentials_ref?: string }- Accept legacy single
path: stringshape transparently (treat aspaths: [path]) - GitHub branch reuses today's
import-githubflow (Git Trees API + contents API) but iterates over every entry inpaths[] - GitLab branch hits
${GITLAB_API_URL || "https://gitlab.com/api/v4"}/projects/<encoded-repo>/repository/tree?recursive=true&per_page=100, then fetches each blob viarepository/files/<encoded>/raw?ref=HEAD. UsePRIVATE-TOKENheader (matchingcrawlGitLabRepo); resolve token viavalidateCredentialsRefwithGITLAB_TOKENfallback. - Multi-path merge: first-wins; populate
conflicts: [{ name, kept_from, dropped_from }]for every dropped duplicate; always return the field (empty array when none). - Response:
{ files, count, conflicts }. Wrapped viasuccessResponselike the legacy route. - Per FR-016, FR-017, FR-018.
-
T024 [US4] Update
ui/src/app/api/skills/import-github/route.tsto be a thin proxy to/api/skills/importwithsource: "github"injected. Mark the file's docstring as "deprecated ā prefer POST /api/skills/import". Keep the legacy behavior of returning just{ files, count }(noconflictsfield) so any existing caller is byte-compatible. Per FR-016.
Phase 6d: UI surfaces (US4)ā
-
T025 [US4] Create
ui/src/components/skills/workspace/RepoImportPanel.tsxbased on the existingGithubImportPanel.tsxwith these additions:- Source toggle above the inputs (radio or segmented control: GitHub / GitLab) ā default GitHub for back-compat
- Multi-path support: render the
pathinput as a stack of inputs with a+ Add another pathbutton (capped at 5 prefixes) - Placeholder + credentials hint switch with the source toggle (GitHub:
anthropics/skills,GITHUB_TOKEN; GitLab:mycorp/platform,GITLAB_TOKEN) - POST to
/api/skills/importwith the new body shape; surfaceconflicts.lengthas a non-blocking toast (e.g. "Imported N files; skipped M duplicates") - Per FR-019.
-
T026 [US4] Replace
ui/src/components/skills/workspace/GithubImportPanel.tsxwith a one-line re-export ofRepoImportPanelso existing imports (import { GithubImportPanel } from ...) keep working without churn. Per FR-019. -
T027 [US4] Update
ui/src/components/admin/SkillHubsSection.tsxto add an "Include paths (optional)" input to the hub registration / edit form:- Multi-line textarea (one prefix per line) is the simplest UI; show a hint: "Leave empty to crawl the entire repo. Trailing slashes are added automatically."
- On submit, split on newlines, send as
include_paths: string[] - On display (existing hubs), render any persisted
include_pathsas a wrapped chip list under the location - Per FR-020.
Phase 6e: Tests (US4)ā
- T028 [P] [US4] New + updated test files (run in parallel ā independent files):
- NEW
ui/src/lib/__tests__/hub-crawl-include-paths.test.ts: assert both crawlers filter the SKILL.md candidate list to the configured prefixes; assertbelongsToNestedSkillinvariant still holds; assert empty/absentincludePathsis identical to today's behavior. - NEW
ui/src/app/api/skills/import/__tests__/route.test.ts: cover GitHub branch (matches legacy behavior), GitLab branch (PRIVATE-TOKEN header, encoded subgroup paths), multi-path merge with conflicts, and the legacy single-pathbody shape. - UPDATE
ui/src/app/api/skill-hubs/__tests__/url-validation.test.ts: add a subgroup-URL case (https://gitlab.com/mycorp/devops/platformāmycorp/devops/platform); add aninclude_pathsvalidation case (rejects.., leading/, non-allowed chars; normalizes trailing slashes; caps at 20). - UPDATE
ui/src/components/skills/workspace/__tests__/import-panels.test.tsx: assert the source toggle switches placeholders + bodysource; assert "+ Add another path" appends a prefix (capped at 5); assert aconflictspayload surfaces a toast. - Per FR-016 through FR-022.
- NEW
Phase 6f: Verification & commitā
-
T029 Run the full UI suite again:
cd ui && npx jest --no-coverage. Result: 148 suites pass / 2899 tests pass / 1 skipped (added 2 new suites āhub-crawl-include-paths+import/__tests__/routeā and extendedurl-validation+import-panels; baseline was 146). -
T030 Conventional Commits + DCO. Commit slicing on
fix/skills-ai-generate-use-dynamic-agents:feat(skills): add include_paths filter to hub crawler(T019, T020)feat(skill-hubs): accept include_paths and fix GitLab subgroup truncation(T021, T022)feat(skills): source-agnostic ad-hoc importer with GitLab + multi-path(T023, T024)feat(ui/skills): RepoImportPanel + admin hub include_paths input(T025, T026, T027)test(skills): cover include_paths + multi-source importer(T028)
Use
git commit -sfor every commit (DCO).
Parallelization notesā
- Phase 1 must run sequentially within itself (T001 ā T002 ā T003 ā T004 ā T005 ā T006 ā T007), because each task touches downstream consumers of the previous one's types.
- Phase 2 (T008, T009) is parallel ā two independent template files.
- Phase 3 (T010) is sequential after Phase 1 ā the UI's path preview reads from
RenderResult.install_paths. - Phase 4 (T011āT015) is parallel ā five independent test files.
- Phase 5 is sequential and final.
- Phase 6:
- 6a (T019, T020) is sequential within itself.
- 6b (T021, T022) is sequential after T019 (the validator helper is shared) and runs as a pair (POST + PATCH must agree).
- 6c (T023, T024) is sequential after 6a (the importer reuses no shared types from the hub crawler today, but T024 depends on T023 existing).
- 6d (T025āT027) can run in parallel with each other once 6c is in (T025 + T026 are coupled by the re-export, so do them together; T027 is independent).
- 6e (T028) is parallel within itself (four independent test files).
Estimated effortā
- Phase 1: ~3ā4 hours (the bulk of the rewrite, dominated by
install.sh/route.ts) - Phase 2: ~15 minutes
- Phase 3: ~45 minutes
- Phase 4: ~1.5ā2 hours
- Phase 5: ~30 minutes (mostly waiting for
make caipe-ui-tests) - Phase 6aā6b: ~1 hour (crawler arg + admin API + normalizer fix)
- Phase 6c: ~1.5 hours (new importer route + GitLab branch + multi-path merge)
- Phase 6d: ~1.5 hours (RepoImportPanel + SkillHubsSection edit)
- Phase 6eā6f: ~1.5 hours (tests + commit slicing)
Total: ~12ā14 hours of focused work (Phases 1-5: ~6-7h, Phase 6: ~5-6h additional).