skillpack 0.12.1

Generate, verify, and maintain AI agent guidance (skills, plugins, AGENTS.md) for Claude Code, Cursor, Codex, Copilot, and 10+ AI coding ecosystems — one command turns any CLI or library into an agent-discoverable skill pack.
Documentation
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
# Changelog

All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.12.1]https://github.com/nordicnode/skillpack/compare/v0.12.0...v0.12.1 - 2026-08-14

### Fixed

- address review findings across CI, docs, and source

### Other

- production-readiness pass from the second review
- remove em dashes from README
- center the Built with Freebuff heading
- rerun benchmark with 3 runs per condition
- rewrite agent-demo for Google Antigravity (agy) + Gemini 3.7 Flash
- switch benchmark to Google Antigravity (agy) + Gemini 3.7 Flash
- drop duplicate skillpack h1 from hero (wordmark is in the logo)
- add Freebuff CTA banner and drop emoji from section headers

_No unreleased changes yet._

## [0.12.0] - 2026-08-14

### Added

- Repeatable Google Antigravity benchmark harness (`scripts/benchmark/`):
  `run.sh` runs an A/B of a plain repo clone vs `init --auto --target all`
  output through the `agy` CLI (Antigravity) — same repo, same default
  agent, same model, same questions (the old demo's `--agent` confound is
  gone). agy print mode does not auto-discover AGENTS.md rules, so condition
  B feeds the skillpack-generated AGENTS.md to the agent as a prompt
  preamble — the only difference between conditions is the guidance.
  `analyze.py` evidence-scores each question and reports per-condition
  medians across `--runs N`; `agy`'s configured model is used by default
  (Gemini 3.7 Flash); pin with `--model`.
- `init --auto`: zero-flag, zero-prompt init. The intent is derived entirely
  from the repo — description from the README hint, author from the manifest
  or `git config user.name`, license from the LICENSE file (or `--license`),
  invocation from the detected CLI. `--import` is required for libraries
  (actionable error otherwise); `--trigger` is optional (falls back to the
  description hint). Implies non-interactive: on a critical verification
  failure it refuses to write (exit 2) instead of prompting.
- Four new harness targets, bringing the total to ten: `claude-md`
  (`CLAUDE.md` — Claude Code / Cline / Roo Code), `gemini` (`GEMINI.md`  Gemini CLI, per the official docs), `windsurf` (`.windsurf/rules/<name>.md`
  — same frontmatter schema as Cursor rules), and `aider` (`CONVENTIONS.md`).
  The three plain-markdown targets share the copilot/AGENTS.md structural
  check (now a single shared implementation); Windsurf reuses the Cursor
  rule check with its own check_id prefix. AGENTS.md coverage is documented
  for Freebuff and 60k+ projects' agents.
- Multi-skill packs (the documented future direction, now shipped): a
  `[[skills]]` array in `skillpack.toml` renders one per-skill file per
  ecosystem (`skills/<name>/SKILL.md`, `.codex/skills/<name>/SKILL.md`,
  `.cursor/rules/<name>.mdc`, `.opencode/agents/<name>.md`), each under its
  own directory name and with its own frontmatter. The `[skill]` table stays
  the PRIMARY skill — pack-level files (`plugin.json`, `marketplace.json`,
  AGENTS.md, copilot-instructions.md) render from it, and hand-adding a
  `[[skills]]` entry keeps it primary. `update`/`diff`/`verify --fix` are all
  multi-skill aware: `verify` accepts any configured skill name in the
  `name_drift` check, and `--fix` splices the RIGHT skill's frontmatter while
  preserving every body. The config normalizes to a `[[skills]]` array on
  first `update`. Author the array by hand; `init` still writes single-skill
  packs. Docs: `docs/reference.md` "Multi-skill packs" section.
- `init --non-interactive` can now bootstrap the FIRST pack from flags
  (`--description`, `--trigger`, `--author`, `--invocation` or `--import`)
  when no `skillpack.toml` exists — previously a chicken-and-egg: CI needed a
  committed config, which needed an interactive run. A fresh checkout can now
  be scaffolded end-to-end with no TTY: `skillpack init --non-interactive
  --description ... --trigger ... --invocation ...`. Missing required fields
  fail with an actionable message naming the flag; passing both
  `--invocation` and `--import` is rejected. With a committed config the
  flags are ignored (config wins), so re-runs stay deterministic.
- Language support expanded to five more ecosystems — **Zig**
  (`build.zig`/`build.zig.zon` + `zig-out/bin/`), **Swift** (`Package.swift`
  + `.build/{debug,release}/` artifacts or `swift run`), **C / C++**
  (`CMakeLists.txt`, `meson.build`, `Makefile` and `build/` / `bin/`
  binaries), **Elixir** (`mix.exs` + `_build/{dev,prod}/rel/` releases or
  `mix escript`), and **Deno** (`deno.json` / `deno.jsonc` + `deno run`
  entrypoints) — bringing the supported language count to thirteen.
  Detection, manifest parsing, CLI candidates, Cursor globs, and snapshot
  coverage all extended.
- New `verify` discovery checks: an unterminated `---` frontmatter block in a
  `SKILL.md` / cursor `.mdc` / opencode agent file now FAILs (`*.frontmatter_unclosed`)
  instead of being silently parsed as valid frontmatter with the body swallowed;
  a `skills/<name>/SKILL.md` (or `.codex/skills/<name>/SKILL.md`) whose directory
  name disagrees with its frontmatter `name:` warns (`*.dir_name_mismatch`).
- `discovery.plugin.url_drift` now reports BOTH `homepage` and `repository`
  drifts in one warning — the old early return hid a second stale field.
- CI gains a `cargo audit` job (RustSec advisory scan of the lockfile).
- `update`/`diff` now honor the global `--verbose`/`--debug` flags (they were
  silently ignored).
- `update` no longer rewrites `skillpack.toml` when its serialized content is
  unchanged (no mtime churn); uses the same frontmatter-splice semantics.
- The interview accepts semicolons as trigger-phrase separators (was
  comma-only).
- `release.yml` reads the crate version from the `[package]` table only — the
  old `^version` grep would break if a workspace-level `version` line ever
  appeared.

### Changed

- README rewritten to be user-facing: plain-language quick start, "what you
  get" per ecosystem, and a measured-results section up front. The full
  check list, every CLI flag, and the platform notes moved to the new
  `docs/reference.md`, which the README links to.
- Declared MSRV raised to Rust 1.85 (`rust-version` in `Cargo.toml`), matching
  what the locked `clap`/`tera` actually require — `cargo install` on Rust
  1.74–1.84 previously failed with a hard toolchain error. README badge and
  toolchain comment updated; CI gains an MSRV job that checks lib + bins at
  1.85.0 so the floor can't drift again.
- Removed three unused direct dependencies (`thiserror`, `walkdir`,
  `indexmap`) — they had zero references in `src/` and only bloated compile
  time and the lockfile.

### Fixed

- `init --auto` README hint no longer leaks badge/markup rows (shields.io,
  CI status, language links) into the derived description — `read_readme_hint`
  now skips lines that are markup-only, so a repo like fd whose README leads
  with a badge banner gets the real tagline (previously the description
  became a badge URL and the generated pack failed verify at 93/100).
- `verify`'s `description_action_verb` check accepts descriptions that open
  with a code-formatted name (`` `fd` is a program to ... ``) — the leading
  backtick pair is stripped before the alpha-word heuristic. Markup/badge
  leakage still fails.
- `.gitignore` now excludes `.freebuff/` — the desktop-app local database was
  being swept into the crates.io tarball (`cargo package` includes untracked,
  non-ignored files). Private local data would have shipped with the crate.
- All 11 rustdoc warnings eliminated (stale `GeneratedFile` links, links to
  private items, an unclosed HTML tag, a redundant link target) — `cargo doc`
  is now warning-free.
- `release.yml` now runs `cargo audit` before `cargo publish`, so a
  vulnerability disclosed after the last `main` CI run blocks publishing.
- `detect_repo_url` now preserves the raw `git remote get-url origin`
  string (what lands in `plugin.json` `homepage` / `repository`) while
  `urls_equivalent` still compares normalized forms — so a trailing-slash
  or `.git`-suffix difference no longer causes false URL drift, and the
  committed plugin.json keeps the exact origin URL.
- The Go CLI candidate test compares the binary file stem
  case-insensitively (matching the Node test pattern) — fixes a
  windows-latest-only failure where PATHEXT resolves `go` to a path ending
  in `go.EXE`.
- `HELP_TIMEOUT` raised 15s → 30s: the Windows CI `go run .` round trip timed
  out again under parallel-test load (the same flake that raised it 8s → 15s
  previously). The go round-trip test now also pre-warms the build cache
  before `init`, mirroring the rust-cli / csharp fixtures.

## [0.11.2] - 2026-07-13

### Added

- `verify_stdin` field in `skillpack.toml` `[skill]` table — feeds stdin bytes
  to the CLI during `verify` spawns (`--help`, subcommand `--help`). For
  interactive CLIs that block on stdin (auth prompts, confirm dialogs); without
  this, `verify` times out and false-flags drift. `None` (default) uses
  `/dev/null`, preserving all existing behavior. Not fed to `--version` (non-
  interactive).

## [0.11.1] - 2026-07-13

### Added

