mind-cli 0.34.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
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
# CLI

The `mind` command surface. Verbs use a knowledge metaphor.

| command | role |
|---------|------|
| `probe [query] [--no-tui]` | interactive browser (default); catalog listing with `--no-tui`/`--json` |
| `meld [<repo>] [--register-only] [--learn <NAME\|GLOB>] [--yes] [-N\|--namespace <prefix>] [--root <dir>] [--flat-skills] [--pin <HEAD\|ref\|branch=NAME\|tag=NAME>]` | connect a source (default `.`), then install its items (`--learn` narrows to the matching subset) |
| `init-source [<path>] [--template] [--marketplace] [--flat-skills] [-N\|--namespace <prefix>]` | scaffold `mind.toml` + detect references (maintainer) |
| `unmeld <name\|glob> [--keep-items] [--yes] [--uninstall-hook <cmd>] [--dangerously-skip-hook-check]` (alias: `remove`/`rm`) | disconnect a source (or all sources matching a glob) and uninstall its items (`--keep-items` leaves them) |
| `learn <item> [--dangerously-skip-install-hook-check]` (alias: `install`) | install |
| `forget <item> [--dangerously-skip-hook-check]` (aliases: `unlearn`, `uninstall`) | uninstall |
| `sync [source]` (alias: `update`) | refresh sources (all, or one matching `[source]`) |
| `upgrade [--yes] [--no-sync] [item]` | upgrade installed; syncs first by default |
| `recall [item] [--sources] [--kind K] [--source S] [--json]` (aliases: `status`, `list`) | status: sources with their items (install state marked); `--sources` narrows to sources |
| `review [<target>] [-N\|--namespace <prefix>]` (default `.`) / `review --policy <path>` | validate a source / a policy file |
| `introspect` (alias: `doctor`) | diagnose |
| `evolve [--check] [--yes] [--to <v>]` (alias: `self-update`) | upgrade the `mind` binary itself |
| `hooks run <target> [--event E] [--force\|--rerun]` / `hooks list <target>` | run or list a source's or item's hooks on demand |
| `link-project [dir] [--preset <name>] [--subdir <rel>] [--snapshot] [--force]` | shorthand: link installed skills into a project's harness skills directory |
| `absorb <item> [--to <path>]` | claim an unmanaged lobe item into a managed source |
| `curate [--check] [--yes] [--prune] [--no-sync] [--adopt <identity>]` (alias: `reconcile`) | apply what your curators now declare: register/install newly listed sources, re-pin, upgrade, report unlisted; `--adopt` claims a pre-existing source a curator lists but does not yet own |
| `dump [--whole-sources] [--output <path>]` | write a super-source `mind.toml` reproducing the melded + installed state |
| `config show` / `config lobes ...` | view/edit config |
| `completions <shell>` | print a shell completion script |
| `man` | print the roff man page |

- `CLI-207` `mind` with no arguments (no verb, no global flag) prints the full
  `--help` text to stdout and exits 0. `command` (the verb) is a required, not
  `Option`al, field, so this is clap's `arg_required_else_help` behavior, not a
  subcommand default: bare `mind` still does not launch the interactive `probe`
  TUI (tui.md TUI-1/TUI-2 stay true -- only `probe` launches it). Without this,
  a required subcommand with no arguments is a clap usage error: 0 bytes on
  stdout, a usage string on stderr, exit 2, which is a poor first command for a
  new install to type.

## Item refs

- `CLI-1` An item ref is one of: `name`, `skill:name`, `agent:name`, `rule:name`,
  or `owner/repo#name` (source-qualified). `name` is the effective (installed)
  name, so a namespaced item is referenced as `<prefix>:<bare>`. Because the same
  `:` separates a kind from a name, the pre-colon token is read as a kind only
  when it is a reserved kind word (NS-26).
- `CLI-2` A bare `name` matches across kinds; a `kind:` prefix narrows to one kind.
- `CLI-3` A ref that matches no catalog item is an error (`ItemNotFound`). A ref
  that matches more than one is an error (`AmbiguousItem`) listing the candidates.
- `CLI-4` A malformed ref is an error (`InvalidItemRef`).
- `CLI-5` The source qualifier in `owner/repo#name` is a **filter**, not a
  `unmeld`-style ref requiring the SOURCE itself to resolve uniquely:
  `source_matches` (`resolve.rs`) tests each candidate source's full
  `host/owner/repo` identity or any trailing component suffix (`repo`,
  `owner/repo`, `host/owner/repo`) independently, with no uniqueness check of
  its own -- a suffix shared by more than one melded source (e.g. two sources
  both ending in `/tools`) matches all of them. What `resolve()`/
  `resolve_installed()`/`select()` actually require unique is the resulting
  ITEM set (by kind and effective name), not the source set: with `a/tools`
  and `b/tools` both melded but only `a/tools` shipping an item named `greet`,
  `tools#greet` resolves silently to that one item even though the qualifier
  matched both sources -- the qualifier's ambiguity across sources is never
  itself reported. An ambiguous suffix is an error only when it also yields
  more than one matching ITEM, and even then the result is `AmbiguousItem`
  (naming each candidate as `source#kind:name`), never `AmbiguousSource` --
  this ref form does not raise `AmbiguousSource` at all. Contrast `unmeld`'s
  `<name>` (CLI-20), which resolves against sources directly (no item identity
  to fall back on for disambiguation) and so DOES require the source match to
  be unique, raising `AmbiguousSource` on a shared suffix; and `sync`'s
  `[source]` argument (CLI-231), a **filter** in the same sense, over sources
  rather than items, acting on every source it matches with no uniqueness
  requirement at all. "Ref" and "filter" name this distinction wherever it
  recurs below (`upgrade`'s `item`, CLI-63/65, whose embedded `owner/repo#`
  qualifier is this same source filter).

## meld

- `CLI-10` `meld <repo>` parses the repo spec, clones it under the sources tree,
  records the current commit, reads `[source].description` from `mind.toml` if
  present, and adds it to the registry.
- `CLI-205` A top-level `meld` that discovers zero items gets a non-success
  glyph and an explicit guidance line instead of a `melded <repo> (0 item(s))`
  line that reads identically to a legitimate empty source. The guidance names
  the convention paths (`skills/<name>/SKILL.md`, `agents/<name>.md`,
  `rules/<name>.md`, `commands/<name>.md`, `tools/<name>/`) and the three
  escapes (`--root <dir>`, `--add-root <dir>`, `--flat-skills`), matching
  `init-source`'s zero-item message. Suppressed for: a nested/curated meld
  (not the caller's decision to act on), an authoritative `mind.toml`
  (`--root`/`--add-root`/`--flat-skills` either don't apply or are ignored
  there, DSC-52/DSC-76), a pure super-source that only curates other sources
  via `[discover].sources` (zero own items is expected, not a discovery
  failure), and a `.claude-plugin/` plugin or marketplace manifest source
  (which has its own guidance path, MKT-15). Suppressed under `--json`; the
  JSON result shape is unchanged.
- `CLI-11` Accepted repo specs: `owner/repo` and `github:owner/repo` (github.com),
  a full git URL (`https://host/owner/repo[.git]`), an SSH form
  (`git@host:owner/repo[.git]`), and a local path or `file://` URL. A spec that
  parses to none of these is an error (`InvalidRepoSpec`).
- `CLI-204` Each of the `host`, `owner`, and `repo` parts a spec parses to must
  be a single safe path component, or the spec is refused (`UnsafeRepoSpec`,
  naming the offending part). A part is refused when it is empty, is `.` or
  `..`, contains `/` or `\`, or contains a control character. Those three parts
  are load-bearing twice: joined with `/` they are the source identity (STO-13),
  which managed policy matches segment by segment (POL-10, POL-67, POL-68), and
  joined as directory components they are the clone path (STO-11), which `meld`
  deletes and re-clones. Without the check the SSH form, which splits only on
  the first `:`, admits a `host` such as `../../elsewhere` or `evil/host@x`: the
  first escapes the sources tree at `remove_dir_all` time, the second forges
  extra identity segments. `@`/`#` legality is per-part (STO-64): `host` refuses
  both; `owner` refuses `#` (it would sit before the repo segment and confuse
  `#`-splitting in item refs and hook targets) but allows `@` (a local path may
  legitimately carry it, `/src/proj@v2/agents`, and it collides with nothing
  there, since the `@<alias>` identity suffix, STO-58, only ever appends to
  `repo`); `repo` refuses both `@` (collides with that alias suffix and with the
  clone-dir leaf, STO-59) and `#` (collides with the item-link marker, LNK-4).
- `CLI-12` Re-melding a repo whose source identity is already registered is not
  an error and does not re-clone or re-register. The identity includes the
  consumer alias (STO-58), so a re-meld is `meld` of a `(repo, alias)` that
  already exists; a differing `--as` is a fresh meld of a new instance, not a
  re-meld. It ensures the source's items are installed: if any are missing it
  installs them (the default-install flow, CLI-23, honoring `--yes` and the
  non-TTY note). When nothing remains to install (or with `--link-only`) it
  prints a status of the source's items: each item's effective name, whether it
  is installed, and the commit it was installed from, flagging items whose commit
  lags the source. Items are matched by stable identity (source, kind, bare name).
- `CLI-206` A re-meld (CLI-12) never re-clones or re-registers, so `--root`,
  `--add-root`, `--flat-skills`, and `--install-hook` cannot take effect on it:
  they are discovery/hook-setup flags set only at the meld that first
  registers a source (DSC-51/STO-17, DSC-84/STO-55, DSC-75/STO-44, HOOK-20).
  `--pin` is NOT in this list (see CLI-209: a re-meld honors it). When any of
  these remaining flags is given against an already-melded source, a one-line
  stderr-suppressed-under-`--json` note lists exactly which flags were ignored
  and directs the user to `mind unmeld <source>` then `mind meld` again to
  apply them, so the drop is not silent. Follows the CLI-203 note pattern.
- `CLI-209` A re-meld (CLI-12) carrying `--pin` re-pins the already-registered
  source, unlike the CLI-206 discovery/hook flags: it resolves the pin request
  (CLI-200/CLI-201) against the source's currently recorded pin (the base
  point a bare `--pin HEAD` freezes, so it freezes whatever the source is
  pinned/following today rather than needing an `unmeld`/`meld` round trip),
  re-checks-out the existing clone at the resolved point (or, for a source
  that is still linked -- local and never pinned -- clones a fresh snapshot
  into the sources tree, same as a first meld's Step 3/4, so the live working
  tree is never touched), and records the resolved pin and commit. Reports
  `re-pinned <old> -> <new>` when the pin or commit changed, or that the
  source is already pinned to the resolved point when the request is a no-op
  (re-pinning to the value already recorded, with no upstream movement).
  Resolution/checkout failure (a bad ref, a network error) is a `Git` error
  that leaves the source's pin, commit, and clone dir exactly as they were
  (CLI-18) -- every git operation runs before any field on the registered
  source is mutated or the registry is saved. The POL-11 allowlist and POL-20
  require-pinned gates apply to the re-pin exactly as they do at a first meld,
  so a source that fell out of policy since it was melded cannot be silently
  re-pinned around the gate. Installed items whose content moved with the
  commit are then reported out of date by `recall`/`upgrade` exactly as any
  other upstream change is (CLI-75/LIFE-11).
- `CLI-13` `--as <prefix>` sets the source's namespace, overriding any
  `[source].prefix`. It is persisted and is not changed by `sync`. A `--as`
  prefix is an identity alias (STO-58): `meld <repo> --as <prefix>` denotes the
  instance `host/owner/repo@<prefix>`, which coexists with a bare meld of the same
  repo and with other aliases of it. It is never an in-place re-prefixing of a
  differently-aliased instance. To change an instance's prefix, edit it in the
  probe TUI (TUI-53, allowed only while no items are installed, CLI-161), or
  `unmeld` it and `meld` again with the new `--as`. `--as ''` removes the prefix
  (the bare `host/owner/repo` identity).
- `CLI-14` After melding, if a prefix is in effect and `--verbose` is in effect
  (CLI-162), unguarded prose references to siblings are reported as warnings (see
  namespacing.md NS-20). Without `--verbose` the warnings are suppressed. Warnings
  do not fail the command.
- `CLI-15` If the melded repo's `mind.toml` lists `[discover].sources`, each is
  melded recursively (see DSC-38), so one `meld` can pull in a curated set. When
  more than one source is added, `meld` reports the total count.
- `CLI-16` `meld --root <dir>` (repeatable) sets the source's scan roots,
  overriding any `[source].roots` (DSC-51). The roots are persisted on the source
  (STO-17). A root that is not a directory in the clone is `InvalidRoot`.
- `CLI-197` `meld --add-root <dir>` (repeatable) adds convention scan roots that
  compose with the source's authoritative discovery layer instead of replacing
  it (DSC-84): a `.claude-plugin/` manifest or an authoritative `mind.toml`
  keeps its items and each added root is convention-scanned in addition. The
  roots are persisted on the source (STO-55). A value that is not a directory in
  the clone is `InvalidRoot`.
- `CLI-158` `meld --flat-skills` force-enables flat skill discovery for the
  source: skills are bare-name directories at a scan root, with no `skills/`
  container (DSC-74). The flag is one-directional (no `--no-flat-skills`): it turns
  the layout on for a source that did not declare `[source].flat-skills`, but
  cannot disable a source's declared flat layout (DSC-75). It applies to the skill
  kind only; agent, rule, and tool discovery are unaffected. It is persisted on the
  source (STO-44). For an authoritative `mind.toml` it is ignored with a note
  (DSC-76).
- `CLI-17` `meld` takes at most one pin flag: `--pin` (with a required value) or a
  deprecated alias (CLI-202). Supplying more than one (two aliases, or `--pin` plus
  an alias) is `ConflictingPin`. The chosen pin is persisted on the source
  (STO-18). With no pin flag, the source's `[source]` pin directive (DSC-41)
  applies, else a remote source declaring `release-tags` pins the caret of its
  highest stable release (VER-50), else the default is following the remote
  default branch. `--pin`
  requires a value, so there is no bare-flag form that could ambiguously consume
  the following positional argument.
