mind-cli 0.29.0

A manager for agent tooling (skills, agents, rules, tools) that melds arbitrary git repos and links items into your agent directories.
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# Item links (single-item source instances)

How `mind` consumes a deep link to one item inside a repo - the URL a user
copies from their forge's file browser - as a self-contained, managed install.
A repo may publish a `.claude-plugin/marketplace.json` that lists only a subset
of its skills (marketplace.md); an item link reaches an item the manifest does
not list, because the consumer names the exact path.

The linked item is a skill directory (the original form) or, when the link names
a single markdown file, an agent, rule, or command (LNK-20..23).

Each accepted link becomes its own *source instance*: a registry entry with an
extended identity, its own clone, pin, and lifecycle. Several links into the
same repo, and a plain meld of that repo, coexist as separate sources.

## Link form

- `LNK-1` An item link is a URL naming a directory or a file inside a repo:
  `https://<host>/<owner>/<repo>/tree/<ref>/<path>` or
  `https://<host>/<owner>/<repo>/blob/<ref>/<path>`, plus the GitLab
  `/-/tree/<ref>/<path>` and `/-/blob/<ref>/<path>` variants, and the
  `file:///<repo-path>/tree|blob/...` form for a local repo (the marker is
  only recognized in the explicit `file://` spelling, so a bare local path
  containing a `tree` directory stays a plain repo spec). A query string or
  fragment (`?plain=1`, `#L10`) is stripped before parsing. The `<path>`
  decides what is linked:
  - ending in `/SKILL.md`: the skill directory that is its parent (the anchor
    file names the skill),
  - any other path ending in `.md`: that single file, an agent, rule, or
    command (LNK-20),
  - any other path: the skill directory itself.

  A `blob` link must end in `.md`: a forge blob URL names a file, and a blob
  path with any other extension (or none) is a `BadItemLink`. A `tree` link
  accepts either shape. A local `file://` link instance is always a cloned
  snapshot at its pin (the CLI-27 pinned-local flow), never a live-read working
  tree. Tools are not linkable: a tool directory has no anchor file to
  classify it by, and a tool is store-only (tooling.md), meaningful only
  alongside the item that references it.
- `LNK-2` An item link is accepted anywhere a repo spec (CLI-11) is: `meld`,
  `learn` (LNK-6), and a `[discover].sources` entry (DSC-38), so a curator can
  curate individual items. A URL with a `tree`/`blob` segment whose tail fails
  to complete as a valid item link is `BadItemLink` (LNK-14), not the generic
  `InvalidRepoSpec`. For a remote URL (the `scheme://host/owner/repo/...`
  branch), this holds only once the `owner/repo` portion ahead of the marker
  itself parses: a malformed `owner/repo` there still reports
  `InvalidRepoSpec` regardless of the marker or the tail, since owner/repo
  splitting runs before the tail is parsed. The local `file://` branch parses
  in the opposite order -- the `<ref>/<path>` tail is parsed before the
  `owner/repo` portion is sliced off the remaining path -- so with no usable
  repo path at all ahead of the marker (e.g. `file:///tree/main`), a broken
  tail still reports `BadItemLink` rather than falling through to
  `InvalidRepoSpec`. This is a real behavioral difference between the two
  branches, not just wording; pinned by
  `file_link_bad_tail_with_no_repo_ahead_of_marker_is_still_bad_item_link` and
  `github_malformed_owner_repo_wins_over_bad_tail` in `src/source.rs`.
- `LNK-3` The `<ref>` segment supplies the instance's pin: a 40-hex ref is a
  commit pin (as `--pin <sha>`), anything else follows that branch (as
  `--pin branch=<name>`). An explicit consumer pin flag (CLI-17) overrides the
  URL's ref; the URL's ref overrides the repo's `[source]` pin directive
  (DSC-41). `learn <url> --pin` (a bare flag on `learn`, CLI-200) freezes the link's
  branch ref to its current commit, so a branch-ref link can be snapshotted in one
  step. The ref is a single path segment: a branch name containing `/` is not
  representable in a link (the first segment after `tree`/`blob` is taken as the
  ref); meld the repo with `--pin branch=<name>` instead.