- `--template-dir <DIR>` flag on `init`, `update`, `diff`, and `verify`  overlay custom `.tera` templates from a directory. Missing templates fall
  back to embedded defaults, so you override only the files you need. Template
  filenames must match the `templates/` directory (e.g. `SKILL.md.tera`,
  `plugin.json.tera`). `verify --template-dir` threads the override into
  `--fix` replays so a custom-template-init followed by `--fix` doesn't
  produce a drift loop from embedded-template output.

### Future direction (not shipped)

- Multi-skill pack support (`[[skill]]` array in `skillpack.toml` with multiple
  `skills/<name>/SKILL.md` entries per ecosystem and looping in `generate`).
  The verify side already handles multiple skill files (`find_skill_files`
  loops `skills/*/SKILL.md`); the gap is in the emit side (one-skill
  hardcoded paths in `generate.rs`). Deferred as YAGNI — no user demand, and
  the config schema change (`[skill]``[[skill]]`) is a silent breaking
  change to every shipped `skillpack.toml` that needs a migration guard and
  upgrade tooling before it's safe to ship.

## [0.11.0] - 2026-07-13

### Added

- `update` subcommand — incrementally refresh distribution files from a
  committed `skillpack.toml` without re-running the interview or the
  pre-commit verify gate. Re-introspects, re-renders every target, and
  writes only files whose content changed. For frontmatter-bearing files
  (`SKILL.md`, cursor `.mdc`, opencode `.md`) the body prose is preserved
  by splicing the fresh frontmatter onto the committed body; the
  frontmatter block is regenerated wholesale. For other files
  (`plugin.json`, `marketplace.json`, `AGENTS.md`, Copilot instructions)
  the content is overwritten wholesale. Common workflow: bump version →
  `skillpack update` → commit. Note: `update` preserves the body, so it
  can't add new subcommand bullets or refresh CLI-surface flags — use
  `init --target all` when the CLI surface changed.

- `diff` subcommand — CI gate for stale distribution files. Re-renders
  every target in memory, compares against on-disk content, reports
  drifted/missing files, and exits 1 if any differ (0 if all clean, 3
  on fatal error). Uses the same candidate computation as `update`
  (frontmatter splice for body files, wholesale for fully-generated
  files), so `diff` and `update` agree on what counts as drift. The
  AGENTS.md collision guard mirrors `update`/`init` — skipped without
  `--force`.

- `verify --format sarif` — SARIF 2.1.0 output for GitHub Code Scanning.
  Each `Warn`/`Error` CheckResult maps to a SARIF result (`ruleId` =
  check_id, `level` = `"warning"` or `"error"`, `message.text` = message +
  suggestion). Pass/Skipped results are omitted (SARIF reports failures
  only). Wires into the `github/codeql-action/upload-sarif` action for
  PR-level discoverability gating. `doctor --format sarif` is rejected
  with a clear error (doctor has no CheckResults).

- `invocation.version_drift` advisory check — `verify` now spawns
  `<cli> --version` and warns when the stdout output does not contain the
  `plugin.json` version string. Skips silently when `--version` exits
  non-zero or prints nothing (older or non-conforming CLIs). Distinct
  from `discovery.plugin.version_drift` which compares plugin.json
  against the project manifest. Surfaces stale CLI↔plugin.json version
  drift that was previously invisible until a user reported a mismatch.

- `init` pre-commit preview — before writing, `init` now prints a
  `+ new` / `~ changed` / `= N unchanged` summary on stderr so you can
  see exactly which distribution files will change before they are
  overwritten. Silent when nothing differs (no noise on idempotent
  re-runs).

- `doctor` category preview — `doctor` now prints a verify-namespace
  preview (`discovery.*` + `invocation.*`) at the end of its human
  output, showing what `verify` would check per category. Honest about
  the gap: when no CLI is detected, `invocation.*` is marked N/A. No
  fabricated score — tells users to run `verify` after `init`.

- `verify --watch` — debounced file watcher that re-runs `verify` on
  every change to the project root. Prints the initial report immediately,
  then clears + re-renders on each debounced event. Filters common noise
  dirs (`target/`, `.git/`, `node_modules/`). Only valid with
  `--format human`; `--watch --format json` exits with `VERIFY_USAGE` (4).
  Ctrl-C terminates the process directly (standard SIGINT behavior).

### Fixed

- `Config::load` now validates `skill.name` at the trust boundary: a
  non-kebab name in `skillpack.toml` is rejected with an actionable error
  instead of silently corrupting every generated artifact. Description
  and trigger phrases remain soft checks left to `verify` (load stays
  lossless).

## [0.10.1] - 2026-07-13

### Fixed

- `init` summary now surfaces skipped targets when the collision guard
  skips `AGENTS.md` (existing file held, `--force` not passed). Previously
  the dropped target was invisible — `--target agentsmd` with a pre-existing
  `AGENTS.md` printed "wrote 0 file(s)" and exited 0 with no audible signal.
  Now `write_files` returns `(written, skipped)` and the summary prints an
  `ℹ skipped N target file(s)` footer on stderr. Exit code unchanged
  (collision is not user-abort).


## [0.10.0] - 2026-07-13

### Added — AGENTS.md support (6th agent target)

- `--target agentsmd` generates `AGENTS.md` at the repo root — a plain
  markdown instructions file (no frontmatter) read natively by 20+ coding
  agents (Codex, Cursor, Windsurf, Copilot, Aider, Zed, Warp, JetBrains
  Junie, etc.) per the [agents.md]https://agents.md/ spec stewarded by the
  Linux Foundation. Template: `templates/AGENTS.md.tera` (skill_body
  partial, same as Copilot). Discovery: `src/verify/discovery/agentsmd.rs`
  validates presence, non-empty, `#` heading, no frontmatter.

### Added — `--target all` shortcut

- `--target all` expands to all six concrete targets (claude, cursor, codex,
  opencode, copilot, agentsmd) pre-dispatch. No `All` variant on the `Target`
  enum — expansion happens in `resolve_targets` before dispatch, so
  generate/verify/fix match arms stay clean. Dedup preserves canonical order:
  `--target all --target claude` emits Claude once (no double-write).

### Added — `--force` flag for AGENTS.md root collision guard

- AGENTS.md lives at the repo root (not a skillpack-owned directory). If it
  already exists, `init` skips it with a warning unless `--force` is passed.
  Other targets are unaffected (they write to skillpack-owned paths). The
  `write_files` function now returns the actually-written subset so the
  summary count reflects what hit disk.

### Changed

- `verify` `discovery.empty` message now lists all six ecosystems
  (Claude / Codex / Cursor / OpenCode / Copilot / AGENTS.md).
- `Cargo.toml` description updated to mention AGENTS.md.
- README flags table + Quick start + Status section updated for 6 targets.


## [0.9.4] - 2026-07-12

### Fixed — `verify --fix --format json` no longer corrupts JSON stdout

- The "✓ applied N fix(es), wrote: ..." summary line was `println!`ed to
  stdout BEFORE the JSON body, so `skillpack verify --fix --format json | jq
  '.ok'` failed with a JSON parse error in CI pipelines. The summary now
  goes to stderr (`eprintln!`); stdout is pure, directly-parseable JSON in
  every `--format json` mode. Regression test:
  `verify_fix_json_stdout_is_pure_json_no_summary_prefix`.

### Fixed — `verify --verbose --format json` no longer corrupts JSON stdout

- `print_profile` used `println!` unconditionally — the "— introspection —"
  block went to stdout before the JSON body, breaking
  `skillpack verify --verbose --format json | jq '.ok'`. `print_profile` now
  takes a `to_stderr: bool` param; in JSON mode the introspection block goes
  to stderr (still visible to humans), while stdout stays pure JSON. Human
  mode is unchanged (introspection on stdout). Regression test:
  `verify_verbose_json_stdout_is_pure_json_introspection_on_stderr`.

## [0.9.3] - 2026-07-12


### Fixed — name_drift WARN no longer shadows name_length FAIL

- `discovery.skill.name_drift` (a WARN) was placed in `check_one_skill_md`
  BEFORE `discovery.skill.name_length` (a FAIL). The 0.9.2 fix that moved
  name_drift after the first batch of FAIL-severity checks (description,
  description_empty, description_length) missed this fourth FAIL further down
  the function — name_drift's early `return Ok(r)` shadowed it. A SKILL.md
  with BOTH a drifted name AND one >64 chars reported only the warn → verify
  exited 0 / "OK" instead of exit 1 / "FAILED", silently passing a structurally
  broken skill that should have hard-failed. Both `name_length` (FAIL) and its
  sibling `name_reserved` (WARN) now run BEFORE name_drift — restoring the
  fail-severity-first invariant the 0.9.2 patch locked down for description.
  Regression test: `name_drift_warn_does_not_shadow_name_length_fail`
  (asserts a 65-char drifted name exits 1 with `discovery.skill.name_length`
  FAIL in the report, not exit 0 / warn-only).

### Fixed — BOM-prefixed (U+FEFF) SKILL.md / .mdc / OpenCode / Copilot files no longer false-fail