- `CLI-18` `meld` clones at the pinned point and records the resolved HEAD commit.
  A `follow` pin (`default-branch` / `follow-branch` / `tag`) checks out that
  branch or tag; a `ref` pin checks out the commit. For a `--pin` value that
  freezes (`HEAD` or a bare ref, CLI-200), the point is first checked out to
  resolve its current commit, which is then persisted as a `ref` pin. A pin that
  does not resolve in the remote is a `Git` error and nothing is registered.
- `CLI-200` `--pin <value>` where the value freezes to an immutable `ref` pin:
  `--pin HEAD` freezes the current resolved tip (the point that would otherwise be
  melded: the remote default branch, a deep-link branch ref (LNK-3), or a
  `[source]` directive) to its current commit sha. `--pin <ref>` (a bare tag name,
  sha, or branch name) resolves `<ref>` to its current commit and freezes that.
  Freezing a branch or tag snapshots its current tip; it does not track it. On
  `learn`, `--pin` is a bare flag that freezes a deep-link URL's ref (LNK-3); for a
  plain (non-URL) item ref it is a no-op and prints a note, and the install still
  proceeds.
- `CLI-201` `--pin branch=<name>` and `--pin tag=<name>` record a floating pin:
  `branch=<name>` tracks that branch (`follow-branch`); `tag=<name>` tracks that
  tag (`tag`), which re-points on sync if the upstream tag moves (CLI-55).
  `--pin version=<range>` records a version pin (VER-20). A value carrying an
  unrecognized `key=` form (anything but `branch=`, `tag=`, or `version=`) is a
  `BadPinSpec` usage error, so a mistyped key is not silently taken as a ref name.
- `CLI-202` The former `--follow-branch <branch>`, `--pin-tag <tag>`, and
  `--pin-ref <commit>` flags remain as hidden deprecated aliases mapping onto the
  new model: `--pin branch=<branch>`, `--pin tag=<tag>`, and `--pin <commit>`
  respectively. They are accepted for one release and count toward the
  at-most-one-pin rule (CLI-17), so mixing a deprecated alias with `--pin` is
  `ConflictingPin`.
- `CLI-203` `learn <url> --pin` freezes the link instance's ref only at
  meld/registration (CLI-200, LNK-3). When the link is already melded, the
  meld+pin step is skipped, so `--pin` has no effect; `learn` prints a one-line
  note that `--pin` was ignored because the instance is already melded, rather
  than dropping the flag silently. The install still proceeds. Suppressed under
  `--json`.
- `CLI-19` An explicit `git@host:owner/repo` (or `ssh://`) spec clones over SSH
  using the user's key/agent, with no username/password prompt. With `ssh = true`
  in `~/.mind/config.toml`, `meld` (and `sync` auto-meld) also rewrites an https
  remote to its `git@host:owner/repo` SSH form before cloning, so the `owner/repo`
  shorthand uses SSH too. A local path and an explicit `git@...` / `ssh://` spec
  are left unchanged; the rewritten URL is recorded, so later `sync`s reuse SSH.
  An https remote still authenticates as git normally does (a credential helper,
  or the interactive prompt).
- `CLI-23` By default, after registering the source, `meld` previews its items
  and prompts to install them all (the interactive form of `learn '<source>#*'`),
  installing the whole source on a yes. The prompt defaults to yes (`[Y/n]`, a
  bare Enter installs), since reaching it means the user chose to meld the source;
  it is reversible with `forget`. `--register-only` (deprecated alias:
  `--link-only`, see CLI-165) stops at registering the source; its items remain
  available to `learn` later. `--yes` installs without prompting, including in a
  non-TTY context; without `--yes` a non-TTY `meld` registers only and prints how
  to install later (mirroring the install-hook non-TTY behavior, HOOK-22). Only
  the top-level source is offered (a curated super-source's nested sources are not
  auto-installed), already-installed items are skipped (DEP-23), and a source
  install hook is still handled by its own prompt during the meld (HOOK-20).
- `CLI-236` `meld <repo> --learn <NAME|GLOB>` replaces the CLI-23 install-all
  offer with a subset install: only the source's items matching the patterns are
  installed. The flag is repeatable, and each value may be an exact name
  (`review`), a kind-qualified name (`skill:review`), or a glob (`skill:*`,
  `pdf-*`). It conflicts with `--register-only`, which asks for the opposite. A
  re-meld (CLI-12) honors it too, and still ends with CLI-12's status report.

  *Matching.* A pattern is matched against each item's BARE name as well as its
  effective (prefixed) name, within the melded source only. Requiring the
  effective name would break the first-run case the flag exists for: at meld
  time the consumer has not necessarily seen the source's declared
  `[source].prefix`, and CLI-24 may prompt for it inside this very command, so
  `--learn review` must still find the item that installs as `team:review`.
  Within one source the two readings cannot disagree about which item is meant.
  Each match is then installed by its EXACT, kind-qualified ref
  (`<source>#<kind>:<effective-name>`), so an item name that itself contains
  glob metacharacters is never re-read as a pattern.

  *Rejected values.* A value that is empty, carries a `#` (it selects within the
  source being melded, so a source-qualified ref is a usage error), is not a
  parseable item ref, or is a malformed glob, is `InvalidLearnPattern`, whose
  message names which of the four it was. The check is pure syntax and runs
  BEFORE the clone, so a typo does not leave a registered source behind.

  *No match.* A pattern that parses but matches no item in the source is
  `LearnPatternNoMatch`, not the generic `ItemNotFound`: the user named it
  explicitly, so a typo must not pass as "nothing to install", and the message is
  scoped to the one source that was searched. It points at `mind probe --source
  <name> --no-tui`, which lists what that source OFFERS; `recall` lists what is
  INSTALLED, and after a no-match nothing from the source is installed, so it
  would answer with an empty listing. The message names the two things that most
  often explain a miss: an inventory that does not declare the item, which needs
  `--add-root <dir>` at the meld that REGISTERS the source (`--add-root` is
  ignored on a re-meld, CLI-206, so an already-melded repo must be unmelded
  first), and a super-source whose nested sources register as their own sources.
  The source stays melded, as it does whenever an install pass fails.

  *Install.* Each matched item installs through the ordinary `learn` path, so its
  dependency closure (DEP-30/31) and the collision checks (CLI-33) apply
  unchanged. The gate around the batch is the CLI-23 meld gate, not `learn`'s:
  one preview and one prompt for the whole batch on a TTY, `--yes` installs
  without prompting, and a non-TTY run without `--yes` installs nothing and
  prints how to install later. When every match is already installed, that is
  reported once for the batch rather than once per pattern (CLI-157).

  *Scope.* The curated chain is not walked: a pattern selects within the source
  being melded, so DSC-54/55/58's nested install pass would install items the
  user did not name. Nested sources are still REGISTERED by the meld; only their
  install pass is skipped. Because `--recursive` (DSC-55) has nothing to act on
  under `--learn`, passing both prints a note naming the ignored flag rather than
  dropping it silently, the same disclosure a re-meld gives an inapplicable flag
  (CLI-206).

  *`--json`.* The installed keys and the pending count fold into the one meld
  object as CLI-156 describes. `pending_items` counts the UNION of the matched
  items and their closures, not the sum over patterns: without `--yes` nothing
  installs between patterns, so summing would count an item two patterns both
  match, or a dependency shared by two of them, more than once.

  This is the one-command form of "meld the whole repo, install just this", which
  is the second half of the remedy an item link's unsatisfiable reference points
  at (LNK-18).
- `CLI-156` In `--json` mode, `meld` is fully non-interactive and never prompts.
  When `--yes` is given the items are installed as part of the single meld result:
  the `installed` array in the JSON object lists the effective keys of every item
  installed in that call. When `--yes` is absent, no install prompt is shown and
  no install occurs; instead the JSON result carries a `pending_items` integer with
  the count of items available to install. In both cases exactly ONE top-level JSON
  object is written to stdout (CLI-153).
- `CLI-24` When a source declares `[source].prefix` and no `--as` was given, an
  interactive `meld` prompts whether to namespace its items under that prefix:
  accept it, type a different prefix, or choose none. The prompt previews the
  resulting installed names under the declared prefix (e.g. `skill:jk:foo`) so the
  effect is visible before choosing. The choice becomes the source alias and
  applies to the scan and the install (`<prefix>:<name>`). A non-interactive meld
  accepts the declared prefix as-is. An empty alias (`--as ''` or the "no prefix"
  answer) explicitly overrides a declared prefix to none. A source that declares
  no prefix is not prompted.
- `CLI-25` `meld` with no `<repo>` argument defaults to the current directory
  (`.`), melding the repo the command is run in. Combined with the default
  install (CLI-23), running `mind meld` inside a source repo (e.g. one with a
  `mind.toml`) registers and installs that source.
- `CLI-27` A local-path source with no pin in effect is *linked*: `mind` reads it
  directly from its working tree (the path `meld` was given) rather than cloning
  it, so the maintainer's in-progress edits -- including an untracked or
  gitignored `mind.toml` -- are seen live by `meld`, `sync`, `upgrade`, and
  `recall`. `mind` never deletes a linked source's directory: `unmeld` removes the
  registry entry (and, by default, its installed items) but leaves the working
  tree, and a failed `meld` never touches it. `sync` does not fetch or reset a
  linked source (it only re-reads its HEAD); a deleted working tree is a per-source
  sync error (CLI-54). A pinned local source (any `--pin` or a `[source]`
  directive) is instead cloned as a snapshot at the pin, so pinning still works.

- `CLI-159` `meld --namespace <prefix>` sets the source's namespace, opting the
  source into prefixing (with no flag and no `[source].prefix`, items install bare,
  NS-2). It is the renamed `--as` (CLI-13); `--as` is retained as a hidden,
  deprecated alias so existing invocations keep working. `review` takes the same
  rename (`--namespace` aliasing `--as`, CLI-133). `--namespace ''` removes the
  prefix (the explicit no-prefix override of a declared `[source].prefix`, as
  `--as ''` did, CLI-13). As of CLI-163, the short form is `-N` (uppercase).
- `CLI-161` Changing a registered source's namespace in place (the TUI
  source-details editor, TUI-53, via `set_source_namespace`) is allowed only
  while no items from the source are installed; a change requested while items
  are installed is an error naming those items and directing the user to `forget`
  them first, with the namespace unchanged (NS-30). This changes the source's
  effective display prefix (`alias`), not its identity: the identity alias
  (`as_alias`, STO-58) is fixed at meld, so an in-place namespace change never
  renames the source or relocates its clone. To make a repo a distinct instance
  under a new prefix, `meld` it with a different `--as` (CLI-13), which registers
  a separate `@<alias>` instance.
- Cross-source collision detection (namespacing.md NS-43/44/45): after catalog
  discovery and before install, `meld` checks incoming skills, rules, and tools
  against already-installed items from other sources. An incoming item whose
  effective `(kind, name)` pair matches an installed item from a different source
  is a cross-source collision (distinct from the same-invocation check at CLI-33
  and from NS-41 for agents). In an interactive session (TTY, no `--yes`), `meld`
  pauses and prompts for a namespace prefix, pre-populated with the repo name
  (NS-44). In a non-interactive session (no TTY or `--yes`), `meld` exits
  non-zero, lists the conflicts, and suggests re-running with `--namespace
  <repo-name>` (NS-45). A source already carrying a namespace whose effective
  names do not collide is not affected.
- `CLI-177` When the top-level `meld` command fails to clone a remote source due
  to an authentication error (DSC-68 patterns), it prints a remediation hint to
  stderr before surfacing the error. The hint lists three alternatives: (1) the
  SSH remote form of the URL (e.g. `mind meld git@host:owner/repo`), (2) setting
  `ssh = true` in `~/.mind/config.toml` to always prefer SSH for HTTPS remotes,
  and (3) configuring a git credential helper. The hint is suppressed for local
  sources (no SSH alternative applies).
- `CLI-178` When a `meld` (top-level) or `sync` (per-source) git operation fails
  with a proxy error -- matching "Received HTTP code 407", "The requested URL
  returned error: 407", or "Could not resolve proxy" (case-insensitive) -- a hint
  pointing at `HTTPS_PROXY` and `git http.proxy` is printed to stderr before the
  error is surfaced. The check is applied to the same `MindError::Git` stderr
  matching used by `is_auth_failure`.
- `CLI-180` Top-level `meld` clone errors (both the initial default-branch clone
  and the re-clone at pin) lead with git's stderr as the first line printed to
  stderr, so the actual cause is immediately visible. The reconstructed git
  command line and the internal store path are shown only under `--verbose`
  (CLI-162); without it those details are suppressed. The git stderr always
  appears regardless of `--verbose`. In human mode the error returned to the
  caller has its args reduced to `["clone"]` and its stderr set to
  `"(git output above)"` so the display does not repeat the store path and does
  not show the literal `<no stderr>` when output was already printed (CLI-185).
  Under `--json` the stderr is preserved intact in the returned error (CLI-184).
- `CLI-184` Under `--json`, `handle_top_level_clone_err` skips all stderr
  printing and returns the `MindError::Git` with git's stderr field intact and
  args reduced to `["clone"]`, so the CLI-181 JSON envelope's `message` field
  contains the actual cause from git rather than the literal `<no stderr>`.
- `CLI-185` In human mode, after printing git's stderr to the terminal,
  `handle_top_level_clone_err` sets the returned error's stderr field to
  `"(git output above)"` instead of clearing it, so the error trailer never
  displays the misleading literal `<no stderr>`.
- `CLI-186` Git stderr and error text echoed during meld clone failures (the
  top-level clone path) and sync per-source failures are passed through
  `strip_ansi` before being printed, preventing a hostile remote from embedding
  ANSI escape or Unicode bidi-override sequences to corrupt or spoof terminal
  output.