- `LNK-14` A spec whose path carries a `tree`/`blob` link marker (LNK-1) is
  unambiguously an attempted item link, so once the `owner/repo` portion ahead
  of the marker parses, every failure in its `<ref>/<path>` tail reports
  `BadItemLink`, never the generic `InvalidRepoSpec`: a missing ref, a missing
  item path, a `blob` link not ending in `.md`, and an unsafe path or
  ref value (LNK-10) all take this branch. The message names the offending URL
  and the three expected shapes, `<repo-url>/tree/<ref>/<skill-dir>`,
  `<repo-url>/blob/<ref>/<skill-dir>/SKILL.md`, and
  `<repo-url>/blob/<ref>/<file>.md` (plus the GitLab `/-/tree/` and
  `/-/blob/` spellings), instead of the generic repo-spec message, so a user
  who pasted a real forge URL is told what shape a link needs rather than that
  the URL is invalid. A spec with no `tree`/`blob` marker at all is not an
  attempted link and keeps reporting `InvalidRepoSpec` unchanged.

## Single-file items

- `LNK-20` A link whose `<path>` names a `.md` file other than `SKILL.md` is a
  FILE link: the linked item is that one file, installed as an agent, rule, or
  command (kind by LNK-21), bare name the file stem. Everything else about the
  instance is the skill-link behavior unchanged: identity (LNK-4), clone and pin
  (LNK-3), lifecycle (LNK-5), no nested sources (LNK-8), reference reconciliation
  (LNK-18). This is what lets a `[discover].sources` entry (LNK-2) curate an
  individual agent or rule the way it already curates a skill.
- `LNK-21` A file link's kind is resolved in this order, first hit wins:
  1. the consumer's explicit kind: `meld`/`learn --kind <kind>` (CLI-239), or a
     `[discover].sources` entry's `kind = "<kind>"` (DSC-100);
  2. the containing directory, by the DSC-11..14 convention names:
     `agents/` -> agent, `rules/` -> rule, `commands/` -> command, so a link
     into a conventional layout needs no annotation;
  3. the file's own frontmatter `kind:` scalar, for a file that sits outside a
     conventional directory.

  Directory outranks frontmatter so that a link installs the SAME item a plain
  meld of the repo would (a whole-repo scan classifies by directory, DSC-11..14):
  a file under `agents/` is an agent to both. Only agent, rule, and command
  resolve here: `skill` and `tool` are directory kinds, so an explicit or
  frontmatter `kind` naming either on a file link is `LinkKindMismatch`, as is an
  explicit kind other than `skill` on a link naming a directory. `meld`/`learn
  --kind` is typed to accept only `agent`, `rule`, or `command` in the first
  place (a clap value enum, distinct from `recall`/`probe --kind`'s five-kind
  filter), so `skill`/`tool` there is refused before any clone rather than
  reaching this mismatch; a curator's `kind =` is a bare TOML string with no
  such upfront check, so it still reaches LNK-21 directly. A file link none of
  the three steps resolves is `LinkKindUnresolved`, which names the three ways
  to resolve it; when step 3's frontmatter `kind:` is present but not an item
  kind word, the error additionally names `--kind` as the override. Every error
  here is raised before anything is installed, and before anything registers.
