@hasna/skills 0.10.71 → 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 +378 -6
- package/bin/index.js +6500 -5264
- package/bin/mcp.js +2080 -1821
- package/bin/migrate.js +2 -2
- package/bin/server.js +2 -2
- package/bin/worker.js +2 -2
- package/dist/cli/commands/context.d.ts +1 -0
- package/dist/index.js +5358 -4592
- package/dist/lib/agent-discovery-directories.d.ts +8 -3
- package/dist/lib/agent-discovery.d.ts +56 -4
- package/dist/lib/agent-integration.d.ts +39 -1
- package/dist/lib/claude-installer-binding.d.ts +65 -0
- package/dist/lib/claude-plugin-cache-marker.d.ts +10 -0
- package/dist/lib/codex-node-modules.d.ts +11 -0
- package/dist/lib/codex-plugin-skill-controls.d.ts +9 -0
- package/dist/lib/codex-settings-witness-v1.d.ts +9 -0
- package/dist/lib/codex-settings-witness.d.ts +18 -0
- package/dist/lib/codex-vendor-refresh.d.ts +54 -0
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/hook-diagnostics.d.ts +3 -1
- package/dist/lib/native-discovery-witness-change.d.ts +25 -0
- package/dist/lib/selection-cache.d.ts +22 -0
- package/dist/lib/selection-resolver.d.ts +3 -1
- package/dist/lib/session-reconciliation.d.ts +57 -1
- package/dist/runtime-prerequisites.js +188 -102
- package/dist/sdk/index.js +2604 -1857
- package/dist/storage.js +8 -4
- package/docs/plugin-admission.md +65 -4
- package/package.json +1 -1
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
|
|
@@ -207,6 +228,82 @@ is recorded at `agent-hooks/codex-native-policy-acceptance.json`; user,
|
|
|
207
228
|
repository and project copies still refuse, and format or digest checks never
|
|
208
229
|
prove the runtime policy on their own.
|
|
209
230
|
|
|
231
|
+
A Codex plugin refresh can deposit a new vendor skill in `~/.codex/plugins/cache`
|
|
232
|
+
(for example `openai-curated-remote/sites/1.0.2-remix-b.1/skills/sites-remix`).
|
|
233
|
+
Skill payloads still reach agents only through the Skills CLI, so an enabled
|
|
234
|
+
vendor copy keeps blocking the Codex prompt hook. Retire it by disabling it in
|
|
235
|
+
`~/.codex/config.toml`. The plugin-qualified name survives later version
|
|
236
|
+
directories, so prefer it:
|
|
237
|
+
|
|
238
|
+
```toml
|
|
239
|
+
[[skills.config]]
|
|
240
|
+
name = "sites:sites-remix"
|
|
241
|
+
enabled = false
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
An exact `path = ".../sites/1.0.2-remix-b.1/skills/sites-remix/SKILL.md"` rule
|
|
245
|
+
also works, but only for that version. On the live Codex prompt hook only, a
|
|
246
|
+
copy is then exempt when every condition holds:
|
|
247
|
+
|
|
248
|
+
- it is package-classified plugin content in `openai-bundled`, `openai-curated`
|
|
249
|
+
or `openai-curated-remote` (exact names), with Codex's remote install receipt
|
|
250
|
+
for the remote marketplace;
|
|
251
|
+
- it carries no Skills or Hasna marker;
|
|
252
|
+
- every cached version of its plugin passes the vendor refresh check described
|
|
253
|
+
below for membership changes, so the plugin can load nothing that the
|
|
254
|
+
inventory and this rule do not account for, and no version is a link;
|
|
255
|
+
- a `[[skills.config]]` rule names its exact path or its plugin-qualified name
|
|
256
|
+
with `enabled = false`, and no matching rule enables it.
|
|
257
|
+
|
|
258
|
+
A whole-plugin `[plugins."sites@openai-curated-remote"] enabled = false` table
|
|
259
|
+
is not enough on its own: remote state can enable a locally disabled remote
|
|
260
|
+
plugin, and a cache copy carries no proof that its plugin source is
|
|
261
|
+
configuration-controlled, so the copy keeps blocking until a path or name rule
|
|
262
|
+
retires it. This exemption only adds to the guard's existing ones; every copy
|
|
263
|
+
that the guard already accepts, such as an exact-path disable in any plugin-cache
|
|
264
|
+
marketplace or a reviewed plugin control, stays accepted on the live hook. Once
|
|
265
|
+
the hook tolerates one of the witness changes below, a state the strict guard
|
|
266
|
+
refuses outright, plugin-cache copies are exempt only through this vendor rule.
|
|
267
|
+
|
|
268
|
+
Instead of editing by hand, `skills hook native-catalog` followed by
|
|
269
|
+
`skills hook install --agent codex --codex-native-catalog <file> --apply` writes
|
|
270
|
+
the same rules through the guarded install path.
|
|
271
|
+
|
|
272
|
+
The hook also tolerates, with a visible `systemMessage` warning, only the
|
|
273
|
+
discovery-witness changes such a refresh provably makes:
|
|
274
|
+
- appended vendor disable rules or vendor plugin disables in `config.toml`,
|
|
275
|
+
accepted when removing exactly those entries or restoring a formerly enabled
|
|
276
|
+
vendor flag reproduces the reviewed digest. Candidates are tried smallest edit
|
|
277
|
+
first, at most 128; with a typed settings witness that covers any change to up
|
|
278
|
+
to two of at most eight vendor plugin tables. A change beyond the bound refuses;
|
|
279
|
+
- directory membership changes under an allowlisted vendor marketplace that
|
|
280
|
+
passes the vendor refresh check: every plugin version has exactly one
|
|
281
|
+
manifest, `.codex-plugin/plugin.json`, alone in its directory, whose keys are
|
|
282
|
+
only `name`, `version`, `description`, `author`, `homepage`, `repository`,
|
|
283
|
+
`license`, `keywords`, `interface`, `skills` (the default `./skills/` only) and
|
|
284
|
+
`apps`. So hooks in any form, commands, agents, extensions, MCP servers and
|
|
285
|
+
unknown keys refuse. There is no `hooks`, `commands` or `agents` directory, no
|
|
286
|
+
`hooks.json` or `.mcp.json` (names compared without regard to case), no root,
|
|
287
|
+
`.claude-plugin` or `.cursor-plugin` manifest, no link
|
|
288
|
+
and no `SKILL.md` below a dot-named or `node_modules` directory, or nested
|
|
289
|
+
in another skill. One exception keeps
|
|
290
|
+
today's vendor plugins working: every current OpenAI vendor plugin declares
|
|
291
|
+
its connector as `apps = "./.app.json"`, so exactly that declaration is
|
|
292
|
+
allowed when `.app.json` is a regular file of at most 1 MiB, strict UTF-8, that
|
|
293
|
+
passes the same connector validation reviewed plugin controls use (one `apps`
|
|
294
|
+
object; app entries with only `id`, `category` and `required`, of the right
|
|
295
|
+
types; no duplicate keys). Any other connector path, a file that fails that
|
|
296
|
+
validation, or the file without the declaration refuses.
|
|
297
|
+
|
|
298
|
+
Every other copy and change still refuses with `NATIVE_SKILL_DRIFT`, including:
|
|
299
|
+
- user, project, `.system` and Hasna-marketplace copies that the guard does not
|
|
300
|
+
already exempt;
|
|
301
|
+
- an enabled vendor copy whose plugin is only disabled by a whole-plugin table;
|
|
302
|
+
- capability-control changes and whole-cache membership changes;
|
|
303
|
+
- plugin activation, including a vendor plugin gaining `enabled = true`;
|
|
304
|
+
- missing coverage and changed roots;
|
|
305
|
+
- every install, trust, migrate and sync check.
|
|
306
|
+
|
|
210
307
|
Native hook invocations must use their installed adapter's selection profile.
|
|
211
308
|
An old client command or environment override naming another profile refuses
|
|
212
309
|
before synchronization or context loading. Review the hook installation and
|
|
@@ -465,6 +562,20 @@ is bounded to 20,000 discovery entries and 4 MiB of UTF-8 path metadata before
|
|
|
465
562
|
directory entries are retained or sorted. Unsupported special files and unsafe
|
|
466
563
|
directory links are refused.
|
|
467
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
|
+
|
|
468
579
|
Native archives persist a version 2 recovery journal before moving payloads;
|
|
469
580
|
`migrate native --json --apply` returns its `receiptPath`. The journal records
|
|
470
581
|
every source, archive path, expected hash, and move status, then marks successful
|
|
@@ -550,6 +661,10 @@ non-null `sha256`, Skills also requires plain byte witnesses for that plugin's
|
|
|
550
661
|
`hooks/hooks.json` (when present) and any string `hooks` target in the
|
|
551
662
|
manifest. A manifest listed as `claude-plugin-manifest-v1` (the form Skills
|
|
552
663
|
writes into the stored policy) or as `path-bytes` triggers no such requirement.
|
|
664
|
+
The version-root `.orphaned_at` timestamp that Claude rewrites at startup is
|
|
665
|
+
bound by presence only, as `claude-plugin-orphaned-at-v1`, and is still
|
|
666
|
+
strictly validated; see
|
|
667
|
+
[the full scope](docs/plugin-admission.md#reviewed-plugin-hook-attestation-scope).
|
|
553
668
|
For plugins outside receipt-backed admission (a `claude-plugin-registry`
|
|
554
669
|
witness, see the
|
|
555
670
|
[discovery transition contract](docs/plugin-admission.md#discovery-transition-contract)),
|
|
@@ -622,6 +737,25 @@ receipt permits only verified registry/cache transitions. See
|
|
|
622
737
|
[plugin admission](docs/plugin-admission.md) for the package, review and runtime
|
|
623
738
|
contracts; these commands do not change live agent registrations or settings.
|
|
624
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
|
+
|
|
625
759
|
If your home `.claude` or `.codex` directory intentionally links to another
|
|
626
760
|
directory within your home, add `--allow-root-aliases` to hook installation and
|
|
627
761
|
native migration. The plan records and rechecks the exact link and target;
|
|
@@ -819,16 +953,37 @@ a skill. Cached use is explicit and expires after 24 hours. When a session
|
|
|
819
953
|
receipt expires, the hook authenticates the current profile. If every loaded
|
|
820
954
|
selection still has identical metadata and bundle digest, it archives the old
|
|
821
955
|
receipt and advances that session to the current profile under an exact receipt
|
|
822
|
-
precondition. Unloaded selections may be added or removed. If
|
|
823
|
-
|
|
824
|
-
|
|
956
|
+
precondition. Unloaded selections may be added or removed. If renewal is refused
|
|
957
|
+
because the same profile moved on (a loaded selection changed or disappeared, or
|
|
958
|
+
another pinned selection's policy, membership or lifecycle changed), the managed
|
|
959
|
+
hook applies the explicit reconciliation planner and applier below to the
|
|
960
|
+
authenticated current revision: it archives the old receipt, retains unchanged
|
|
961
|
+
loaded bundles, retires changed or removed ones and delivers context for the
|
|
962
|
+
prompt. The first hook context that succeeds and reaches the agent afterwards
|
|
963
|
+
names each retired loaded skill once and how to reload its replacement; earlier
|
|
964
|
+
instructions from those skills must not be relied on. Identity, integrity and receipt failures, an
|
|
965
|
+
unavailable authority and anything the planner refuses keep their refusal;
|
|
966
|
+
session write-lock contention keeps `SESSION_WRITE_BUSY`. A
|
|
967
|
+
session created from a project lock (`.skills/selection.lock.json`) records
|
|
968
|
+
that lock in its receipt and is never migrated automatically, whatever
|
|
969
|
+
directory it later prompts from. A session without that record is not migrated
|
|
970
|
+
while a lock sits in the prompt's working directory or any ancestor. A loaded
|
|
971
|
+
skill whose lifecycle epoch rotated while its bytes stayed the same also keeps
|
|
972
|
+
the refusal. If the migration cannot finish within the four-second renewal window,
|
|
973
|
+
the hook reports `SESSION_RECONCILIATION_REQUIRED` with that reason and the next
|
|
974
|
+
prompt retries. Turn this off station-wide with `skills config set
|
|
975
|
+
sessionPinMigration off --global` (the key is refused without `--global`, and a
|
|
976
|
+
global configuration that is a symlink, cannot be read or cannot be parsed also
|
|
977
|
+
turns it off); the hook
|
|
978
|
+
then keeps today's refusal until the explicit reconciliation below. A missing
|
|
979
|
+
cache may be recovered through authenticated context.
|
|
825
980
|
|
|
826
981
|
After verifying the native bridge and input, ordinary lifecycle hooks continue
|
|
827
982
|
without skill payload on typed hosted authentication/refusal/availability failures,
|
|
828
983
|
context timeouts, valid concurrent writers, context CAS conflicts, or required
|
|
829
984
|
session reconciliation. They emit a fixed warning that Skills-dependent actions
|
|
830
985
|
are unavailable; previously loaded instructions are not an authorization fallback.
|
|
831
|
-
|
|
986
|
+
Apart from the automatic migration above, the hook does not rewrite a session pin or remove another writer’s lock to bypass these refusals. Explicit `skills
|
|
832
987
|
load`, `skills context`, and `skills run` still refuse unavailable or unauthorized
|
|
833
988
|
instructions. Native skill tool guards still deny unmanaged payloads. Invalid
|
|
834
989
|
input/receipts, integrity or authority mismatches, unknown failures, and native
|
|
@@ -908,6 +1063,209 @@ reports an incomplete outcome, inspect `sessions show` and the preserved
|
|
|
908
1063
|
operation receipt before retrying. The archive contains both original and
|
|
909
1064
|
prepared bytes even if the final receipt write fails.
|
|
910
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
|
+
|
|
911
1269
|
## Profiles, station sync and rollback
|
|
912
1270
|
|
|
913
1271
|
```bash
|
|
@@ -1009,7 +1367,10 @@ hosted publication access.
|
|
|
1009
1367
|
`--profile` option chooses an isolated credential file; these are separate
|
|
1010
1368
|
settings. `HASNA_SKILLS_SELECTION_PROFILE` overrides the installed selection
|
|
1011
1369
|
profile. A project lock and an existing session keep exact versions until they
|
|
1012
|
-
are explicitly changed or a new session starts.
|
|
1370
|
+
are explicitly changed or a new session starts. The one exception is the
|
|
1371
|
+
managed hook's automatic migration of an expired session pin described above.
|
|
1372
|
+
It never applies to a session created from a project lock, and never while a
|
|
1373
|
+
lock sits in the prompt's working directory or an ancestor.
|
|
1013
1374
|
|
|
1014
1375
|
## Executable skills
|
|
1015
1376
|
|
|
@@ -1367,7 +1728,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
|
|
|
1367
1728
|
| `skills setup-info` | | Version, pinned skills, agent configs, paths |
|
|
1368
1729
|
| `skills export` | | Export pinned skills as JSON |
|
|
1369
1730
|
| `skills import <file>` | | Pin skills from a JSON export |
|
|
1370
|
-
| `skills config set <key> <value>` | | Set default agent, scope, output format, or API origin |
|
|
1731
|
+
| `skills config set <key> <value>` | | Set default agent, scope, output format, or API origin; `sessionPinMigration off --global` keeps managed hooks from migrating refused session pins (global scope only) |
|
|
1371
1732
|
| `skills config unset <key>` | | Remove a configuration value (`skills config unset apiUrl` clears the stored service address and returns to running on this machine) |
|
|
1372
1733
|
| `skills new <name>` | `scaffold` | Scaffold a portable skill under `~/.hasna/skills/installed/<name>` |
|
|
1373
1734
|
| `skills port <path>` | `add` | Import an existing skill folder into the portable standard |
|
|
@@ -2746,6 +3107,17 @@ Review the plan before adding `--apply`; the original review preimage must match
|
|
|
2746
3107
|
its original mode, and unrelated changes refuse. This preserves the native
|
|
2747
3108
|
configuration, root aliases, other witnesses and active session pins.
|
|
2748
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
|
+
|
|
2749
3121
|
`claude-settings-v4` is an explicit successor to `claude-settings-v3`. It
|
|
2750
3122
|
additionally ignores only a top-level `theme` whose value is exactly one of the
|
|
2751
3123
|
documented built-in values (`auto`, `dark`, `light`, `dark-daltonized`,
|