@hasna/skills 0.10.80 → 0.10.81

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
@@ -1109,6 +1109,230 @@ reports an incomplete outcome, inspect `sessions show` and the preserved
1109
1109
  operation receipt before retrying. The archive contains both original and
1110
1110
  prepared bytes even if the final receipt write fails.
1111
1111
 
1112
+ ### Codex plugin skill name reconcile (`hook codex-reconcile`)
1113
+
1114
+ A Codex or plugin update can add skill names to a plugin Skills already
1115
+ enrolled, or bump its manifest version. The strict guard then refuses with
1116
+ `NATIVE_SKILL_DRIFT`. Run the package-owned reconcile instead of turning guards
1117
+ off:
1118
+
1119
+ ```bash
1120
+ skills hook codex-reconcile --json # read-only preview
1121
+ skills hook codex-reconcile --apply --json # write, then prove the guard passes
1122
+ ```
1123
+
1124
+ It reads Codex's qualified skill and installed-plugin catalog through the same
1125
+ owned client as `skills hook native-catalog` (`--codex-command <path>`, default
1126
+ `codex`). It then runs the full strict guard and collects the plugin-cache copies
1127
+ the guard refuses. The work it does:
1128
+
1129
+ - It enrolls each new qualified name of an enrolled plugin whose capability
1130
+ tuple is unchanged, and denies it in `~/.codex/config.toml` with
1131
+ `[[skills.config]] name = "<plugin>:<skill>"` and `enabled = false`.
1132
+ - It enrolls a plugin Skills had not enrolled only when every version is
1133
+ skills-only. A plugin is remote when the native catalog reports a remote
1134
+ source or any remote identity (`remotePluginId`), whatever else it says about
1135
+ the source; this one rule also governs the install review and the inactive
1136
+ rule. A remote plugin's install receipt is bound only when the receipt read
1137
+ before Codex started named the catalog's identity and the current receipt
1138
+ still does, because starting Codex rewrites receipts. Any other remote plugin,
1139
+ and a receipt beside a plugin the catalog does not report as remote, refuses
1140
+ as `remote`.
1141
+ - It binds the verified install receipt onto every control of an enrolled
1142
+ remote plugin whose stored controls lack it (policies written before every
1143
+ control recorded one), and lists the plugin in `bound`. The proof is the one
1144
+ a fresh `hook install` uses: the pre-start reading and the current receipt
1145
+ both name the catalog's identity, and every control that already carries one
1146
+ agrees. Otherwise it refuses with `CODEX_REMOTE_RECEIPT_MISSING`,
1147
+ `CODEX_REMOTE_RECEIPT_MISMATCH` or reason `remote`, and writes nothing. A new
1148
+ name of an enrolled plugin always carries the plugin's receipt. A plugin the
1149
+ catalog no longer reports as remote whose controls are only partly bound
1150
+ refuses as `remote`. An enrolled plugin whose bound receipt was absent or
1151
+ named another installation before Codex started refuses as `remote`, even
1152
+ when nothing else needs writing.
1153
+ - It denies skills that Codex loads from its runtime cache
1154
+ (`~/.cache/codex-runtimes`) by their stable qualified name. A skill there with
1155
+ no stable qualified name gets an exact path deny instead, listed in
1156
+ `pathDenied` with the reason `no-qualified-name`. No plugin control exists for
1157
+ runtime-cache skills.
1158
+ - It classifies every installed plugin that is active, including plugins the
1159
+ saved binding does not know: each one enabled with `enabled = true` under
1160
+ `[plugins]`, each one config disables that is not inactive by the rule
1161
+ below, and each one the native catalog reports installed and enabled. That
1162
+ holds even when none of the plugin's skills block today. Installed means the
1163
+ plugin has a directory in the plugin cache; an enabled plugin with no cache
1164
+ directory has nothing to classify yet. An enabled plugin id that is not
1165
+ `<name>@<marketplace>` (letters, digits, `_` and `-`) refuses as
1166
+ `unsupported`. A known plugin is enrolled, reviewed inactive, an installation
1167
+ input, or a semantic-cache role. It can also be known through a full witness
1168
+ under its exact cache parent, or through a full witness of a reviewed local
1169
+ marketplace that maps exactly that plugin, together with a full witness of the
1170
+ manifest that marketplace row points to. A manifest witness elsewhere that
1171
+ only shares the plugin's name does not count. Every other enabled plugin is
1172
+ scanned like a new plugin, and one that carries a capability refuses.
1173
+ - It leaves an inactive plugin unenrolled and never refuses because of one.
1174
+ Inactive uses the same rule as the reviewed plugin controls, because config
1175
+ alone cannot prove a plugin is off (remote reconciliation can enable a plugin
1176
+ while config still disables it): `[plugins]` sets `enabled = false`, the
1177
+ native catalog reports the plugin installed and disabled, its source is
1178
+ config-controlled (a local, git or npm source with a source fingerprint, no
1179
+ remote identity and not a remote marketplace), and the catalog lists none of
1180
+ its skills. Any other plugin is active and gets every capability and receipt
1181
+ check. The guard does not trust a plugin-level disable alone, so an inactive
1182
+ plugin's cache copies keep exact path denies, listed with the reason
1183
+ `plugin-disabled`.
1184
+ - For a reviewed binding whose full-byte witnesses name an enrolled plugin's
1185
+ manifest, app, MCP or skill files (byte-witness bindings), it re-captures
1186
+ those witnesses in the new version root once the capability tuple is proven
1187
+ unchanged and the strict scan below finds nothing beyond that tuple. This
1188
+ reconcile handles skills-only manifest bumps for byte-witness bindings. `skills hook rebind-cache` does not: it still refuses bindings that
1189
+ never had exact cache-directory coverage.
1190
+
1191
+ The plan comes from the reviewed `hook install` planner. Apply holds the corpus
1192
+ lock, re-plans from the same catalog, checks every preimage and keeps private
1193
+ backups with readback. The deny rows are written by Codex's own writer, not by
1194
+ Skills: an owned `codex app-server` reads the user layer (`config/read`), and
1195
+ `config/batchWrite` writes `skills.config` with that layer's `expectedVersion`
1196
+ and `reloadUserConfig: true`. A concurrent Codex writer, such as the desktop
1197
+ app, makes the write refuse with `CODEX_RECONCILE_NATIVE_WRITE_REFUSED` instead
1198
+ of being overwritten, and this run writes nothing else. With no Codex writer
1199
+ reachable the run refuses with `CODEX_RECONCILE_NATIVE_WRITER_UNAVAILABLE`; it
1200
+ never falls back to a direct file write. The managed policy is written in the
1201
+ Skills transaction only after `config.toml` reads back as exactly the planned
1202
+ configuration. If a step fails, the policy is restored while it still holds this
1203
+ run's bytes, and `config.toml` is restored only through Codex's writer, only
1204
+ while Codex still reports this run's version; otherwise the result is
1205
+ `CODEX_RECONCILE_COMPENSATION_INCOMPLETE` and names the private backup. Only an
1206
+ error answer from Codex counts as a refusal. When Codex does not answer (a
1207
+ timeout or a lost app-server), or answers without a valid config version, the
1208
+ write cannot be verified: the result is `CODEX_RECONCILE_NATIVE_WRITE_UNCERTAIN`,
1209
+ naming the deny rows that may remain and the private backup, and the policy is
1210
+ not written. The next run finishes the policy without another config write once
1211
+ Codex reports those skills disabled. After the policy write, the run re-checks
1212
+ the written state, and either outcome below compensates both writes. A new
1213
+ capability location that appeared while Codex was writing (a root `hooks.json`,
1214
+ `commands/`, `agents/` or another manifest directory) refuses with
1215
+ `CODEX_PLUGIN_CAPABILITY_REVIEW_REQUIRED`. A change to a manifest, `.app.json`
1216
+ or remote install receipt that the plan observed ends as the error
1217
+ `CODEX_RECONCILE_FAILED` ("Native identity source changed during application").
1218
+ A plugin Codex uninstalled after it was enrolled stays unchanged, as the guard
1219
+ treats it, while its retained controls are remote, skills-only and denied by
1220
+ name, and the native catalog no longer reports it enabled. The receipt records each writer and Codex's new
1221
+ config version. The run finishes only when the strict guard accepts the written
1222
+ state. The plan may
1223
+ append disable rows to the Codex configuration. In the managed policy it may
1224
+ only add the new controls and refresh the configuration and re-captured cache
1225
+ witnesses, and bind a verified receipt onto stored controls that lack one.
1226
+ Anything else refuses with `CODEX_RECONCILE_SCOPE_EXCEEDED`. The reconcile
1227
+ never adds an allow entry.
1228
+
1229
+ Residual: the strict guard checks every control that carries a receipt, and it
1230
+ still tolerates a plugin none of whose controls carries one, because policies
1231
+ written before receipts were recorded have none and refusing them would block
1232
+ every Codex session on upgrade. Such a plugin is receipt-protected only after a
1233
+ reconcile or `skills hook install` binds its receipt.
1234
+
1235
+ The allowed set is computed from Codex's own report, not from a simulation of
1236
+ the existing configuration. Every skill the native catalog reports as enabled
1237
+ counts as allowed unless a disable row this run appends matches it. The run
1238
+ succeeds only when that leaves exactly the bridge. A skill Codex still reports
1239
+ enabled despite a deny row already in the configuration it read (an ineffective
1240
+ deny, or a wrong name) refuses with `CODEX_NATIVE_SKILL_ALLOWED_SET_INVALID`.
1241
+ The catalog is read before the write, so the result shows what Codex reported
1242
+ then plus the rows this run appended. Run the reconcile again to confirm Codex
1243
+ applied them: `unchanged` means a fresh catalog reports every denied skill
1244
+ disabled.
1245
+
1246
+ Unsupported boundary: a change that touches more than skill names stays
1247
+ quarantined until a person reviews it. That covers a new or changed app, MCP
1248
+ declaration, hooks, manifest content other than `version`, or remote install
1249
+ receipt of an enrolled plugin. It also covers a plugin Skills has not enrolled
1250
+ that carries an app, MCP or hooks. The plugin stays disabled, or its skills stay
1251
+ denied, as they already were. The command refuses with
1252
+ `CODEX_PLUGIN_CAPABILITY_REVIEW_REQUIRED`, writes nothing, and lists each plugin
1253
+ with its capability kinds (`app`, `mcp`, `hooks`, `commands`, `agents`,
1254
+ `manifest`, `manifest-location`, `remote`, `unsupported`). The reconcile is what
1255
+ refuses these cases; the guard alone does not catch every one yet. The guard
1256
+ blocks new skill copies of a plugin it does not know, and blocks with
1257
+ `NATIVE_SKILL_DRIFT: CODEX_PLUGIN_CAPABILITY_REVIEW_REQUIRED: ...` when an
1258
+ enrolled plugin's reviewed tuple changes (its manifest apart from `version`, a
1259
+ declared app or MCP file, or its remote receipt). It does not yet notice an
1260
+ enrolled plugin gaining a root `hooks.json`, `commands/`, `agents/` or another
1261
+ manifest directory in place, without a version or skill change. The reconcile
1262
+ refuses those, including when they appear while it is writing, and the guard
1263
+ gap is tracked as a follow-up (UW-00793). Review the plugin with
1264
+ `skills hook install --agent codex --discovery-inputs <reviewed-file>
1265
+ --codex-native-catalog <reviewed-catalog> --apply`.
1266
+
1267
+ Reviewer check, the method rule for capability classification. Confirm each
1268
+ point before accepting a reconcile result or a change to it:
1269
+
1270
+ - Every version root is scanned against the shared location sets in
1271
+ `codex-plugin-capability-paths.ts`, the single source, without regard to
1272
+ case. The scan covers every manifest location Codex reads. The Agent Plugins
1273
+ root `plugin.json` comes first, then `.codex-plugin`, `.claude-plugin` and
1274
+ `.cursor-plugin`. It also covers `hooks/`, `hooks.json`, `.mcp.json`,
1275
+ `.app.json`, `commands/` and `agents/`. Declaration files and manifest
1276
+ directories count at any depth. A skill document not spelled exactly
1277
+ `SKILL.md` counts, and so does any link or special file. Capability-named
1278
+ entries beside the versions count too.
1279
+ - `.codex-plugin` must hold exactly `plugin.json`. A new plugin's manifest may
1280
+ carry only the shared metadata keys. `apps` counts as the app capability,
1281
+ every other key counts as a capability or unsupported, and `skills` must be
1282
+ the default `./skills` root. Inline manifest hooks count: a missing
1283
+ `hooks.json` is never evidence of no hooks. `chrome@openai-bundled` declares
1284
+ its `mcp_tool` hooks only in its manifest.
1285
+ - An enrolled plugin gets the same strict scan over every current version root,
1286
+ as well as the reviewed-tuple check. Only the reviewed `.codex-plugin`
1287
+ manifest, and an exact root `.app.json` or `.mcp.json` its tuple declares, are
1288
+ bound. Anything else refuses. The witness rebase runs only for a plugin that
1289
+ passes both checks.
1290
+ - Every skills-only result cites the manifest paths that were read, in
1291
+ `classified[].manifests`. Every refusal entry carries `manifests` the same
1292
+ way. A root whose manifest cannot be read is `unsupported`, never skills-only.
1293
+ - Every installed plugin enabled in `[plugins]` is classified, not only the
1294
+ ones the current binding knows, and an enabled id that does not parse
1295
+ refuses.
1296
+
1297
+ These also refuse and write nothing:
1298
+
1299
+ - `CODEX_RECONCILE_DISCOVERY_REVIEW_REQUIRED`: a binding with no recorded
1300
+ installed-plugin catalog review. That is the older shape. It needs a fresh full
1301
+ discovery review.
1302
+ - `CODEX_NATIVE_SKILL_NAME_CONFLICT`: an existing `[[skills.config]]` rule that
1303
+ is not `enabled = false` and either names a skill this run enrolls, denies or
1304
+ already controls (including the catalog name of a skill it denies by path),
1305
+ or has a path that resolves to a document this run denies. Those documents
1306
+ are the plugin-cache copies it handles and the runtime-cache skills it denies,
1307
+ by name or by path. Enable rules for any other skill are left to the strict
1308
+ guard.
1309
+ - `CODEX_NATIVE_SKILL_ALLOWED_SET_INVALID`: a skill other than the bridge that
1310
+ the native catalog reports enabled and no row appended by this run disables.
1311
+ - `CODEX_NATIVE_SKILL_CATALOG_UNSUPPORTED_VERSION` or
1312
+ `CODEX_HOOK_TRUST_NATIVE_UNSUPPORTED_VERSION`: an unmeasured Codex release.
1313
+ - `NATIVE_SKILL_DRIFT`: copies outside the plugin cache, or any other drift.
1314
+ - `CODEX_RECONCILE_SYMLINK_REFUSED`: a link in a native path.
1315
+ - `CODEX_RECONCILE_NATIVE_WRITER_UNAVAILABLE`: no Codex app-server could be
1316
+ started to write the deny rows.
1317
+ - `CODEX_RECONCILE_NATIVE_CONFIG_MISMATCH`: Codex reports a user configuration
1318
+ other than the one the run planned against.
1319
+ - `CODEX_RECONCILE_NATIVE_WRITE_REFUSED`: Codex refused the versioned write, for
1320
+ example because `config.toml` changed after Codex read it.
1321
+ - `CODEX_RECONCILE_SEMANTIC_CACHE_UNSUPPORTED`: a binding that uses the semantic
1322
+ cache witness.
1323
+
1324
+ Every invocation prints exactly one JSON line with `schema`
1325
+ (`hasna.skills-codex-reconcile.v1`), `mode` (`preview` or `apply`) and
1326
+ `outcome`. The outcome is `applied`, `unchanged`, `planned` (preview only),
1327
+ `refused` or `error`. `code` and `message` are present exactly when the outcome
1328
+ is refused or error. The line also carries `counts` (`enrolled`,
1329
+ `alreadyEnrolled`, `denied`, `pathDenied`, `skipped`, `allowed`), `enrolled`,
1330
+ `denied`, `pathDenied`, `inactive`, `classified`, `rebasedWitnesses`, `bound`, `allowed` (always
1331
+ `["skills-cli"]` on success), `refusals`, `changes`, and on apply `receipt` and
1332
+ `backups`. The exit status is 0 for applied, unchanged and planned, 2 for
1333
+ refused and 1 for error. A second apply returns `unchanged`. The command never
1334
+ retries within one invocation and never disables a guard.
1335
+
1112
1336
  ### Claude installer witness CLI (`capture-claude-installer`)