- `LNK-22` An explicit kind (LNK-21 step 1) is recorded on the source instance
  (`item_kind` in sources.json, STO-81) and applies to every later scan of it,
  so `recall`, `upgrade`, and `sync` classify the item exactly as the meld did,
  and it is fixed at meld/registration time: `--kind` on a re-meld, or on
  `learn <url>` of an already-melded link, changes nothing and is noted rather
  than silently dropped (CLI-206's ignored-flags note covers it on the re-meld
  path). A kind resolved by directory or frontmatter records nothing, and is
  re-read from the clone on every scan: for a commit-pinned link those inputs
  cannot change, but a branch-following link's clone moves on `sync`, so an
  upstream edit to the file's directory or frontmatter can change the resolved
  kind (and so the item's `(source, kind, bare_name)` identity) between scans.
  `--kind` is the way to freeze it against that. The field is absent from every
  source an older binary registered, and absent means the pre-existing reading
  (a skill link), so an existing registry is unaffected.

## Identity and lifecycle

- `LNK-4` An item-link source's identity is `host/owner/repo#<path>`, where
  `<path>` is the linked item's repo-root-relative path: the skill directory,
  or the file itself for a file link (LNK-20). An identity alias
  composes as a trailing `@<alias>` segment (STO-58): `host/owner/repo#<path>@<alias>`.
  Instances from the same repo, and a plain meld of `host/owner/repo`, are
  distinct registry entries with independent pins, commits, and lifecycles.
  Re-melding an identical link follows the CLI-12 re-meld flow. The `#<path>` IS
  part of the clone leaf (STO-70: an item-link instance's leaf encodes its
  `item_path`, not just its `@<alias>`), so every link instance -- aliased or
  not -- gets its own independent checkout, distinct from a plain meld of the
  repo and from every other link into it. Only a genuinely bare instance (no
  `item_path`, no `@<alias>`) can ever share the plain `sources/<host>/<owner>/<repo>`
  clone, and by construction there is at most one such instance per identity
  (a second bare meld of the same repo is a re-meld, CLI-12, not a second
  instance), so in practice nothing shares a checkout post-STO-70. (Before
  STO-70, the `#<path>` was NOT part of the leaf, so every non-aliased link
  into a repo, and a plain meld of it, all shared one clone; two non-aliased
  links pinned to different refs would contend over that one working tree,
  each re-clone silently discarding the others' checkout. STO-70's migration
  note applies to any instance registered under that older behavior.)
- `LNK-5` `sync`, `upgrade`, `introspect`, `recall`, and `unmeld` treat the
  instance as an ordinary source: `sync` fetches per its pin (CLI-55),
  `upgrade` compares source content hash and commit, `unmeld` uninstalls its
  item (CLI-21). `forget` of the instance's skill leaves the instance
  registered; when the instance has no installed items left, `forget` prints a
  hint to `mind unmeld <identity>`.
- `LNK-15` The identity-alias suffix (STO-58) distinguishes link instances of
  the SAME `<path>` exactly as it distinguishes any other source instance: a
  bare link `host/owner/repo#<path>` and an aliased link
  `host/owner/repo#<path>@<alias>` are distinct registry entries that coexist,
  each with its own clone (STO-70: the `item_path` alone is now enough to earn
  an independent checkout, so both the bare and the aliased instance here get
  one), pin, and installed items, untouched by the other's lifecycle.
  Consequently `learn <url>`
  (LNK-6), with or without `--pin`, checks membership under the SPECIFIC
  alias it is melding as (here, none): if the repo has only been melded under
  a DIFFERENT alias (or no alias at all when the new call supplies one), the
  bare identity is not yet registered, so `learn` registers it as a new,
  coexisting instance rather than reusing or being blocked by the
  differently-aliased one. This is not a collision: it is the normal STO-58
  per-instance model applied to links.
- `LNK-16` A link's `<path>` may not contain `@` or `#`; a link URL whose skill
  path carries either is a `BadItemLink`. This is STO-64's rule one segment
  over. A link instance's identity is `host/owner/repo#<path>`, and an identity
  alias appends `@<alias>` to the whole identity (STO-58), so an unaliased link
  to `skills/foo@bar` and an `@bar`-aliased link to `skills/foo` would compute
  the same identity and, per STO-59, the same clone directory; a `#` would put a
  second marker into an identity that is split on the first one. Refusing both
  at parse time keeps a link identity decomposable back into the parts it was
  built from. Note that `#` reaches the path only through the `file://` form: a
  remote URL is truncated at its first `#` as a pasted browser fragment (LNK-1)
  before the path is taken, so `.../skills/foo#bar` links to `skills/foo`.
- `LNK-17` The `<path>` is lexically normalized before it becomes identity:
  `.` segments are dropped, so `.../tree/main/./skills/foo` and
  `.../tree/main/skills/./foo` parse to the same `skills/foo` instance
  (identity, clone directory, and re-meld detection) as the plain form,
  rather than registering a duplicate instance of the same on-disk skill.

## Install

- `LNK-6` `learn <url>` is the one-shot form: it registers the link instance
  exactly as `meld <url>` would (clone, hook consent, prompts) and installs its
  skill in one step. `meld <url>` follows the standard meld flow (the CLI-23
  install offer, `--register-only`, `--yes`). The meld flags apply to a link
  meld unchanged (`--namespace`, pin flags, `--install-hook`).
- `LNK-7` A link instance's catalog is exactly one item: for a skill link the
  directory at `<path>` containing `SKILL.md`, bare name the directory's
  basename; for a file link (LNK-20) the file at `<path>`, bare name its stem,
  kind resolved by LNK-21. Either way the
  description comes from frontmatter (DSC-30). Discovery bypasses the repo's declared
  inventory: an authoritative `mind.toml` (DSC-3) and a `.claude-plugin/`
  manifest (MKT-2/MKT-14) do not gate the link, so it can install an item the
  repo does not export - the consumer named the exact path. The
  `min-mind-version` gate (DSC-40) and `[source]` metadata (description,
  declared prefix) still apply. A skill link whose `<path>` has no `SKILL.md`
  in the clone is an error (`LinkNotASkill`), and a file link whose `<path>` is
  not a file there is an error (`LinkNotAFile`); either way nothing is
  registered.
- `LNK-8` A link instance registers no nested sources: `[discover].sources`
  entries and marketplace external plugins in the linked repo are not walked.
  The link is a single-item grab, not a super-source adoption.
- `LNK-18` A link instance's catalog is exactly the linked item (LNK-7), so an
  intra-source reference (dependencies.md, tooling.md) to any other name can
  never resolve, however the repo is laid out. The two reference FORMS are
  handled differently, following DEP-4's own distinction between them:
  - A `requires:` entry (DEP-4) that names something other than the linked item
    is NOT the DEP-6 `BadReference` error here. It is dropped, recorded
    (LNK-19), and warned about, and the item installs. `requires` is pure
    metadata (DEP-4): it is never rewritten into the item body, so a dangling
    entry degrades the item rather than corrupting it, and refusing would make an
    item that declares one unreachable by link at all. Other `requires`
    failures keep their DEP-7 causes as hard errors: a malformed ref
    (`InvalidRef`) and a source-qualified ref (`CrossSource`) are wrong
    regardless of the catalog. `AmbiguousKind` cannot arise, since a single-item
    catalog offers at most one match. An entry naming the linked item's own
    name under the WRONG kind (`agent:<skill-name>`) is a `NoMatch` like any
    other and is dropped with the rest.
  - A TOKEN naming a sibling stays a hard error and nothing is installed: a
    token IS rewritten into the item's text at install, so it cannot be left
    dangling. This covers every token family that resolves against siblings, not
    just `{{ns:name}}` (NS-10/DEP-1): `{{tools:name}}` (TOOL-15) and
    `{{path:[kind:]name}}` (TOOL-18) are equally unsatisfiable in a one-item
    catalog, and a skill shipping alongside a sibling tool is a common shape.
    `{{self}}` is not a sibling reference and always resolves. The scan covers
    exactly the files install expands, markdown (NS-53) plus any non-markdown
    file the item lists in `expand:` (NS-57); scanning a narrower set would let a
    reference slip through to the blunt error this rule exists to replace. The
    error is `LinkRefUnsatisfiable`, not the generic `BadReference`/TOOL-17
    cause: it names the token as written, says the source is a single-item
    link with no siblings, and carries the remedy, so a user who pasted a deep
    URL is told what to run instead of being told the item references something
    missing.

  *The remedy.* Both paths name one of two forms of the same two-step command,
  decided by scanning the clone already on disk as an ordinary whole-repo
  source (the same `Source`, with `item_path` cleared). When that scan finds an
  item of the linked kind at the exact path the link points at (a same-named
  item declared at a DIFFERENT path in the repo does not count), a plain meld
  would discover it on its own:

  ```
  mind unmeld '<identity>' --yes && mind meld '<repo-url>' --learn '<kind>:<name>' --yes
  ```

  Otherwise -- an authoritative `mind.toml` or `.claude-plugin` manifest that
  does not declare the item, the case item links exist for -- a plain meld
  would not reach it, so the remedy adds a scan root:

  ```
  mind unmeld '<identity>' --yes && mind meld '<repo-url>' --add-root '<root>' --learn '<kind>:<name>' --yes
  ```

  (CLI-236, DSC-84). Either form drops the link instance and then melds the
  whole repo, installing just that item together with its dependency closure
  (DEP-30). Any error from the reachability scan -- an unreadable clone, a
  malformed `mind.toml`, a version gate -- answers "not reachable" rather than
  propagating, so the remedy falls back to the added-root form; the scan
  runs only when a remedy is about to be printed, not on every link install.

  The `<root>` is DERIVED from the link's own path, not fixed at `.`. An added
  root is convention-scanned one level deep (flat child directories, plus the
  kind containers `skills/`, `agents/`, `rules/`, `commands/`, `tools/`
  directly under it), so the root that reaches the linked item is its parent
  directory, or its grandparent when that parent is the kind's container:
  `skills/foo` and a flat `foo` both give `.`, `agents/dev.md` gives `.`, while
  `vendor/pkg/skills/foo` gives `vendor/pkg` and a flat `vendor/foo` gives
  `vendor`. A fixed `.` would reach only an item at the repo root, so for
  anything deeper the meld half would fail with `LearnPatternNoMatch` AFTER the
  unmeld half had already succeeded -- the destroy-then-fail sequence this
  two-branch remedy exists to prevent.

  A file link (LNK-20) whose parent directory is NOT its kind's container is
  the one shape with no working remedy: convention discovery finds a file item
  only at `<root>/<kind>s/<name>.md` (there is no flat pass for file kinds, the
  way there is for skills), so no `--add-root` value reaches it. Such a link is
  reachable only as a link, so no command is printed: the error says plainly
  that the item cannot be installed any other way, names why (the file sits
  outside a conventional `<kind>s/` directory), and names the consumer's own
  options -- drop the reference, ask upstream to move the file and re-link it,
  or fork the repo, move the file there, and link the fork -- rather than only
  the upstream-only remedy a repo author could act on. Printing a command that
  unmelds first and then fails to install is the outcome this replaces.

  The `unmeld` step is required. The link instance is registered before the
  install runs, so it is registered on both paths when this is printed -- the
  warning path goes on to install the skill, the error path installs nothing
  -- and a bare `meld ... --learn` would therefore leave a second source for
  the same repo, and collide with the name the link already installed on the
  warning path (NS-43/CLI-33). Removing the instance first is what makes the
  printed command work when pasted.

  The `<identity>` is the instance's registered identity (LNK-4) and the
  `<repo-url>` its recorded clone URL, never the deep link URL. The pattern is
  kind-qualified (`<kind>:<name>`) with the linked item's own kind (LNK-21):
  a bare name would additionally match a same-named item of another kind in the
  repo, installing content the user never asked for. Each of the three values
  is sanitized (DSC-95) and shell-quoted (CLI-225) before composition, and the
  identity and the name are also glob-escaped: `--learn`'s pattern and
  `unmeld`'s selector (CLI-28) both read `*`, `?`, and `[` as glob syntax, and
  `is_safe_item_name` (DSC-71/DSC-96) permits those characters. A skill named
  `pdf[x]` would otherwise make the pattern match some OTHER item, and --
  since a link identity embeds the skill's repo path (LNK-4) -- would make the
  raw identity compile as a pattern matching no source, stopping the remedy at
  its first command. Escaping is a no-op for an identity or name with no
  metacharacters, so both commands above are unchanged for the common case.

  *Where it applies.* Wherever a link instance's item is installed: `learn
  <url>` (LNK-6), the install pass of `meld <url>`, and a later `upgrade` of the
  instance, so an upstream edit that adds a `requires` entry does not turn every
  subsequent upgrade of that link into a hard failure.

  *What it cannot fix.* One shape defeats the added-root form: a declared
  inventory that offers a DIFFERENT item of the same bare name (say it declares
  `vendor/review` while the link points at `skills/review`). The reachability
  scan correctly answers "not reachable" there, since it matches by path, but
  the added root then makes both items visible at once and the meld half fails
  with the DSC-85 `DuplicateItem` guard ("appears under more than one scan
  root"). The unmeld half has already run at that point, so this is the one
  remaining case where the remedy leaves the user worse off than before pasting
  it. Reaching that skill needs `--root` (replacing the inventory's roots)
  rather than `--add-root` (composing with them), which is a choice about what
  the whole source offers, not something a per-item remedy should make.

  The added-root form otherwise reaches a skill an authoritative
  `mind.toml` or `.claude-plugin` manifest does not declare (DSC-84) at any
  depth, since the root is derived from the link's path. What it does not carry
  over is the rest of the source's consumer configuration: the remedy melds the
  repo fresh, so a `--namespace`, `--pin`, or `--flat-skills` the original link
  was melded with is not reproduced, and neither are any `--add-root` values
  beyond the one derived here. A link melded with extra scan roots therefore
  needs them restated by hand on the meld half.

- `LNK-19` A `requires:` entry dropped under LNK-18 is recorded on the installed
  item (`dropped_requires` in the manifest) rather than existing only as the
  install-time warning. The warning scrolls away, is reduced to a summary line by
  the TUI's output capture, and goes to stderr under `--json`, so without a
  durable record an item can carry a silently absent dependency with no way to
  discover it later. The record is what `recall <item>` shows as a `dropped`
  line, what `recall <item> --json` carries as a `dropped_requires` array (absent
  when empty), and what `introspect` reports as a `dropped-requires` issue naming
  the item and the entry. `introspect --fix` does not repair it: the only fix is
  to replace the link with the whole repo, which changes which sources are
  registered and so is the user's call. The field is re-derived on every install
  and upgrade of the item, so it always describes the version on disk; it is
  empty (and omitted from `manifest.json`) for every ordinary install, so an item
  installed by an older binary, or from a non-link source, reads back unchanged.

- `LNK-9` Namespacing and collisions are unchanged: the effective prefix is
  the alias or `[source].namespace` (NS rules), and collisions with installed
  items surface through the existing checks (NS-41, NS-43, CLI-33). Source
  install hooks are disclosed and consent-gated as at any meld (HOOK-20,
  HOOK-50..60).

## Safety and policy

- `LNK-10` The `<path>` must be a safe relative path (the DSC-71..73 rule: not
  absolute, not `~`-rooted, no `..` component, no NUL); the `<ref>` is
  validated as a git ref value (DSC-66). A violation is `InvalidRepoSpec` at
  parse, before any clone.
- `LNK-11` Managed-policy allowlist matching (POL-11, POL-36) uses the base
  repo identity `host/owner/repo`, not the extended instance identity: a
  policy that allows the repo allows links into it. `require-pinned` (POL-20)
  evaluates the instance's effective pin as usual, so a branch-ref link is
  refused under a pinned policy.

## Display

- `LNK-12` `recall --sources` and the probe source view show a link instance
  under its full `#`-suffixed identity. Compare and browse URLs (CLI-176,
  HOOK-24) derive from the base repo and keep their host-gated shapes.

