@ccoalm/ccl-skills 0.18.1 → 0.18.2

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.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-policy.md +50 -0
  3. package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-start.md +30 -40
  4. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-delegation-owner.sh +9 -122
  5. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-edit-isolation.sh +43 -9
  6. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-merge-authorization.sh +95 -2
  7. package/dist/assets/marketplace/plugins/ccl-skills/hooks/hooks.json +32 -1
  8. package/dist/assets/marketplace/plugins/ccl-skills/hooks/host-input.py +544 -0
  9. package/dist/assets/marketplace/plugins/ccl-skills/hooks/merge-authorization-prompt.sh +103 -26
  10. package/dist/assets/marketplace/plugins/ccl-skills/hooks/owner-dispatch-guard.sh +17 -8
  11. package/dist/assets/marketplace/plugins/ccl-skills/hooks/proposed-next-stop.sh +14 -0
  12. package/dist/assets/marketplace/plugins/ccl-skills/hooks/session-start.sh +31 -2
  13. package/dist/assets/marketplace/plugins/ccl-skills/hooks/skill-context-compact.sh +10 -0
  14. package/dist/assets/marketplace/plugins/ccl-skills/hooks/skill-extraction-gate-stop.sh +13 -4
  15. package/dist/assets/marketplace/plugins/ccl-skills/hooks/skill-loading.py +277 -0
  16. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_delegation_owner.sh +61 -55
  17. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_merge_authorization.sh +140 -0
  18. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_host_input.py +597 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_merge_authorization_prompt.sh +56 -3
  20. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_proposed_next.py +260 -0
  21. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_session_start.sh +85 -3
  22. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_skill_loading.py +278 -0
  23. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +27 -6
  24. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/README.md +23 -15
  25. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +41 -2
  26. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/pre-final-continuation-gate.md +1 -1
  27. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +4 -0
  28. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +5 -0
  29. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/SKILL.md +1 -1
  30. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/references/hook-authorization.md +13 -0
  31. package/dist/assets/release.json +80 -25
  32. package/dist/codex-hooks.d.ts +15 -0
  33. package/dist/codex-hooks.js +186 -0
  34. package/dist/opencode-adapter.js +8 -3
  35. package/dist/operations.js +13 -9
  36. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "npmPackage": "@ccoalm/ccl-skills",
4
- "version": "0.18.1",
5
- "sourceCommit": "40dc287d2a786256bf37242458b2ec57853171b7",
4
+ "version": "0.18.2",
5
+ "sourceCommit": "688ff9f11871259729fc49a5230106c2f6432539",
6
6
  "sourceState": "clean",