1113
1337
 
1114
1338
  ```text
@@ -1795,6 +2019,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
1795
2019
  | `skills provenance [--session <id>] [--station <id>] --json` | `--cached` | Read-only receipt of the selected, activated, applied, pinned and actually loaded skill versions, each with its status and reason; writes nothing |
1796
2020
  | `skills sessions reconcile <id> --from-profile <id> --from-revision <rev> --receipt-sha256 <sha> --selection-profile <id> --profile-revision <rev>` | | Plan an explicit migration of one live session; `--apply --plan-digest <digest> --plan-issued-at <time> --plan-expires-at <time>` (within five minutes) preserves its old receipt and applies the reviewed replacement |
1797
2021
  | `skills hook agents --json` | | Report maintained adapters and explicit coverage limits |
2022
+ | `skills hook codex-reconcile` | `--apply`, `--codex-command <path>`, `--json` | Deny every Codex plugin skill except the bridge by stable qualified name; enroll skills-only plugin changes, refuse capability changes with `CODEX_PLUGIN_CAPABILITY_REVIEW_REQUIRED`; one JSON line per run |
1798
2023
  | `skills migrate native` | | Inventory native copies; `--apply` archives managed copies, with explicit `--include-unmanaged`, `--include-vendor`, or reviewed `--target-manifest` selection |
1799
2024
  | `skills pull --all --selection-profile <id>` | | With CLI loading active, refresh the selected profile into the verified cache |
1800
2025
  | `skills review-export <slug@version> --output <path>` | | Export one immutable hosted bundle to a private archive for review without selecting it |