@hasna/skills 0.10.72 → 0.10.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -113,6 +113,27 @@ skills hook install --selection-profile default --json
113
113
  skills hook install --selection-profile default --apply --json
114
114
  ```
115
115
 
116
+ `--include-vendor` resolves configured plugin discovery, so it needs a reviewed
117
+ `--discovery-inputs` file when a configured plugin declares hooks. Without it,
118
+ `skills migrate native --include-unmanaged` archives user and project copies
119
+ only. Migration never archives Codex system skills (`.codex/skills/.system`),
120
+ even with `--include-vendor`. Codex refreshes its OpenAI marketplace plugin
121
+ caches on its own; retire those copies with a Codex `[[skills.config]]` rule
122
+ (`name = "<plugin>:<skill>"` or the exact `path` of the `SKILL.md`, with
123
+ `enabled = false`) rather than by archiving. The live Codex prompt hook accepts
124
+ a name rule only for a copy with no Hasna marker, its remote install receipt, a
125
+ refresh-safe plugin and no rule that enables it. Other checks (`skills hook
126
+ install` reusing a saved review, `skills hook trust`) do not accept a
127
+ hand-added name rule; for them, and for system skills, run `skills hook install
128
+ --agent codex --discovery-inputs <file> --apply` with a fresh discovery review
129
+ (no `--discovery-inputs` on an automatic binding; add a fresh
130
+ `--codex-native-catalog <file>` for a plugin Skills has enrolled), which writes
131
+ and records the disables. Run the same refresh after editing `config.toml` when
132
+ the binding's recorded configuration witness counts the edit: on automatic
133
+ bindings, after removing a rule that enables skills, and on reviewed bindings
134
+ whose witness counts disable rows. A `NATIVE_SKILL_DRIFT` refusal names the
135
+ step for each copy it lists, including that follow-up.
136
+
116
137
  Without `--agent`, hook installation selects configured or detected consumers:
117
138
  existing managed bridge bindings, provider-specific home configuration, or an
118
139
  installed native CLI found through PATH. Detection reads metadata and never
@@ -264,8 +285,8 @@ discovery-witness changes such a refresh provably makes:
264
285
  unknown keys refuse. There is no `hooks`, `commands` or `agents` directory, no
265
286
  `hooks.json` or `.mcp.json` (names compared without regard to case), no root,
266
287
  `.claude-plugin` or `.cursor-plugin` manifest, no link
267
- and no `SKILL.md` that the inventory cannot see (below a dot-named or
268
- `node_modules` directory, or nested in another skill). One exception keeps
288
+ and no `SKILL.md` below a dot-named or `node_modules` directory, or nested
289
+ in another skill. One exception keeps
269
290
  today's vendor plugins working: every current OpenAI vendor plugin declares
270
291
  its connector as `apps = "./.app.json"`, so exactly that declaration is
271
292
  allowed when `.app.json` is a regular file of at most 1 MiB, strict UTF-8, that
@@ -541,6 +562,20 @@ is bounded to 20,000 discovery entries and 4 MiB of UTF-8 path metadata before
541
562
  directory entries are retained or sorted. Unsupported special files and unsafe
542
563
  directory links are refused.
543
564
 
565
+ Codex 0.162.1 loads `SKILL.md` files below `node_modules` inside its skill
566
+ roots, so Codex inventory and the prompt guard walk `node_modules` in
567
+ `~/.codex/skills`, `~/.agents/skills`, each project's `.codex/skills` and
568
+ `.agents/skills` and configured plugin skill roots. In `~/.codex/plugins/cache`
569
+ they walk it only where it can lie in a plugin skill root: below a version's
570
+ default `skills` directory or a manifest-declared skills path, or as a
571
+ marketplace, plugin or version directory named `node_modules`; an unreadable
572
+ manifest walks every `node_modules` in its version. Dot-named directories such
573
+ as `node_modules/.pnpm`, a plugin version's own dependencies and any
574
+ `node_modules` outside a skill root stay unscanned, because Codex does not load
575
+ them; other agents' roots keep skipping `node_modules`. Depth limits are
576
+ unchanged: Codex reads six directory levels below a root, and an ordinary root
577
+ records a skill four levels down and refuses anything deeper.
578
+
544
579
  Native archives persist a version 2 recovery journal before moving payloads;
545
580
  `migrate native --json --apply` returns its `receiptPath`. The journal records
546
581
  every source, archive path, expected hash, and move status, then marks successful
@@ -702,6 +737,25 @@ receipt permits only verified registry/cache transitions. See
702
737
  [plugin admission](docs/plugin-admission.md) for the package, review and runtime
703
738
  contracts; these commands do not change live agent registrations or settings.
704
739
 
740
+ The read-only `skills hook review-claude-candidate` check requires its reviewed
741
+ discovery inputs to bind the user settings, both project settings files and
742
+ both plugin registries. A project `.claude/settings.json` or
743
+ `.claude/settings.local.json` that the review omits is exempt only when no other
744
+ expected source shares its path and it is either absent, or present with exact
745
+ preimage bytes that parse strictly as one JSON object (strict UTF-8, no
746
+ byte-order mark, comments, trailing content or duplicate keys) and carry only
747
+ allowlisted settings: `permissions` with `allow`, `deny`, `ask` and
748
+ `defaultMode`, plus `outputStyle`, `model`, `theme`, `effortLevel`, `verbose`
749
+ and `spinnerTipsEnabled`. Any discovery key (`enabledPlugins`,
750
+ `extraKnownMarketplaces`, `additionalMarketplaces`, `skillOverrides`), `env`,
751
+ `hooks`, `permissions.additionalDirectories` or unknown key keeps the file
752
+ required. The allowlist is a reviewed list of settings, not a proof that Claude
753
+ cannot discover anything from the file. This admits the permissions-only
754
+ `~/.claude/settings.local.json` that Claude writes when it saves a permission
755
+ rule. Each exempt file is re-checked against its exact preimage until the
756
+ result; user settings and the registries are never exempt. See
757
+ [prospective Claude candidate review](docs/plugin-admission.md#prospective-claude-candidate-review).
758
+
705
759
  If your home `.claude` or `.codex` directory intentionally links to another
706
760
  directory within your home, add `--allow-root-aliases` to hook installation and
707
761
  native migration. The plan records and rechecks the exact link and target;
@@ -1009,6 +1063,209 @@ reports an incomplete outcome, inspect `sessions show` and the preserved
1009
1063
  operation receipt before retrying. The archive contains both original and
1010
1064
  prepared bytes even if the final receipt write fails.
1011
1065
 
1066
+ ### Claude installer witness CLI (`capture-claude-installer`)
1067
+
1068
+ ```text
1069
+ skills hook capture-claude-installer --request /absolute/request.json --json
1070
+ ```
1071
+
1072
+ Installers obtain Skills-owned witnesses through the configured Skills command
1073
+ instead of reimplementing them. Every request is a JSON object of at most
1074
+ 64 KiB with exactly the keys of one operation: `schema`
1075
+ (`skills.claude-installer-capture/v1`), `commandPath` (the normalized absolute
1076
+ path of the configured Skills command), `operation`, and that operation's
1077
+ inputs. The command checks that `commandPath` resolves to this installed
1078
+ `@hasna/skills` CLI and runtime, before and after the operation, and prints
1079
+ one receipt on stdout with exit status 0:
1080
+
1081
+ ```json
1082
+ {
1083
+ "schema": "skills.claude-installer-capture-result/v1",
1084
+ "producer": {
1085
+ "schema": "skills.cli-producer-identity/v1",
1086
+ "configuredCommandPath": "<commandPath>",
1087
+ "physicalLauncher": { "path": "<absolute>", "sha256": "<64 hex>" },
1088
+ "runtimeEntry": { "path": "<absolute>", "sha256": "<64 hex>" },
1089
+ "runtime": { "path": "<absolute>", "sha256": "<64 hex>" },
1090
+ "package": { "name": "@hasna/skills", "version": "<x.y.z>", "manifestSha256": "<64 hex>" }
1091
+ },
1092
+ "operation": "<operation>",
1093
+ "result": {}
1094
+ }
1095
+ ```
1096
+
1097
+ A refusal prints nothing on stdout, exits 1 and writes one line to stderr,
1098
+ `CODE: fixed text`, which never echoes request or file contents. The
1099
+ per-file operations (`settings`, `marketplace-registry`,
1100
+ `managed-plugin-registry`, `path-bytes`, `discovery-directories`,
1101
+ `plugin-manifest` and `candidate`) are described in
1102
+ [docs/plugin-admission.md](docs/plugin-admission.md#claude-installer-witness-cli).
1103
+
1104
+ #### Verify the stored Claude discovery binding (`discovery-binding`)
1105
+
1106
+ `discovery-binding` checks the Claude discovery binding stored in the Skills
1107
+ policy (`bridge.discovery.claude` in `agent-policy.json`) with the discovery
1108
+ verification the managed prompt hook runs on it: the root alias recheck,
1109
+ `verifyAgentDiscovery` and, for an automatic binding, the automatic closure
1110
+ check. It then reports every stored witness. An installer calls it once
1111
+ instead of re-capturing each stored witness. Per-file capture requires every
1112
+ file to exist, so it refuses witnesses that Skills keeps on purpose after
1113
+ Claude deletes a retired plugin-cache version; this operation reports those as
1114
+ `retained-absent`. It does not check the bridge files, hook commands or
1115
+ project-directory settings, which the hook checks separately.
1116
+
1117
+ The request has exactly these keys:
1118
+
1119
+ ```json
1120
+ {
1121
+ "schema": "skills.claude-installer-capture/v1",
1122
+ "commandPath": "/home/me/.local/bin/skills",
1123
+ "operation": "discovery-binding",
1124
+ "policyPath": "/home/me/.hasna/skills/agent-policy.json"
1125
+ }
1126
+ ```
1127
+
1128
+ `policyPath` must equal `agent-policy.json` in the data directory the installed
1129
+ package resolves (`$HASNA_SKILLS_DIR`, default `~/.hasna/skills`), as for the
1130
+ `candidate` operation.
1131
+
1132
+ The receipt's `result`:
1133
+
1134
+ ```json
1135
+ {
1136
+ "schema": "skills.claude-discovery-binding-verification/v1",
1137
+ "ok": true,
1138
+ "policySha256": "<64 hex>",
1139
+ "discoverySha256": "<64 hex>",
1140
+ "agent": "claude",
1141
+ "method": "reviewed",
1142
+ "rootCount": 2,
1143
+ "sources": [
1144
+ { "index": 0, "path": "/home/me/.claude/settings.json", "hashMode": "claude-settings-v4", "format": null, "sha256": "<64 hex>", "status": "current" },
1145
+ { "index": 3, "path": "/home/me/.claude/plugins/cache/market/plugin/1.0.0/.claude-plugin/plugin.json", "hashMode": "claude-plugin-manifest-v1", "format": null, "sha256": "<64 hex>",
1146
+ "status": "retained-absent", "retention": { "rule": "absent-retired-claude-version", "root": "/home/me/.claude/plugins/cache/market/plugin/1.0.0", "witness": "claude-plugin-manifest-v1" } }
1147
+ ],
1148
+ "directories": [ { "index": 0, "path": "/home/me/.claude/plugins/cache/market/plugin/2.0.0", "status": "current" } ],
1149
+ "failures": []
1150
+ }
1151
+ ```
1152
+
1153
+ - `policySha256` is the SHA-256 of the exact policy file bytes that were
1154
+ verified. It is the value a prospective review request passes as
1155
+ `skills.policySha256`. The policy is read again before the result is
1156
+ returned; if it changed, the request is refused.
1157
+ - `discoverySha256` is `hashClaudeDiscoveryBinding` of the stored binding, the
1158
+ same digest the `candidate` operation returns.
1159
+ - `agent`, `method` (`reviewed` or `automatic`) and `rootCount` (the number of
1160
+ stored discovery roots) come from the stored binding.
1161
+ - `sources` has one row per stored source, in stored order. `index` is the
1162
+ position in `bridge.discovery.claude.sources`. `path`, `hashMode`, `format`
1163
+ and `sha256` repeat the stored values: `hashMode` is `null` for a legacy
1164
+ full-file witness without a mode, `format` is `null` unless the source is a
1165
+ field projection (Claude reviews also keep the automatic `json` projection of
1166
+ `settings.json`), and a `null` `sha256` pins absence. `status` is one of:
1167
+ - `current`: the source's own witness reproduces the stored digest now.
1168
+ - `retained-absent`: the file was not read, because the
1169
+ `absent-retired-claude-version` rule keeps its stored digest. The rule
1170
+ holds when the whole version directory `retention.root`
1171
+ (`<...>/.claude/plugins/cache/<marketplace>/<plugin>/<version>`) is absent,
1172
+ the reviewed witnesses of `settings.json` and
1173
+ `plugins/installed_plugins.json` are current, settings enable the plugin
1174
+ and no settings hook names that directory, every registration of the
1175
+ plugin is a valid user or project row for one existing sibling version in
1176
+ the same plugin directory (with a user row among them), no registration of
1177
+ any plugin points into or contains the absent directory, and the sibling's
1178
+ `.claude-plugin/plugin.json` is bound and current. It applies only to
1179
+ reviewed bindings. `retention.witness` names the retained witness class:
1180
+ `bytes` (a full byte witness, `hashMode` `null` or `bytes`),
1181
+ `claude-plugin-manifest-v1` or `claude-plugin-orphaned-at-v1` (the typed
1182
+ presence witness of a version-root `.orphaned_at` marker). No other
1183
+ witness is retained: `path-bytes`, field projections and typed settings
1184
+ or registry witnesses stay checked. Settings and registry files are
1185
+ never retained.
1186
+ - `drift`, with `reason`: `MISSING` (the path is absent and the rule does
1187
+ not retain it), `APPEARED` (the stored witness pins absence and the path
1188
+ now exists), `CHANGED` (the path exists but its witness does not
1189
+ reproduce the stored digest or refuses the current file, for example
1190
+ edited settings or a symlink), `MARKER_INVALID` (a
1191
+ `claude-plugin-orphaned-at-v1` marker exists but is not a valid Claude
1192
+ marker; see below) or `REFUSED` (the path's presence could not be
1193
+ determined).
1194
+ - `directories` has one row per stored directory membership witness, with
1195
+ `status` `current`, or `drift` with `reason` `MEMBERSHIP_CHANGED` or
1196
+ `REFUSED`.
1197
+ - `failures` lists every drifted source and directory as
1198
+ `{ "kind": "source" | "directory", "index", "path", "reason" }`, followed by
1199
+ `{ "kind": "binding", "reason": "DISCOVERY_REFUSED" }` whenever the hook's
1200
+ own discovery verification refuses the binding. That covers checks no single
1201
+ row shows, such as a changed root alias, a retired directory that reappeared
1202
+ during verification or an invalid witness combination.
1203
+ - `ok` is `true` only when `failures` is empty: the hook's verification
1204
+ accepts the binding, every source is `current` or `retained-absent` and
1205
+ every directory is `current`.
1206
+
1207
+ `ok: false` is a completed verification and still exits 0, so the caller
1208
+ reads which witnesses drifted and why. Installers must require `ok === true`.
1209
+ To bind the result, compare each `sources` row (`index`, `path`, `hashMode`,
1210
+ `format`, `sha256`) with the binding they read themselves, compare
1211
+ `discoverySha256` with the `candidate` capture and `policySha256` with the
1212
+ policy bytes they review, and repeat the call immediately before the first
1213
+ native change, because the verdict describes one moment.
1214
+
1215
+ Claude writes `.orphaned_at` at the root of a plugin-cache version it plans to
1216
+ delete (`<...>/.claude/plugins/cache/<marketplace>/<plugin>/<version>/.orphaned_at`),
1217
+ rewrites its timestamp at each start, may replace the file, and later deletes
1218
+ the marker or the whole version. A fresh Skills discovery review binds such a
1219
+ marker as `claude-plugin-orphaned-at-v1`, a witness of its presence only, and
1220
+ `discovery-binding` reports it with that witness's own check, exactly as the
1221
+ hook does:
1222
+
1223
+ - A rewritten timestamp or a replaced file (new inode) is `current`, provided
1224
+ the marker is a regular, non-symlink file owned by the current user whose
1225
+ content is exactly 13 ASCII digits without a leading zero (no byte-order
1226
+ mark, no newline).
1227
+ - Any other content, a symlink or another owner is `drift` with
1228
+ `MARKER_INVALID`.
1229
+ - A deleted marker whose version directory still exists is `drift` with
1230
+ `MISSING`; the hook refuses this state too.
1231
+ - When Claude deletes the whole version directory and the registered sibling
1232
+ version stays bound and current, the marker and every other retainable
1233
+ witness below that directory are `retained-absent` under
1234
+ `absent-retired-claude-version`.
1235
+ - A recreated version directory is no longer absent, so each of its witnesses
1236
+ is checked again: a valid marker and an unchanged manifest are `current`,
1237
+ and a missing marker is `drift` with `MISSING`.
1238
+ - Marker witnesses stored by an older review as `bytes` or `path-bytes` keep
1239
+ their exact meaning and report `CHANGED` once Claude rewrites the marker,
1240
+ until a fresh discovery review rebinds them as typed witnesses.
1241
+
1242
+ Installers must not restore, copy back or rewrite `.orphaned_at` files, or any
1243
+ other witness file, to make a binding verify. A restored file has a new inode,
1244
+ which a `path-bytes` witness rejects, and it can hide a change Claude made. Act
1245
+ on the `discovery-binding` result instead. When it reports drift, the binding
1246
+ needs a fresh Skills discovery review (`skills hook install` with fresh
1247
+ `--discovery-inputs`), not a file repair.
1248
+
1249
+ Refusal codes for this operation, besides the shared request and producer
1250
+ codes (`INVALID_REQUEST_FILE`, `INVALID_REQUEST_JSON`, `INVALID_REQUEST`,
1251
+ `INVALID_SCHEMA`, `INVALID_REQUEST_FIELDS`, `INVALID_PATH`,
1252
+ `PRODUCER_UNVERIFIED`, `PRODUCER_CHANGED` and `CAPTURE_REJECTED`):
1253
+
1254
+ - `POLICY_PATH_MISMATCH`: `policyPath` is not the package-owned policy path.
1255
+ - `POLICY_UNAVAILABLE`: the policy file is absent.
1256
+ - `POLICY_UNREADABLE`: the policy is not a regular, bounded, valid managed
1257
+ policy file.
1258
+ - `BINDING_UNAVAILABLE`: the policy stores no Claude discovery binding.
1259
+ - `BINDING_INVALID`: the stored binding is not a Claude binding with a
1260
+ `reviewed` or `automatic` method, or its sources, directories or root
1261
+ aliases do not match the binding schema.
1262
+ - `POLICY_CHANGED`: the policy changed while it was being verified.
1263
+ - `DISCOVERY_BINDING_REJECTED`: any other failure.
1264
+
1265
+ The operation is read-only. It reads the policy and the stored witness paths
1266
+ and directories. It writes no file, cache, selection pin or policy, and it does
1267
+ not start Claude or change native state.
1268
+
1012
1269
  ## Profiles, station sync and rollback
1013
1270
 
1014
1271
  ```bash