7
7
  "files": [
8
8
  {
@@ -40,9 +40,14 @@
40
40
  "sha256": "5a06ab4ac6a633be7ebb542de579a1d2153f6cf5bbe1551702a5e3d923642181",
41
41
  "mode": 420
42
42
  },
43
+ {
44
+ "path": "marketplace/plugins/ccl-skills/agent-context/session-policy.md",
45
+ "sha256": "a54a3a1bde6a3e0691995865250d2b021eeb22ccebfa5f99a31b9447325eda55",
46
+ "mode": 420
47
+ },
43
48
  {
44
49
  "path": "marketplace/plugins/ccl-skills/agent-context/session-start.md",
45
- "sha256": "48d6589066a033b3b24429292e18f331859f42b8fc608f07ed64244f0d02c95c",
50
+ "sha256": "ea0109f642fb331250e64bc28691439250575751165334f2b0175a9e3c286175",
46
51
  "mode": 420
47
52
  },
48
53
  {
@@ -57,32 +62,37 @@
57
62
  },
58
63
  {
59
64
  "path": "marketplace/plugins/ccl-skills/hooks/guard-delegation-owner.sh",
60
- "sha256": "244e5f691e893f90f336bbdb401258105cb055ee03f05dd487b88241180779c0",
65
+ "sha256": "8f5350f4a7a57edc5e9f57fb08ab211955007902e79979b6f7cc24e56e5a3695",
61
66
  "mode": 493
62
67
  },
63
68
  {
64
69
  "path": "marketplace/plugins/ccl-skills/hooks/guard-edit-isolation.sh",
65
- "sha256": "edb2f15cd81dcecb1abf872d5e8c2ba078ce626301b898d63774a7189ac90f0d",
70
+ "sha256": "5ef7a68518bc72d1640a395db1d7c12112e24e59e6fb5b9a6dae212ba7a34272",
66
71
  "mode": 493
67
72
  },
68
73
  {
69
74
  "path": "marketplace/plugins/ccl-skills/hooks/guard-merge-authorization.sh",
70
- "sha256": "f61d736fd61de507539584a9d7a8d4d696f7e0c56b5de096c3691b0218f1b5dc",
75
+ "sha256": "d12e8c81122babe65d12cca2576b0e2b73b40f4e82090ef31de26bbc893a1cba",
71
76
  "mode": 493
72
77
  },
73
78
  {
74
79
  "path": "marketplace/plugins/ccl-skills/hooks/hooks.json",
75
- "sha256": "a9bfda0c4f36d762a171684720745418640da90f549ce0e844fca9749b229c6d",
80
+ "sha256": "5786127311b1b4969e5e10377d98f4a1f341c4e2e6fac8ab690464787ad25f09",
81
+ "mode": 420
82
+ },
83
+ {
84
+ "path": "marketplace/plugins/ccl-skills/hooks/host-input.py",
85
+ "sha256": "129a115c9442b6cf4b0d23f657ede58b87d34a9c7913af0626b0d004080e2ba9",
76
86
  "mode": 420
77
87
  },
78
88
  {
79
89
  "path": "marketplace/plugins/ccl-skills/hooks/merge-authorization-prompt.sh",
80
- "sha256": "295bbf8dbeab5c3241831ca9442a92cd4b60d10315aae6aa98ca99d25f14ab03",
90
+ "sha256": "aba3a7fc3e6411da710dcc027f965f9042bc8ef72d74fccce0f56b527711f0a8",
81
91
  "mode": 493
82
92
  },
83
93
  {
84
94
  "path": "marketplace/plugins/ccl-skills/hooks/owner-dispatch-guard.sh",
85
- "sha256": "6a3aef4fff6ebe724c802fe18de79198178acd9f5fac6ab6815bd0efbc5504af",
95
+ "sha256": "5d9ff755de829663f69e2f7dcf4351af9767f540a6c193458fd562eec0ee2d44",
86
96
  "mode": 493
87
97
  },
88
98
  {
@@ -90,6 +100,11 @@
90
100
  "sha256": "f36d6fe6c8b7c43f43301a2a3cf0f6756d4ca2e942ed8495ccd5940f2ce519bc",
91
101
  "mode": 493
92
102
  },
103
+ {
104
+ "path": "marketplace/plugins/ccl-skills/hooks/proposed-next-stop.sh",
105
+ "sha256": "b513f0e429a79f80fee8ccd7c54c14509b0f4540ce926e1893de2e93be30039f",
106
+ "mode": 493
107
+ },
93
108
  {
94
109
  "path": "marketplace/plugins/ccl-skills/hooks/remind-post-merge-cleanup.sh",
95
110
  "sha256": "c912bd8bbb9afe15b4ed3ac546175ecb9185752087a818d1426ffecd68ffb623",
@@ -117,12 +132,22 @@
117
132
  },
118
133
  {
119
134
  "path": "marketplace/plugins/ccl-skills/hooks/session-start.sh",
120
- "sha256": "9caf94833c361455dbd4eba7e0dc09889d78d06c4d870e0a84b07496e907156c",
135
+ "sha256": "fb5bb29239130df92146b3afb38384d15e0f32c0463f7c7e903683aa085d2dd6",
136
+ "mode": 493
137
+ },
138
+ {
139
+ "path": "marketplace/plugins/ccl-skills/hooks/skill-context-compact.sh",
140
+ "sha256": "1bf21e91517d4b56a9cbe1e98bfc8f09201ee07a5f9d32441b55875df03113a8",
121
141
  "mode": 493
122
142
  },
123
143
  {
124
144
  "path": "marketplace/plugins/ccl-skills/hooks/skill-extraction-gate-stop.sh",
125
- "sha256": "3e1077c0bf37d08b0697e52b1299c9694287aa5880ba92029ded33f461785100",
145
+ "sha256": "aa3fa14f00c46ea7e5f12d35c3a360d9128715da6b554ccbe499794ac78dc564",
146
+ "mode": 493
147
+ },
148
+ {
149
+ "path": "marketplace/plugins/ccl-skills/hooks/skill-loading.py",
150
+ "sha256": "f59162a353d905ef419a0075a2cf20063e7cd757799d4da7b9f7210e3ecd0272",
126
151
  "mode": 493
127
152
  },
128
153
  {
@@ -132,7 +157,7 @@
132
157
  },
133
158
  {
134
159
  "path": "marketplace/plugins/ccl-skills/hooks/test_guard_delegation_owner.sh",
135
- "sha256": "e402ea7909b54ba7d4486d6b1b8b9fc86097656f2cb7824ce66e118047c473b6",
160
+ "sha256": "0a93622a22c9b0a2dc543f77e9bc4eca892608def1cf1be2392d3dda0837bf0d",
136
161
  "mode": 420
137
162
  },
138
163
  {
@@ -142,14 +167,24 @@
142
167
  },
143
168
  {
144
169
  "path": "marketplace/plugins/ccl-skills/hooks/test_guard_merge_authorization.sh",
145
- "sha256": "c86be620e7cef11dd834ee4cdd399f753fcb784a4b4d56ca3fc1f30623bafb1b",
170
+ "sha256": "46aa31fb242e9096545d00554cb17eaf9bd4fef68fa4e49ee581148b638e3a08",
146
171
  "mode": 493
147
172
  },
173
+ {
174
+ "path": "marketplace/plugins/ccl-skills/hooks/test_host_input.py",
175
+ "sha256": "0278186bda45a9f164acad048649d7fedc558e6552d2580abd4facf5be9232eb",
176
+ "mode": 420
177
+ },
148
178
  {
149
179
  "path": "marketplace/plugins/ccl-skills/hooks/test_merge_authorization_prompt.sh",
150
- "sha256": "4acafbec16910119c2e5cb714418aa9486881833d2694d43ec114986e9564156",
180
+ "sha256": "acb48c3fd690bd0c7353d2071e1cdd81b28a414d10452a579bf3bb044da1462d",
151
181
  "mode": 420
152
182
  },
183
+ {
184
+ "path": "marketplace/plugins/ccl-skills/hooks/test_proposed_next.py",
185
+ "sha256": "304bcba1cb622b4e3a14f6bf2a41eb4755352548aa791bc4f576feff6e6b129f",
186
+ "mode": 493
187
+ },
153
188
  {
154
189
  "path": "marketplace/plugins/ccl-skills/hooks/test_remind_post_merge_cleanup.sh",
155
190
  "sha256": "f57ede7e26be676f3759d97e5707c4f9390623701103f2cfc4465a9d1669abf3",
@@ -172,9 +207,14 @@
172
207
  },
173
208
  {
174
209
  "path": "marketplace/plugins/ccl-skills/hooks/test_session_start.sh",
175
- "sha256": "6f7a80cd148c93f88a7b68f6643df1420ed9a31f3bd3692e51cd7cfe1c621d0b",
210
+ "sha256": "e79bf4046ddb527a19c6599be9ede656dfe280583002706645cebfcc4ec6856b",
176
211
  "mode": 420
177
212
  },
213
+ {
214
+ "path": "marketplace/plugins/ccl-skills/hooks/test_skill_loading.py",
215
+ "sha256": "78ca5d46dc116328a8eb0552011313dafdf20c472b0bd981937ec4f9f897f462",
216
+ "mode": 493
217
+ },
178
218
  {
179
219
  "path": "marketplace/plugins/ccl-skills/packages/opencode-plugin/AGENTS.md",
180
220
  "sha256": "6b0a214f91bf2abe637d2bbd430b815caf2ab4c20cd25e33a80e70f21bc02b3a",
@@ -182,7 +222,7 @@
182
222
  },
183
223
  {
184
224
  "path": "marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts",
185
- "sha256": "4706c8345df3a68836c6e09daf9fb0d8e4a828d544b751a94c47b39029ff706c",
225
+ "sha256": "b5c6c3d29f3b8406cee9ef2de2f9f364720eccbcec5e5d313aea044546aaa07f",
186
226
  "mode": 420
187
227
  },
188
228
  {
@@ -217,12 +257,12 @@
217
257
  },
218
258
  {
219
259
  "path": "marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh",
220
- "sha256": "76efb7c5d98b8944aaee11c537aabc82259faddfa6b60bb2bfabdbf7d0602ce2",
260
+ "sha256": "af57aaecf6bcb1c396d3f1072a5dc4a260807555d45755e824b082cbfd43baea",
221
261
  "mode": 493
222
262
  },
223
263
  {
224
264
  "path": "marketplace/plugins/ccl-skills/scripts/owner-dispatch/README.md",
225
- "sha256": "6e82edb997669aa82ec35c58bf5d497fac8352e91fc1ae1f0cc3f3aae90f1563",
265
+ "sha256": "00c5f07f97a24901ec97bb311cf077f5cb1d49b228449603d9f9291f36bd93af",
226
266
  "mode": 420
227
267
  },
228
268
  {
@@ -1427,7 +1467,7 @@
1427
1467
  },
1428
1468
  {
1429
1469
  "path": "marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/pre-final-continuation-gate.md",
1430
- "sha256": "084b801e8eb4c0d3ccabf102d7bc75721a00666030f3fcbc00dd74d5725ac408",
1470
+ "sha256": "f1a34ebdf9be63381c20d592f56c7bd1983421d27fcb01489b3e166de5aaf97a",
1431
1471
  "mode": 420
1432
1472
  },
1433
1473
  {
@@ -2157,7 +2197,7 @@
2157
2197
  },
2158
2198
  {
2159
2199
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md",
2160
- "sha256": "6012341863017f917334c6906eecf533af5af481e0b88edff8f3b1849103a8a3",
2200
+ "sha256": "fa473d3b861716e0f8369a3f5583e8c57896f736104194bb854174e7bacd8009",
2161
2201
  "mode": 420
2162
2202
  },
2163
2203
  {
@@ -2372,7 +2412,7 @@
2372
2412
  },
2373
2413
  {
2374
2414
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh",
2375
- "sha256": "58436a78a32aaf2bb007d342a56ca05d1eac5b47032af0d79f0d47b280c9a2a7",
2415
+ "sha256": "3c133a45044e2cc665e5e6113ad084f52f385ca82a51ddfaccaa7125d7990d2b",
2376
2416
  "mode": 493
2377
2417
  },
2378
2418
  {
@@ -3255,6 +3295,11 @@
3255
3295
  "sha256": "63ebeb661eeb644497e57a2412f4fb7bc08bb9b19fc568ecb2b6f45f4ecfb070",
3256
3296
  "mode": 420
3257
3297
  },
3298
+ {
3299
+ "path": "marketplace/plugins/ccl-skills/skills/worktree-isolation/references/hook-authorization.md",
3300
+ "sha256": "989be64bb1d59bdf92c72c4bed407381c25b906e2b436ba8ab601e5590600ce8",
3301
+ "mode": 420
3302
+ },
3258
3303
  {
3259
3304
  "path": "marketplace/plugins/ccl-skills/skills/worktree-isolation/references/shared-branch-rebase.md",
3260
3305
  "sha256": "99423dfd5acb461385de9f21f65eca802f975355a81590bec0617f401088abe8",
@@ -3287,7 +3332,7 @@
3287
3332
  },
3288
3333
  {
3289
3334
  "path": "marketplace/plugins/ccl-skills/skills/worktree-isolation/SKILL.md",
3290
- "sha256": "d5fbed89424aa1f803c6bdfc5ed9cf6772e0879ebfdaa5874b54f3ad37ee77a3",
3335
+ "sha256": "a9671ca9a82aa4f2171b9f2fc129bf1d5125317e062dc29e262d75824217e22c",
3291
3336
  "mode": 420
3292
3337
  }
3293
3338
  ],