- `CLI-224` A `review` finding's message (built from source-controlled text: a
  token, a hardcoded path, an `item.key()`) is passed through the same
  `strip_ansi` sanitization CLI-186 applies to git stderr, applied once at
  `Finding::hard`/`Finding::advisory` construction so both the human `error
  [kind]: ...`/`advisory [kind]: ...` text (CLI-131) and the `--json` document
  (CLI-219, including its CLI-221 `details` form on a hard failure) inherit the
  stripped string from the one place rather than each sanitizing separately.
  Serde alone keeps `--json` structurally valid but does not stop an embedded
  ANSI escape or bidi-override sequence from corrupting or spoofing a
  terminal that renders the message. `strip_ansi` (`src/sanitize.rs`) collapses
  a run of C0/DEL/C1 control characters that reach its own filter to a single
  space rather than deleting it -- in practice, the only such character is
  `\n` (every other C0/DEL/C1 control is already consumed with no trace by the
  first pass, `strip_ansi_escapes::strip`, before this function's filter ever
  runs) -- so text built by joining originally-separate lines with `\n` (a
  multi-line hook command embedded in a disclosure, an `item-hook` finding's
  `'{cmd}'`) is not silently fused into one word once the separator is
  stripped. A security-blocked Unicode code point (a bidi override, a
  directional mark U+200E/U+200F/U+061C, or a zero-width character
  U+200B/U+2060/U+FEFF) is still dropped with no space substituted, since none
  of those is a word boundary. A finding or disclosure built from more than
  one source-controlled field sanitizes EACH FIELD before composing them
  together, never the already-composed line: a dangling/incomplete escape
  sequence in one field can otherwise swallow a later field (or the literal
  text between them) once the two are joined, so sanitizing post-composition
  does not reliably clean every field individually.