- Four text-entry points (`parse_skill_frontmatter`, `parse_cursor_mdc_frontmatter`,
  `parse_opencode_agent_frontmatter`, `check_copilot_instructions`) had no
  handling for a leading UTF-8 BOM (U+FEFF, bytes EF BB BF). `fs::read_to_string`
  preserves BOM (valid UTF-8); Rust's `char::is_whitespace` returns false for
  U+FEFF (Unicode 3.2+), so `str::trim()` and `trim_start()` do NOT strip it →
  a `\u{feff}---` opening delimiter failed the `== "---"` guard → empty
  SkillFrontmatter → false "frontmatter missing description" FAIL on a
  structurally valid file, and a BOM-prefixed `#` heading in Copilot
  instructions false-warned "not a markdown heading". Windows editors
  (Notepad, VS Code "UTF-8 with BOM" save) emit this prefix, so any hand-edited
  distribution file from a Windows user was exposed. Added a `strip_bom`
  helper (`discovery.rs`) applied at the raw-text read boundary in all 4 entry
  points plus `verify --fix`'s committed-SKILL.md read (defensive — the
  latent `split_frontmatter` body-deletion path was unreachable today but is
  now closed). Regression test: `bom_prefixed_skill_md_validates_clean`
  (asserts a BOM-prefixed SKILL.md with valid frontmatter exits 0 with no
  `discovery.skill.description` fail).

### Fixed — invocation multi-CLI counter no longer silently drops unreadable skills

- `verify::run`'s multi-CLI counter (`mod.rs`) swallowed `read_to_string` Err
  to `None` via `.ok()`, dropping unreadable skills (non-UTF8, permission
  denied, EBUSY) from the count — while the parallel discovery loader
  propagated the same error class via `.with_context(...)?` to abort verify.
  A corrupt `skills/<x>/SKILL.md` would FATAL-abort via discovery but silently
  no-op in the invocation counter — inconsistent. Now surfaces an
  `invocation.read_failed` WARN per unreadable skill (in addition to discovery's
  existing abort behavior) so the maintainer sees the read failure on both
  paths rather than the multi-CLI counter silently under-firing.

### Fixed — misleading "description is empty" message on malformed nested-YAML `description:`

- A multi-line YAML map under `description:` (e.g. `description:\n  broken:
  yaml`) parses without panic but flushes the key with an empty value — verify
  hard-failed with "description is empty", which hid the real cause (the user
  wrote a nested map instead of a same-line scalar). The message now reads
  "is empty (it may be missing, OR the line `description:` has no value on the
  same line — e.g. a nested map)" and the suggestion points at writing the
  value on the SAME line as `description:` to avoid indented YAML blocks.

### Docs — README flags table now covers `--root` and `--min-score`

- The Flags table in README.md was missing two shipped flags: `--root <DIR>`
  (the project-root override available on init / verify / doctor) and
  `--min-score <N>` (shipped in 0.9.2, the opt-in CI discoverability-score
  gate). Both are now in the table.

### Internal — dropped `let _ = &…` anti-idiom in `verify::render`

- `render()` destructured `counts()` as `(pass, warn, fail, skip)` then emitted
  three `let _ = &pass; let _ = &skip; let _ = &warn;` lines to bypass
  `unused_variable` (only `fail` was actually consumed by the summary line +
  `pass`/`warn` were used in the format string). Replaced with a clean
  `(pass, warn, fail, _skip)` discard.
### Changed — internal: decompose `introspect.rs` into `cli_probe.rs` + `repo.rs`

- Pure internal refactor; no user-facing behavior or API change. `src/introspect.rs`
  shrank from 1091 to 260 lines (76% reduction) — it is now a thin top-level
  orchestrator that calls `detect_language` then delegates to five sibling
  submodules:
  - `cli_probe.rs` (517, new) — the guarded `--help` spawn pipeline
    (`detect_cli`, `spawn_candidate`, `capture_subcommand_help`) plus the
    Cargo/npm workspace member walks (`walk_cargo_workspace`,
    `walk_npm_workspace`) and the root-manifest probes (`has_gemspec`,
    `has_csproj`).
  - `repo.rs` (136, new) — repo-metadata reads: `git remote get-url origin`
    (`detect_repo_url`, inline `spawn::run` so it shares no helper with the
    CLI probe pipeline), the LICENSE SPDX heuristic (`detect_license`), the
    README description hint (`read_readme_hint`), and `repo_url_name`.
  - `cli_candidates.rs` — gained the `candidate_tests` module (moved from the
    parent; tests the candidate *resolution* fns that already lived here),
    and its module doc was updated to point at `super::cli_probe` (the
    orchestrator no longer "stays in the parent").
  - `manifest.rs`, `workspace.rs` — unchanged.