@@ -3322,6 +3367,16 @@
3322
3367
  "sha256": "5b5ab7f39b7faa81ea209b3e479295da64f2a834969ee5ac8d76354a182c2822",
3323
3368
  "mode": 493
3324
3369
  },
3370
+ {
3371
+ "path": "codex-hooks.d.ts",
3372
+ "sha256": "c87b1afaffe146fcce3d1e8a948e67db91e7b671fd1f944b879ef4b5114a56ee",
3373
+ "mode": 420
3374
+ },
3375
+ {
3376
+ "path": "codex-hooks.js",
3377
+ "sha256": "cd2a131514391ed37e83cf8b5e90eb0d34c3f049c4d1780fa9b0443afa242de4",
3378
+ "mode": 420
3379
+ },
3325
3380
  {
3326
3381
  "path": "codex-host.d.ts",
3327
3382
  "sha256": "0e7c4e32f4627983d04d670efbd63ffe72384fb39b5ebe3bcf22a281421c7985",
@@ -3379,7 +3434,7 @@
3379
3434
  },
3380
3435
  {
3381
3436
  "path": "opencode-adapter.js",
3382
- "sha256": "637d3df5182ad6a51ed310736be65471fd0b432c078483af163d340ac04d6460",
3437
+ "sha256": "213ee1d313a8318617f3865ca261718830c497f9de79ec35f7523bd100ad9c95",
3383
3438
  "mode": 420
3384
3439
  },