@@ -2850,6 +3107,17 @@ Review the plan before adding `--apply`; the original review preimage must match
2850
3107
  its original mode, and unrelated changes refuse. This preserves the native
2851
3108
  configuration, root aliases, other witnesses and active session pins.
2852
3109
 
3110
+ Verifying a typed Codex settings witness from `codex-settings-v1` to
3111
+ `codex-settings-v5` also accepts the one write Codex 0.162.1 makes on its own
3112
+ first TUI or desktop launch: the boolean `tui.screen_reader_detection_done`
3113
+ receipt, added, removed or flipped (V6 already omits it). No digest changes
3114
+ meaning, and verification alone records or rebinds nothing. A non-boolean receipt, any other TUI key
3115
+ (including the `animations = false` Codex adds when it detects a screen
3116
+ reader) and every other change still drift, including Codex's
3117
+ `[projects."<dir>"]` trust entries. A planned Skills write to `config.toml`
3118
+ rebinds the witness to the written document, which adopts at most that
3119
+ receipt.
3120
+
2853
3121
  `claude-settings-v4` is an explicit successor to `claude-settings-v3`. It
2854
3122
  additionally ignores only a top-level `theme` whose value is exactly one of the
2855
3123
  documented built-in values (`auto`, `dark`, `light`, `dark-daltonized`,