## Reproduction

- `LNK-13` `dump` emits a link instance as a `[discover].sources` entry: its
  `source` is a `tree/<ref>/<path>` deep URL reconstructed from the recorded
  `host`/`owner`/`repo` (a `file://`-prefixed path for a local instance,
  matching LNK-1's local form) and `item_path`, with the recorded commit as
  the `<ref>` segment. The entry also carries `pin-ref = <commit>` (dump.md
  DUMP-1/DUMP-4), the SAME commit as the URL's own ref: `pin-ref` is what
  actually pins the reproduction (a curator pin outranks an item link's own
  URL-ref pin, DSC-65 over LNK-3), and the URL's ref exists to keep the URL
  syntactically complete (a `tree`/`blob` link always needs one) and in
  agreement with `pin-ref` rather than a possibly-since-moved branch tip. The
  entry's `namespace` carries the recorded identity alias (`as_alias`,
  STO-58), not a display-only prefix, so re-melding reproduces the exact
  `host/owner/repo#path@alias` instance (LNK-4) an aliased link was
  registered under; an unaliased link emits no `namespace` and, on re-meld,
  naturally re-reads the repo's own `[source].prefix` (if any) exactly as the
  original meld did. `roots`/`add-roots`/`flat-skills` are never emitted for
  a link entry: a link instance's catalog is exactly its one skill (LNK-7),
  so convention scan roots do not apply to it. A link instance always has a
  recorded commit once registered (LNK-3/LNK-4: its checkout point is never
  the default branch, so `meld`/`learn`/`sync` always resolve and record one
  before it is added to the registry); the only way `dump` cannot reconstruct
  the entry is a hand-edited registry with a missing commit, in which case
  `dump` skips the instance with a note rather than emit an unpinned entry.
  The reconstructed URL is always the `https://` form built from the recorded
  `host`/`owner`/`repo`, never the source's own `url` field: `prefer_ssh`
  (DSC-66) may have rewritten `url` to the `git@host:owner/repo` SSH form
  after the meld, and that form cannot express a `tree/<ref>/<path>` link
  suffix, so `dump` always derives the link URL from the identity parts
  rather than echoing `url` verbatim.
- `LNK-23` A file link instance (LNK-20) is emitted with the `blob/<ref>/<path>`
  shape instead of `tree/<ref>/<path>`: a forge blob URL is what names a file,
  and it is the shape the link was pasted in. The entry also carries
  `kind = "<kind>"` when, and only when, the instance recorded an explicit kind
  (LNK-22); a kind that came from the containing directory or the file's
  frontmatter re-resolves identically from the pinned clone, so emitting it
  would add nothing. Everything else about the entry is LNK-13 unchanged.