3385
3440
  {
@@ -3389,7 +3444,7 @@
3389
3444
  },
3390
3445
  {
3391
3446
  "path": "operations.js",
3392
- "sha256": "254bf44226fa305e98ea2dac36ddd6ca4f1a2e42e88ee669a5bb83736ba01077",
3447
+ "sha256": "c7d2150f3158a34181c5234f02727b347c6612cdd474877d277e31f1369b31d0",
3393
3448
  "mode": 420
3394
3449
  },
3395
3450
  {
@@ -3453,5 +3508,5 @@
3453
3508
  "mode": 420
3454
3509
  }
3455
3510
  ],
3456
- "snapshotHash": "f0485aa2402d6f90224d19e22d3736178f00834e1dbd469ab1435affaed20a16"
3511
+ "snapshotHash": "d0da3b335908383d4b8fd193d5755f978933a5e430e93eb48f49914669ad3491"
3457
3512
  }
@@ -0,0 +1,15 @@
1
+ import type { OwnedFile } from "./types.js";
2
+ type HookStatus = "trusted" | "managed" | "untrusted" | "modified" | "disabled" | "missing" | "unknown";
3
+ export interface HookInventory {
4
+ status: HookStatus;
5
+ expected: number;
6
+ observed: number;
7
+ runtime: "unverified";
8
+ reason?: string;
9
+ }
10
+ export declare function parseHookInventory(value: unknown, cwd: string, pluginRoot: string, runtime?: {
11
+ codexHome: string;
12
+ files: OwnedFile[];
13
+ }): HookInventory;
14
+ export declare function inspectHooks(codexHome: string, pluginRoot: string, files: OwnedFile[], cwd?: string): HookInventory;
15
+ export {};
@@ -0,0 +1,186 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { canonicalAlias, contained, verifyTree } from "./fs-safe.js";
6
+ import { MARKET, REF } from "./paths.js";
7
+ const TIMEOUT_MS = 10000;
8
+ const OUTPUT_LIMIT = 1024 * 1024;
9
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
10
+ const unknown = (reason, expected = 0, observed = 0) => ({ status: "unknown", expected, observed, runtime: "unverified", reason });
11
+ // Compare native metadata with the verified package snapshot. Merely finding a
12
+ // similarly named plugin or one trusted handler cannot certify the installation.
13
+ export function parseHookInventory(value, cwd, pluginRoot, runtime) {
14
+ let expected = [];
15
+ try {
16
+ const config = JSON.parse(readFileSync(join(pluginRoot, "hooks/hooks.json"), "utf8"));
17
+ if (!record(config) || !record(config.hooks))
18
+ return unknown("invalid-owned-hooks");
19
+ for (const [event, groups] of Object.entries(config.hooks)) {
20
+ if (!Array.isArray(groups))
21
+ return unknown("invalid-owned-hooks");
22
+ for (const group of groups) {
23
+ if (!record(group) || !Array.isArray(group.hooks) || (group.matcher !== undefined && typeof group.matcher !== "string"))
24
+ return unknown("invalid-owned-hooks");
25
+ for (const hook of group.hooks) {
26
+ if (!record(hook) || hook.type !== "command" || typeof hook.command !== "string" || !hook.command)
27
+ return unknown("invalid-owned-hooks");
28
+ expected.push({ eventName: event[0].toLowerCase() + event.slice(1), matcher: group.matcher ?? null, command: hook.command });
29
+ }
30
+ }
31
+ }
32
+ }
33
+ catch {
34
+ return unknown("invalid-owned-hooks");
35
+ }
36
+ if (!expected.length)
37
+ return unknown("empty-owned-hooks");
38
+ const count = expected.length;
39
+ if (!record(value) || !Array.isArray(value.data) || value.data.length !== 1)
40
+ return unknown("malformed-inventory", count);
41
+ const entry = value.data[0];
42
+ if (!record(entry) || typeof entry.cwd !== "string" || !isAbsolute(entry.cwd) || canonicalAlias(resolve(entry.cwd)) !== canonicalAlias(resolve(cwd)) || !Array.isArray(entry.hooks))
43
+ return unknown("malformed-inventory", count);
44
+ if (!Array.isArray(entry.errors) || !Array.isArray(entry.warnings) || entry.errors.length || entry.warnings.length)
45
+ return unknown("inventory-diagnostics", count);
46
+ const statuses = [], keys = new Set();
47
+ let disabled = false;
48
+ const expectedSource = canonicalAlias(join(pluginRoot, "hooks/hooks.json"));
49
+ const verifiedRoots = new Set([canonicalAlias(pluginRoot)]);
50
+ for (const hook of entry.hooks) {
51
+ if (!record(hook))
52
+ return unknown("malformed-inventory", count);
53
+ if (hook.pluginId !== REF)
54
+ continue;
55
+ if (hook.source !== "plugin" || typeof hook.sourcePath !== "string" || !isAbsolute(hook.sourcePath))
56
+ return unknown("hook-source-mismatch", count, statuses.length);
57
+ const source = canonicalAlias(resolve(hook.sourcePath)), root = dirname(dirname(source));
58
+ if (source !== expectedSource) {
59
+ if (!runtime || source !== join(root, "hooks/hooks.json"))
60
+ return unknown("hook-source-mismatch", count, statuses.length);
61
+ const cache = canonicalAlias(join(runtime.codexHome, "plugins/cache", MARKET, "ccl-skills"));
62
+ try {
63
+ if (root === cache || contained(cache, relative(cache, root)) !== root)
64
+ return unknown("hook-source-mismatch", count, statuses.length);
65
+ if (!verifiedRoots.has(root)) {
66
+ if (!runtime.files.length || verifyTree(root, runtime.files).length)
67
+ return unknown("runtime-drift", count, statuses.length);
68
+ verifiedRoots.add(root);
69
+ }
70
+ }
71
+ catch {
72
+ return unknown("hook-source-mismatch", count, statuses.length);
73
+ }
74
+ }
75
+ if (hook.handlerType !== "command" || typeof hook.command !== "string" || typeof hook.eventName !== "string" || (hook.matcher !== null && typeof hook.matcher !== "string") || typeof hook.enabled !== "boolean" || typeof hook.key !== "string" || !hook.key || keys.has(hook.key) || !["trusted", "untrusted", "modified", "managed"].includes(String(hook.trustStatus)))
76
+ return unknown("malformed-hook", count, statuses.length);
77
+ keys.add(hook.key);
78
+ const commandRoot = dirname(dirname(resolve(hook.sourcePath)));
79
+ const index = expected.findIndex(item => item.eventName === hook.eventName && item.matcher === hook.matcher && item.command.replaceAll("${CLAUDE_PLUGIN_ROOT}", commandRoot).replaceAll("${CODEX_PLUGIN_ROOT}", commandRoot) === hook.command);
80
+ if (index < 0)
81
+ return unknown("hook-definition-mismatch", count, statuses.length);
82
+ expected.splice(index, 1);
83
+ statuses.push(String(hook.trustStatus));
84
+ disabled ||= !hook.enabled;
85
+ }
86
+ const base = { expected: count, observed: statuses.length, runtime: "unverified" };
87
+ if (expected.length)
88
+ return { ...base, status: "missing", reason: "missing-package-hooks" };
89
+ if (statuses.includes("modified"))
90
+ return { ...base, status: "modified" };
91
+ if (statuses.includes("untrusted"))
92
+ return { ...base, status: "untrusted" };
93
+ if (disabled)
94
+ return { ...base, status: "disabled" };
95
+ return { ...base, status: statuses.every(status => status === "managed") ? "managed" : "trusted" };
96
+ }
97
+ // The CLI's operation API is synchronous. Isolate the handshake in a short-lived
98
+ // helper so initialization completes before hooks/list, without starting a turn.
99
+ export function inspectHooks(codexHome, pluginRoot, files, cwd = process.cwd()) {
100
+ const result = spawnSync(process.execPath, [fileURLToPath(import.meta.url), "--inventory-worker", resolve(cwd)], {
101
+ encoding: "utf8", env: { ...process.env, CODEX_HOME: codexHome },
102
+ stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32",
103
+ timeout: TIMEOUT_MS + 1000, killSignal: "SIGKILL", maxBuffer: OUTPUT_LIMIT,
104
+ });
105
+ // The helper and its descendants share an isolated process group. This also
106
+ // catches hosts which leave children alive or hold stdout open after exit.
107
+ if (process.platform !== "win32" && result.pid) {
108
+ try {
109
+ process.kill(-result.pid, "SIGKILL");
110
+ }
111
+ catch { /* group already gone */ }
112
+ }
113
+ if (result.error || result.status !== 0)
114
+ return unknown(result.error?.code === "ETIMEDOUT" ? "timeout" : "unavailable");
115
+ try {
116
+ const payload = JSON.parse(result.stdout);
117
+ if (!record(payload) || payload.ok !== true)
118
+ return unknown(record(payload) && typeof payload.reason === "string" && ["timeout", "unavailable", "malformed-output", "output-limit", "unsupported"].includes(payload.reason) ? payload.reason : "malformed-output");
119
+ return parseHookInventory(payload.result, cwd, pluginRoot, { codexHome, files });
120
+ }
121
+ catch {
122
+ return unknown("malformed-output");
123
+ }
124
+ }
125
+ function inventoryWorker(cwd) {
126
+ const child = spawn("codex", ["app-server", "--stdio"], { stdio: ["pipe", "pipe", "pipe"] });
127
+ let finished = false, initialized = false, received = 0, pending = "";
128
+ const timer = setTimeout(() => finish({ ok: false, reason: "timeout" }), TIMEOUT_MS);
129
+ function finish(value) {
130
+ if (finished)
131
+ return;
132
+ finished = true;
133
+ clearTimeout(timer);
134
+ child.kill("SIGKILL");
135
+ child.stdin.destroy();
136
+ child.stdout.destroy();
137
+ child.stderr.destroy();
138
+ process.stdout.write(JSON.stringify(value));
139
+ }
140
+ const send = (value) => child.stdin.write(`${JSON.stringify(value)}\n`);
141
+ child.on("error", () => finish({ ok: false, reason: "unavailable" }));
142
+ child.on("exit", () => finish({ ok: false, reason: "unavailable" }));
143
+ child.stdin.on("error", () => finish({ ok: false, reason: "unavailable" }));
144
+ child.stderr.on("data", (chunk) => {
145
+ received += chunk.length;
146
+ if (received > OUTPUT_LIMIT)
147
+ finish({ ok: false, reason: "output-limit" });
148
+ });
149
+ child.stdout.setEncoding("utf8");
150
+ child.stdout.on("data", (chunk) => {
151
+ received += Buffer.byteLength(chunk, "utf8");
152
+ if (received > OUTPUT_LIMIT)
153
+ return finish({ ok: false, reason: "output-limit" });
154
+ pending += chunk;
155
+ let newline;
156
+ while (!finished && (newline = pending.indexOf("\n")) >= 0) {
157
+ const line = pending.slice(0, newline);
158
+ pending = pending.slice(newline + 1);
159
+ let response;
160
+ try {
161
+ response = JSON.parse(line);
162
+ }
163
+ catch {
164
+ return finish({ ok: false, reason: "malformed-output" });
165
+ }
166
+ if (!record(response))
167
+ return finish({ ok: false, reason: "malformed-output" });
168
+ if (!Object.hasOwn(response, "id") && typeof response.method === "string")
169
+ continue;
170
+ if (response.id !== (initialized ? 2 : 1))
171
+ return finish({ ok: false, reason: "malformed-output" });
172
+ if (Object.hasOwn(response, "error"))
173
+ return finish({ ok: false, reason: "unsupported" });
174
+ if (!record(response.result))
175
+ return finish({ ok: false, reason: "malformed-output" });
176
+ if (initialized)
177
+ return finish({ ok: true, result: response.result });
178
+ initialized = true;
179
+ send({ method: "initialized", params: {} });
180
+ send({ id: 2, method: "hooks/list", params: { cwds: [cwd] } });
181
+ }
182
+ });
183
+ send({ id: 1, method: "initialize", params: { clientInfo: { name: "ccl_hook_audit", version: "1.0" }, capabilities: { experimentalApi: true } } });
184
+ }
185
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url) && process.argv[2] === "--inventory-worker" && isAbsolute(process.argv[3] ?? ""))
186
+ inventoryWorker(process.argv[3]);
@@ -69,16 +69,21 @@ function source(context) {
69
69
  throw new Error("invalid CCL_SKILLS_REPO: skills, OpenCode commands, or hook runtime are missing");
70
70
  regular(plugin);
71
71
  regular(bootstrap);
72
+ for (const helper of ["host-input.py", "skill-loading.py"])
73
+ regular(join(hooks, helper));
72
74
  const skillNames = readdirSync(skills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync(join(skills, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
73
75
  if (!skillNames.length)
74
76
  throw new Error("invalid CCL_SKILLS_REPO: no skills found");
75
77
  let entries = walk(skills, "skills", "skills");
76
78
  entries.push({ source: "packages/opencode-plugin/ccl-skills.ts", destination: "plugins/ccl-skills.ts", sha256: sha256(plugin), mode: regular(plugin).mode & 0o777 });
77
79
  entries.push({ source: "agent-context/session-start.md", destination: "ccl-skills/bootstrap.md", sha256: sha256(bootstrap), mode: regular(bootstrap).mode & 0o777 });
80
+ const policy = join(agentContext, "session-policy.md");
81
+ regular(policy);
82
+ entries.push({ source: "agent-context/session-policy.md", destination: "ccl-skills/session-policy.md", sha256: sha256(policy), mode: regular(policy).mode & 0o777 });
78
83
  entries.push(...walk(commands, "packages/opencode-plugin/commands", "commands").filter((entry) => /^commands\/ccl-[^/]+\.md$/.test(entry.destination)));
79
- entries.push(...walk(hooks, "hooks", "ccl-skills/runtime/hooks").filter((entry) => entry.source === "hooks/hooks.json" || /^hooks\/(?!test_)[^/]+\.sh$/.test(entry.source)));
84
+ entries.push(...walk(hooks, "hooks", "ccl-skills/runtime/hooks").filter((entry) => ["hooks/hooks.json", "hooks/host-input.py", "hooks/skill-loading.py"].includes(entry.source) || /^hooks\/(?!test_)[^/]+\.sh$/.test(entry.source)));
80
85
  entries.push(...walk(ownerDispatch, "scripts/owner-dispatch", "ccl-skills/runtime/scripts/owner-dispatch").filter((entry) => entry.source === "scripts/owner-dispatch/owner-dispatch.sh"));
81
- entries.push(...walk(agentContext, "agent-context", "ccl-skills/runtime/agent-context").filter((entry) => /^agent-context\/(session-start|subagent-start)\.md$/.test(entry.source)));
86
+ entries.push(...walk(agentContext, "agent-context", "ccl-skills/runtime/agent-context").filter((entry) => /^agent-context\/(session-start|session-policy|subagent-start)\.md$/.test(entry.source)));
82
87
  entries = entries.sort((a, b) => a.destination.localeCompare(b.destination));
83
88
  const snapshotHash = createHash("sha256").update(JSON.stringify(entries.map(({ source: s, sha256: hash, mode }) => ({ source: s, sha256: hash, mode })))).digest("hex");
84
89
  const release = readRelease(join(p.assets, "release.json"));
@@ -90,7 +95,7 @@ function source(context) {
90
95
  return { root, entries, snapshotHash, version: release.version, sourceCommit, sourceKind: override ? "override" : "bundled" };
91
96
  }
92
97
  function allowedDestination(path) {
93
- return /^skills\/[^/]+\/.+/.test(path) || path === "plugins/ccl-skills.ts" || path === "ccl-skills/bootstrap.md" || /^commands\/ccl-[^/]+\.md$/.test(path) || /^ccl-skills\/runtime\/(hooks|scripts\/owner-dispatch|agent-context)\/[^/]+$/.test(path);
98
+ return /^skills\/[^/]+\/.+/.test(path) || path === "plugins/ccl-skills.ts" || path === "ccl-skills/bootstrap.md" || path === "ccl-skills/session-policy.md" || /^commands\/ccl-[^/]+\.md$/.test(path) || /^ccl-skills\/runtime\/(hooks|scripts\/owner-dispatch|agent-context)\/[^/]+$/.test(path);
94
99
  }
95
100
  function readManifest(path, root) {
96
101
  if (!existsSync(path))
@@ -5,6 +5,7 @@ import { checkHost, marketplaceAdd, marketplaceRemove, pluginAdd, pluginRemove,
5
5
  import { manifestFor, MetadataError, readManifest, readRelease, } from "./manifest.js";
6
6
  import { paths, } from "./paths.js";
7
7
  import { compare } from "./version.js";
8
+ import { inspectHooks } from "./codex-hooks.js";
8
9
  const pending = (message, details = {}) => ({
9
10
  code: 3,
10
11
  status: "installed-hooks-pending",
@@ -485,9 +486,13 @@ function doctor(context) {
485
486
  status: "public-state-mismatch",
486
487
  message: "owned files exist but Codex registration does not match the manifest",
487
488
  };
488
- return pending("plugin is installed; hooks trust is pending/unverified", {
489
- version: manifest.active.version,
490
- });
489
+ const pluginPrefix = "marketplace/plugins/ccl-skills/";
490
+ const pluginFiles = manifest.active.ownedFiles.filter(file => file.path.startsWith(pluginPrefix)).map(file => ({ ...file, path: file.path.slice(pluginPrefix.length) }));
491
+ const hooks = inspectHooks(p.codexHome, join(marketPath(p.root, manifest.active.path), "plugins/ccl-skills"), pluginFiles);
492
+ const details = { version: manifest.active.version, hooks };
493
+ if (hooks.status === "trusted" || hooks.status === "managed")
494
+ return { code: 0, status: "installed-hooks-trusted", message: "plugin hooks are configured and trusted; runtime execution is not verified", details };
495
+ return pending(`plugin is installed; hook trust: ${hooks.status}; runtime execution is not verified`, details);
491
496
  }
492
497
  function installOrUpdate(command, options, context) {
493
498
  const p = paths(context);
@@ -534,13 +539,12 @@ function installOrUpdate(command, options, context) {
534
539
  message: "public state does not match the owned manifest",
535
540
  };
536
541
  const comparison = compare(release.version, old.active.version);
537
- if (comparison === 0)
542
+ if (comparison === 0) {
543
+ const report = doctor(context);
538
544
  return command === "install"
539
- ? {
540
- ...doctor(context),
541
- message: "same version is already installed; hooks trust remains pending/unverified",
542
- }
543
- : doctor(context);
545
+ ? { ...report, message: `same version is already installed; ${report.message}` }
546
+ : report;
547
+ }
544
548
  if (command === "install")
545
549
  return {
546
550
  code: 3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccoalm/ccl-skills",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "Reusable workflows that help coding agents plan, build, test, review, and release software — for Claude Code, Codex, and OpenCode",
5
5
  "keywords": ["skills", "agent-skills", "claude", "claude-code", "codex", "opencode", "agent", "ai", "ai-agents", "cli", "anthropic", "developer-tools"],
6
6
  "type": "module",