Skip to content

Commit ddf6a9e

Browse files
committed
chore: add phase 5.3 task spec
1 parent dab46c0 commit ddf6a9e

1 file changed

Lines changed: 332 additions & 0 deletions

File tree

tasks/phase-5.3-install.md

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
# Phase 5.3 — Install script (skills and subagents deployment)
2+
3+
## Goal
4+
5+
Build a unified Python tool `tools/install.py` that deploys skills and
6+
subagents from this curated library to their runtime location.
7+
Without this tool, the library is purely catalog: skills sit in
8+
`skills/<name>/` but Claude Code does not see them. The tool bridges
9+
that gap by creating symlinks or copies in the appropriate
10+
`.claude/skills/` or `.claude/agents/` location.
11+
12+
## Design choices (decided with user)
13+
14+
- **Deployment modes**: symlink (default) and copy. Symlink keeps the
15+
library as single source of truth; copy is for sharing across
16+
systems where the source path won't exist.
17+
- **Install tracking**: a log file at `~/.aithos-install-log.yaml`
18+
records every install (what, where, mode, date). Enables `--list`
19+
and `--uninstall`.
20+
- **Default targets**:
21+
- Skills install to `~/.claude/skills/<name>/` by default (global).
22+
- Subagents require an explicit `--target` (they live in a project's
23+
`.claude/agents/`, not globally).
24+
25+
## Prerequisites
26+
27+
- Phase 5.2-bis complete and pushed.
28+
- The current branch should be a new feature branch off `main`:
29+
30+
```bash
31+
git checkout main
32+
git pull # main is currently behind, PR #3 is open
33+
```
34+
35+
However, PR #3 (containing 5.2 + 5.2-bis) is still open. The user
36+
prefers to merge it at end of day along with 5.4 and 5.5.
37+
Therefore Phase 5.3 must be developed on a new branch that
38+
**branches off `feat/ingest-assets-batch-1`** (the current state),
39+
not off `main`:
40+
41+
```bash
42+
git checkout feat/ingest-assets-batch-1
43+
git pull # ensure local is up to date
44+
git checkout -b feat/install-script
45+
```
46+
47+
This way Phase 5.3 builds on the work just done. After all phases
48+
are complete, the user will merge them in order (5.2-bundle, 5.3,
49+
5.4, 5.5) into `main`.
50+
51+
- `git status` clean, current branch `feat/install-script`.
52+
53+
## High-level flow
54+
55+
1. **Design** the CLI (`install`, `uninstall`, `list`, `info`).
56+
2. **Implement** `tools/install.py` with the Typer framework
57+
(consistent with existing tooling).
58+
3. **Test** locally by installing one skill, listing, then
59+
uninstalling.
60+
4. **Update CONTRIBUTING.md** with a deployment section explaining
61+
how to use the script.
62+
5. **Update README.md** briefly to mention the install tool.
63+
6. **Commit and push**.
64+
65+
## Deliverables
66+
67+
### 1. `tools/install.py` — main script
68+
69+
Implement using Typer (already in the project's dependencies). The CLI
70+
must expose four subcommands:
71+
72+
#### `install <item-path> [--target <path>] [--mode symlink|copy] [--force]`
73+
74+
Install a single skill or subagent.
75+
76+
Arguments:
77+
78+
- `item-path` (positional, required): relative path from repo root
79+
to the item to install. Must be either `skills/<name>` or
80+
`subagents/<name>`. Path validation is strict — the directory must
81+
exist, must contain the expected entrypoint (`SKILL.md` for skills,
82+
the manifest-declared `entrypoint` for subagents).
83+
84+
Options:
85+
86+
- `--target <path>` (optional): destination directory. Defaults:
87+
- For skills: `~/.claude/skills/<name>`
88+
- For subagents: **required** (no default; if missing, error and
89+
exit with code 2 and a message explaining that subagents need a
90+
project-specific target).
91+
- `--mode symlink|copy` (default: `symlink`): how to deploy.
92+
- `--force` (flag, default off): if the target already exists, remove
93+
it (recursively if it's a directory, unlink if it's a symlink) and
94+
proceed. Without `--force`, exit with code 3 and an error message.
95+
96+
Behavior:
97+
98+
1. Resolve the absolute path of the source (canonical, follows the
99+
current working dir's repo root).
100+
2. Validate the source structure (skill or subagent).
101+
3. Resolve the target path (expand `~`, normalize, make absolute).
102+
4. Check for target collision; honor `--force` or abort.
103+
5. Ensure the parent directory of the target exists; create it
104+
recursively if needed.
105+
6. Apply the deployment:
106+
- `symlink`: `os.symlink(source_abs, target_abs)`
107+
- `copy`: `shutil.copytree(source_abs, target_abs,
108+
symlinks=False)` (recursive copy, dereference any internal
109+
symlinks)
110+
7. Record the install in the log (see §2 below).
111+
8. Print a confirmation message: `✓ Installed <name> to <target>
112+
(mode=<mode>)`.
113+
114+
#### `uninstall <name-or-target>`
115+
116+
Uninstall a previously installed item.
117+
118+
Argument:
119+
120+
- `name-or-target` (positional, required): either the item name
121+
(kebab-case, matches a logged install) or the full path of an
122+
installed item.
123+
124+
Behavior:
125+
126+
1. Look up in the log: find one or more matching entries.
127+
2. If multiple matches (e.g. the same skill installed to multiple
128+
targets), list them and ask the user to disambiguate. Print exit
129+
code 4 if non-interactive.
130+
3. For each match: remove the target (`os.unlink` if symlink,
131+
`shutil.rmtree` if directory). Remove the entry from the log.
132+
4. Print: `✓ Uninstalled <name> from <target>`.
133+
134+
#### `list`
135+
136+
Show all currently installed items.
137+
138+
Output as a `rich` table with columns: `Name`, `Type` (skill/subagent),
139+
`Target`, `Mode`, `Installed`.
140+
141+
If the log is empty: print `No items installed.`.
142+
143+
If a target listed in the log no longer exists on disk (user
144+
manually deleted it): mark the row with a warning icon `⚠️` and
145+
include the column `Status` with values `ok` or `missing`.
146+
147+
#### `info <item-path>`
148+
149+
Show metadata about a library item without installing.
150+
151+
Output: name, type, version, status, description, tags, and (for
152+
subagents) declared tools/mcp_servers/skills_dependencies.
153+
154+
### 2. Install log — `~/.aithos-install-log.yaml`
155+
156+
Format:
157+
158+
```yaml
159+
installs:
160+
- name: librarian
161+
type: skill
162+
source: /home/riccardo/projects/aithos-selection/skills/librarian
163+
target: /home/riccardo/.claude/skills/librarian
164+
mode: symlink
165+
installed: 2026-05-16T01:15:00Z
166+
167+
- name: automation-architect
168+
type: subagent
169+
source: /home/riccardo/projects/aithos-selection/subagents/automation-architect
170+
target: /home/riccardo/projects/martina-os/.claude/agents/automation-architect
171+
mode: copy
172+
installed: 2026-05-16T02:30:00Z
173+
```
174+
175+
The file is created on first install if missing. Operations on it
176+
must be **atomic** (write to temp file, then rename) to avoid
177+
corruption on partial writes.
178+
179+
The log file is NOT tracked in git (it's a user-local artifact).
180+
181+
### 3. Error handling and exit codes
182+
183+
Use distinct exit codes for scriptability:
184+
185+
- `0`: success
186+
- `1`: generic error / unexpected exception
187+
- `2`: invalid arguments (e.g. subagent without `--target`)
188+
- `3`: target collision without `--force`
189+
- `4`: ambiguous uninstall (multiple matches, non-interactive)
190+
- `5`: source not found / not a valid skill or subagent
191+
- `6`: log file corrupt or unreadable
192+
193+
All error messages should be printed via `rich` to stderr, with a
194+
prefix `Error:` in red.
195+
196+
### 4. Idempotency
197+
198+
Re-installing the same item to the same target is a no-op (the log
199+
already has the entry, the target already exists and points to the
200+
same source). Print a message: `Already installed at <target>
201+
(mode=<mode>). Use --force to reinstall.` and exit code 0.
202+
203+
This is important because the `nightly-sync` phase (5.4) may
204+
re-install skills periodically; it must not error on each run.
205+
206+
### 5. Edge cases
207+
208+
- **Source moved/renamed**: if a previously-installed item's source
209+
no longer exists, `list` shows `Status: missing` for that entry.
210+
`uninstall` still works (removes target and log entry).
211+
- **Target is a regular file (not directory or symlink)**: error,
212+
refuse to clobber.
213+
- **Cross-device symlink**: if target is on a different filesystem
214+
and symlinks aren't supported, error explicitly and suggest
215+
`--mode copy`.
216+
- **Permissions**: if target requires elevated permissions, error
217+
explicitly (don't try to sudo).
218+
- **Relative source paths**: must be relative to the current working
219+
directory. If the user runs from elsewhere, the script should
220+
resolve correctly via `Path.cwd()` and `Path.resolve()`.
221+
222+
### 6. CONTRIBUTING.md update
223+
224+
Add a new section `## Deploying skills and subagents` after the
225+
existing `## Adding a subagent` section. Cover:
226+
227+
- Why deployment is needed (catalog vs runtime location).
228+
- Symlink vs copy mode — when to choose each.
229+
- Default targets and how to override.
230+
- Example commands for each subcommand.
231+
- Mention of the install log location.
232+
233+
Length: ~30-40 lines.
234+
235+
### 7. README.md update
236+
237+
In the existing folder map, augment the `tools/` entry to mention
238+
`install.py` briefly. One line is enough:
239+
240+
> `tools/` — Python utilities (index generator, validator, install
241+
> script for deploying skills/subagents).
242+
243+
### 8. Test the script
244+
245+
Before committing, run a self-test inside the phase:
246+
247+
```bash
248+
# Install the canonical example skill (a no-op skill, safe to test)
249+
uv run python tools/install.py install skills/librarian
250+
251+
# Verify the symlink was created and resolves correctly
252+
ls -la ~/.claude/skills/librarian
253+
test -L ~/.claude/skills/librarian
254+
readlink ~/.claude/skills/librarian
255+
test -f ~/.claude/skills/librarian/SKILL.md
256+
257+
# List installs
258+
uv run python tools/install.py list
259+
260+
# Uninstall
261+
uv run python tools/install.py uninstall librarian
262+
263+
# Verify it's gone
264+
test ! -e ~/.claude/skills/librarian
265+
```
266+
267+
All eight commands must succeed (exit 0 where expected). If any
268+
fails, fix the script before committing.
269+
270+
After the test, clean up: the log file should be back to its initial
271+
state (no installs).
272+
273+
## Done criteria
274+
275+
```bash
276+
# Script exists
277+
test -f tools/install.py
278+
279+
# Script is executable in the project venv
280+
uv run python tools/install.py --help
281+
282+
# All four subcommands work (smoke test)
283+
uv run python tools/install.py install skills/librarian
284+
test -L ~/.claude/skills/librarian
285+
uv run python tools/install.py list
286+
uv run python tools/install.py info skills/librarian
287+
uv run python tools/install.py uninstall librarian
288+
test ! -e ~/.claude/skills/librarian
289+
290+
# CONTRIBUTING.md extended
291+
grep -q "Deploying skills and subagents" CONTRIBUTING.md
292+
293+
# README.md extended
294+
grep -q "install" README.md
295+
296+
# Validation still passes (no regressions)
297+
uv run python tools/generate_index.py --check
298+
uv run python tools/check.py
299+
300+
git status --short
301+
```
302+
303+
## Commit
304+
305+
```bash
306+
git add .
307+
git commit -m "feat(phase-5.3): unified install.py for deploying skills/subagents
308+
309+
Adds tools/install.py with four subcommands (install, uninstall, list,
310+
info) supporting symlink and copy modes. Install log at
311+
~/.aithos-install-log.yaml. Default target for skills is
312+
~/.claude/skills/; subagents require an explicit --target.
313+
CONTRIBUTING.md extended with a deployment section."
314+
git push -u origin feat/install-script
315+
```
316+
317+
## Stop here
318+
319+
After commit and push, stop. Do not start Phase 5.4.
320+
321+
Do not auto-create a PR. The user prefers to merge all remaining
322+
phases at end of day in a single coherent batch.
323+
324+
## Safety brakes
325+
326+
- No network calls in this phase.
327+
- Do not modify the install log if testing fails partway — clean up
328+
on error.
329+
- Do not modify any imported assets from Phase 5.2 or 5.2-bis.
330+
- Test installation must be done on a real path
331+
(`~/.claude/skills/librarian`) but cleaned up before commit, so the
332+
user's `~/.claude/skills/` is untouched after the phase completes.

0 commit comments

Comments
 (0)