- `CLI-225` Every error message or printed note that interpolates a
  source-or-user-influenced identity (a source name, a `meld --as`/
  `[source].prefix` alias, an item-link `#<path>` segment, an agent's harness
  `name:`, a suggested namespace prefix derived from a repo name, or any similar
  value) into a RUNNABLE command a reader is invited to copy and paste (`mind
  unmeld ...`, `mind forget ...`, `mind meld ...`, `mind hooks run ...`, or any
  shell command) shell-quotes that identity -- the POSIX single-quote `'\''`
  idiom (`src/error::shell_quote`) -- before it lands in the command, applied
  unconditionally rather than only when the value "looks dangerous". None of
  these identities is restricted against shell metacharacters (`validate_prefix`
  and `is_safe_manifest_path` reject only path traversal, not `;`/`'`/`$`/a
  backtick/whitespace), so an unquoted splice would let a crafted identity turn
  the pasteable remedy into an injection: a `'` breaks out of a
  single-quote-framed raw value, after which `;`/`$(...)`/a backtick ride along.
  This generalizes the `hooks run` family's rule (HOOK-106) to every printed
  remedy: `LinkedSourceGone` (`mind unmeld`), `AgentCollision` (`mind forget
  agent:<name>`), and `SkillCollision` (`mind meld --namespace <prefix>`) each
  quote their interpolated identity by exactly this rule. A bare mention of the
  same identity in surrounding English prose (naming *what* is wrong, not a
  command to run) is not quoted; only the runnable command is. The rule is not
  limited to a `MindError` variant: an ordinary `println!`/`eprintln!` note in
  `commands.rs`/`install.rs` that offers a pasteable remedy is the same class
  and is quoted the same way -- the re-meld ignored-flags note (`mind unmeld
  <name>`), the emptied item-link forget hint (`mind unmeld <link-id>`), the
  `learn` not-found hint (`mind probe <query>`, echoing the user's own
  argument) and its melded-source hint (`mind learn --all <query>`), the
  unreachable-lobe note (`mind config lobes remove <path>`, a path taken from
  user config), the DSC-60 add-roots-yourself hint (`mind meld <source>
  --add-root <dir>`), `unmeld --unlink-only`'s and the non-TTY post-meld
  install note's remaining-items hints (`mind forget`/`mind learn
  <source>#*`), the agent-collision skip warning (`mind forget
  <kind>:<name>`), a re-meld's per-item "not installed" listing (`mind learn
  <kind>:<name>`), and `init-source`'s `--template` hint (`mind init-source
  <dir> --template`, the user's own path argument).
- `CLI-187` When no sources are melded, all verbs that report the empty state
  (sync, recall, probe) emit the same message: "no sources melded; run `mind
  meld <owner/repo>` to add one". This consistent phrasing always names the
  next command so a first-run user has a clear path forward regardless of which
  verb they tried first.

## unmeld

- `CLI-20` `unmeld <name>` removes the source's clone and registry entry.
  `name` is a **ref** (CLI-5): it must resolve to exactly one source. It is
  the full identity (`host/owner/repo`, plus the `@<alias>` suffix for an
  identity-aliased instance, STO-58) or an unambiguous trailing suffix (e.g.
  `repo`, `owner/repo`, or `repo@<alias>`). The `@<alias>` is part of the
  identity, so a bare suffix reaches only the un-aliased instance: with both a
  bare `repo` and a `repo@jk` melded, `unmeld repo` targets only the bare one and
  `unmeld repo@jk` the aliased one (use a glob, CLI-28, to remove several at
  once). An unknown name is `SourceNotFound` and an ambiguous suffix is
  `AmbiguousSource` -- unlike `sync`'s `[source]` **filter** (CLI-231), which
  intentionally allows a plain name or suffix to match several sources, a
  non-glob `unmeld` ref never widens to more than one. The former visible
  alias `detach` is removed and is now a usage error (CLI-172).
- `CLI-21` `unmeld <name>` by default uninstalls every item installed from the
  source (each via its file registry, then its manifest entry), mirroring meld's
  install-by-default (CLI-23): dropping a source cleans up after itself in one
  step. It first lists the items it will remove; the multi-item confirmation
  (CLI-42) applies, and `--yes` skips it.
- `CLI-22` `unmeld --keep-items` (deprecated alias: `--unlink-only`, see CLI-166)
  removes only the source (clone and registry entry) and leaves its installed items
  in place. It lists those orphaned items and suggests the `forget` command to
  remove them later. This is the opt-out from the default item removal (CLI-21),
  mirroring `meld --register-only` (CLI-165).
- `unmeld` runs the source's uninstall hooks before removal and accepts
  `--dangerously-skip-hook-check` to run them unattended, and
  `--uninstall-hook <cmd>` to supply or override the uninstall hook (see
  install-hooks.md, HOOK-54, HOOK-59).
- `CLI-227` `unmeld` and `forget`'s hook-consent flag is
  `--dangerously-skip-hook-check`. `--dangerously-skip-install-hook-check` (the
  original spelling) is kept as a hidden alias: both gate an UNINSTALL hook on
  these two verbs, despite the "install" in the old name, which the rename
  fixes without breaking a script that already passes the old spelling.
  `meld`/`learn`/`sync`/`upgrade`/`hooks run`, whose install-hook flag really
  does gate an install hook, keep `--dangerously-skip-install-hook-check`
  unchanged.
- `CLI-230` `unmeld` accepts `remove` and `rm` as additional visible aliases,
  for symmetry with `learn`'s `install` alias and `forget`'s `unlearn`/
  `uninstall` aliases.
- `CLI-28` `unmeld <pattern>` accepts a glob (`*`, `?`, `[`) in place of an exact
  name or suffix (CLI-20), matched against each melded source's `host/owner/repo`
  identity and its trailing-suffix forms, mirroring `learn`/`forget` glob selection
  (CLI-31, CLI-41). The pattern is matched against the identity as a plain string,
  so `*` spans any run including `/`: `mind unmeld '*agents'` removes
  `github.com/jaemk/agents`. Every matching source is unmelded, each per its normal
  path (CLI-21 by default, or CLI-22 under `--unlink-only`). A glob is what permits
  a multi-source match: a plain name or suffix (the CLI-20 ref) that resolves to
  several sources is still `AmbiguousSource` (CLI-20), but a glob removes all it
  matches. A glob matching no source is `SourceNotFound`. When a glob matches more
  than one source, `unmeld` lists the matched sources and confirms before removing
  them (the multi-item confirmation of CLI-42, applied at source granularity);
  `--yes` skips the confirmation.

## learn

- `CLI-30` `learn <item>` with an exact ref installs the single matching item:
  it copies the item into the store and links it into every configured agent
  home (see lifecycle.md, STO-14), except a store-only tool, which installs to
  the store with no link (tooling.md TOOL-3). It records the item in the
  manifest; a ref matching none is `ItemNotFound` and one matching several is
  `AmbiguousItem`.
- `CLI-31` When the ref name is a glob (`*`, `?`, `[`), `learn` installs every
  matching item. The kind prefix, source qualifier, and glob compose: `'*'` is
  everything, `'skill:*'` all skills, `'owner/repo#*'` all items of one source,
  `'owner/repo#skill:*'` all skills of one source. A glob matching nothing is
  `ItemNotFound`.
- `CLI-32` `--dry-run` (`-n`) lists the items that would be installed and installs
  nothing.
- `CLI-33` The collision check runs before any install: if the selected set
  contains two items that would install under the same `kind:name`, `learn`
  errors (`AmbiguousItem`) and installs nothing.
- `CLI-34` If a later item in a multi-item `learn` fails, the items already
  installed are still recorded in the manifest (state stays consistent with
  disk) and the failure is reported.
- `CLI-35` `learn --force` (`-f`) overwrites a link target that already exists
  and is not managed by mind (the clobber guard, LIFE-41), replacing the user's
  file, directory, or foreign link. Without `--force`, hitting such a conflict
  prompts on a TTY to overwrite that one target (a yes installs it forced, a no
  refuses it, an `a` per CLI-247) and, in a non-TTY context, refuses with
  `LinkOccupied` as before. The forced overwrite stays transactional: it is
  decided before staging, so a refusal changes nothing. `meld --force` applies
  the same to its default install.
- `CLI-246` `meld --overwrite` and `learn --overwrite` accept every overwrite
  of a link target mind did not create in the run, without a prompt per
  conflict, on a TTY or not. For `learn` it is the same as `--force`. For
  `meld` it is only that: unlike `meld --force`, a re-meld does not re-offer
  install hooks already run at the current commit (HOOK-60). `--force` wins
  when both are given.
- `CLI-247` The CLI-35 overwrite prompt reads `[y/N/a]`. `y` overwrites this
  target, `a` (or `all`) overwrites it and every later conflict in the same
  run without asking again, and anything else (a bare Enter, EOF) refuses.
- `CLI-36` `learn <source> --all` is shorthand for `learn '<source>#*'`: it
  appends the `#*` selector to the positional ref, promoting it from an item name
  to a source qualifier and installing every item of that source (CLI-31), deps
  and all. `--all` is rejected with `InvalidItemRef` when the ref already carries
  a `#` selector, since the selector would be doubled.
- `CLI-157` When every item in a `learn` selection is already installed (the
  dependency closure after DEP-23 filtering is empty), `learn` exits 0 but treats
  this as a distinct no-op, not a silent success. In human output it prints a line
  such as "already installed; nothing to do". Under `--json` the outcome token is
  `"up-to-date"` rather than `"installed"`, so callers can distinguish a real
  install from a re-run that changed nothing.
- `CLI-179` When `learn <name>` finds no item matching the query and at least one
  source is melded (`sources > 0`), a hint is printed to stderr directing the user
  to `mind probe <query>` to search the available catalog. The zero-sources case
  (`sources == 0`) is unchanged: it directs the user to `mind meld` instead.
  `mind sync` is not mentioned because syncing cannot surface an item that does not
  exist in any melded source; only browsing with `probe` can confirm what is
  available.
- `CLI-208` When `learn <name>` finds no item matching the query (`ItemNotFound`)
  and the query names -- exactly, or as an unambiguous trailing suffix (the same
  match rule as a source qualifier, CLI-5) -- exactly one already-melded source, a
  second stderr hint (alongside CLI-179) names that source and points at
  `mind learn --all <name>` to install all of its items. `learn <source-name>`
  reads like `meld`'s `<repo>` argument, and `install` (the `learn` alias) trains
  exactly that habit, so this is the most common way a query resolves to a source
  rather than an item. Suppressed under `--json`, matching the CLI-179 hint. Not
  printed when the query matches zero or more than one melded source (an ambiguous
  suffix is not a case this hint disambiguates).

## forget

- `CLI-40` `forget <item>` (alias: `unlearn`) removes an installed item using its
  file registry and deletes its manifest entry. The ref is matched against the
  manifest by effective name, honoring a `kind:` prefix and an `owner/repo#`
  source qualifier; a bare name that matches more than one installed item (e.g.
  a skill and an agent of the same name) is `AmbiguousItem`, and one matching
  none is `NotInstalled`.
- `CLI-41` When the ref name is a glob, `forget` uninstalls every matching
  installed item, mirroring `learn`'s glob selection (CLI-31). The kind prefix
  and source qualifier compose with the glob. A glob matching no installed item
  is `NotInstalled`.
- `CLI-42` When `forget` would remove more than one item (a glob that matched
  more broadly than intended), it lists the matched items and confirms before
  removing any. `--yes` (`-y`) skips the prompt; a non-TTY run without `--yes`
  refuses (`ConfirmationRequired`) rather than removing silently. Removing a
  single exact match is not prompted.
- `CLI-245` `forget --dropped` removes every installed item its source no
  longer offers: no catalog item shares its stable identity (source, kind, bare
  name), as `recall` marks "removed upstream", or its source is no longer
  melded (`unmeld --keep-items`, CLI-22). An item whose source is melded but
  whose clone is missing is kept, since nothing says it was dropped. An optional
  `<item>` ref or glob narrows the set. CLI-42's confirmation applies when more
  than one item would go. With nothing dropped it says so and removes nothing;
  under `--json` the outcome is `no-op`.

## sync

- `CLI-50` `sync` fetches every source, resets its clone to the remote default
  branch, and updates the recorded commit and `[source].description`. A linked
  local source (CLI-27) is not fetched or reset: `sync` only re-reads its HEAD
  and updates the recorded commit from the working tree.
- `CLI-231` `sync` accepts an optional `[source]` **filter**: `mind sync
  <source>` fetches every melded source whose name matches it -- exactly
  (`host/owner/repo`), by trailing suffix (e.g. `repo` or `owner/repo`), or by
  glob -- leaving every other source untouched. A `sync` filter is a
  deliberately different concept from `unmeld`'s/`upgrade`'s **ref** (CLI-5,
  CLI-20): a filter is NOT required to be unambiguous, and is not an error
  when it is not. A suffix shared by more than one melded source (e.g. two
  sources both ending in `/tools`) matches and syncs all of them rather than
  erroring, the same way a glob does -- appropriate because `sync` is a
  non-destructive refresh, unlike `unmeld` (destroys) or `upgrade` (mutates a
  specific identity), which both refuse to guess. A filter that matches no
  melded source (against a non-empty registry) is a `SourceNotFound` error,
  so a typo is reported rather than silently syncing nothing; a malformed
  glob is an `InvalidPattern` error.
  With a filter, two whole-registry passes are skipped: the managed-policy
  auto-meld provisioning (POL-32) and the `[discover].sources`/marketplace
  nested-source re-walk (DSC-57) run only for a full `sync`. This does NOT
  extend to the `--upgrade` pass (CLI-53): see `CLI-232`, which scopes that
  pass to the same filter rather than leaving it global, and `CLI-233`, which
  names the filter's matches in the confirmation when it matched more than
  one. With no filter, every source is fetched (CLI-50, the unchanged
  default).
- `CLI-51` With no sources melded, `sync` reports that and exits successfully.
- `CLI-52` `sync` does not change consumer aliases.
- `CLI-53` `sync --upgrade` runs an `upgrade` pass after refreshing sources
  (reporting pending upgrades and prompting before applying, exactly like
  `upgrade`), so a single command both fetches upstream and applies pending
  upgrades.
- `CLI-232` With a `[source]` filter, `sync <source> --upgrade`'s `--upgrade`
  pass is scoped to the filter's matched source(s) (CLI-231), the same set
  `sync` itself just refreshed: only installed items whose recorded source is
  in that set are considered for upgrade, and a source's install hook is
  re-run (HOOK-11) only when its source is in that set. Without this,
  `--upgrade` pass ran unscoped regardless of the filter: `mind sync alpha
  --upgrade --yes` would upgrade every installed item from every melded
  source, and re-run install hooks (arbitrary shell) for sources the caller
  never named -- exactly the protection a scoped `upgrade <item>` already
  provides (HOOK-11), silently lost when the same scoping is expressed
  through `sync <source>` instead. With no filter, the pass is unscoped
  (every source), unchanged.
- `CLI-233` When an upgrade pass's scope (CLI-234) spans more than one source,
  the multi-source disclosure (CLI-235) names every source in that scope: a
  filter is allowed to match more sources than a plain name suggests
  (CLI-231), so applying the upgrade pass to all of them -- and possibly
  re-running each one's install hook (HOOK-11), arbitrary shell -- can
  silently widen past what `mind sync skills --upgrade` (or `mind upgrade
  'suffix#*'`, CLI-234) reads as. A scope of exactly one source, or an
  unscoped `--upgrade` pass, leaves the confirmation unchanged: naming a
  single match is not new information. `--yes` does not suppress the
  disclosure (CLI-235): unlike a `[Y/n]` prompt, this is not a confirmation
  step to skip.
- `CLI-234` The multi-source disclosure's scope (CLI-233) is not
  `sync <source> --upgrade`'s filter alone: it covers every scoping
  mechanism in play for the pass (their intersection when more than one
  applies, CLI-244, since the pass touches only sources every one admits) --
  a `sync`/`upgrade` source filter, an
  `upgrade <item>` item-ref filter (whose embedded `owner/repo#` qualifier is
  itself a filter over sources, CLI-63/CLI-5), and the TUI's confirmed-key
  scope (TUI-72/TUI-73) -- each of which can independently span more than one
  source. `mind upgrade '<suffix>#*'` has the identical HOOK-11 blast radius
  as a multi-match `sync <filter> --upgrade` (every source the filter matched
  has its items upgraded and its install hook possibly re-run), so it
  discloses the same way: naming every source its item-ref filter matched,
  before consent.
- `CLI-235` The multi-source disclosure (CLI-233/CLI-234) fires BEFORE
  `upgrade`'s HOOK-11 install-hook re-run pass runs, and unconditionally
  whenever the scope spans more than one source -- independent of whether any
  upgrade ends up pending, and independent of `--yes`. Earlier, the
  disclosure printed only from inside the `--yes`-absent confirmation branch,
  which is reached AFTER the hook re-run pass already ran and only when a
  pending upgrade existed: a filter that re-ran two sources' install hooks
  and then found nothing pending reported "everything is up to date" with no
  disclosure at all, and `--yes` (the common unattended/CI form) skipped the
  confirmation branch entirely, so it never disclosed either -- in both cases
  the wording ("possibly re-running the install hook of") read as a
  forward-looking warning for a side effect that, by the time it printed
  (or would have printed), had either not printed at all or had already run.
  In text mode the disclosure prints directly (before any hook-execution
  output); under `--json`, whose stdout carries exactly one JSON document
  (CLI-181), it prints to stderr instead, since `--json --yes` has no
  `ConfirmationRequired` error to carry it and even `--json` without `--yes`
  reaches the confirmation (where the note is still folded into
  `ConfirmationRequired.action`, CLI-233) only after the hook pass has
  already run. The disclosure names the filter/selector TEXT that produced
  the scope (when the caller has one to offer; the TUI's key-scoped apply
  does not) and every source in that scope -- not only the sources that end
  up contributing a pending upgrade, since the disclosure fires before
  `pending` is computed -- and ends by naming the remedy: narrow with a
  longer suffix, or the full `host/owner/repo` identity.
- `CLI-244` `upgrade --source <filter>` syncs and upgrades the melded sources
  the filter matches and no others. The filter is `sync`'s `[source]` filter
  (CLI-231): an exact name, a trailing suffix, or a glob; one that matches
  nothing is a `SourceNotFound` error and a malformed glob is
  `InvalidPattern`. The pre-pass fetch (CLI-169) is limited to the matched
  sources, so every other source's clone and recorded commit are untouched;
  `--no-sync` still skips the fetch. The pass is the one `sync <filter>
  --upgrade` runs (CLI-232): only items whose recorded source matched are
  considered, only the matched sources' hooks re-run (HOOK-11), and their
  profiles are re-expanded (PROF-21). An `[item]` argument narrows the pass
  further: the scope is the intersection of the two, the multi-source
  disclosure (CLI-233) names that intersection and quotes both filters'
  text (CLI-235), and profiles are not
  re-expanded (PROF-21's `upgrade <item>` rule).
- `CLI-54` A per-source failure (e.g. a network error on one remote) does not
  abort the run: `sync` refreshes each source independently, persists the
  progress made (the recorded commits of the sources that succeeded), reports
  each failure, and exits non-zero (`SyncFailed`). With a failure, the `--upgrade`
  pass is skipped.
- `CLI-55` `sync` resolves each source against its recorded pin (STO-18): a
  `follow-branch` source resets to that branch's current tip and updates the
  recorded commit; a `pin-tag` / `pin-ref` source re-fetches but stays at the
  pinned tag / commit, so its recorded commit moves only if the upstream tag was
  moved (a moved tag is reset to). A version pin re-resolves its range
  (VER-43). `sync` never changes the pin itself (cf.
  CLI-52 for aliases). `upgrade` and `introspect` operate on the synced (pinned)
  content, so a `pin-tag` source does not report drift as upstream's default
  branch advances past the tag.

## upgrade

- `CLI-60` `upgrade` reports pending upgrades and, unless `--yes` is given, prompts
  `[Y/n]` (default Yes, a bare Enter applies; EOF counts as No) before applying
  anything. The affirmative is the default because reaching the prompt means the
  user asked to upgrade, and an upgrade is reversible (re-pin and `sync`/`upgrade`,
  or `forget`).
- `CLI-61` The report lists, per item, the hash and commit deltas, and a compare
  URL when the source host supports one. A namespace change is shown as a rename.
- `CLI-176` The compare URL is produced for https remotes using the GitHub
  `/compare/<old>...<new>` URL shape by constructing
  `https://<host>/<owner>/<repo>/compare/<old>...<new>`. This covers GitHub.com,
  GitHub Enterprise Server, and Gitea/Forgejo instances. SSH remotes and
  local/file paths return no compare URL because there is no web host to link to.
- `CLI-188` Hosts whose hostname contains the substring "gitlab" or "bitbucket"
  (case-insensitive) use a different compare URL shape and therefore return no
  compare URL. This supersedes the broader claim in CLI-176 that the GitHub shape
  applies to any https forge: those two forge families are suppressed because the
  GitHub-shaped link would 404. A self-hosted instance on a neutral hostname (e.g.
  `git.corp.internal`) continues to produce the GitHub-shaped link.
- `CLI-62` `--yes` applies upgrades without prompting.
- `CLI-63` An optional `item` limits upgrade to the matching installed item(s),
  matched against the manifest by effective name and honoring a `kind:` prefix
  and an `owner/repo#` source qualifier (`installed_matches_glob`, `resolve.rs`).
  `item` is itself a **filter** over installed items, not a `unmeld`-style ref:
  it may match several installed items and, unlike `unmeld`'s `<name>`
  (CLI-20), that is never an error -- every match is upgraded, from every
  source it matches. The embedded `owner/repo#` qualifier is this same kind of
  filter, not a ref (CLI-5): `source_matches` tests each candidate source's
  suffix independently, so when it matches more than one source (e.g. two
  melded sources both ending in `/tools`), `upgrade`'s selection spans every
  installed item from every one of them -- silently, in the sense that it is
  not itself an error; CLI-234 separately discloses that scope before the
  confirmation when it spans more than one source. This is never
  `AmbiguousItem` on account of the qualifier alone: `upgrade`'s filter always
  selects every match (CLI-65) rather than requiring a single one, so it never
  goes through the single-match `resolve()` path (CLI-3) that could raise it.
- `CLI-64` With nothing pending, `upgrade` reports up to date and changes nothing.
- `CLI-65` When the `item` filter's name is a glob (`*`, `?`, `[`), `upgrade`
  limits the pass to every installed item whose effective name matches the
  glob, mirroring `forget`'s glob selection (CLI-41). The `kind:` prefix and
  `owner/repo#` source qualifier compose with the glob exactly as they do for
  an exact match (CLI-63), so `upgrade 'jk:*'` upgrades a namespace, `upgrade
  'skill:*'` a kind, and `upgrade 'owner/repo#*'` a whole source in one pass.
  Like any filter, a glob that matches no installed item -- or matches only
  items already up to date -- reports nothing pending and changes nothing
  (CLI-64); it is not an error (this is where `upgrade` differs from
  `forget`, whose no-match glob is `NotInstalled`, CLI-41).
- `CLI-211` A source `upgrade` cannot scan (its clone is missing, or scanning it
  otherwise fails, e.g. an item-link instance whose linked path vanished) does not
  silently drop out of the delta computation: `upgrade` prints a warning naming the
  source and pointing at `mind sync` / `mind introspect` to diagnose it, and the run
  is never reported as fully up to date (CLI-64) while any source could not be
  checked, even when no OTHER pending upgrade exists -- an unscannable source is
  itself an actionable condition, not silence.
- `CLI-212` A linked local source (`Source::is_linked`, CLI-27) whose working
  tree has vanished since it was melded (the docs' own `/tmp` recipe with no
  teardown, or a repo directory a user deleted) is a `LinkedSourceGone` error
  naming the source and the missing path, with the exact `mind unmeld <name>`
  remedy, raised by `catalog::scan_source` before a less specific error (a
  convention-scan `InvalidRoot`, or `MindToml::load` silently treating the
  missing directory as "no mind.toml") could obscure the real cause.
- `CLI-213` Listing every source's catalog (`recall`, `probe`, `learn`'s item
  resolution) degrades past a single `LinkedSourceGone` source (CLI-212)
  instead of hard-failing the whole call: the dead source is skipped with a
  warning on stderr, and the sources that DID scan are still shown/searched/
  resolved against. Every OTHER scan failure (a version gate, a bad `--root`, a
  duplicate item, ...) still aborts the whole call exactly as before; this is a
  narrow degradation for the one failure mode a user cannot fix by re-running
  the same command. `meld`'s own scan of the source it just cloned, and
  `dump`'s reconstruction, are unaffected: both still hard-fail on any scan
  error, including this one -- a partial dump is worse than none, and naming a
  specific broken source (rather than merely listing everything) should still
  surface the problem plainly. `introspect` (CLI-210) and `upgrade` (CLI-211)
  already scan per-source independently, so they surface `LinkedSourceGone` as
  their existing `source-scan-failed`/unscannable-source finding with no
  further change.

## hooks

- `CLI-194` `mind hooks run <target>` runs a melded source's hooks -- or a
  named item's hooks -- on demand, outside the meld/learn/forget/upgrade flows, so a
  user can run hooks they earlier skipped (HOOK-21/22/72/83), re-run a hook whose
  effect was later lost (a deleted build output or side effect), or re-run an install
  or uninstall whose prior run failed transiently. Every hook it runs goes through the
  same disclosure and consent prompt as an automatic run (HOOK-100); it never runs a
  hook more silently than meld/learn would, and a required hook's failure or abort is a
  non-zero exit (HOOK-53). `<target>` is a source ref (the source's own hooks,
  HOOK-101) or an item ref `<source>#<item>` (that item's hooks, HOOK-102); a ref that
  matches several sources or items runs each in turn.
- `CLI-195` `mind hooks run` selects the lifecycle event with `--event
  <install|update|uninstall|build>` (default `install`). `install`, `update`
  (HOOK-126), and `uninstall` are valid
  for a source or an item target; `build` is valid only for an item target (a source
  has no build hook, HOOK-103). For a source install or update run, only *pending* hooks
  run by default (HOOK-55, HOOK-124); `--force` also re-runs hooks already recorded at
  the current commit (HOOK-101). The `--dangerously-skip-install-hook-check` and
  `--dangerously-skip-build-hook-check` flags apply as they do to the automatic flows
  (HOOK-23/74).
- `CLI-228` `mind hooks run --rerun` is a visible alias for `--force` (CLI-195):
  same flag, same field, just a name that reads as "re-run recorded hooks"
  rather than the borrowed `meld --force` clobber sense. Both spellings are
  interchangeable in `--help` and on the command line.
- `CLI-196` `mind hooks list <target>` lists the hooks in effect for a source
  and its installed items -- each hook's event, required/optional flag,
  command, and, for a recorded source install or update hook, whether it is
  pending and the commit it last ran at -- without running anything
  (HOOK-104). It is the read-only companion to `hooks run` and the detail
  behind the `recall --sources` hook marker (HOOK-58). Under `--json` it
  answers with a document instead of this text listing (CLI-220).
- `CLI-220` `mind hooks list --json` answers with one JSON document instead of
  the CLI-196 text listing:

  ```json
  {
    "schema": 1,
    "action": "hooks-list",
    "target": "<target as typed>",
    "sources": [
      {
        "source": "<source name>",
        "hooks": [
          {"event": "install|uninstall", "required": true,
           "command": "<run>", "status": "<CLI-196 status text>"}
        ],
        "items": [
          {"item": "<kind:name>", "hooks": [
            {"event": "install|uninstall", "required": true, "command": "<run>"}
          ]}
        ]
      }
    ],
    "items": [
      {"item": "<kind:name>", "source": "<source name>", "hooks": ["..."]}
    ]
  }
  ```

  `sources` is populated when `<target>` resolved as a source (HOOK-104's
  source branch, possibly several under a glob); the top-level `items` is
  populated instead when it resolved as an item ref, one entry per matched
  item, each carrying its own `source`. The two are mutually exclusive (a
  target resolves one way or the other, CLI-194) and whichever is empty is
  omitted rather than serialized as `[]`. That elision applies ONLY to the
  top-level `sources`/`items` pair: a nested `hooks` or `items` array is
  always present, empty when the source declares no hooks or has no installed
  items with hooks. `status` is present only for a recorded source install or
  update hook (mirrors the text mode's pending/last-ran report, HOOK-55,
  HOOK-124); an uninstall hook and every item-level hook carry no `status`. A
  source's nested installed items are objects (an `item` name plus its own
  `hooks` array), not bare strings, so a later addition (e.g. a per-item
  install/uninstall disclosure record) has somewhere to go without a
  breaking shape change.
- `CLI-222` `mind hooks run --json` on a successful run (no `MindError`)
  answers with a count-based result rather than nothing:

  ```json
  {
    "schema": 1,
    "action": "hooks-run",
    "target": "<target as typed>",
    "event": "install|uninstall|build",
    "existed": 0,
    "ran": 0,
    "skipped": 0
  }
  ```

  `existed`/`ran`/`skipped` are the HOOK-107/HOOK-108 tally, accumulated
  across every matched source or item: how many hooks for the selected event
  were considered, how many actually ran, and how many were skipped
  specifically for want of consent. A run with nothing to do (no hooks
  declared, or every install hook already recorded at the current commit)
  still answers with the document, all three counts zero, rather than
  silence. `--event build`'s tally is always zero (HOOK-103's transactional
  reinstall reports success or failure, not a hook-by-hook count). `skipped`
  counts ONLY skips for want of consent, so it is `0` in every printed success
  document: an interactive decline is the user's own choice and is not a
  consent failure (it reports `existed: 1, ran: 0, skipped: 0`), while a
  non-TTY run that could consent to nothing is the HOOK-107 error path below.
  The three counts therefore need not sum to `existed`. On the
  HOOK-107 failure path (`MindError::HooksNotRun`) no success document is
  printed; the CLI-181 error envelope is the one document (CLI-217).

## recall

- `CLI-70` `recall` (no argument, alias: `status`) is a status view of everything `mind` manages:
  each melded source, with its catalog items nested beneath it. It shows both
  installed and not-yet-installed items, so a single `recall` answers "what is
  melded, and what is installed". The `--kind` / `--source` filters narrow the
  items shown (CLI-83).
- `CLI-71` `recall <item>` shows one installed item's detail: description, source,
  commit, hash, store path, and link path(s). The ref is matched against the
  manifest by effective name, honoring a `kind:` prefix and an `owner/repo#`
  source qualifier; an ambiguous bare name is `AmbiguousItem` and one matching
  none is `NotInstalled`.
- `CLI-72` `recall --sources` narrows the status view to the source list only
  (name, url, short commit, alias, install-hook token, description), without the
  nested items.
- `CLI-73` `recall --json` emits the data as JSON on stdout instead of the table:
  the default view emits the sources each with their nested items (carrying the
  installed flag and, when installed, the commit); a lookup emits the single item
  as a plain JSON object (not wrapped); `--sources` emits the source list. Array
  outputs (default view, `--sources`) are wrapped in the envelope introduced by
  CLI-167. An empty registry emits `{"schema": 1, "items": []}`.
- `CLI-74` In the default status view, each item line marks its install state
  inline: an installed item shows that it is installed and its short commit; a
  not-installed item is marked available. Items are grouped under their source, so
  the source a given item comes from is unambiguous.
- `CLI-75` The status view marks an installed item out of date exactly when
  `upgrade` would act on it (LIFE-11): its current source-content hash differs from
  the hash recorded at install (LIFE-15), or its effective name changed (a
  namespace change). The marker is independent of the source commit: a commit that
  advanced without changing an item's content or effective name does NOT mark that
  item, because `upgrade` would report it up to date and the marker must stay
  actionable -- it appears only when `mind upgrade` will change the item. This still
  surfaces drift for a melded local directory (no upstream commit to advance) and
  for a real checkout whose source files were edited in place (commit unchanged,
  content not). The marker points to `mind upgrade` and matches `introspect`'s
  `drifted` finding (CLI-90) and `upgrade`'s pending condition (LIFE-11). It applies
  to `recall` (the default status view and a single-item lookup, CLI-70/71/74) and
  the `probe` non-interactive listing (CLI-80, CLI-81). The marker is a human-view
  concern; the JSON outputs are unchanged.

## probe

`probe` launches the interactive TUI by default (tui.md, TUI-1). The IDs below
define the non-interactive catalog listing, which `probe` prints instead when
`--no-tui` or `--json` is given or stdout is not a TTY (TUI-2).

- `CLI-80` `probe [query]` lists available catalog items (effective name, source,
  one-line description), filtered to those whose effective name contains `query`.
  An empty query lists everything.
- `CLI-81` `probe` marks installed items with a leading `*` and shows each item's
  short content hash.
- `CLI-82` List outputs (`probe`, `recall`) left-align columns padded to the
  widest value in each column, so rows stay aligned regardless of item-name
  length.
- `CLI-83` `probe` and `recall` accept `--kind <skill|agent|rule|command|workflow|tool>` and
  `--source <selector>` filters that narrow the listing, composing with `probe`'s
  substring query. For `recall` they apply to the installed-items listing, not to
  `--sources` or a single-item lookup (use a `kind:` / `owner/repo#` ref there);
  passing them with `--sources` or a single item prints a note that they are
  ignored.
- `CLI-84` `probe --json` emits the rows on stdout instead of the table, wrapped
  in the envelope introduced by CLI-167; each row carries the installed flag,
  kind, effective name, source, content hash, and description.
- `CLI-85` `probe`'s query matches an item whose effective name *or* description
  contains the query, case-insensitively. This supersedes the name-only matching
  of CLI-80 so an item is found by what it does, not only by its name. The
  `--kind` / `--source` filters (CLI-83) still compose with the query.
- `CLI-86` The `probe` / `recall` `--source <selector>` filter (CLI-83) accepts a
  glob (`*`, `?`, `[`), matched against each source's `host/owner/repo` identity
  and its trailing-suffix forms as a plain string (so `*` spans `/`), mirroring
  `unmeld`'s source glob (CLI-28). `--source '*agents'` narrows the listing to
  items from every source whose identity matches. A non-glob value keeps the
  exact/unambiguous-suffix match. Unlike `unmeld`, a multi-source match is the
  normal, non-error case for a filter: every matching source's items are shown,
  with no confirmation. A glob matching no source yields an empty listing, as any
  fully-excluding filter does.

## review

`review` is the author-side counterpart to `introspect`: it validates a source
*before* it is published or melded, surfacing the problems that would otherwise
only appear at meld or install time. It is read-only and installs nothing.

- `CLI-130` `review <target>` validates a source for publishing. `<target>` is a
  local path, a repo spec (the forms accepted by `meld`, CLI-11; cloned to a temp
  area for the check), or the selector of an already-melded source (matched like
  `unmeld`, CLI-20).
- `CLI-214` Between the registry-selector match and repo-spec parsing, `review`
  checks whether `<target>` names an EXISTING directory (resolved against the
  cwd) and, if so, reviews it as that local path regardless of what the string
  would otherwise parse as. This closes a disagreement with `init-source`
  (which takes a bare `PATH` and always treats it as a directory): without
  this check, a two-segment relative path like `skills/greet` parses as
  `owner/repo` (`meld`'s repo-spec grammar recognizes only `/abs/path`,
  `./rel/path`, `../rel/path`, or `file://` as local) and `review`
  shallow-clones it from `github.com` -- a surprise network call for a
  directory the user meant literally. `review` is read-only, so taking this
  branch persists nothing. When `<target>` ALSO parses as a valid remote repo
  spec, `review` prints a note naming which reading it took and the exact
  command to force the remote one instead (`mind review github:<target>`).
  `parse_spec` itself is NOT changed to prefer an existing directory
  globally: `meld` persists an identity and a clone path from it, and a repo
  whose name happens to match a local directory must not silently change
  meaning depending on the caller's cwd (contrast CLI-215, which only warns).
- `CLI-215` `parse_spec`'s bare `owner/repo` / `github:owner/repo` branch
  prints a stderr note, before any clone, when the given spec ALSO names an
  existing directory relative to the cwd, pointing at the `./` form that would
  mean the directory instead (e.g. `mind meld skills/greet` warns and then
  still melds `github.com/skills/greet`, naming `./skills/greet` as the
  local-path alternative). This does not change which reading `parse_spec`
  takes; it is advisory only, so a script relying on the remote reading is
  unaffected, while an interactive user gets a chance to notice the ambiguity
  before the clone runs. `MindError::InvalidRepoSpec`'s message also names the
  local-path forms it previously omitted (`/abs/path`, `./rel/path`,
  `../rel/path`, `file:///abs/path`), matching what `mind review --help` (and
  now CLI-214) already document as accepted.
- `CLI-216` The CLI-215 note is printed at most ONCE per ambiguous spec, by the
  parse that DECIDES which reading gets cloned. A caller that parses a spec to
  answer a question rather than to act on it uses the non-printing
  `parse_spec_quiet`: deriving a registry identity (`instance_name`, the
  already-registered guards, the clone-failure arms that name the entry that
  just failed, the install walks over an already-melded chain), classifying an
  entry (the DSC-93 predicate), or building a different note about a reading
  already taken. The note is about which reading a clone is ABOUT to take, so
  from those sites it is at best a duplicate and at worst a contradiction.
  The motivating case is `review`, which emits at most one note about how it
  read its target: when it takes the CLI-214 local-directory reading, its own
  note (naming that reading and the `mind review github:<target>` escape) is the
  only one, because CLI-215 would tell the user to write `./<target>` to get the
  local reading, which is the reading `review` just took. The same rule bounds
  `meld`: melding a shadowing spec (`mind meld skills/greet` where
  `./skills/greet` exists) notes it exactly once even though the dispatcher, the
  verb, and the recursion each parse it, and a curated `[discover].sources`
  entry that shadows a directory in the CONSUMER's cwd is noted once by the
  recursion that clones it, not again by each identity lookup along the way.
  This is a discipline at the call sites rather than a special case inside
  `parse_spec`, so the parse on the path that really is about to clone a remote
  still notes the ambiguity exactly as CLI-215 requires.
- `CLI-26` `review` with no `<target>` (or an explicit `.`/`./`) validates the
  current directory, so a maintainer can `mind review` in their repo. It is the
  read-only counterpart to `init-source` (init-source.md). `--policy` is the
  separate policy-validation mode and takes no current-directory default.
- `CLI-131` `review` reports, for the source and per item: `mind.toml` parse and
  schema errors (DSC-30, DSC-31), items whose frontmatter yields no description
  (DSC-20), `{{ns:}}` tokens whose referent is not a real sibling (which would be
  `BadReference` at install), and unguarded prose references to siblings under the
  effective prefix (the meld-time heuristic, CLI-14).
- `CLI-132` `review`'s exit status: a hard error (malformed `mind.toml`, an unknown
  item kind, a conflicting `[source]` pin, or an unresolved `{{ns:}}` / path token
  in a markdown item file) exits non-zero; advisory findings only (unguarded
  references, missing descriptions, hardcoded paths, bare tool references, and an
  unresolved `{{ns:}}` / path token in a non-markdown item file) exit zero. It
  changes nothing on disk in either case, except under `--fix` (CLI-138). An
  unresolved `{{ns:}}` token is hard only in a file install expands
  (`namespace::expands_tokens`, NS-53): a markdown file, a workflow's own `.js`
  (workflows.md WF-25, WF-27), or a file an item's `expand:` list names
  (NS-57, CLI-226). The identical unresolved token in any other item file (a
  script, data) is dead text that cannot break an install and is downgraded to
  advisory, mirroring the path-token treatment (CLI-135).
