@yaklabs-ai/plugin-sdk 0.8.4

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.
@@ -0,0 +1,873 @@
1
+ // packages/plugin-sdk/dist/disclosure/disclosure-conformance.js
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ // packages/herd-uri/dist/connection.js
5
+ var CONNECTION_SELECTOR_RE = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)@([^/]+)(?:\/(.*))?$/;
6
+ function parseUriConnectionSelector(input) {
7
+ const match = CONNECTION_SELECTOR_RE.exec(input.trim());
8
+ if (match?.[1] === void 0 || match[2] === void 0) {
9
+ return { rest: input };
10
+ }
11
+ return { selector: match[2], rest: `${match[1]}${match[3] ?? ""}` };
12
+ }
13
+
14
+ // packages/herd-uri/dist/template-route.js
15
+ var PLACEHOLDER_RE = /<([^<>]+)>/g;
16
+ var REST_SUFFIX = "...";
17
+ var SCHEME_RE = /^([a-zA-Z][a-zA-Z0-9+.-]*):(?:\/\/)?(.*)$/s;
18
+ function parseRoute(address) {
19
+ const trimmed = address.trim();
20
+ const match = SCHEME_RE.exec(trimmed);
21
+ const scheme = match?.[1]?.toLowerCase() ?? "";
22
+ const rest = match?.[2] ?? trimmed;
23
+ const [rawPath = "", rawQuery] = splitQuery(rest);
24
+ const path = rawPath.replace(/^\/+/, "").replace(/\/+$/, "");
25
+ const query = /* @__PURE__ */ new Map();
26
+ if (rawQuery !== void 0) {
27
+ for (const pair of rawQuery.split("&")) {
28
+ if (pair.length === 0)
29
+ continue;
30
+ const eq = pair.indexOf("=");
31
+ const key = eq === -1 ? pair : pair.slice(0, eq);
32
+ const value = eq === -1 ? "" : pair.slice(eq + 1);
33
+ if (key.length === 0)
34
+ continue;
35
+ if (!query.has(key) || isPlaceholder(value))
36
+ query.set(key, value);
37
+ }
38
+ }
39
+ return { scheme, path, query };
40
+ }
41
+ function splitQuery(rest) {
42
+ const hash = rest.indexOf("#");
43
+ const withoutFragment = hash === -1 ? rest : rest.slice(0, hash);
44
+ const q = withoutFragment.indexOf("?");
45
+ return q === -1 ? [withoutFragment, void 0] : [withoutFragment.slice(0, q), withoutFragment.slice(q + 1)];
46
+ }
47
+ function isPlaceholder(value) {
48
+ return /^<[^<>]+>$/.test(value);
49
+ }
50
+ function selectorPlaceholder(templateUri) {
51
+ const selector = parseUriConnectionSelector(templateUri).selector;
52
+ if (selector === void 0)
53
+ return void 0;
54
+ const match = /^<([^<>]+)>$/.exec(selector);
55
+ return match?.[1];
56
+ }
57
+ function isRestPlaceholder(name) {
58
+ return name.endsWith(REST_SUFFIX);
59
+ }
60
+ function placeholderParameterName(name) {
61
+ return isRestPlaceholder(name) ? name.slice(0, -REST_SUFFIX.length) : name;
62
+ }
63
+ function templateAddressProblem(templateUri) {
64
+ const parsed = parseRoute(stripUriSelector(templateUri));
65
+ for (const match of parsed.path.matchAll(PLACEHOLDER_RE)) {
66
+ const name = match[1] ?? "";
67
+ if (name.includes("/")) {
68
+ return `placeholder <${name}> contains a slash; use <${name.split("/")[0] ?? "path"}...> as the last segment for a rest-of-path placeholder`;
69
+ }
70
+ if (isRestPlaceholder(name) && match.index + match[0].length !== parsed.path.length) {
71
+ return `rest-of-path placeholder <${name}> must be the last path segment`;
72
+ }
73
+ }
74
+ return void 0;
75
+ }
76
+ function stripUriSelector(address) {
77
+ return parseUriConnectionSelector(address).rest;
78
+ }
79
+ function templatePlaceholders(templateUri) {
80
+ return deriveTemplateParameters({ uri: templateUri, description: "" }).map((parameter) => parameter.name);
81
+ }
82
+ function deriveTemplateParameters(template) {
83
+ const authored = new Map((template.parameters ?? []).map((parameter) => [parameter.name, parameter]));
84
+ const parsed = parseRoute(stripUriSelector(template.uri));
85
+ const derived = [];
86
+ const selector = selectorPlaceholder(template.uri);
87
+ if (selector !== void 0) {
88
+ derived.push(authored.get(selector) ?? {
89
+ name: selector,
90
+ in: "selector",
91
+ required: false,
92
+ schema: { type: "string" }
93
+ });
94
+ }
95
+ for (const match of parsed.path.matchAll(PLACEHOLDER_RE)) {
96
+ const name = placeholderParameterName(match[1] ?? "");
97
+ if (name.length === 0 || derived.some((p) => p.name === name))
98
+ continue;
99
+ derived.push(authored.get(name) ?? {
100
+ name,
101
+ in: "uri",
102
+ required: true,
103
+ schema: { type: "string" }
104
+ });
105
+ }
106
+ for (const [key, value] of parsed.query) {
107
+ if (derived.some((p) => p.name === key))
108
+ continue;
109
+ const schema = isPlaceholder(value) ? { type: "string" } : { type: "string", enum: value.split("|").filter((v) => v.length > 0) };
110
+ derived.push(authored.get(key) ?? {
111
+ name: key,
112
+ in: "query",
113
+ required: false,
114
+ schema
115
+ });
116
+ }
117
+ const derivedNames = new Set(derived.map((parameter) => parameter.name));
118
+ const extra = (template.parameters ?? []).filter((parameter) => !derivedNames.has(parameter.name));
119
+ return [...derived, ...extra];
120
+ }
121
+
122
+ // packages/herd-uri/dist/template-lint.js
123
+ function lintUriTemplate(template) {
124
+ const findings = [];
125
+ const addressProblem = templateAddressProblem(template.uri);
126
+ if (addressProblem !== void 0) {
127
+ findings.push({
128
+ rule: "template-address-unmatchable",
129
+ uri: template.uri,
130
+ message: `${template.uri} can never be matched by help: ${addressProblem}.`
131
+ });
132
+ }
133
+ const operations = template.operations ?? [];
134
+ if (operations.filter((operation) => operation !== "help").length === 0) {
135
+ findings.push({
136
+ rule: "template-operations-missing",
137
+ uri: template.uri,
138
+ message: `${template.uri} lists no operations; a route must declare the verbs it supports.`
139
+ });
140
+ }
141
+ for (const operation of operations) {
142
+ if ((operation === "write" || operation === "edit") && template.accepts?.[operation] === void 0) {
143
+ findings.push({
144
+ rule: "template-accepts-missing",
145
+ uri: template.uri,
146
+ operation,
147
+ message: `${template.uri} lists ${operation} but declares no accepts.${operation} payload contract (use payload() from the handler's validator, or an explicit contract without a schema for an unstructured body).`
148
+ });
149
+ }
150
+ if (operation === "delete" && template.deleteEffect === void 0) {
151
+ findings.push({
152
+ rule: "template-delete-effect-missing",
153
+ uri: template.uri,
154
+ operation,
155
+ message: `${template.uri} lists delete but declares no deleteEffect (trash, permanent, or conditional).`
156
+ });
157
+ }
158
+ if (operation === "help")
159
+ continue;
160
+ const sentence = template.operationDocs?.[operation];
161
+ const soleVerb = operations.filter((candidate) => candidate !== "help").length === 1;
162
+ if (sentence === void 0 && !soleVerb) {
163
+ findings.push({
164
+ rule: "template-operation-docs-missing",
165
+ uri: template.uri,
166
+ operation,
167
+ message: `${template.uri} lists ${operation} alongside other verbs and has no operationDocs.${operation} sentence.`
168
+ });
169
+ }
170
+ }
171
+ const placeholders = new Set(templatePlaceholders(template.uri));
172
+ for (const parameter of template.parameters ?? []) {
173
+ if (placeholders.has(parameter.name))
174
+ continue;
175
+ findings.push({
176
+ rule: "template-parameter-unknown",
177
+ uri: template.uri,
178
+ parameter: parameter.name,
179
+ message: `${template.uri} authors parameter "${parameter.name}" but the address has no <${parameter.name}> placeholder.`
180
+ });
181
+ }
182
+ return findings;
183
+ }
184
+
185
+ // packages/herd-uri/dist/operations.js
186
+ import { z } from "zod";
187
+ var URI_OPERATIONS = [
188
+ "help",
189
+ "read",
190
+ "write",
191
+ "edit",
192
+ "list",
193
+ "search",
194
+ "delete"
195
+ ];
196
+ var UriOperationSchema = z.enum(URI_OPERATIONS);
197
+
198
+ // packages/herd-uri/dist/help-schema.js
199
+ import { z as z2 } from "zod";
200
+ var UriContractJsonSchema = z2.record(z2.string(), z2.unknown());
201
+ var UriParameterSchema = z2.strictObject({
202
+ name: z2.string().min(1),
203
+ in: z2.enum(["uri", "query", "selector"]),
204
+ required: z2.boolean(),
205
+ description: z2.string().optional(),
206
+ schema: UriContractJsonSchema,
207
+ examples: z2.array(z2.unknown()).optional()
208
+ });
209
+ var UriPayloadContractSchema = z2.strictObject({
210
+ name: z2.string().min(1),
211
+ mediaType: z2.string().optional(),
212
+ description: z2.string().optional(),
213
+ schema: UriContractJsonSchema.optional(),
214
+ semanticConstraints: z2.array(z2.string().min(1)).min(1).optional(),
215
+ examples: z2.array(z2.unknown()).optional()
216
+ });
217
+
218
+ // packages/herd-uri/dist/text-edit.js
219
+ import { z as z3 } from "zod";
220
+ var findReplaceArm = z3.strictObject({
221
+ find: z3.string().min(1),
222
+ replace: z3.string(),
223
+ all: z3.boolean().optional()
224
+ });
225
+ var oldNewEntry = z3.strictObject({
226
+ oldText: z3.string().min(1),
227
+ newText: z3.string()
228
+ });
229
+ var uriTextEditPayloadArms = z3.union([
230
+ findReplaceArm,
231
+ z3.strictObject({ edits: z3.array(oldNewEntry).min(1) }),
232
+ oldNewEntry,
233
+ z3.array(oldNewEntry).min(1)
234
+ ]);
235
+
236
+ // packages/herd-uri/dist/schema/to-json-schema.js
237
+ import { z as z4 } from "zod";
238
+
239
+ // packages/herd-agent/dist/core/tools/tool-parameters.js
240
+ import { z as z5 } from "zod";
241
+ var URI_PAYLOAD_MARKER = "x-kay-uri-payload";
242
+ var uriPayloadSchema = z5.union([
243
+ z5.string(),
244
+ z5.record(z5.string(), z5.unknown()),
245
+ z5.array(z5.unknown()),
246
+ z5.number(),
247
+ z5.boolean(),
248
+ z5.null()
249
+ ]).meta({ [URI_PAYLOAD_MARKER]: true });
250
+
251
+ // packages/herd-agent/dist/core/tools/uri/hint-tiers.js
252
+ var FIELD_LIMIT_CHARS = 220;
253
+ var FLOW_LABEL_LIMIT_CHARS = 80;
254
+ var FLOW_WHEN_LIMIT_CHARS = 120;
255
+ var HELP_URI_LIMIT_CHARS = 140;
256
+ var LIST_LIMIT = 8;
257
+ var LIST_ITEM_LIMIT_CHARS = 60;
258
+ var URI_HINT_FIELD_LIMITS = {
259
+ /** Max chars for `agentHint.summary` (and, today, each flow's `example`). */
260
+ summary: FIELD_LIMIT_CHARS,
261
+ /** Max chars for a `commonFlows[].label`. */
262
+ flowLabel: FLOW_LABEL_LIMIT_CHARS,
263
+ /** Max chars for a `commonFlows[].when`. */
264
+ flowWhen: FLOW_WHEN_LIMIT_CHARS,
265
+ /** Max chars for a `commonFlows[].example`. */
266
+ flowExample: FIELD_LIMIT_CHARS,
267
+ /** Max chars for `agentHint.helpUri`. */
268
+ helpUri: HELP_URI_LIMIT_CHARS,
269
+ /** Max entries in `triggers.keywords` / `triggers.patterns`. */
270
+ triggerListItems: LIST_LIMIT,
271
+ /** Max chars for one `triggers.keywords` / `triggers.patterns` entry. */
272
+ triggerItem: LIST_ITEM_LIMIT_CHARS
273
+ };
274
+
275
+ // packages/plugin-sdk/dist/disclosure/disclosure-conformance.js
276
+ var PLUGIN_DISCLOSURE_RULES = [
277
+ // (a) the agentHint a module must ship to be discoverable at all
278
+ "agent-hint-missing",
279
+ "agent-hint-summary-empty",
280
+ "agent-hint-flows-empty",
281
+ "agent-hint-triggers-missing",
282
+ // (b) help() well-formedness, per claimed scheme root
283
+ "scheme-claim-missing",
284
+ "help-missing",
285
+ "help-malformed",
286
+ "help-throws",
287
+ "help-uri-malformed",
288
+ "help-uri-throws",
289
+ // (c) the verb-documentation floor (#2863, #2935) — what a permission surface renders
290
+ "verb-coverage-missing",
291
+ "template-operation-docs-missing",
292
+ "verb-docs-missing",
293
+ // (c2) the route-contract floor (#6880, contract 2): a route's template is the whole
294
+ // contract for that route, so a mutating verb without its payload contract, a delete
295
+ // without its effect, or an authored parameter with no placeholder is a load-time defect.
296
+ // Rules are evaluated by `@herd/uri`'s `lintUriTemplate` so the daemon's built-in
297
+ // modules, a plugin's own suite, and this audit run the identical function.
298
+ "template-accepts-missing",
299
+ "template-address-unmatchable",
300
+ "template-delete-effect-missing",
301
+ "template-operations-missing",
302
+ "template-parameter-unknown",
303
+ // (c3) a template's address must be one the module can be asked about: its scheme
304
+ // is one the module claims (a scheme-less shorthand like `<conversation>/messages`
305
+ // never matches a `teams://` address). Module-aware, so it lives here and not in
306
+ // the pure per-template lint.
307
+ "template-scheme-mismatch",
308
+ // (d) the field clamps, measured against herd's own render limits
309
+ "clamp-summary",
310
+ "clamp-flow-label",
311
+ "clamp-flow-when",
312
+ "clamp-flow-example",
313
+ "clamp-trigger-count",
314
+ "clamp-trigger-item",
315
+ "clamp-help-uri",
316
+ // (e) module <-> bundled-skill cross-link integrity
317
+ "skill-link-dangling",
318
+ "skill-link-missing-scheme",
319
+ // (f) auditSkillCodegenPairs: a declared SKILL.md and its exported codegen pair must say the
320
+ // same thing. Rule ids below deliberately KEEP the historical `skill-twin-*` prefix even
321
+ // though the function and its prose were renamed (see PR that renamed `auditSkillCodegenPairs` to
322
+ // free "twin" for the Skills v2 product concept): `test/disclosure/conformance-contract.test.ts`
323
+ // pins CONTRACT_HISTORY's contract-1 entry byte-for-byte as the published SDK 0.3.0 floor, and
324
+ // changing a rule id string is exactly the kind of edit that test exists to catch — it would
325
+ // require appending a new CONTRACT_HISTORY entry, bumping `PLUGIN_DISCLOSURE_CONTRACT`, and
326
+ // cutting a new SDK release, none of which this rename earns. A rule id is a wire-visible
327
+ // string a third-party plugin's own suite may already assert on; renaming the function is free,
328
+ // renaming a published contract's rule ids is not.
329
+ "skill-twin-count-mismatch",
330
+ "skill-twin-missing-declaration",
331
+ "skill-twin-description-mismatch",
332
+ "skill-twin-file-unreadable",
333
+ "skill-twin-frontmatter-name-mismatch",
334
+ "skill-twin-frontmatter-description-mismatch",
335
+ "skill-twin-body-mismatch"
336
+ ];
337
+ var PLUGIN_DISCLOSURE_CONTRACT = 2;
338
+ var FAKE_RESOLVE_CONTEXT = { cwd: "/" };
339
+ async function auditPluginDisclosure(input) {
340
+ const violations = [];
341
+ const hintBearing = input.modules.filter((module) => module.agentHint !== void 0);
342
+ violations.push(...auditHintPresence(input.modules));
343
+ for (const module of hintBearing) {
344
+ const hint = module.agentHint;
345
+ violations.push(...auditHintCompleteness(module, hint));
346
+ violations.push(...auditClampFit(module, hint));
347
+ const helpProbe = await auditHelp(module);
348
+ violations.push(...helpProbe.violations);
349
+ violations.push(...await auditHelpUri(module, hint));
350
+ violations.push(...auditVerbCoverage(module, helpProbe.helps));
351
+ violations.push(...auditRouteContracts(module, helpProbe.helps));
352
+ violations.push(...auditVerbDocs(module));
353
+ violations.push(...auditSkillsCrossLink(module, hint, input.declaredSkills));
354
+ }
355
+ violations.push(...auditUnlinkedSkills(hintBearing, input.declaredSkills, new Set(input.commandBoundSkillIds ?? [])));
356
+ return violations;
357
+ }
358
+ function auditHintPresence(modules) {
359
+ const violations = [];
360
+ for (const module of modules) {
361
+ if (module.agentHint !== void 0)
362
+ continue;
363
+ const roots = schemeRoots(module);
364
+ if (roots.length === 0)
365
+ continue;
366
+ violations.push({
367
+ moduleId: module.id,
368
+ rule: "agent-hint-missing",
369
+ detail: `Module '${module.id}' claims ${roots.join(", ")} but ships no agentHint, so no agent can ever learn it exists: the hint is the only thing the system prompt renders for a URI module, and the only thing the catalog projects. The module would dispatch correctly and stay invisible. Add an agentHint with a summary, at least one commonFlows entry, and at least one trigger.`
370
+ });
371
+ }
372
+ return violations;
373
+ }
374
+ function auditHintCompleteness(module, hint) {
375
+ const violations = [];
376
+ if (hint.summary.trim().length === 0) {
377
+ violations.push({
378
+ moduleId: module.id,
379
+ rule: "agent-hint-summary-empty",
380
+ detail: "agentHint.summary must be a non-empty string."
381
+ });
382
+ }
383
+ if (hint.commonFlows === void 0 || hint.commonFlows.length === 0) {
384
+ violations.push({
385
+ moduleId: module.id,
386
+ rule: "agent-hint-flows-empty",
387
+ detail: "agentHint.commonFlows must carry at least one worked flow."
388
+ });
389
+ }
390
+ const keywords = hint.triggers?.keywords ?? [];
391
+ const patterns = hint.triggers?.patterns ?? [];
392
+ if (keywords.length === 0 && patterns.length === 0) {
393
+ violations.push({
394
+ moduleId: module.id,
395
+ rule: "agent-hint-triggers-missing",
396
+ detail: "agentHint.triggers must declare at least one keyword or pattern."
397
+ });
398
+ }
399
+ return violations;
400
+ }
401
+ function isWellFormedHelp(value) {
402
+ if (typeof value !== "object" || value === null)
403
+ return false;
404
+ const help = value;
405
+ return typeof help.uri === "string" && help.uri.trim().length > 0 && typeof help.title === "string" && help.title.trim().length > 0 && typeof help.summary === "string" && help.summary.trim().length > 0;
406
+ }
407
+ function schemeRoots(module) {
408
+ return (module.claims ?? []).filter((claim) => claim.kind === "scheme").map((claim) => `${claim.scheme}://`);
409
+ }
410
+ async function auditHelp(module) {
411
+ if (module.help === void 0) {
412
+ return {
413
+ violations: [
414
+ {
415
+ moduleId: module.id,
416
+ rule: "help-missing",
417
+ detail: "Module has no help(); since #1125 the system prompt promises help() works for every space."
418
+ }
419
+ ],
420
+ helps: []
421
+ };
422
+ }
423
+ const roots = schemeRoots(module);
424
+ if (roots.length === 0) {
425
+ return {
426
+ violations: [
427
+ {
428
+ moduleId: module.id,
429
+ rule: "scheme-claim-missing",
430
+ detail: `Module has an agentHint but no { kind: "scheme" } claim, so its scheme root can't be located to call help() on.`
431
+ }
432
+ ],
433
+ helps: []
434
+ };
435
+ }
436
+ const violations = [];
437
+ const helps = [];
438
+ for (const root of roots) {
439
+ try {
440
+ const result = await module.help(root, FAKE_RESOLVE_CONTEXT);
441
+ if (isWellFormedHelp(result)) {
442
+ helps.push(result);
443
+ } else {
444
+ violations.push({
445
+ moduleId: module.id,
446
+ rule: "help-malformed",
447
+ detail: `help(${JSON.stringify(root)}) returned a value missing a non-empty uri/title/summary.`
448
+ });
449
+ }
450
+ } catch (error) {
451
+ violations.push({
452
+ moduleId: module.id,
453
+ rule: "help-throws",
454
+ detail: `help(${JSON.stringify(root)}) threw: ${errorMessage(error)}`
455
+ });
456
+ }
457
+ }
458
+ return { violations, helps };
459
+ }
460
+ function supportedVerbs(module) {
461
+ const verbs = [];
462
+ if (module.verbs.readable)
463
+ verbs.push("read");
464
+ if (module.verbs.listable && module.list !== void 0)
465
+ verbs.push("list");
466
+ if (module.verbs.listable && module.search !== void 0) {
467
+ verbs.push("search");
468
+ }
469
+ if (module.verbs.writable)
470
+ verbs.push("write");
471
+ if (module.verbs.editable)
472
+ verbs.push("edit");
473
+ if (module.verbs.deletable === true || module.delete !== void 0) {
474
+ verbs.push("delete");
475
+ }
476
+ return verbs;
477
+ }
478
+ function helpTemplates(helps) {
479
+ return helps.flatMap((help) => help.templates ?? []);
480
+ }
481
+ function auditVerbCoverage(module, helps) {
482
+ if (helps.length === 0)
483
+ return [];
484
+ const covered = new Set(helpTemplates(helps).flatMap((template) => template.operations ?? []));
485
+ const violations = [];
486
+ for (const verb of supportedVerbs(module)) {
487
+ if (covered.has(verb))
488
+ continue;
489
+ violations.push({
490
+ moduleId: module.id,
491
+ rule: "verb-coverage-missing",
492
+ detail: `Module '${module.id}' supports '${verb}' but no help() template lists it in 'operations', so there is no documented route for the verb. Add (or extend) a template naming the address shape '${verb}' acts on.`
493
+ });
494
+ }
495
+ return violations;
496
+ }
497
+ function auditRouteContracts(module, helps) {
498
+ const schemes = new Set((module.claims ?? []).flatMap((claim) => claim.kind === "scheme" ? [claim.scheme.toLowerCase()] : []));
499
+ const hasPatternClaim = (module.claims ?? []).some((claim) => claim.kind !== "scheme");
500
+ return helpTemplates(helps).flatMap((template) => {
501
+ const violations = lintUriTemplate(template).map((finding) => ({
502
+ moduleId: module.id,
503
+ rule: finding.rule,
504
+ detail: finding.message
505
+ }));
506
+ const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(template.uri.trim())?.[1];
507
+ if (schemes.size > 0 && !hasPatternClaim) {
508
+ if (scheme === void 0 || !schemes.has(scheme.toLowerCase())) {
509
+ violations.push({
510
+ moduleId: module.id,
511
+ rule: "template-scheme-mismatch",
512
+ detail: `Template '${template.uri}' ${scheme === void 0 ? "has no scheme" : `uses scheme '${scheme}'`}, but module '${module.id}' claims ${[...schemes].map((s) => `'${s}://'`).join(", ")}. help selects a route by scheme and path, so this template can never be asked about; write the full address (e.g. '${[...schemes][0]}://\u2026').`
513
+ });
514
+ }
515
+ }
516
+ return violations;
517
+ });
518
+ }
519
+ function auditVerbDocs(module) {
520
+ const violations = [];
521
+ for (const verb of supportedVerbs(module)) {
522
+ const doc = module.verbDocs?.[verb];
523
+ if (doc !== void 0 && doc.trim().length > 0)
524
+ continue;
525
+ violations.push({
526
+ moduleId: module.id,
527
+ rule: "verb-docs-missing",
528
+ detail: `Module '${module.id}' supports '${verb}' but verbDocs.${verb} is ${module.verbDocs === void 0 ? "absent (no verbDocs at all)" : "missing or empty"}. Document the verb's concrete provider effect in one plain sentence (and, for delete, how recoverable it is). Never wire mechanics or URI shapes.`
529
+ });
530
+ }
531
+ return violations;
532
+ }
533
+ async function auditHelpUri(module, hint) {
534
+ if (hint.helpUri === void 0 || module.help === void 0)
535
+ return [];
536
+ try {
537
+ const result = await module.help(hint.helpUri, FAKE_RESOLVE_CONTEXT);
538
+ if (!isWellFormedHelp(result)) {
539
+ return [
540
+ {
541
+ moduleId: module.id,
542
+ rule: "help-uri-malformed",
543
+ detail: `help(${JSON.stringify(hint.helpUri)}) (agentHint.helpUri) returned a value missing a non-empty uri/title/summary.`
544
+ }
545
+ ];
546
+ }
547
+ } catch (error) {
548
+ return [
549
+ {
550
+ moduleId: module.id,
551
+ rule: "help-uri-throws",
552
+ detail: `help(${JSON.stringify(hint.helpUri)}) (agentHint.helpUri) threw: ${errorMessage(error)}`
553
+ }
554
+ ];
555
+ }
556
+ return [];
557
+ }
558
+ function auditClampFit(module, hint) {
559
+ const violations = [];
560
+ const limits = URI_HINT_FIELD_LIMITS;
561
+ if (hint.summary.length > limits.summary) {
562
+ violations.push(clampViolation(module.id, "clamp-summary", "agentHint.summary", hint.summary.length, limits.summary));
563
+ }
564
+ for (const [index, flow] of (hint.commonFlows ?? []).entries()) {
565
+ if (flow.label.length > limits.flowLabel) {
566
+ violations.push(clampViolation(module.id, "clamp-flow-label", `agentHint.commonFlows[${index}].label`, flow.label.length, limits.flowLabel));
567
+ }
568
+ if (flow.when !== void 0 && flow.when.length > limits.flowWhen) {
569
+ violations.push(clampViolation(module.id, "clamp-flow-when", `agentHint.commonFlows[${index}].when`, flow.when.length, limits.flowWhen));
570
+ }
571
+ if (flow.example.length > limits.flowExample) {
572
+ violations.push(clampViolation(module.id, "clamp-flow-example", `agentHint.commonFlows[${index}].example`, flow.example.length, limits.flowExample));
573
+ }
574
+ }
575
+ for (const kind of ["keywords", "patterns"]) {
576
+ const values = hint.triggers?.[kind] ?? [];
577
+ if (values.length > limits.triggerListItems) {
578
+ violations.push({
579
+ moduleId: module.id,
580
+ rule: "clamp-trigger-count",
581
+ detail: `agentHint.triggers.${kind} has ${values.length} entries, over the ${limits.triggerListItems}-item limit.`
582
+ });
583
+ }
584
+ for (const [index, value] of values.entries()) {
585
+ if (value.length > limits.triggerItem) {
586
+ violations.push(clampViolation(module.id, "clamp-trigger-item", `agentHint.triggers.${kind}[${index}]`, value.length, limits.triggerItem));
587
+ }
588
+ }
589
+ }
590
+ if (hint.helpUri !== void 0 && hint.helpUri.length > limits.helpUri) {
591
+ violations.push(clampViolation(module.id, "clamp-help-uri", "agentHint.helpUri", hint.helpUri.length, limits.helpUri));
592
+ }
593
+ return violations;
594
+ }
595
+ function clampViolation(moduleId, rule, field, actual, max) {
596
+ return {
597
+ moduleId,
598
+ rule,
599
+ detail: `${field} is ${actual} chars, over the ${max}-char limit.`
600
+ };
601
+ }
602
+ function auditSkillsCrossLink(module, hint, declaredSkills) {
603
+ const violations = [];
604
+ const roots = schemeRoots(module);
605
+ for (const skillName of hint.skills ?? []) {
606
+ const skill = declaredSkills.find((s) => s.localId === skillName);
607
+ if (skill === void 0) {
608
+ violations.push({
609
+ moduleId: module.id,
610
+ rule: "skill-link-dangling",
611
+ detail: `agentHint.skills names '${skillName}', which is not a declared skill localId of this plugin.`
612
+ });
613
+ continue;
614
+ }
615
+ if (roots.length > 0 && !roots.some((root) => skill.description.includes(root))) {
616
+ violations.push({
617
+ moduleId: module.id,
618
+ rule: "skill-link-missing-scheme",
619
+ detail: `Skill '${skillName}' is linked from module '${module.id}' but its description names none of its scheme(s) (${roots.join(", ")}); the skill index shows only name/description before load, so the description is the only place this link can live.`
620
+ });
621
+ }
622
+ }
623
+ return violations;
624
+ }
625
+ function auditUnlinkedSkills(hintBearingModules, declaredSkills, commandBoundSkillIds) {
626
+ const linkedNames = /* @__PURE__ */ new Set();
627
+ for (const module of hintBearingModules) {
628
+ for (const name of module.agentHint?.skills ?? [])
629
+ linkedNames.add(name);
630
+ }
631
+ const pluginRoots = hintBearingModules.flatMap((module) => schemeRoots(module));
632
+ if (pluginRoots.length === 0)
633
+ return [];
634
+ const violations = [];
635
+ for (const skill of declaredSkills) {
636
+ if (linkedNames.has(skill.localId))
637
+ continue;
638
+ if (commandBoundSkillIds.has(skill.localId))
639
+ continue;
640
+ if (!pluginRoots.some((root) => skill.description.includes(root))) {
641
+ violations.push({
642
+ moduleId: skill.localId,
643
+ rule: "skill-link-missing-scheme",
644
+ detail: `Declared skill '${skill.localId}' is not linked from any module's agentHint.skills, but its description names none of the plugin's scheme(s) (${pluginRoots.join(", ")}); the skill index shows only name/description before load, so the description is the only place this link can live.`
645
+ });
646
+ }
647
+ }
648
+ return violations;
649
+ }
650
+ function errorMessage(error) {
651
+ return error instanceof Error ? error.message : String(error);
652
+ }
653
+ async function auditSkillCodegenPairs(input) {
654
+ const violations = [];
655
+ const { declaredSkills, exportedSkills, packageDir } = input;
656
+ if (declaredSkills.length !== exportedSkills.length) {
657
+ violations.push({
658
+ moduleId: "skills",
659
+ rule: "skill-twin-count-mismatch",
660
+ detail: `${declaredSkills.length} declared skill(s) but ${exportedSkills.length} exported PluginSkillDefinition(s).`
661
+ });
662
+ }
663
+ for (const skill of exportedSkills) {
664
+ const declaration = declaredSkills.find((d) => d.localId === skill.name);
665
+ if (declaration === void 0) {
666
+ violations.push({
667
+ moduleId: skill.name,
668
+ rule: "skill-twin-missing-declaration",
669
+ detail: `Exported skill '${skill.name}' has no manifest declaration with a matching localId.`
670
+ });
671
+ continue;
672
+ }
673
+ if (declaration.description !== skill.description) {
674
+ violations.push({
675
+ moduleId: skill.name,
676
+ rule: "skill-twin-description-mismatch",
677
+ detail: `Manifest description for '${skill.name}' does not match the exported PluginSkillDefinition's description.`
678
+ });
679
+ }
680
+ violations.push(
681
+ ...await auditSkillCodegenPairFile(skill, declaration, packageDir)
682
+ );
683
+ }
684
+ return violations;
685
+ }
686
+ async function auditSkillCodegenPairFile(skill, declaration, packageDir) {
687
+ let file;
688
+ try {
689
+ file = await readFile(new URL(declaration.path, packageDir), "utf8");
690
+ } catch (error) {
691
+ return [
692
+ {
693
+ moduleId: skill.name,
694
+ rule: "skill-twin-file-unreadable",
695
+ detail: `Could not read declared SKILL.md at '${declaration.path}': ${errorMessage(error)}`
696
+ }
697
+ ];
698
+ }
699
+ const codegenPairMatch = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/.exec(file);
700
+ const frontmatter = codegenPairMatch?.[1] === void 0 ? void 0 : `${codegenPairMatch[1]}
701
+ `;
702
+ const body = codegenPairMatch?.[2];
703
+ const violations = [];
704
+ if (frontmatter === void 0 || !frontmatter.includes(`name: ${skill.name}
705
+ `)) {
706
+ violations.push({
707
+ moduleId: skill.name,
708
+ rule: "skill-twin-frontmatter-name-mismatch",
709
+ detail: `SKILL.md frontmatter does not declare 'name: ${skill.name}'.`
710
+ });
711
+ }
712
+ if (frontmatter === void 0 || !frontmatter.includes(`description: ${skill.description}
713
+ `)) {
714
+ violations.push({
715
+ moduleId: skill.name,
716
+ rule: "skill-twin-frontmatter-description-mismatch",
717
+ detail: "SKILL.md frontmatter description does not match the exported description."
718
+ });
719
+ }
720
+ if (body === void 0 || body.trim() !== skill.content.trim()) {
721
+ violations.push({
722
+ moduleId: skill.name,
723
+ rule: "skill-twin-body-mismatch",
724
+ detail: "SKILL.md body does not match the exported PluginSkillDefinition content."
725
+ });
726
+ }
727
+ return violations;
728
+ }
729
+
730
+ // packages/plugin-sdk/dist/disclosure/probe-kay-agent-capabilities.js
731
+ var inertResult = () => new Promise(() => {
732
+ });
733
+ var INERT_FILESYSTEM = {
734
+ readText: inertResult,
735
+ writeText: inertResult,
736
+ mkdir: inertResult,
737
+ readDir: inertResult,
738
+ stat: inertResult,
739
+ rename: inertResult,
740
+ realpath: inertResult
741
+ };
742
+ var INERT_NETWORK = {
743
+ fetch: inertResult
744
+ };
745
+ var INERT_WEB_SESSION = {
746
+ request: inertResult,
747
+ download: inertResult
748
+ };
749
+ var INERT_PROCESS = {
750
+ run: inertResult
751
+ };
752
+ var INERT_WHITEBOARD = {
753
+ listTypes: async () => [],
754
+ forType: () => ({
755
+ payload: {
756
+ read: inertResult,
757
+ create: inertResult,
758
+ replace: inertResult
759
+ },
760
+ inventory: async () => ({ entries: [] }),
761
+ inspect: async () => void 0,
762
+ list: inertResult,
763
+ read: inertResult,
764
+ create: inertResult,
765
+ update: inertResult,
766
+ mutate: inertResult,
767
+ remove: inertResult
768
+ })
769
+ };
770
+ var INERT_WITH_JEV = async (_use, fallback) => fallback();
771
+ function createKayAgentProbeCapabilityHandles() {
772
+ return {
773
+ filesystem: INERT_FILESYSTEM,
774
+ network: INERT_NETWORK,
775
+ webSession: INERT_WEB_SESSION,
776
+ process: INERT_PROCESS,
777
+ whiteboard: INERT_WHITEBOARD,
778
+ interceptToolCall: () => {
779
+ },
780
+ withJev: INERT_WITH_JEV,
781
+ // Fire-and-forget on the real api too, so dropping is the inert shape: nothing to hang on.
782
+ events: { publish: () => {
783
+ } }
784
+ };
785
+ }
786
+
787
+ // packages/plugin-sdk/dist/disclosure/probe-kay-agent-activate.js
788
+ function collectUriModulesFromKayAgentActivate(activate) {
789
+ const modules = [];
790
+ const fakeApi = {
791
+ uri: {
792
+ registerModule: (module) => {
793
+ modules.push(module);
794
+ }
795
+ },
796
+ ...createKayAgentProbeCapabilityHandles(),
797
+ registerTool: () => {
798
+ },
799
+ registerSearchProvider: () => {
800
+ },
801
+ registerConverter: () => {
802
+ },
803
+ auth: {
804
+ advertiseService: () => {
805
+ },
806
+ declareOAuthPkceScheme: () => {
807
+ },
808
+ declareApiKeyScheme: () => {
809
+ },
810
+ declareExternalCliScheme: () => {
811
+ },
812
+ declareDelegatedScheme: () => {
813
+ },
814
+ registerDelegatedServiceSource: () => {
815
+ }
816
+ }
817
+ };
818
+ activate(fakeApi);
819
+ return modules;
820
+ }
821
+
822
+ // packages/plugin-sdk/dist/disclosure/probe-kay-agent-auth.js
823
+ function collectAuthDeclarationsFromKayAgentActivate(activate) {
824
+ const services = [];
825
+ const oauthPkceSchemes = [];
826
+ const apiKeySchemes = [];
827
+ const delegatedSchemes = [];
828
+ const fakeApi = {
829
+ uri: {
830
+ registerModule: () => {
831
+ }
832
+ },
833
+ ...createKayAgentProbeCapabilityHandles(),
834
+ registerTool: () => {
835
+ },
836
+ registerSearchProvider: () => {
837
+ },
838
+ registerConverter: () => {
839
+ },
840
+ auth: {
841
+ advertiseService: (descriptor) => {
842
+ services.push(descriptor);
843
+ },
844
+ declareOAuthPkceScheme: (serviceId, scheme) => {
845
+ oauthPkceSchemes.push({ serviceId, scheme });
846
+ },
847
+ declareApiKeyScheme: (serviceId, scheme) => {
848
+ apiKeySchemes.push({ serviceId, scheme });
849
+ },
850
+ declareExternalCliScheme: () => {
851
+ },
852
+ declareDelegatedScheme: (serviceId, scheme) => {
853
+ delegatedSchemes.push({ serviceId, scheme });
854
+ },
855
+ registerDelegatedServiceSource: (source) => {
856
+ for (const { descriptor, scheme } of source()) {
857
+ services.push(descriptor);
858
+ delegatedSchemes.push({ serviceId: descriptor.id, scheme });
859
+ }
860
+ }
861
+ }
862
+ };
863
+ activate(fakeApi);
864
+ return { services, oauthPkceSchemes, apiKeySchemes, delegatedSchemes };
865
+ }
866
+ export {
867
+ PLUGIN_DISCLOSURE_CONTRACT,
868
+ PLUGIN_DISCLOSURE_RULES,
869
+ auditPluginDisclosure,
870
+ auditSkillCodegenPairs,
871
+ collectAuthDeclarationsFromKayAgentActivate,
872
+ collectUriModulesFromKayAgentActivate
873
+ };