aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs/2026-10-19-clone-tool-design.md
blob: fde90021b536e2719db4f98ddfa731964a0b0e59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# Clone tool: design

Date: 2026-10-19. Status: implemented (2026-10-19).

## Goal

Ask the assistant to clone a public repository ("clone this repo:
https://github.com/owner/repo") and have it land in the right folder of
`~/Programming/GIT`: `GITHUB/`, `GITLAB/`, `FORGE/`, or a folder already
approved for that host. Public repositories only, plain `git clone`, no
SSH aliases or keys.

The assistant must not decide two things alone: a host with no folder yet,
and a repository that is already cloned. Both are asked, with a concrete
action, never a bare "skip".

## Tool

`examples/tools/clone.toml`, installed to
`~/.config/desktop-assistant/tools/` like the others. No change to
`assistant.py`.

```toml
command = ["clone.sh", "{url}", "{to}", "{update}"]
timeout = 300
```

- `url`: the repository URL.
- `to`: only when the user approved a new host folder; the folder name.
- `update`: `1` to pull an existing clone.

`examples/tools/clone.sh`:

- Parses `https://host/owner/repo` and `git@host:owner/repo` alike; `repo`
  is the last path segment with a trailing `/` and a `.git` suffix removed,
  and `owner` is the segment before it (a nested group keeps only its last
  segment). `host`, `repo` and `owner` are validated as plain names, so
  none can escape the tree.
- Resolves `host -> folder` from the mapping file. Absent, it is seeded
  with `github.com -> GITHUB`, `gitlab.com -> GITLAB`,
  `forge.slackware.nl -> FORGE`.
- Target is `<root>/<folder>/<owner>-<repo>` (`CLONE_ROOT`, default
  `~/Programming/GIT`). The name is just `<repo>` when the URL carries no
  owner (`git@host:repo.git`) or when `owner` and `repo` are the same
  (`firefly-iii/firefly-iii`), so a generic repo name like `wallpapers`
  becomes `evgen2571-wallpapers` while a self-named one is not doubled.
- Known host, target free: `git clone` and report it. No question.
- Unknown host: prints the folder to offer (hostname without its last
  label, uppercased: `codeberg.org -> CODEBERG`) and does nothing.
- Target exists: prints "already cloned" and does nothing. With
  `update=1`, runs `git pull --ff-only` and reports the result; a diverged
  clone is an error, not a merge.
- A `to` given for an unknown host clones there and appends
  `host<TAB>folder` to the mapping, so that host is known from then on.
  An existing mapping is never duplicated or overwritten.

## Mapping file

`~/.config/desktop-assistant/clone-hosts` (`CLONE_MAP`), one
`host<TAB>folder` line per host, user-editable. It lives outside the tool
directory so reinstalling the tools does not lose it, and the file is the
single source of truth: nothing is hardcoded in the script beyond the
seed.

## Check

`test-clone.sh`: a scratch `CLONE_ROOT` and `CLONE_MAP`, and a `git` stub
on `PATH` that records its argv and creates the target, so no network and
no real tree. Covers seeding, clone argv, trailing slash and `.git`, the
unknown-host message, the approved folder being remembered and used on
the next clone without a question, no duplicate map line, the
already-cloned message, the update argv, and the refused inputs.

Live, against the running model: ask for a known host and an unknown host,
accept the offered folder, ask for the same host again (must ask nothing),
then ask for an existing repo (must offer an update, never a second
clone).

## Not included

Private repositories (no credentials), clone depth or branches, and
opening the clone in an editor or agent after it lands.