- `CLI-133` `review --as <prefix>` evaluates the source under a prospective
  namespace, so token expansion and the unguarded-reference scan are checked as
  they would install under that prefix. With no flag the effective prefix is the
  source's own `[source].prefix` if any, else none.
- `CLI-134` Supplying both `<target>` and `--policy` to `review` is a usage
  error: clap rejects the combination before any logic runs, exits non-zero, and
  prints a conflict diagnostic to stderr.
- `CLI-135` `review` validates an item's path-reference tokens the same way it
  validates `{{ns:}}` (CLI-131): a `{{self}}` / `{{tools:name}}` / `{{path:ref}}`
  token whose referent does not resolve in this source (a `{{tools:}}` naming a
  non-tool or a tool with no entrypoint, a `{{path:}}` miss or cross-kind
  ambiguity) is a hard `bad-reference` finding, which would be a `BadReference` at
  install (tooling.md, TOOL-11/12), in a file install expands
  (`namespace::expands_tokens`, NS-53): a markdown file, a workflow's own `.js`
  (workflows.md WF-25), or an `expand:`-listed file (NS-57, CLI-226). The
  identical unresolved token in any other item file (a script, data) is only an
  advisory `bad-reference` finding, never hard: install never expands any token
  there either, so it is dead text that cannot break an install, matching how
  Check 9 (CLI-136) and Check 11 (CLI-139) already treat such a file. Every bad
  token is reported, not just the first.