- Refactor coverage preserved verbatim (62 lib + 49 integration + 3 property + 13
  snapshot, all green); the walk_* tests live in `cli_probe::tests`, the
  `read_readme_hint` HTML-skip regression lives in `repo::tests`, and the
  dir-tail canonicalization (Bug #3) + `which_on_path` real-exercise tests
  stay in the parent `parse_tests`. The bug fixes below add 2 integration tests
  (51 integration total, 132 tests overall), each documented inline.
- All release gates green: `cargo fmt -- --check`, `cargo clippy --all-targets
  --all-features -- -D warnings`, `cargo test -- --include-ignored`, self-dogfood
  `skillpack verify --root .` → 12 pass / 0 warn / 0 fail / score 100, and
  `skillpack doctor --root .` (human + json) clean.

## [0.9.2] - 2026-07-12

### Added — verify catches SKILL.md name drift + opt-in score gate

- `verify` now warns when a generated `SKILL.md` frontmatter `name:` field
  no longer matches the canonical project name (`coerce_kebab(profile.name)`,
  the exact value the template renders). Fires under two check IDs —
  `discovery.skill.name_drift` (Claude) and `discovery.codex.skill.name_drift`
  (Codex) — both mapped to the new `FixAction::RegenSkillMdFrontmatter`.
  Warning (not failure): drift signals a hand-edited skill file or a renamed
  project that wasn't regenerated. Skips silently when either side is absent
  (e.g. no detectable project name).
- `verify --fix` regenerates ONLY the frontmatter block of the drifted
  `SKILL.md`, splicing the fresh frontmatter onto the preserved body prose.
  The applicator derives the target (Claude vs Codex) and which skill file
  from the warning's `location` rel-path, re-renders that single target, and
  reads the committed file with CRLF→LF normalization before splicing — so
  maintainer body prose (gotchas, examples, hand-tailored sections) survives
  the fix byte-for-byte on Windows autocrlf checkouts. Wholesale `init` regen
  remains the path for body drift; `--fix` is the surgical tool for
  frontmatter-only drift.
- `verify --min-score <N>` (0–100) is an opt-in CI gate: when passed, verify
  exits **2** if the discoverability score is below `N` and no critical check
  failed. Exit 2 (`VERIFY_SCORE_BELOW_MIN`) is distinct from exit 1
  (`VERIFY_FAIL`) so CI can distinguish a structurally broken pack from a
  score below an opt-in threshold — the latter is often `verify --fix`-
  actionable. A human-readable stderr line names the threshold and actual
  score when the gate fires; the JSON body still lands on stdout under
  `--format json`. Exit precedence is now load-bearing: **critical failure
  (1) > score below min (2) > ok (0)** — a broken pack surfaces the
  structure error first, the score gate fires only when structure passed.
  Omitted by default; the score continues not to gate exits unless asked.

### Internal — verify plumbing threads profile_name + location to discovery & fix

- `VerifyInput` gains a `profile_name: Option<String>` field (the
  `coerce_kebab`'d canonical name, threaded from both callers). `discovery::run`
  forwards it to `check_one_skill_md`, where the name_drift check compares it
  against the frontmatter `name:`. Threading the pre-coerced value (rather
  than re-coercing inside discovery) keeps the comparison a raw string match
  with no duplicated normalization, and keeps discovery a pure function of
  its inputs.
- `fix::apply` now threads `location: Option<&(String, Option<usize>)>` so the
  `RegenSkillMdFrontmatter` applicator can locate the drifted file without
  re-scanning. `RegenPluginJson` ignores `location` (fixed path); the verify
  dispatcher retains `r.location.clone()` so the applicator isn't dead. The
  distillation is one new enum variant + one applicator + one `action_for`
  arm — the exhaustive match makes forgetting the arm a compile failure.
- `exit::VERIFY_SCORE_BELOW_MIN = 2` joins `VERIFY_OK = 0` and `VERIFY_FAIL = 1`.
  Documented for consumers scripting `verify` — exit 2 is recoverable via
  `--fix` or by raising the pack's score, exit 1 is structural.

## [0.9.1] - 2026-07-12

### Added — verify catches plugin.json URL drift (`discovery.plugin.url_drift`)

- `verify` now warns when `.claude-plugin/plugin.json`'s `homepage` or
  `repository` field no longer matches the `git remote get-url origin` URL.
  `init` writes both fields from the detected origin, so drift means a
  hand-edited plugin.json or a renamed/stale remote that wasn't regenerated.
  Warning (not failure): a maintainer may intentionally host the plugin
  elsewhere.
- The check stays pure — no subprocess spawn inside `verify::discovery`. The
  git origin URL is detected once at introspection (`introspect::detect_repo_url`,
  already cached on `ProjectProfile.repo_url`) and threaded into the
  discovery stage via a new `VerifyInput.repo_url` field, preserving the
  module's "pure functions over their inputs" contract.
- Extends `verify --fix`: `discovery.plugin.url_drift` is now mapped to
  `RegenPluginJson` (same mechanical fix as `version_drift` — regenerate ONLY
  plugin.json, leave SKILL.md/marketplace.json intact). `action_for` is the
  only registration point: an exhaustive match makes forgetting the arm a
  compile failure.
- Skips silently on a repo with no git origin (no canonical URL to drift
  against) — `profile.repo_url = None` short-circuits the check.

### Internal — verify plumbing surfaces repo_url to discovery

- `VerifyInput` gains a `repo_url: Option<String>` field. Both callers
  (`verify_rendered` in `init`'s pre-commit gate, `run_verify_inner` in the
  `verify` subcommand) populate it from `profile.repo_url`. `discovery::run`
  and `check_plugin_json` take it as a parameter — no new spawn, no global
  state, no breaking change to the public `verify::run(input)` signature
  beyond the added struct field.


## [0.9.0] - 2026-07-12

### Added — reusable GitHub Action workflow for CI drift gating

- `.github/workflows/skillpack.yml`: a reusable workflow consumers pin via
  `uses: nordicnode/skillpack/.github/workflows/skillpack.yml@v0.9.0`. Installs
  skillpack from crates.io (`cargo install skillpack --version <input> --locked`)
  and runs `skillpack verify --format json` on a multi-OS + multi-runtime matrix
  mirroring the project's own `ci.yml`. Inputs: `skillpack-version` (default
  `0.9.0`). README documents the drop-in usage between Quick start and the
  dogfood section.

### Added — `doctor --format json` (stable machine-readable diagnosis)

- `doctor --format json` emits the serialized `ProjectProfile` (introspection
  result + decision trace) as a stable JSON object for CI/scripts. Mirrors
  `verify --format json`'s contract. The `diag` decision trace is ALWAYS
  present as an array (empty `[]` on clean runs, non-empty when candidate
  fns pushed falsy-branch notes) — removed the `skip_serializing_if =
  DiagTrace::is_empty` so `profile["diag"]` never KeyErrors. Read-only,
  non-gating — `doctor` always exits 0 (human + JSON form alike). Test
  `doctor_format_json_is_machine_readable` pins the contract: scalar field
  types + always-present `diag` array + per-entry `{ stage, note }` shape.

### Added — `discovery.skill.allowed_tools` grammar check

- `verify` now checks `allowed-tools` frontmatter against the Anthropic
  GRAMMAR (not an enumerated allowlist): each comma-separated token must be a
  bare identifier (`Read`) or a namespaced call (`Bash(npm test:*)`). Validates
  unbalanced parens, non-alpha identifiers, and empty tokens; reports
  `discovery.skill.allowed_tools` at WARN severity (doesn't gate). Applied to
  both Claude (`skills/<name>/SKILL.md`) and Codex (`.codex/skills/<name>/SKILL.md`)
  SKILL.md fronts. Membership-validation rejected as a brittle false-fail the
  moment Anthropic ships new tools. The `allowed_tools_hint` emit also moved
  from space-separated `"Read Bash"` to comma-separated `"Read, Bash"` to
  match the grammar — freshly-init'd packs no longer self-warned. Tests:
  `verify_warns_on_malformed_allowed_tools_grammar` (control: `Read`, `Bash(*)`
  pass; `Bash(`, `4R3ad` flagged) + `verify_passes_on_valid_allowed_tools_grammar`.

### Added — `verify --fix` (surgical drift repair)

- `verify --fix` mechanically repairs detected drift. Scope is deliberately
  narrow: only the file the drift lives in is rewritten — never wholesale regen
  (that's `skillpack init`). One fixable drift supported at launch:
  `discovery.plugin.version_drift` — regenerates ONLY `.claude-plugin/plugin.json`
  from the current manifest + intent, leaving `SKILL.md`/`marketplace.json`
  intact. Pre-fix report is suppressed; the post-fix verify re-runs and prints
  the post-fix report. An `✓ applied N fix(es), wrote: <files>` summary line
  precedes the report. Empty when no fixable drift — `--fix` is a no-op.
  The `FixAction` enum uses exhaustive `apply` match so adding a variant +
  forgetting an applier arm is a compile error, not a silent skip. Tests:
  `verify_fix_repairs_version_drift_surgically` (asserts surgical) +
  `verify_fix_is_noop_when_no_fixable_drift`.

### Added — self-dogfood drift gate (byte-identical regen test)

- `self_dogfood_regenerated_artifacts_match_committed_byte_identical`
  integration test: copies the repo's minimum files to a temp dir, runs
  `skillpack init --target <all 5> --non-interactive`, and asserts the five
  body files (`skills/skillpack/SKILL.md`, `.codex/skills/skillpack/SKILL.md`,
  `.cursor/rules/skillpack.mdc`, `.opencode/agents/skillpack.md`,
  `.github/copilot-instructions.md`) are byte-identical to the committed
  versions. Catches the F1-class drift that `verify`-passing hides:
  missing `globs:`, wrong opencode `mode`, stale `allowed-tools` grammar.
  Skips `plugin.json` / `marketplace.json` (their `url` / `repository`
  fields carry the git origin URL, which differs between the temp-dir copy
  and the GitHub-hosted source) — `verify`-passes covers those two.

### Fixed

- `DiagTrace` serialization no longer uses `skip_serializing_if = "is_empty"`:
  the `diag` field now ALWAYS serializes (empty `[]` on clean runs) so
  consumers of `doctor --format json` can rely on `profile["diag"]` existing.

### Internal

- The 0.8.8-class committed-artifact drift (cursor `globs:` missing, opencode
  `mode: subagent` when should be `primary`, body-stale subcommand flag lists
  after adding `--fix` / `doctor --format`) is now caught at test time by
  the new byte-identical self-dogfood gate, not only by ad-hoc dogfood runs.

## [0.8.8] - 2026-07-12

### Fixed — doctor `desc_hint` surfaces raw HTML when README leads with markup

- `doctor --verbose`'s `desc_hint` (the surfaced README preview) returned raw
  `<div align="center"><img src="docs/logo.png" ...></div>` markup on READMEs
  that begin with an HTML block (logos, banners). The `read_readme_hint`
  `skip_while` predicate only skipped markdown headings (`#`) and image lines
  (`!`); it didn't skip raw HTML tags. Extended the predicate to also skip
  lines starting with `<`, so the hint lands on the first real prose line.
  Affects the `--verbose` diagnostic display only — generated distribution
  files (`SKILL.md`, `plugin.json`, etc.) never read `desc_hint`; their
  description comes from `intent.one_line_description` in `skillpack.toml`,
  so no shipped artifacts changed.
  Regression test `read_readme_hint_skips_leading_html_div` in
  `src/introspect.rs` asserts the predicate drops HTML and lands on prose;
  reproduces the skillpack self-dogfood gap (this repo's own README leading
  with a div-wrapped logo).

### Fixed — committed distribution artifacts regenerated after introspection drift

- Re-running `skillpack init` on this repo (self-dogfood) surfaced two
  committed-artifact drifts caused by hand-edits + a changed default heuristic:
  - `.cursor/rules/skillpack.mdc` was missing `globs: ["*.rs"]` (the cursor
    auto-attach frontmatter line derived from `Language::Rust` via
    `cursor_globs_hint`).
  - `.opencode/agents/skillpack.md` carried `mode: subagent`; for a detected
    Rust CLI `opencode_mode_hint` correctly returns `"primary"` (Unknown →
    `"subagent"`). The demo doc (`docs/agent-demo.md` condition-B) confirms
    `primary` is the right mode for a Rust CLI agent invoked via
    `opencode run --agent <name>`.
  Neither was a code bug — the binary was correct; the committed files were
  stale. Regenerated all 5 targets so the committed artifacts match what a
  fresh `skillpack init` produces on this repo. Also picks up the new
  transparent-background `docs/logo.png`.

## [0.8.7] - 2026-07-12

### Changed — internal: introspect workspace helpers split

- Extracted the workspace-root heuristics from `src/introspect.rs` into a new
  sibling module `src/introspect/workspace.rs` (123 lines): `is_cargo_workspace_only`,
  `is_npm_workspace_only`, `pyproject_has_tool`, `first_cargo_member_name`, and
  `first_npm_member_name`. These are pure manifest-structure reads (no spawn)
  shared by `detect_language` and `detect_cli`; kept separate from
  `manifest.rs` because `manifest`'s contract is "pull a scalar from one
  manifest", whereas these walk `[workspace].members` / `workspaces` arrays
  and reason about structure. `introspect.rs` shrank from 1153 to 1052 lines
  (-9 %). Pure refactor — no public-API or behavior change: a `pub(crate) use
  workspace::{…}` re-export at the parent root preserves the flat
  `crate::introspect::is_*_workspace_only` / `first_*_member_name` import paths,
  so `detect_language`, `introspect`, and `detect_cli` bare call sites are
  unchanged. `walk_cargo_workspace` + `walk_npm_workspace` stay in the parent
  (they call parent-side `spawn_candidate`); `has_gemspec` / `has_csproj` stay
  parent-side (pure presence checks used only by `detect_language`).
  Self-dogfood: `skillpack verify` on this repo still reports 12 passed,
  discoverability score 100/100.

### Fixed — empty invocation block on config replay

- `init --non-interactive` re-runs from a `skillpack.toml` that omits
  `invocation_command` (the field is elided on save by
  `skip_serializing_if = "Option::is_none"`) produced a SKILL.md with an
  empty fenced invocation block for a CLI project — the template rendered
  `{{ invocation_command }}` as the empty string because `intent.invocation_command`
  yielded `None`. The interactive interview path already had a `suggest`
  default for blank answers; the config replay path had no such recovery.
  Fixed in `templates/skill_body.md.tera` by falling back to `cli_binary`
  (already derived from `cli_command` in `build_context`) via Tera's
  `default(value=cli_binary)` filter. Self-dogfood surfaced this on a fresh
  Rust CLI fixture: the generated SKILL.md now shows `frob-cli` in the
  invocation block instead of empty ticks. Regression test
  `invocation_block_falls_back_to_cli_binary_when_intent_omits_command`
  in `src/generate.rs` asserts the fallback fires; verified it fails when
  the template fix is reverted. Existing snapshots unchanged (fixtures
  supply `invocation_command: Some("chronicle --new \"entry\"")`).

## [0.8.6] - 2026-07-12

### Changed — internal: discovery module split

- Extracted the per-ecosystem discovery checks from `src/verify/discovery.rs`
  into three sibling modules under `src/verify/discovery/`:
  `cursor.rs` (`CursorFrontmatter` + `parse_cursor_mdc_frontmatter` +
  `check_one_mdc` + `find_cursor_mdc_files`, 172 lines),
  `opencode.rs` (`OpenCodeFrontmatter` + `parse_opencode_agent_frontmatter` +
  `check_one_opencode_agent` + `find_opencode_agent_files`, 147 lines), and
  `copilot.rs` (`find_copilot_instructions` + `check_copilot_instructions`,
  70 lines). `discovery.rs` shrank from 1041 to 689 lines (-34 %); the three
  new leaves total 389 lines. Pure refactor — no public-API or behavior
  change: `pub use` re-exports preserve the `crate::verify::discovery::`
  import paths for `parse_cursor_mdc_frontmatter`, `CursorFrontmatter`,
  `parse_opencode_agent_frontmatter`, and `OpenCodeFrontmatter`, so the
  property test (`tests/property_proptest.rs` L11) and the `run()` orchestrator
  are unchanged. Shared helpers `rel_unix` + `find_kv_colon` were lifted to
  `pub(crate)`; `is_valid_kebab`, `validate_relative_source`, `read_optional`,
  and the Claude/Codex/`store` core (marketplace.json + plugin.json +
  SKILL.md frontmatter) stay in the parent. Self-dogfood: `skillpack verify`
  on this repo still reports 12 passed, discoverability score 100/100.

## [0.8.5] - 2026-07-11

### Changed — internal: introspect module split

- Extracted the per-language CLI candidate resolvers (rust, node, go, python,
  ruby, php, jvm, csharp) plus their backing structs (`CliCandidate`,
  `DetectCli`), the language dispatch (`primary_cli_candidate`), the private
  helpers (`cargo_bin_names`, `has_go_main`, `python_script_package`,
  `canonicalize_for_argv`), and the Windows-aware `which_on_path` PATH resolver
  from `src/introspect.rs` into a new sibling module
  `src/introspect/cli_candidates.rs`. `introspect.rs` shrank from 1578 to 1153
  lines (-27 %); the new `cli_candidates.rs` holds 451 lines. Pure refactor —
  no public-API or behavior change: the `pub(crate)` re-exports
  (`CliCandidate`, `DetectCli`, `primary_cli_candidate`, `which_on_path`) keep
  the flat import paths unchanged, so `detect_cli`/`spawn_candidate`/
  `capture_subcommand_help` and the candidate/parse test modules need no edits.
  The `which_on_path` re-export and the `std::path::PathBuf` import are
  `#[cfg(test)]`-gated since only the test modules consume them.

## [0.8.4] - 2026-07-11

### Changed — internal: introspect module split

- Extracted the per-language manifest field parsers (name, version, authors,
  license) plus their helpers (`extract_xml_tag`, `extract_gradle_string`,
  `extract_ruby_string_value`, `select_csproj`, `strip_author_email`) from
  `src/introspect.rs` into a new sibling module `src/introspect/manifest.rs`.
  `introspect.rs` shrank from 2055 to 1578 lines (-23 %); the new
  `manifest.rs` holds 535 lines, including the 5 manifest-field regression
  tests that moved with the code. Pure refactor — no public-API or
  behavior change: the public `introspect()` entry and the `pub(crate)`
  re-exports (`project_manifest_version`, `select_csproj`) keep the old
  flat import paths unchanged, so `verify::discovery` and
  `csharp_cli_candidate` call sites need no edits. `strip_author_email`
  now strips inside `project_manifest_authors` (was at the call site).


## [0.8.3] - 2026-07-11

### Added — discoverability score in verify output

- `verify` now computes a 0-100 `discoverability_score`: each check
  contributes Pass = 1.0, Warn = 0.5, Error = 0.0, divided over non-skipped
  checks (Skipped excluded from the denominator; all-skipped or empty → 0,
  an honest "nothing verified" rather than a misleading 100). Exposed as
  `discoverability_score` (integer) in the `--format json` report and in
  the human summary line. The score does not gate the exit code — only
  critical failures do — so it's a tracking signal, not a pass/fail gate.
- New `VerifyReport::discoverability_score` method + 7 unit tests covering
  edge cases (all-pass=100, warn=partial, error lowers, all-skipped=0,
  empty=0, skipped-excluded, all-errors=0).
- Integration test `verify_format_json_is_machine_readable` extended to
  assert the score field is present, numeric, and matches the expected
  value (90 — the rust-cli fixture emits one `discovery.plugin.author`
  warning, so 4 pass + 1 warn = 4.5/5 = 90).

## [0.8.2] - 2026-07-11

### Added — verify catches plugin.json version drift

- New `discovery.plugin.version_drift` check: `verify` now compares the
  `version` in `.claude-plugin/plugin.json` against the project manifest
  version (`Cargo.toml [package].version`, `package.json "version"`,
  `pyproject.toml [project].version`, etc.) and warns on mismatch. Drift
  is the canonical stale-dogfood / bumped-manifest-without-regeneration bug;
  warns (not fails) so maintainers may intentionally pin a plugin version.
  Previously drift was invisible — `verify` only checked plugin.json had *a*
  version field, never compared it to the manifest.
- `detect_language` and `project_manifest_version` in `src/introspect.rs`
  bumped to `pub(crate)` so verify's discovery checks reuse the canonical
  per-language version parser (no duplicate logic, no new public API).
- Self-dogfood surfaced real drift: committed `.claude-plugin/plugin.json`
  was `0.6.4` while `Cargo.toml` was `0.8.1` (stale from a 0.6.4-era dogfood
  run, never refreshed). Fixed committed `plugin.json` to `0.8.2`.
- New integration test `verify_warns_on_plugin_json_version_drift`
  exercises both control (fresh `init` → no drift) and drift (mutate to
  `9.9.9-fake` → warning fires naming both versions, `verify` exits 0).
- README "What verify checks" adds a bullet for `version_drift`. Plugin.json
  version updated 0.6.4 → 0.8.2 to match Cargo.toml.

## [0.8.1] - 2026-07-11

### Fixed — introspection + flag extraction (self-dogfood on fd-find)

- `rust_cli_candidate` now parses `[[bin]].name` from `Cargo.toml` and probes
  those artifact paths before the package-name fallback. Crates that rename
  their binary (e.g. `fd-find``fd`, `ripgrep``rg`) were misdetected as
  libraries — `has_cli=false`, template emitted "library not CLI" branch.
  PATH fallback also extended to probe `[[bin]].name` candidates first.
- `extract_flags` now strips clap-style `[=<value>]` optional-arg suffix
  *before* punctuation trim. Previously raw `--help` tokens kept `[` (interior
  → survived `trim_matches`) while backtick-wrapped SKILL.md tokens stripped
  `[` (edge → punct) → same flag extracted differently → false flag-drift
  failure on verify. Now produces consistent `--flag` both sides.
- `extract_flags` now rejects prose tokens from rich clap help: multi-char
  short flags (`-tf` from `fd -tf` examples), example patterns (`-foo`),
  short/long pair separators (`-x'/'--exec`), and find(1) comparisons
  (`-mount`, `-xdev`). Filters: reject tokens containing `/` or `'`; reject
  single-dash flags with >1 char after dash (real clap short flags are single
  letter).
- `cli_binary` in generated SKILL.md now derives from the actual CLI command
  argv (`Path::file_stem()`) rather than the package name. SKILL.md prose
  now reads "Ensure `fd` is installed" (binary) instead of "Ensure `fd-find`
  is installed" (crate) — correct binary name for agent invocation.
- 4 new tests pin these fixes: `rust_candidate_probes_bin_name_not_package_name`,
  `strips_clap_optional_arg_suffix_consistently`, `ignores_prose_examples_in_help_text`.

## [0.8.0] - 2026-07-11

### Added — C# / .NET ecosystem support

- New **language** ecosystem: C# / .NET (`*.csproj` detection via
  `has_csproj` glob, SDK-style projects). `select_csproj` picks
  deterministically: sort by filename, prefer `OutputType=Exe`, skip
  `WinExe` (GUI, no stdout), lexicographic tiebreak. `csharp_cli_candidate`
  emits `dotnet run --project <csproj> --` (trailing `--` separator so
  appended `--help` reaches the app, not dotnet). Cursor globs:
  `["*.cs", "*.csproj", "*.sln"]`. CI adds `setup-dotnet@v4` (net8.0).
  Fixture `tests/fixtures/repos/csharp-cli/` with SDK-style csproj +
  `Program.cs` `--help` handler. Integration test `#[ignore]`-gated
  (self-skips without dotnet, pre-builds with `dotnet build -v q`).

## [0.7.0] - 2026-07-11

### Added — multi-ecosystem depth + language coverage

- Two new **language** ecosystems: PHP (`composer.json`) and JVM (Maven
  `pom.xml` + Gradle `build.gradle`/`.kts`). Introspection detects manifests,
  extracts name/version/authors, and resolves CLI candidates (`php_cli_candidate`
  mirrors `node_cli_candidate` with `php` runtime + `bin` script resolution;
  `jvm_cli_candidate` probes pre-built Gradle `installDist` scripts + Maven/
  Gradle shaded jars — no build invocation, pure filesystem reads per design
  §6.3). Cursor globs derive from language: PHP `["*.php", "composer.json"]`,
  JVM `["*.java", "*.kt", "*.scala", "pom.xml", "build.gradle",
  "build.gradle.kts"]`.
- **Cursor `globs` frontmatter** now derived from detected language for
  auto-attach (was missing entirely — generated Cursor rules wouldn't
  trigger on real work).
- **OpenCode `mode` frontmatter** now derived from project shape (`primary`
  for CLI tools, `subagent` for libraries); previously hardcoded `subagent`.
- **Template refactor**: 4 near-identical ecosystem templates → shared
  `skill_body.md.tera` partial + per-target frontmatter-only wrappers.
  Adding a 6th `Target` is now ~10 lines instead of ~60.
- New insta snapshots for Cursor, OpenCode, Copilot, PHP-Cursor, JVM-Cursor
  lock rendered output from regression.
- CI matrix adds `setup-php` and `setup-java` for cross-OS runtime coverage
  on `--include-ignored` round-trip tests.

### Changed

- `Language::Php`, `Language::Jvm` variants extend the `Language` enum and
  every match site (`category_hint`, `cursor_globs_hint`, introspect
  `detect_language`, manifest name/version/authors, `primary_cli_candidate`).
- `cli.rs` `about` string updated to name all 5 agent ecosystems (was
  leaking "Claude Code skill packs" into `--help`).

## [0.6.4] - 2026-07-11

### Fixed — Windows spawn + report normalization

- **`\\?\` UNC prefix from `std::fs::canonicalize` broke node module loading
  on Windows**. `canonicalize` returns `\\?\C:\path\bin\cli.js` on Windows; the
  kernel's `CreateProcess` accepts that for native exes, but Node's V8 module
  loader rejects `\\?\` paths — `node \\?\C:\foo\cli.js --help` exits non-zero
  `verify`'s `invocation.help_present` fired `RanNonZero` → init's critical
  gate failed → init refused to write on Windows. New `canonicalize_for_argv`
  helper strips the `\\?\` prefix post-canonicalize, applied at the rust,
  node, and ruby CLI candidate sites. Unix is a no-op.
- **`HELP_TIMEOUT` raised 8s → 15s**. Windows CI's cold-cache `go run .
  --help` first-compile cost (GOCACHE build + AV scan) exceeded 8s, false-
  timing-out the go round-trip test. 15s covers CI cold-cache compile
  while still bounding hung CLIs (a CLI that can't print `--help` in 15s is
  not one an agent should invoke anyway, so the cap doubles as fail-safe).
- **`verify` report paths normalized to forward slashes**. `discovery.rs`
  rendered relative paths via `path.strip_prefix(root).to_string_lossy()`,
  which on Windows emits `skills\skillpack\SKILL.md` — but the marketplace
  schema requires forward-slash-only paths, snapshot tests pin forward
  slashes, and downstream tools grep the report on `/`. New `rel_unix`
  helper (replace `\` with `/`) applied at the SKILL.md, .mdc, OpenCode
  agent, and Copilot instructions check sites. Unix unchanged.

## [0.6.3] - 2026-07-11

### Fixed — Windows rust CLI detection

- **`rust_cli_candidate` missed built artifacts on Windows**. The fn joined
  `target/{release,debug}/<name>` with the bare name, but `cargo build` on
  Windows writes `<name>.exe`. `p.exists()` returned false → `has_cli=false`
  on a real built CLI → `doctor` falsely reported no CLI and `verify`'s
  invocation checks descended into the `not_runnable_here` warning path +
  critical gate failure, refusing to write init output on Windows. Now
  appends `.exe` on Windows (`cfg!`), leaves Unix untouched. The PATH
  fallback already handles PATHEXT via `which_on_path`.

## [0.6.2] - 2026-07-11

### Fixed — Windows build correctness

- **Three lib tests failed on Windows** (introduced/exposed by the 0.6.1
  matrix; the code was unix-only before). All three now cross-OS:
  - `python_candidate_uses_m_module_when_importable` asserted
    `argv[0].ends_with("python")`; the PATHEXT fix correctly resolves
    `python.exe`, so the old assert rejected a valid Windows path. Replaced
    with a `Path::file_stem().eq_ignore_ascii_case("python")` check that
    accepts `python`, `python.exe`, `python3.exe`, etc.
  - `node_cli_detected_via_bin_absolute_argv` had the same `ends_with("node")`
    bug plus a string-suffix `script.ends_with("bin/cli.js")` that misses on
    `\`-separated Windows paths. Now uses `Path::ends_with` (component-aware,
    cross-OS) for the script tail + `file_stem` for the node binary stem.
  - `skill_md_has_description_and_when_to_use_in_frontmatter` failed because
    the repo had no `.gitattributes`: a Windows checkout with the default
    `core.autocrlf=true` converted the `.tera` template sources to CRLF,
    `include_str!` pulled CRLF bytes, Tera preserved them, and the generated
    SKILL.md started with `---\r\n` instead of `---\n`. The real fix is not a
    test-string patch — a generated SKILL.md shipping with CRLF on Windows
    and LF on Unix violates byte-identical output. Added `.gitattributes`
    pinning `*.tera` (and the rest of the source) to `eol=lf`, so templates
    keep LF on every checkout regardless of the host's autocrlf setting.

## [0.6.1] - 2026-07-11

### Fixed — CI

- **Windows CI hard-failed on `unused_imports`**: `src/spawn.rs` test module
  imported `use super::*` but every test fn inside is `#[cfg(unix)]`, so Windows
  compiled the import with zero usages → `clippy -D warnings` turned the
  `unused_imports` warning into a hard error. The new windows-latest matrix
  entry surfaced this pre-existing bug (the 0.6.0 PATHEXT work was unrelated).
  The import is now `#[cfg(unix)] use super::*` to match the tests it serves.

## [0.6.0] - 2026-07-11

### Added — Cross-platform CI matrix

- **CI now runs on ubuntu-latest, macos-latest, and windows-latest**, not
  just ubuntu. The matrix uses the official `setup-node`, `setup-python`,
  `setup-go`, and `ruby/setup-ruby` actions so every OS gets working runtimes
  without hand-rolled apt/choco/brew logic. A previously-unchecked Windows
  build regression would have shipped undetected.

### Fixed — Windows PATH resolution (PATHEXT)

- **`which_on_path` now enumerates `PATHEXT` on Windows**. `cmd.exe` appends
  `PATHEXT` (`.EXE;.CMD;.BAT;…`) to a bare name; Rust's `Command::new` does
  not, so probing `"node"` missed `node.exe` and `has_cli` silently reported
  `false`. The probe now checks `name` plus `name{ext}` for each PATHEXT
  entry on Windows; Unix path lookup is unchanged.
- **Three CLI candidates discarded the resolved binary path and re-spawned
  the bare name**, which defeats PATHEXT resolution even when `which_on_path`
  finds the binary. `rust_cli_candidate`, `python_cli_candidate`, and
  `ruby_cli_candidate` now use the resolved `PathBuf` as `argv[0]` (the
  `node_cli_candidate` pattern), so the spawn finds the real
  `node.exe`/`python.exe`/`ruby.exe` on Windows instead of failing with
  `NotFound`. The README "Platform" caveat is retired — Windows now works.

### Tests

- `which_on_path_returns_existing_file` sanity-checks that a PATH lookup
  returns a real file on every CI OS. The PATHEXT-specific branch is
  exercised end-to-end by the new windows-latest CI matrix entry against
  real `cmd.exe` / `node.exe` lookups, not a racy synthetic env mutation.

## [0.5.1] - 2026-07-11

### Fixed — Template correctness (BLOCKER)

- **Double-escaped `one_line_description` in generated output**: the YAML-escaped
  value was reused in markdown bodies and JSON templates, producing literal `\"`
  in skill files and marketplace/plugin JSON. The render context now exposes two
  keys: `one_line_description` (YAML-escaped, frontmatter only) and
  `one_line_description_raw` (raw, used in markdown bodies + JSON). Both JSON
  templates pipe through `json_encode | safe`; the markdown templates emit raw.
- **Dangling sentences from `{%-` trim markers**: all four markdown templates
  (`SKILL.md`, `cursor-rule.mdc`, `opencode-agent.md`, `copilot-instructions.md`)
  used Tera `{%-`/`-%}` whitespace-trim markers that over-trimmed and glued
  headings to preceding content. Rewritten to bare `{% if %}` tags on own lines
  (no trim markers); the tag emits nothing but its source-line `\n` survives as
  exactly one blank line. Inline tags (`{% if cond %}content{% endif %}`) on the
  same line as content emit no extra newlines.
- **Empty sections rendered when phrases/flags absent**: the "When to use"
  section now wraps in `{% if when_to_use_phrases %}...{% endif %}` and the
  flags intro+loop wraps in `{% if documented_flags %}...{% endif %}` across
  all four markdown templates. A library with no `when_to_use_phrases` no longer
  emits a "When to use" heading with an empty bullet list.

### Fixed — Introspection bugs

- **`HELP_TIMEOUT` divergence**: `introspect.rs` used 5s while
  `verify::invocation` used 8s — a CLI that printed help in 6s passed verify but
  had its flags dropped during `init` introspection (silent false-negative).
  Unified to a single `pub const HELP_TIMEOUT: Duration = Duration::from_secs(8)`
  in `spawn.rs`; both call sites import it.
- **Go module line trailing comment bleed**: `project_manifest_name` for Go
  trimmed outer whitespace only, so `module github.com/foo/bar // bar tool`
  produced a module name of "tool" (or worse) by taking the last `/`-segment
  of the comment-bleeded path. Now takes the first whitespace-delimited token
  before splitting, correctly yielding "bar". New test:
  `go_module_name_strips_trailing_line_comment`.
- **Workspace walk early-return on nameless member**: `walk_cargo_workspace`
  and `walk_npm_workspace` used `?` on the `member_name` resolution, so a
  single member manifest with no resolvable name aborted the entire walk —
  hiding CLI detection for every *other* member and silently reporting
  `has_cli=false`. Replaced with `let ... else { diag.push(...); continue; }`
  so the walk skips the pathological member and continues. New tests:
  `walk_cargo_workspace_continues_past_no_artifact_member`,
  `walk_npm_workspace_continues_past_no_cli_member`.
- **`detect_repo_url` git hang**: spawned `git remote get-url origin` with no
  timeout — a credential-prompt stall (`git@github.com` with no SSH agent on a
  fresh host) blocked introspection indefinitely. Routed through
  `spawn_with_timeout(&mut cmd, Duration::from_secs(3))`; `RanClean` returns
  the trimmed URL, all other `SpawnOutcome` variants return `None`.

### Fixed — Verify discovery sentinels

- **Empty ecosystem directories passed silently**: a `.codex/skills/` directory
  that existed but contained no `SKILL.md` (e.g. files removed after init)
  passed verify with no check emitted — a silent false-negative. Added
  `.missing` fail sentinels for empty `.codex/skills/`, `.cursor/rules/`, and
  `.opencode/agents/` directories. New check ids:
  `discovery.codex.skill.missing`, `discovery.cursor.mdc.missing`,
  `discovery.opencode.agent.missing`. New test:
  `codex_empty_skills_dir_fails_verify`.
- **Copilot frontmatter passed verify**: a `.github/copilot-instructions.md`
  starting with a `---` frontmatter block passed verify (the existing heading
  check fired on the YAML content). The Copilot spec says "plain markdown, no
  frontmatter" — now a `---` prefix triggers
  `discovery.copilot.instructions` at fail severity before the heading check.
  New test: `copilot_frontmatter_fails_verify`.

### Added — Test coverage

- **All-5-targets round trip**: `all_five_targets_init_then_verify_round_trip`
  asserts `init --target claude --target cursor --target codex --target opencode --target copilot`
  emits all 5 files and `verify` passes with all 5 `discovery.*` check ids at
  `pass` severity.
- **Discovery.empty on bare repo**: `verify_on_empty_repo_fails_with_discovery_empty`
  asserts a repo with no ecosystem files emits `discovery.empty` at fail severity.
- **Self-dogfood all ecosystems**: `self_dogfood_verify_asserts_all_ecosystems`
  runs `verify` against the skillpack repo itself and asserts all 6 check ids
  (marketplace, skill, cursor.mdc, codex.skill, opencode.agent, copilot.instructions) pass.
- **Doctor on plain Rust CLI**: `doctor_on_plain_rust_cli_reports_has_cli_true`
  builds the `rust-cli` fixture, runs `doctor`, and asserts `has_cli:  true`.

## [0.5.0] - 2026-07-10

### Added — `doctor` subcommand

- **`skillpack doctor`** — diagnose why introspection chose `has_cli` / language
  as it did. Prints the detected profile + a chronological decision trace
  (every falsy branch in `detect_cli` / `detect_language` pushes a `DiagNote`
  explaining why a candidate was rejected). Read-only, exits 0. Answers the
  user's "what happens with monorepos / workspaces / uv / poetry?" question
  directly: the trace explains the workspace walk and the uv/poetry gap.

### Added — Workspace member walking

- **Cargo workspace support**: a workspace-only `Cargo.toml` (no `[package]`)
  no longer silently reports `has_cli=false`. Introspection now walks
  `[workspace].members`, probes each member crate's built/installed binary,
  and derives the profile name from the member that ships the CLI.
- **npm workspace support**: a root `package.json` with `workspaces` but no
  `bin` walks member packages, probing each for a `bin` entry.
- **uv / poetry**: explicitly diagnosed as "member walking not yet
  implemented — run `skillpack init` in the member dir" (honest gap, no
  false promise).

### Added — OpenCode + GitHub Copilot targets

- **`--target opencode`**: emits `.opencode/agents/<name>.md` with
  `description` (required) + `mode` frontmatter per opencode.ai/docs/agents.
  `verify` validates the frontmatter (`discovery.opencode.agent.*` check ids).
- **`--target copilot`**: emits `.github/copilot-instructions.md` (plain
  markdown, no frontmatter) per docs.github.com/copilot. `verify` checks
  the file is non-empty and starts with a `#` heading
  (`discovery.copilot.instructions` check id).
- The repo's own committed distribution now dogfoods all 5 ecosystems.


## [0.4.0] - 2026-07-10

### Added — Multi-ecosystem verify (design §3 Phase 4)

- **`verify` now checks the Cursor and Codex distribution files**, not just
  Claude's. Previously `init --target cursor --target codex` emitted those
  files but `verify` silently ignored them — a broken `.mdc` or Codex
  `SKILL.md` would pass `verify` and ship undetected. The discovery suite
  now runs per-ecosystem:
  - **Cursor** (`.cursor/rules/<name>.mdc`): new `check_one_mdc` parses the
    YAML frontmatter and fails on a missing/empty `description` or a
    description over the 1,536-char listing cap, warns on a missing or
    non-boolean `alwaysApply`. Schema sourced from cursor.com/docs/rules
    (verified July 2026).
  - **Codex** (`.codex/skills/<name>/SKILL.md`): reuses the existing
    `check_one_skill_md` validator with a distinct `discovery.codex.skill`
    check_id prefix — same `SKILL.md` frontmatter shape as Claude, different
    output path.
  - Check ids are namespaced per ecosystem (`discovery.skill.*`,
    `discovery.codex.skill.*`, `discovery.cursor.mdc.*`) so a CI report
    names *which* ecosystem file drifted.

### Fixed

- **`verify` no longer hard-fails on a missing `.claude-plugin/`** — the
  `marketplace.json` / `plugin.json` checks now run only when the Claude
  distribution directory is present. A `--target cursor`-only pack
  (legitimately without `.claude-plugin/`) was previously blocked by a
  false positive `discovery.marketplace.missing` failure; `verify` now
  skips the Claude checks entirely. An empty repo (no ecosystem files at
  all) still emits a single `discovery.empty` failure so a typo'd `verify`
  doesn't silently pass.

### Notes

- The human `verify` report now prints the per-file message for passing
  checks (previously suppressed), so multi-ecosystem passes are visually
  distinguishable (e.g. `skills/skillpack/SKILL.md validates` vs
  `.codex/skills/skillpack/SKILL.md validates`). JSON output was already
  per-file.
- Schema constants live in `src/verify/schema.rs` with cited sources:
  `CLAUDE_PLUGIN_DIR`, `CODEX_SKILLS_DIR`, `CURSOR_RULES_DIR` join the
  existing `PLUGIN_JSON_PATH` / `MARKETPLACE_JSON_PATH`.
- Invocation drift still runs against the first documented CLI's
  SKILL.md only (design §5.2 step 3, unchanged from 0.2.1). Cursor/Codex
  bodies are identical to the Claude SKILL.md body (same template), so a
  Claude invocation-drift pass transitively validates the parallel
  ecosystem files — per-ecosystem invocation drift is deferred to when a
  pack legitimately ships divergent CLI surfaces per ecosystem.

## [0.3.0] - 2026-07-10

### Added — Multi-ecosystem emitter (design §3 Phase 4)

- **`--target` flag on `init`** — generate distribution files for multiple
  agent ecosystems in a single run. Repeatable: `--target claude --target
  cursor --target codex`. Defaults to `claude` only (backward compatible).
  New `Target` enum (`Claude`, `Cursor`, `Codex`) drives `render_targets`,
  which dispatches per ecosystem:
  - **Claude** (unchanged): `.claude-plugin/marketplace.json` +
    `plugin.json` + `skills/<name>/SKILL.md`.
  - **Cursor**: `.cursor/rules/<name>.mdc` — YAML frontmatter
    (`description`, `alwaysApply: false`) + rule body. Matches the Cursor
    `.mdc` format documented at cursor.com/docs/rules.
  - **Codex**: `.codex/skills/<name>/SKILL.md` — same `SKILL.md` frontmatter
    as Claude (cross-agent-compatible), different output path per Codex's
    `.codex/skills/` convention.
- **Self-dogfood** now generates all three: the repo is installable as a
  Cursor rule (`@skillpack`) and a Codex skill (`.codex/skills/skillpack/`)
  in addition to `claude plugin marketplace add nordicnode/skillpack`.

### Notes

- `verify` remains Claude-only for V1; cursor/codex files are emitted but
  not yet checked by the discovery/invocation suite. Multi-ecosystem verify
  is a follow-up.
- `--target` is CLI-only (not persisted in `skillpack.toml`); targets are a
  per-run choice, not project metadata.

## [0.2.5] - 2026-07-10

### Fixed

- **`plugin.json` author defaulted to `Unspecified`** — same drift class as
  the version fix in 0.2.4: `generate` fell back to the template's
  `"Unspecified"` sentinel whenever the interview / `skillpack.toml` didn't
  supply an author, ignoring the manifest entirely. `introspect` now reads
  the first author from `Cargo.toml [package].authors`, `package.json
  "author"` (string or `{ name }` object), `pyproject.toml [project].authors`,
  and `*.gemspec spec.authors`. `generate` resolves `intent.author` with
  `profile.authors` as fallback, so a non-interactive `init` (the CI path,
  where no interview happens) still emits a real author. The Cargo
  `"Name <email>"` format is stripped to the display name only — `author.name`
  in the plugin schema is a display name, not a contact record.

### Added

- **`discovery.plugin.author` verify check** — warns when `plugin.json` has
  no author or defaults to `"Unspecified"`, with a suggestion pointing at
  the manifest key to set. Parallel to `discovery.plugin.version` (0.2.4).

## [0.2.4] - 2026-07-10

### Fixed

- **`plugin.json` version hardcoded `0.1.0`**`generate::build_context`
  emitted a literal `"0.1.0"` regardless of the project's real version. An
  agent installing via a marketplace would see the wrong version signal for
  every skill pack `skillpack` produced — the exact class of distribution-
  layer drift `verify` exists to catch, yet `verify` itself never checked the
  field. `introspect` now reads the version from the language manifest
  (`Cargo.toml [package].version`, `package.json "version"`,
  `pyproject.toml [project].version`, `*.gemspec spec.version`), stores it
  on `ProjectProfile`, and `generate` passes it through to the template. Go
  (`go.mod` has no version field) and manifests lacking a version key yield
  an empty `version` rather than a fake sentinel — the honest signal.

### Added

- **`discovery.plugin.version` verify check** — warns on a missing or empty
  `version` field in `plugin.json`, with a suggestion pointing at the
  manifest key to set. Surfaced by the self-dogfood below: the old code
  shipped `0.1.0` for a `0.2.3` crate and `verify` never flagged it.
- **Self-dogfood**`skillpack` now generates and verifies its own skill
  pack (`skills/skillpack/SKILL.md`, `.claude-plugin/marketplace.json`,
  `.claude-plugin/plugin.json`) via a committed `skillpack.toml`. Closes the
  Phase-1 §10 "use it on yourself" gap; the repo is now installable as
  `claude plugin marketplace add nordicnode/skillpack`.

## [0.2.3] - 2026-07-10

### Fixed

- **Pipe-deadlock on >64KB `--help`** — subprocess spawns in `introspect`
  (`spawn_with_timeout`), `verify::invocation` (`run_help` + `spawn_capture`)
  piped stdout/stderr but only drained pipes *after* the child exited. A CLI
  whose help output exceeded the ~64KB pipe buffer would block on the write,
  `try_wait` would keep returning `Ok(None)`, and the deadline fired → false
  `TimedOut`. Extracted a shared `spawn::run` helper that drains pipes on
  reader threads while polling, killing + reaping on timeout. The new boundary
  replaces the hand-rolled poll loops in all three call sites.
- **Byte-slice panic on multibyte README hints**`print_profile` (`--verbose`)
  truncated the description hint with `&hint[..120]`, slicing by byte index.
  A multibyte UTF-8 char across byte 120 (emoji, CJK, accented — common in
  real OSS) panicked with "byte index 120 is not a char boundary" →
  `catch_unwind` → false `INIT_FATAL` exit for a display-only path. Fixed to
  truncate by chars: `hint.chars().take(120).collect::<String>()`.
- **`coerce_kebab` leading-digit names** — names starting with digits (e.g.
  `"123foo"`) passed through unchanged, but the schema regex
  `^[a-z][a-z0-9-]*[a-z0-9]$` requires a letter first. The generated
  `marketplace.json` / `plugin.json` name would then fail `verify`'s own
  `is_valid_kebab` check. Now strips leading digits + re-trims hyphens,
  falling back to `"tool"` if the result is empty.

### Added

- **Subcommand-drift e2e coverage** — the `capture_subcommand_help` (introspect)
  and `check_subcommand_drift` (verify) code paths — real spawn reassembly of
  `<base> <sub> --help` — were reasoned about but never exercised against a
  compiled CLI (every fixture set `cli_subcommand_help: Vec::new()`). Added a
  zero-dep `subcommand-cli` fixture with a hand-rolled clap-shaped `Commands:`
  section + per-subcommand `--help`, and an integration test asserting the
  generated `SKILL.md` contains `### Subcommands` with real sub names/flags and
  `verify --format json` emits `invocation.subcommand_drift` pass results.

## [0.2.2] - 2026-07-09

### Fixed

- Generated `SKILL.md` "When NOT to use" prose no longer reads as broken
  grammar ("outside the systems programming it was built for") — `category_hint`
  now yields a noun per language ("the Rust tooling", "the JavaScript/Node
  tooling", etc.).
- `verify --format json` now reports per-check `severity: "pass"` for passes,
  matching the `counts.pass` key. Previously it emitted `"ok"` for a passed
  check while the aggregate `counts` object used `pass` — two words for one
  concept in the same payload.
- Removed the dead `has_cli` field from `VerifyInput` / `InvocationInput`.
  CLI *presence* is derived from the SKILL.md itself, not the introspected
  binary, so the field was threaded in but never read.

## [0.2.1] - 2026-07-09

### Fixed

- `init` now distinguishes its exit codes per design §8.1: a fixable
  verify critical that the user declines to keep exits `INIT_FIXABLE` (2),
  not the clean-abort `INIT_ABORTED` (1); fatal errors (introspect / render /
  I/O) still exit `INIT_FATAL` (3). Previously the interactive decline path
  returned 1, collapsing the fixable/fatal contract.
- Removed a redundant second `wait` in both spawn helpers
  (`introspect::spawn_with_timeout`, `verify::invocation::run_help`): the
  piped stdout/stderr are drained by a single `wait_with_output`, whose exit
  status is used rather than re-reading the one `try_wait` already probed.

### Changed

- The invocation check is documented (README + design §5.2) as running
  against the first documented CLI; discovery checks still cover every
  `SKILL.md` in a multi-skill plugin. `init` only ever emits one skill, so
  per-skill spawn plumbing is deferred until a CLI-backed multi-skill plugin
  is a real case.

## [0.2.0] — 2026-07-09

### Fixed

- `init` no longer emits a `when_to_use: "(unspecified)"` placeholder when no
  trigger phrases are given — it emits an empty `when_to_use`, so `verify`'s own
  emptiness warning now fires honestly. Previously a generated skill with no
  triggers would pass `verify` silently (the "looks fine until an agent tries
  it" failure mode).
- `verify`'s invocation/flag-drift check now derives CLI presence from the
  `SKILL.md` itself (a `## Invocation` section, or a fenced block with
  `--flags`), not from whether introspect found a built binary on the local
  machine. Hand-written packs that document a CLI but ship no source tree now
  get a visible warning instead of a silent "pure-library" skip.
- `--debug` now prints every subprocess spawn in `verify` (it was previously a
  no-op for the `verify` subcommand).
- `--accept-warnings` now matches its docs: in interactive mode, `verify`
  warnings prompt before writing; `--accept-warnings` skips the prompt.
  `--non-interactive` warnings never block (CI gates on criticals only).
- The `property_proptest.rs` placeholder is now a real property test exercising
  `extract_flags` and `parse_skill_frontmatter` directly (the lib re-exports it
  already exposed).

### Added

- `verify --format json`: machine-readable report with per-check ids, counts,
  and an `ok` flag, for CI gating / scripting.
- Reverse flag-drift warning: `--help` flags the skill doesn't document are
  reported as a warning (discoverability gap for an agent).
- Multi-skill verification: `verify` checks every SKILL.md under `skills/`
  (sorted, deterministic) instead of an arbitrary first one.
- `--format`, scoped flag-drift extraction (the documented invocation area,
  not the whole body), and a testable confirmation path for the pre-commit gate.

### Changed

- `tempfile` is now a runtime dependency; the hand-rolled `mod tempfile` in
  `main.rs` is removed.

## [0.1.0] — 2026-07-09

### Added

- `skillpack init` — introspect a repo (Rust, npm, Python, Go, Ruby), interview
  the maintainer (or run non-interactive from `skillpack.toml`), and generate
  the three Claude Code distribution files (`marketplace.json`, `plugin.json`,
  `SKILL.md`). Pure-library path when no CLI is detected (documents the install
  + import pattern instead of an invocation).
- `skillpack verify` — discovery checks (kebab-case + reserved-name validation,
  1,536-char listing cap, `when_to_use` trigger phrases, `./`-prefixed paths)
  and invocation checks (`--help` runs under timeout; every documented flag
  exists in real `--help` output). Exits non-zero on critical failure for use
  as a CI PR gate.
- Pre-commit verification: `init` runs the full `verify` suite against its own
  output before writing files.
- `--non-interactive`, `--accept-warnings`, `--license <SPDX>`, `--verbose`,
  `--debug` flags.
- GitHub Actions CI: `cargo fmt --check`, `cargo clippy --all-targets -D
  warnings`, and `cargo test -- --include-ignored` (runs the Go + Ruby
  `#[ignore]`-gated round trips where those runtimes are present).
- Insta snapshot tests pinning byte-identical output of all three generated
  files for CLI and pure-library profiles.
- Test fixtures for rust, node, python, go, ruby CLIs and a node library;
  a `broken-cli` fixture for drift detection.

### Toolchain

- Pin Rust 1.95.0 via `rust-toolchain.toml` so local and CI fmt/clippy agree.