- `CLI-136` `review` reports, as an advisory `hardcoded-path` finding, an item
  file that hardcodes a mind install path that a path token should replace. It
  recognizes the three install layouts (`.mind/store/<kind>/...`, the agent-home
  `.claude/<kinddir>/...`, and `.agents/<kinddir>/...`) under any home-root
  spelling: a leading `~`, `$HOME`, `${HOME}`, or an absolute `/home/<user>` or
  `/Users/<user>` path. When the path maps confidently to a token (the item's own
  dir -> `{{self}}`, a sibling tool's entrypoint -> `{{tools:name}}`, another
  sibling -> `{{path:kind:name}}`) the finding names the suggested token. The
  message reflects what the path resolves to at runtime (CLI-145). Advisory, not
  hard: `--fix` rewrites the confidently-mapped ones, but only in a markdown file
  (CLI-138, NS-54); the identical finding in a non-markdown file is reported and
  left unrewritten. It is
  non-prescriptive about resources an item bundles: keeping a helper in the item's
  own directory, or having an install hook place it at a fixed location and
  referencing it there, are equally valid; the advisory points out only that a
  literal mind install path is fragile, not that tokens are required. (CLI-146
  adds the install-hook-safe note to the message.)
- `CLI-137` `review` reports, as an advisory `bare-tool-reference` finding, a
  sibling tool named in an item's prose without a token. Unlike the unguarded
  sibling-reference scan (CLI-131), which only matters under a prefix, a bare tool
  reference is flagged regardless of prefix, since a tool item is reached by a path
  token, never by name. Non-prescriptive: a source need not adopt the `tool` kind
  at all. Bundling the helper with the item, or installing it to a well-known
  location via an install hook and calling it there, are equally valid; the
  advisory only flags a `tool` item named by bare name where a token would be
  needed. (CLI-146 adds the install-hook-safe note to the message.)
- `CLI-138` `review --fix` rewrites the source in place and is the sole exception
  to `review` being read-only (CLI-132). It applies only to a local-path target;
  a registry selector or a repo spec (whose clone is a discarded temp) refuses
  `--fix` and changes nothing. For each markdown item file (NS-53, NS-54) it
  rewrites confidently recognized hardcoded install paths into the matching
  token (CLI-136), un-wraps misplaced `{{ns:}}` tokens (CLI-139) back to the
  bare name, and templatizes bare sibling names into `{{ns:}}` (the
  `init-source --template` transform, INIT-5), then reports each file it
  changed. A non-markdown item file is never rewritten, even one whose tokens
  do expand (a workflow's own `.js`, WF-25): the rewrite gate stays on the
  markdown-extension test alone, not on whether a token there would actually
  expand (NS-54). A finding there is still reported by the CLI-135..139
  checks, which scan every text file regardless of extension; it is just left
  unrewritten.
- `CLI-139` `review` flags a misplaced `{{ns:}}` token -- one in a non-prose
  context (NS-24) where name-substitution is wrong. A token inside a fenced code
  block, an inline code span, or adjacent to a path separator is an advisory
  `misplaced-reference` (a name token belongs in prose; code and paths use the
  path tokens, tooling.md). A token in the frontmatter `name:` field is a hard
  `misplaced-reference`: an item must not namespace its own name. This is the dual
  of the unguarded-reference scan (CLI-131): one finds a bare name that should be
  a token, the other a token that should be a bare word. The finding is reported
  in any text file, markdown or not, but `--fix` only un-wraps it in a markdown
  file (CLI-138, NS-54); in any other file the misplaced token is left as
  written. For a file that does not expand, it never expanded there either
  (NS-53); for a workflow's `.js`, which does expand (WF-25), `--fix` still
  leaves it alone, because the rewrite passes match sibling names as words and a
  workflow file is code (WF-27).
- `CLI-223` `review` reports, as an advisory `inert-token` finding, every
  `{{...}}` token found in a non-markdown item file, regardless of whether the
  token would resolve. The exceptions are the two files a token does reach
  outside markdown: one on an item's NS-57 `expand:` list, and a `workflow`
  item's own file (workflows.md, WF-25, WF-27). Outside those, no token family
  expands outside markdown (NS-53), so a
  token there never reaches install either way, and one that would resolve if
  the file were markdown (e.g. a `{{tools:name}}` naming a real sibling tool,
  in a bundled `.sh`) is otherwise silently left literal and breaks at
  runtime -- CLI-135 only flags an unresolved one, and CLI-139 only covers
  `{{ns:}}`, so a resolvable path/self token in a non-markdown file was
  previously unreported by either. The finding names the file and the
  token(s), states that tokens expand in markdown only, and names the three
  remedies: move the reference into markdown prose, have the script
  self-locate, or list the file in the item's `expand:` frontmatter to expand
  it there (NS-57). "Markdown only" is the message's shorthand for the full
  rule (a token expands when the file has a markdown extension, or the item
  is of the `workflow` kind, or the file is on the item's `expand:` list) and
  stays accurate where it is read: the two non-markdown files a token does
  reach are exempt from this check above, so every file that draws the finding
  really is one where only a markdown extension would have expanded it. Never
  hard, and `--fix` never rewrites the file (CLI-138,
  NS-54). A token this generic net
  would otherwise re-report is excluded when another check already reported
  the same span for the same file, so a single broken or dead reference draws
  one finding, not two or three: every `{{ns:...}}` token in a non-markdown
  file is excluded (CLI-139's `misplaced-reference` unconditionally covers all
  of them there, whether or not they resolve, which also subsumes what
  CLI-135's downgraded `bad-reference` would report, since that is always an
  `{{ns:}}` token too), and the one path token (`{{tools:}}`/`{{path:}}`/
  `{{self}}`) CLI-135 reported as `bad-reference` for the file, if any, is
  excluded as well. A resolvable path/self token that no other check mentions
  -- the case this finding exists for -- is unaffected and still reported.
- `CLI-226` A file listed in an item's `expand:` frontmatter (NS-57) is treated
  as markdown by every `review` token check: its tokens do expand at install, so
  no `inert-token` finding (CLI-223) is raised for it, an unresolved token in it
  is a hard `bad-reference` (CLI-135) rather than the downgraded dead-text
  advisory a non-markdown file gets, and a `{{ns:}}` in it is not `misplaced`
  (CLI-139). An `expand:` entry naming a file the item does not ship is itself a
  hard `bad-expand` finding, matching the install-time `BadReference` (NS-57), so
  `review` catches a typo before a meld does. `--fix` still never rewrites a
  non-markdown file, `expand:`-listed or not (NS-54).
- `CLI-144` `review` reports, as an advisory `duplicate-tooling` finding, a
  non-markdown helper file whose contents are byte-identical across two or more
  items. The finding names the file and the items that carry it and notes the
  duplicate COULD be shared once as a `tool` referenced by a path token
  (`{{tools:name}}` / `{{path:}}`), while stating that keeping the per-item copies
  is equally valid: a source that namespaces its items and deliberately silos each
  helper with the skill that uses it is not doing anything wrong, and adopting a
  tool means buying into mind's token references. The message is non-prescriptive
  (it presents both as acceptable), not a defect to fix. Markdown is excluded (it
  is prose, not tooling) and empty files are ignored. Advisory only, and `--fix`
  never touches it: adopting a shared tool is an opt-in structural change the
  author re-references deliberately.
- `CLI-145` The `hardcoded-path` advisory (CLI-136) classifies the reference by
  what it resolves to at runtime, because the cases differ in severity. A skill
  that hardcodes its OWN resources (the `{{self}}` case) works as written but
  assumes every install lands at that exact agent-home path; it breaks once a
  prefix renames the item or a second home is configured, and `{{self}}`
  generalizes it (fragile, not broken). A reference to a
  `tool` is broken regardless of prefix: a tool is store-only and never linked
  into an agent home (tooling.md TOOL-3), so the hardcoded location does not
  exist. Any other hardcoded item path is reached by a token, not an install
  path. The advisory's message states which of the three cases it is.
- `CLI-146` The `hardcoded-path` (CLI-136) and `bare-tool-reference` (CLI-137)
  advisory messages note that a location the source's own install hook populates
  is safe: when a `[source].install` or `[[hooks]]` step installs the resource or
  tool to that path, referencing it there is intentional, not a defect. The
  findings stay advisory and are still emitted regardless of prefix (CLI-137), so
  the maintainer keeps the visibility, but the message no longer reads as a flaw
  for a source that deliberately installs to a fixed location. The `{{self}}`
  self-resource case (CLI-145), which a hook does not populate, keeps its
  fragile-not-broken wording.
- `CLI-190` `review` reports, as an advisory `unshipped-tooling` finding, a `tool`
  whose entrypoint resolves in the author's working tree only through a file git
  does not track, so the tool ships without that file and `{{tools:name}}`
  references to it break on a clone/remote meld though they resolve locally. It
  checks the resolved entrypoint script and, when present, the tool's `TOOL.md`
  (which declares the `bin`), and names each untracked file. It applies only to a
  local working-tree target that is a git repository: a remote/clone target
  already contains only what ships, so the discrepancy cannot arise there (a
  genuinely-missing entrypoint is a plain `bad-reference`, CLI-135), and a non-repo
  local dir cannot be assessed for shippability. A tool with a per-item build hook
  (HOOK-70) is skipped -- its entrypoint is generated in staging, so its absence
  from git is intentional. Advisory, and `--fix` never touches it: committing the
  file or adding a build hook is the author's decision. This is the working-tree
  counterpart of the install-time entrypoint `BadReference` (tooling.md TOOL-17),
  caught before a push rather than at a consumer's meld.
- `CLI-191` `review` extends the `unshipped-tooling` advisory (CLI-190) to any
  item's bundled files, not just a tool's entrypoint: a file addressed by a
  `{{self}}/...` or `{{path:[kind:]name}}/...` reference (tooling.md TOOL-10/TOOL-11)
  that is present in the author's working tree but git does not track is flagged,
  because the whole item directory is copied on a local meld (picking up the
  gitignored file) while a clone contains only tracked files, so the reference
  resolves locally and breaks on a remote meld. It names the untracked file and
  the token that addresses it. A token with no `/`-path remainder addresses the
  item directory itself, not a specific file, and is not checked. Local
  working-tree git target only and `--fix` never touches it, exactly as CLI-190.
  A file that does not exist at all is a plain `bad-reference` handled elsewhere,
  not this advisory.
- `CLI-192` `review` reports, as an advisory `ns-tool-reference` finding, a
  `{{ns:name}}` reference (namespacing.md) whose only matching sibling is a `tool`.
  A tool is store-only (tooling.md TOOL-3), reached by `{{tools:name}}` for its
  entrypoint or `{{path:tool:name}}` for its directory; its bare name is not a
  runnable path and is never linked into an agent home, so a `{{ns:tool}}` expands
  to a name that resolves to nothing at runtime -- the silent failure mode of a
  `{{tools:}}` mistyped or "fixed" into a `{{ns:}}`. It fires only when the name
  matches no non-tool sibling, so a genuine skill/agent/rule reference that merely
  shares a name with a tool is not flagged. It applies to any target (a
  wrong-token content smell, not a shippability check).
- `CLI-193` `review` reports, as an advisory `unshipped-tooling` finding, an
  authoritative `mind.toml` (one declaring `[[items]]` or `[discover]` globs, so it
  turns off convention scanning) that git does not track. A linked local source
  reads even an untracked/gitignored `mind.toml` live (CLI-27), so its declared
  inventory, prefix, and bins apply to the author's working tree yet are absent
  from a clone, which falls back to convention discovery or a different item set --
  the source-wide form of the working-tree-vs-clone discrepancy. A metadata-only
  `[source]` block changes no discovery and is not flagged. Local working-tree git
  target only.
- `CLI-237` `review` reports, as an advisory `command-content` finding, a
  `command` item whose frontmatter declares `allowed-tools` or whose body
  contains a `!` bash-execution directive -- the one item payload the
  harness executes with no `mind` hook at all. This is disclosure only:
  `mind` still neither reads nor validates a command's frontmatter or body
  (spec/commands.md CMD-3), and the finding does not gate, block, or refuse
  the meld. Whether to add a consent prompt at install time for a command
  carrying `allowed-tools` is a separate, deliberately deferred design
  decision, not something this advisory implements. The command file is read
  through the same size-capped path (DSC-91) every metadata read in the
  codebase uses, since `review` runs against an untrusted, not-yet-melded
  source; an over-cap file is a hard `metadata-too-large` finding rather than
  a silently skipped disclosure. The `workflow` kind has its own analogous
  set of four `review` codes, each covered in [workflows.md]workflows.md:
  `workflow-content` (WF-53), the unconditional disclosure counterpart of
  `command-content` above, since `mind` neither reads nor validates a
  workflow's body either; `workflow-unloadable` (WF-30), a file the harness
  would skip; `workflow-name` (WF-24), a `meta.name` diverging from the
  installed name; and `workflow-name-collision` (WF-29), two workflows
  answering to one harness name. All four are disclosure only, like
  `command-content`.
- `CLI-238` The `item-hook` advisory (HOOK-85) states whether each hook is
  required or optional, reusing the same `required`/`optional` composition
  as the source-hook `install-hook` advisory above it (Check 6). Without
  this, an optional item hook read identically to a required one on a
  surface whose entire job is pre-install disclosure.
- `CLI-239` `meld <url> --kind <agent|rule|command>` and `learn <url> --kind
  <...>` declare what an item link's file is (item-link.md LNK-21), for a file
  that does not sit under a conventional `agents/`, `rules/`, or `commands/`
  directory and carries no frontmatter `kind:`. The flag applies to an item
  link only: passing it with a plain repo spec is an error naming the link
  forms, since nothing else about a meld is kind-scoped. The value is recorded
  on the instance (STO-81) and reused by every later scan (LNK-22).
- `CLI-219` `mind review --json` (either mode: a `<target>` source, or
  `--policy <path>`) answers with one JSON document instead of the CLI-131/132
  text findings:

  ```json
  {
    "schema": 1,
    "action": "review",
    "outcome": "clean|advisory|failed",
    "hard": [{"kind": "<slug>", "message": "<text>"}],
    "advisory": [{"kind": "<slug>", "message": "<text>"}],
    "fixed": ["<path>"]
  }
  ```

  `outcome` is `"clean"` when both `hard` and `advisory` are empty,
  `"advisory"` when only `advisory` findings exist, and `"failed"` when any
  hard finding is present (the last value appears only inside the CLI-221
  `details` member, never in a printed success document); `kind` is the same
  machine-stable finding tag the text mode prints in `error [kind]: ...` /
  `advisory [kind]: ...` (e.g. `bad-reference`, `missing-description`). `fixed`
  lists the files `--fix` rewrote (CLI-138), empty otherwise. When any hard
  finding is present, `review` still exits non-zero (CLI-132 is unconditional):
  the document above is not printed as a success envelope in that case, but
  recorded and folded into the CLI-181 error envelope's `details` member
  (CLI-221) instead, so a machine caller sees exactly what failed without
  scraping stderr's `error [kind]: message` lines. The envelope's own `kind`
  (CLI-182) is the fixed slug `review-failed` (`MindError::ReviewFailed`), so
  a machine caller can branch on the failure before even looking at
  `details`. `review --policy`'s document
  uses the same shape (`hard`/`advisory` only; `fixed` is always empty, since
  `--policy` has no `--fix` mode).

## introspect

- `CLI-90` `introspect` reports: sources with no clone or never synced, installed
  items whose links are missing, items no longer present upstream, items whose
  namespace changed, and items whose source content drifted. It reports a clean
  summary when there are no issues.
- `CLI-91` `introspect --fix` repairs what it can without changing versions: it
  recreates missing link(s) for installed items from their file registry
  (re-linking the existing store copy). If the store copy itself is gone the link
  is left reported, not recreated. Drifted or renamed items are still left to
  `upgrade`.
- `CLI-92` `introspect --json` emits the findings as JSON on stdout: an object
  with an `issues` array (each carrying a stable `kind` tag, a `target`, and a
  `message`) plus the source and item counts. An empty `issues` array means clean.
- `CLI-189` The `introspect --json` output includes a top-level `"schema": 1`
  field, matching the envelope version used by other `--json` verbs (CLI-167).
  The full shape is `{"schema": 1, "issues": [...], "sources": N, "items": N}`
  where `sources` and `items` are integer counts, not arrays. This field is
  additive; existing consumers keying on `issues`, `sources`, or `items` are
  unaffected.
- `CLI-210` A source `introspect` cannot scan (its clone is missing, or scanning
  it otherwise fails, e.g. an item-link instance whose linked path vanished) does
  not abort the run: `introspect` scans each source independently, reports the
  failure as a `source-scan-failed` issue naming the source, and completes with
  the findings computed from the sources that DID scan successfully (CLI-90/92).

## evolve

`evolve` upgrades the `mind` executable itself (distinct from `upgrade`, which
upgrades installed items, and `sync`, which refreshes sources). It uses the same
native curl/wget downloader as `resources/install.sh` and resolves the same
release artifacts as the install script and the Homebrew formula.

- `CLI-140` `evolve` compares the running version against the latest published
  release. With nothing newer it reports up to date and changes nothing. With a
  newer release it replaces the running executable in place with the release binary
  for the current platform. The target passed to `decision()` is not a plain
  dotted-numeric string: it is the release tag `evolve` resolved (the latest
  `releases/latest` tag, or an explicit `--to`), validated by
  `mindfile::is_plausible_release_tag` -- not `is_plausible_version`, which still
  governs `min-mind-version` and policy version pins (STO-76) -- before
  `decision()` ever sees it, so it may itself carry a semver prerelease/build
  suffix: `evolve --to 1.2.3-rc1` is accepted. A prerelease running version (a
  version string carrying a `-suffix`, e.g. `0.23.1-dev` or the dotted
  `1.0.0-rc.2` -- this repo's own releases never carry one, but a fork's or
  packager's build might) is treated as strictly below a release target of the
  same numeric version: `evolve` on a `0.23.1-dev` build offers the `0.23.1`
  release rather than reporting up to date, and an explicit `--to 0.23.1` onto
  its own base is an update, not a no-op. This tie-break requires the target to
  NOT itself be a prerelease (the `!is_prerelease(target)` guard at
  `decision()`'s numeric-tie branch): a target that is itself a prerelease is
  excluded from the tie-break and falls through to the ordinary numeric
  comparison instead, rather than being offered as an update onto another
  prerelease at the same numeric version.
  The numeric comparison strips the `-suffix` from the WHOLE version string
  before splitting on `.` (not per dotted component), so a dotted prerelease
  like `1.0.0-rc.2` parses as `[1, 0, 0]` and ties with `1.0.0` in both
  directions, rather than a per-component strip mis-parsing it as `[1, 0, 0,
  2]` (reading numerically ABOVE the release it should tie with, which would
  report up to date instead of offering the update). A numerically newer
  prerelease (`0.24.0-dev` vs release `0.23.1`) is unaffected: see `CLI-147`,
  which governs the opposite direction (an explicit `--to` strictly below the
  running version) and would otherwise read as the wrong case for a newer
  prerelease build.
- `CLI-141` Unless `--yes` is given, `evolve` prompts `[y/N]` (default No, EOF
  counts as No) before replacing the binary, mirroring `upgrade` (CLI-60). `--check`
  reports the latest available version and whether an update is pending, then exits
  without downloading or replacing anything.
- `CLI-142` The release artifact is selected exactly as the install script and the
  Homebrew formula select it (`mind-<version>-<target>.tar.gz` from the GitHub
  release for the running platform), so every install path resolves the same
  binary. A platform with no published artifact is an error and nothing is changed.
- `CLI-143` The replacement is atomic: the new binary is downloaded and verified,
  then swapped for the running executable, so any failure leaves the existing
  binary intact. `evolve` replaces whatever binary it runs from, except a
  Homebrew-managed one (CLI-243). A target path that is not writable is
  `TargetNotWritable` and nothing is changed.
- `CLI-147` `evolve` never downgrades the binary. When `--to V` is given
  explicitly and V is strictly below the running version, `evolve` exits 0 without
  downloading anything and reports that the pinned version is below the running
  version (e.g. "pinned 0.1.0 is below the running 0.3.0; not downgrading"). This
  is distinct from the "up to date" message, which applies when V equals the running
  version or when no `--to` is given and the running version is already current.
  `--check` surfaces the same message. Under `--json`, the outcome is
  `"not-downgrading"` rather than `"up-to-date"`, so callers can distinguish the
  two cases.
- `CLI-229` `evolve`'s pin-a-version flag is `--to <VERSION>`.
  `--version <VERSION>` (the original spelling) is kept as a hidden alias: on
  every other verb `--version`/`-V` is the global flag that prints the running
  `mind` version and exits, so an arg of the same name taking a value on
  `evolve` read as a collision even though `evolve` disables the global flag
  for itself (`disable_version_flag`) and never actually conflicted at the
  parser level.
- `CLI-243` `evolve` refuses to replace a Homebrew-managed binary: one whose
  canonicalized path (so the `<prefix>/bin/mind` symlink resolves) has a
  `Cellar` component. Replacing it would leave brew's record of the installed
  version stale, so the update belongs to `brew upgrade`. The refusal is
  `HomebrewManaged` (kind `homebrew-managed`), names the resolved path and
  `brew upgrade mind`, and happens before any network call. `evolve --check`
  is not refused: it reports as usual and, in human mode when an update is
  available, adds a note pointing at `brew upgrade mind`. Other package
  managers are not detected.

## config

- `CLI-110` `config show` creates the config if absent (STO-15), then prints the
  config file path and its key/value pairs (`lobes`, with the default shown when
  unset). It also notes when `MIND_AGENT_HOMES` is set and overrides `lobes`.
- `CLI-111` `config lobes list` lists the configured agent homes, or the default
  home when none are configured. `target` was formerly a visible alias of `lobes`;
  as of CLI-172 it is removed and `config target` is a usage error.
- `CLI-112` `config lobes add <path>` appends an agent home to `config.toml`,
  creating the file if needed; adding one already present is a no-op.
- `CLI-113` `config lobes remove <path>` drops a configured agent home; a path
  that is not configured is an error (`UnknownLobe`).

`config lobes add` also accepts `--preset <name>` to add a non-Claude harness
home with its canonical path and `kinds` filter in one step, and `config lobes
detect` scans the machine for known harness directories and offers to add the
matching presets (opt-in; nothing is added without confirmation). Both are
covered by HARN-4 and HARN-5; see harness-lobes.md for the preset names, paths,
and per-harness `kinds` defaults.

- `CLI-198` `link-project [dir] [--preset <name>] [--subdir <rel>] [--snapshot]
  [--force]` is a shorthand for `config lobes add` aimed at a project directory.
  `dir` defaults to cwd; `--preset` defaults to `windsurf`. It is subject to the
  same HARN-7 backfill contract and the same HARN-9 claude_home preservation as
  `config lobes add`. For a managed add, gitignore guidance is printed (the skills
  dir contains symlinks into `~/.mind/store`). `--snapshot` writes frozen real-file
  copies instead of registering a lobe (HARN-12). The command takes the exclusive
  lock (STO-41).
- `CLI-199` `config lobes add` is extended with three new optional flags: `--subdir
  <REL>` (the lobe path is `base/<REL>`, `kinds = [skill]`; conflicts with
  `--preset`), `--snapshot` (write frozen copies, no config entry; HARN-12), and
  `--force` (overwrite a colliding target in snapshot mode). `--preset` no longer
  conflicts with the positional base path: `config lobes add <dir> --preset
  windsurf` resolves the lobe as `<dir>/.windsurf`. `config lobes remove` gains
  `--snapshot` (convert symlinks to frozen copies before dropping the config entry;
  HARN-12).

## completions / man

- `CLI-120` `completions <shell>` writes a shell completion script for the named
  shell (bash, zsh, fish, elvish, powershell) to stdout, generated from the
  command tree.
- `CLI-121` `man` writes the roff man pages for `mind` to stdout, generated
  from the command tree: the top-level page followed by one page per visible
  subcommand (`mind-meld(1)`, `mind-learn(1)`, ...), so the top-level page's
  SUBCOMMANDS cross-references resolve within the same output rather than
  dangling. Hidden subcommands get no page.

## Output and global flags

- `CLI-150` `--json`, `--yes`, and `--ascii` are global flags accepted before or
  after the verb. They apply uniformly to every command: the parser resolves them
  at the top level so no verb needs to declare them individually, and a flag given
  in any position (e.g. `mind --json recall` or `mind recall --json`) is
  equivalent.

- `CLI-151` The color/Unicode capability gate is ON when ALL of the following hold:
  stdout is a TTY; the locale is UTF-8 (the first of `LC_ALL`, `LC_CTYPE`, `LANG`
  that is set contains the substring `UTF-8` or `utf8`, case-insensitively); the
  environment variable `NO_COLOR` is unset; the `--json` flag is not in
  effect; and the `--ascii` flag is not in effect. An unset locale (none of the
  three variables is set) is treated as non-UTF-8. When the gate is OFF, all output
  is plain ASCII with no ANSI escape sequences. (`NO_COLOR` set to an empty string
  still forces the gate OFF, same as any other value: see CLI-154.)

- `CLI-152` When the capability gate (CLI-151) is ON, output uses ANSI color and
  Unicode glyphs with these semantics: green = installed / ok; yellow = warning /
  drift / removed-upstream / installed-but-stale; red = error; dim = available /
  inactive. When the gate
  is OFF, output uses a plain-ASCII fallback for every glyph and no color escapes.
  The ASCII fallback replaces each glyph with a visually equivalent ASCII character
  or short string (e.g. `+` for installed, `^` for installed-but-stale, `!` for
  warning, `x` for error, `-` for available), so all information is preserved
  without terminal support.

- `CLI-153` Every mutating verb (`meld`, `learn`, `forget`, `sync`, `upgrade`,
  `unmeld`, and `config lobes add`/`remove`) emits a structured JSON result object
  on stdout under `--json` and writes nothing else on stdout. The stable fields of
  this object are:

  ```json
  {
    "action":  "<verb>",
    "target":  "<item-or-source ref>",
    "outcome": "<short verb-specific token; see below>"
  }
  ```

  `action` is the CLI verb (e.g. `"learn"`, `"forget"`, `"meld"`); `config lobes
  add`/`remove` report `action` as `"lobe-add"`/`"lobe-remove"`. `target` is the
  effective name of the item or source the verb acted on (e.g. `"skill:review"`,
  `"github.com/owner/repo"`). `outcome` is a short token describing what the verb
  did. The tokens by verb are: `meld` -> `"melded"`, or `"already-melded"` when the
  source was already registered with nothing new to install; `learn` -> `"installed"`,
  `"up-to-date"` (already installed), or `"dry-run"` (`--dry-run`); `forget` and
  `unmeld` -> `"removed"`, with `unmeld --unlink-only` -> `"unlinked"`; `sync` ->
  `"synced"`, or `"no-op"` when there are no sources; `upgrade` -> `"upgraded"`,
  `"renamed"`, or `"up-to-date"`; `absorb` -> `"absorbed"`; `config lobes add`/`remove`
  -> `"added"`/`"removed"`, or `"no-op"` when the lobe was already in the desired
  state. `"up-to-date"` means the verb completed successfully but every item was
  already at the requested state; `"no-op"` means it completed successfully but had
  nothing to act on. A verb MAY add extra fields where it
  genuinely returns more data (for example, `learn` MAY include an `"installed"`
  array listing the effective names of all items installed in that call, including
  dependency-closure items). The read-only verbs (`recall`, `probe`, `introspect`)
  keep their existing JSON shapes (CLI-73, CLI-84, CLI-92) and are not affected by
  CLI-153. `absorb` is also a mutating verb covered by CLI-153; see ABS-11 for its
  specific extra field. `hooks run` is a mutating verb too (HOOK-101/HOOK-103) but
  its result is a count-based tally rather than an `action`/`target`/`outcome`
  object; see CLI-222 for its distinct shape.

- `CLI-217` Under `--json`, stdout carries exactly one JSON document and nothing
  else, and that document answers the verb the user INVOKED. Two failure modes
  are ruled out, and both are enforced structurally rather than by a rule each
  call site has to remember:

  1. *Anything that is not the document.* An advisory note (`note: lobe '...' is
     unreachable`, `note: skipped install hook ...`), a warning about a step that
     failed without stopping the verb (HARN-17's unlinkable backfill target), a
     progress line (`running install hook '...' for ...`), and the output of a
     hook mind ran on the source's behalf all belong on stderr while `--json` is
     in effect. Printed ahead of the JSON they leave stdout holding prose
     followed by an object, which no longer parses; a hook's output is the worst
     case, since it is arbitrary text chosen by the source author and could
     otherwise forge a result envelope. Mechanically, a verb covered by this
     statement runs with the process's stdout pointed at stderr for its whole
     duration, so no `println!` on any path -- and no child process that inherits
     stdout -- can reach stdout at all. Notes are not dropped: they stay visible
     on stderr. `render::note` / `render::warn` remain the way to write a line
     that belongs on stdout in text mode (byte for byte what a `println!` would
     have written) and on stderr under `--json`.
  2. *A second document.* A verb that performs another verb's work internally
     (`meld` and `sync` installing items through `learn`, `sync --upgrade`
     running the upgrade pass) must fold that outcome into its own CLI-153
     object rather than letting the inner verb emit one: `mind meld <repo>
     --json` answers with a `meld` object whose `installed` array names what the
     install step did, `mind sync --json` with one `sync` object covering the
     POL-58 provisioning pass and the `--upgrade` pass, never N+1 objects. This
     holds on the already-melded (re-meld) branch as well as the fresh one, and
     a re-meld that installs nothing still answers with its object rather than
     with silence -- a CLI-153 mutating verb always tells a machine caller what
     happened. On failure the CLI-181 error envelope REPLACES whatever a verb
     recorded before failing, so stdout is one document either way.

  The statement binds the verbs that answer `--json` with a document. It does
  not apply where another statement makes stdout a different product: `dump`
  writes TOML (DUMP-9), `completions` and `man` write their script and roff
  page, and `evolve` writes its own document from `selfupdate.rs` on a path
  that never goes through this module. `init-source` defines no JSON output at
  all, so it prints its human text on stdout in every mode. CLI-218 states the
  exclusion list this paragraph draws from as a closed boundary rather than an
  enumeration; `review` (CLI-219) and `hooks list` (CLI-220) are NOT
  exclusions, and both answer `--json` with a document like every other verb.

- `CLI-218` `--json` is universal: every verb answers it with exactly one JSON
  document on stdout (CLI-217), except a closed, named exclusion list -- the
  boundary is a rule, not an enumeration of what a given release happens to
  implement. The excluded verbs, and why each is excluded: `dump` writes TOML
  by design (DUMP-9); `completions` and `man` print the artifact itself (a
  shell script, a roff page) as their entire output; `evolve` writes its own
  result document from `selfupdate.rs` rather than through the CLI-153/CLI-217
  machinery (its document IS JSON, just emitted on a separate path); and
  `init-source` is a maintainer scaffolder that edits the target repo in place
  and has no JSON result to offer. Every other verb answers with a document,
  including `review` (CLI-219) and `hooks list` (CLI-220), which for earlier
  releases had none. A verb added later that is neither given a JSON shape nor
  added to this exclusion list is a bug, not a silent gap: the boundary is
  meant to be closed at every point in time, not merely today.

- `CLI-154` `NO_COLOR` being set (to any value, including empty) forces the
  capability gate (CLI-151) OFF regardless of TTY or locale. A non-UTF-8 locale or
  an unset locale also forces the gate OFF even on a TTY. `--ascii` forces the gate
  OFF regardless of `NO_COLOR`, locale, or TTY state. These conditions are
  independent: any one of them alone is sufficient to disable color and Unicode
  glyphs.

- `CLI-155` In the `recall` status views (the default forest and `recall <source>`),
  an installed-but-out-of-date item (CLI-75) uses a distinct left-edge marker from a
  current install: the stale glyph (Unicode `↑` in yellow, ASCII `^`) rather than the
  installed glyph (Unicode `✓` in green, ASCII `+`). This marks a third state
  between installed-and-current and not-installed, so the out-of-date condition is
  visible from the marker alone and not only from the trailing `(outdated)` text.
  The marker is a human-view concern; the JSON output is unchanged.

- `CLI-157` `learn` when every item in the requested set is already installed (the
  closure is empty after DEP-23 exclusion, with no dry-run in effect) prints
  "already installed; nothing to do" to stdout and under `--json` emits a single
  result object with `outcome: "up-to-date"` (distinct from `"installed"`, which
  requires at least one item was actually installed). Exit 0 in both cases.

- `CLI-162` `--verbose` (short `-v`) is a global flag accepted before or after the
  verb, resolved at the top level like `--json`, `--yes`, and `--ascii` (CLI-150).
  It enables extra advisory output that is otherwise suppressed: the unguarded-
  reference warning emitted during `meld` when a prefix is in effect (CLI-14,
  NS-20). It does not affect the color/Unicode capability gate (CLI-151).

- `CLI-240` `--max-metadata-size <SIZE>` is a global flag accepted before or after
  the verb, resolved at the top level like `--json`, `--yes`, and `--ascii`
  (CLI-150). It sets the ceiling on every source-controlled metadata read for
  that invocation (DSC-103), outranking `MIND_MAX_METADATA_SIZE` and the
  `max-metadata-size` config key (DSC-104). The accepted forms and the refusal
  of an unparseable one are DSC-105. Being global, it applies to every verb that
  reads a source, including the ones that read one without installing anything
  (`review`, `probe`, `recall`, `introspect`).

- `CLI-241` `config show` reports `max-metadata-size` alongside `lobes` and
  `ssh`, as the effective cap rather than the config key's value (DSC-106), so a
  run under a flag or an environment override does not report a number that is
  not in force. `--json` carries it twice: `max_metadata_size` as the rendered
  string a human reads, and `max_metadata_size_bytes` as the byte count a
  consumer compares against, with an unlimited cap rendering as `"unlimited"` /
  `9223372036854775807` (CLI-242).

- `CLI-242` Every integer mind emits in `--json` fits a signed 64-bit integer,
  so a consumer that parses numbers as `i64` never overflows. The one value that
  could exceed it, the unlimited metadata cap (held as `u64::MAX`), is emitted
  as `max_metadata_size_bytes: 9223372036854775807` (`i64::MAX`). The rendered `max_metadata_size` string
  still reads `unlimited`.

- `CLI-163` The short flag `-n` is reserved for `--dry-run` on `learn` (CLI-32),
  which already owned it. As a consequence, `--namespace` on `meld`, `review`, and
  `init-source` moves to short `-N` (uppercase). No other short is assigned to
  `--namespace`; the long form and `-N` are the two accepted spellings.

- `CLI-164` `probe --no-tui` is long-only; its former short `-n` (TUI-3) is removed
  to free `-n` globally (CLI-163). See also TUI-54.

- `CLI-165` `meld --register-only` replaces `--link-only` as the canonical name for
  "register the source without installing its items" (CLI-23). `--link-only` is
  retained as a hidden deprecated alias and continues to work; it does not appear in
  `--help` output.

- `CLI-166` `unmeld --keep-items` replaces `--unlink-only` as the canonical name for
  "remove the source but leave its installed items in place" (CLI-22).
  `--unlink-only` is retained as a hidden deprecated alias.

- `CLI-167` `probe --json` and `recall --json` (default view, `--sources`) wrap their
  array output in a versioned envelope:

  ```json
  {"schema": 1, "items": [...]}
  ```

  The top-level `"schema"` field is a monotonically increasing integer; readers
  should treat an absent field as `1`. Single-item `recall <item> --json` is already
  a plain JSON object and is not wrapped. This supersedes the bare-array form of
  CLI-73 and CLI-84.

- `CLI-168` The mutation result envelope (CLI-153) gains a top-level `"schema": 1`
  field. Existing stable fields (`action`, `target`, `outcome`, and verb-specific
  extras) are unchanged; `"schema"` is additive.

- `CLI-169` `upgrade` fetches each involved source before computing deltas (syncs
  first by default). The sync uses the same per-source resilience as CLI-54:
  individual source failures are reported and skipped; the upgrade pass runs on
  the sources that did succeed. Pass `--no-sync` to skip the fetch and compute
  deltas from the current (potentially stale) clone. `sync --upgrade` continues to
  work but its `--upgrade` flag is noted as deprecated in help text; prefer
  `upgrade` (which now syncs) or `upgrade --no-sync` (to match the old
  `sync --upgrade` behavior of explicit sync then upgrade).

- `CLI-170` `MIND_DEFAULT_LOBE` is the primary environment variable for setting the
  default agent home (lobe). When set, it takes precedence over `CLAUDE_HOME`.
  `CLAUDE_HOME` is kept as a documented legacy fallback: if `MIND_DEFAULT_LOBE` is
  unset, `CLAUDE_HOME` is used; if neither is set, the default is `~/.claude`.

- `CLI-171` The `absorb-to` config key in `~/.mind/config.toml` is the canonical
  (kebab-case) spelling. The underscore form `absorb_to` is accepted as a
  backwards-compatible alias during parsing; new writes always emit `absorb-to`.

- `CLI-172` Visible aliases added: `add` for `meld`, `install` for `learn`,
  `uninstall` for `forget`, `update` for `sync`, `search` for `probe`, `list` for
  `recall`, `doctor` for `introspect`, `self-update` for `evolve`, `reconcile`
  for `curate`. Former aliases `detach` (for `unmeld`) and `target` (for
  `config lobes`) are removed and are now usage errors; `unlearn` (for
  `forget`) and `status` (for `recall`) remain visible.

- `CLI-173` The one-line help for `meld` reflects that melding installs items by
  default (interactive prompt): "Meld with a source repo and install its items."

- `CLI-174` The long help (`--help` body) for `unmeld` leads with: "Unmelds a
  source and uninstalls every item the source installed; use `--keep-items` to keep
  them."

## Exit status

- `CLI-100` A command that completes its work exits 0. Any `MindError` is printed
  to stderr (with its source chain) and exits non-zero.
- `CLI-175` The exit-code contract: 0 for success, 1 for a runtime error
  (`MindError`), and 2 for a usage error (clap parse failure). Clap handles code 2
  automatically. Code 1 comes from `ExitCode::FAILURE` in `main`.
- `CLI-181` Under `--json`, when a `MindError` occurs the process emits a JSON
  error envelope on stdout and exits with code 1 (unchanged). The envelope shape
  is `{"schema": 1, "error": {"kind": "<slug>", "message": "<display-text>"}}`.
  The `schema` field matches the success-envelope version (currently 1). The
  `message` field is the full `Display` text of the error. Nothing is written to
  stderr in this path. In non-json mode the existing behavior is unchanged: the
  error message and its source chain are printed to stderr and the process exits 1.
- `CLI-182` The `kind` field in the JSON error envelope (CLI-181) is a stable
  kebab-case slug assigned once per `MindError` variant and never changed. Scripts
  may branch on `kind` to handle specific failures. Example slugs: `ItemNotFound`
  -> `"item-not-found"`, `DigestMismatch` -> `"digest-mismatch"`,
  `SelfUpdatePolicy` -> `"self-update-policy"`, `ReviewFailed` ->
  `"review-failed"` (the `review --json` hard-failure kind, CLI-219). The slug
  set is exhaustive: every variant has exactly one slug.
- `CLI-221` The CLI-181 error envelope carries an optional `details` member when
  the failing verb recorded structured findings before returning its error (a
  hard-finding `review` under `--json`, CLI-219, is the first user). `details`
  is whatever that verb recorded, verbatim; its shape is verb-specific and
  documented at the verb's own JSON-shape entry (e.g. CLI-219), not here.
  Absent entirely -- not `null` -- for the ordinary case of a `MindError` with
  no recorded findings. This gives a machine caller the full structured
  picture on the non-zero exit, not just the CLI-182 `kind` slug and a prose
  `message`.
- `CLI-183` Clap argument-parse failures (exit code 2) are not JSON-enveloped.
  They occur before flag parsing settles and are rendered by clap as plain text to
  stderr. Scripts may treat exit 2 as a usage error without inspecting stdout.
  Only the post-parse `MindError` path (exit 1) emits the envelope.