@subbly/plugin-lint 0.0.5 → 0.0.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @subbly/plugin-lint
2
2
 
3
+ ## 0.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - A config field may carry `description` beside `label`: a string of 1-300 characters after trimming, shown under the input in the install dialog. Before, the linter rejected it as an unknown key (rule `manifest/namespace-unknown-key`), though the builder accepts it. A blank or over-long description fails rule `field/description`.
8
+
9
+ ## 0.0.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 937b7e6: A connector marker may carry `liveTools` beside `auth` and `tools`: the read tools that a live page may call. It is a non-empty array of unique tool names, 1-100 characters each, as `tools` is. When `tools` is there too, each live tool must also be in `tools` (rule `connector/live-tools`). A marker with none of `auth`, `tools` and `liveTools` is rejected, as the builder refresh rejects it.
14
+
3
15
  ## 0.0.5
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subbly/plugin-lint",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The Subbly marketplace release gate, offline. Lints marketplace.json, plugin manifests, mcp.json, skills, agents and automations.",
package/src/report.mjs CHANGED
@@ -41,6 +41,7 @@ export const RULES = {
41
41
  'field/key-format': 'field keys must match /^[A-Z][A-Z0-9_]*$/',
42
42
  'field/key-duplicate': 'a duplicate field key silently overrides the first',
43
43
  'field/label': 'label is required, 1-100 characters',
44
+ 'field/description': 'description is optional, 1-300 characters after trimming',
44
45
  'field/type': 'type must be "text" or "select"',
45
46
  'field/options': 'options belong to select fields and select fields require them',
46
47
  'field/public-secret': 'public and secret are mutually exclusive',
@@ -64,9 +65,10 @@ export const RULES = {
64
65
  'setup/missing-install-skill': 'setup: true requires a skills/install skill',
65
66
 
66
67
  // connectors and mcp.json
67
- 'connector/marker': 'a connector marker holds auth, tools or both, and nothing else',
68
+ 'connector/marker': 'a connector marker holds one or more of auth, tools and liveTools, and nothing else',
68
69
  'connector/auth': 'auth, when present, is exactly "oauth"',
69
- 'connector/tools': 'tools is a non-empty array of unique tool names, 1-100 characters each',
70
+ 'connector/tools': 'tools and liveTools are each a non-empty array of unique tool names, 1-100 characters each',
71
+ 'connector/live-tools': 'each live tool is also in tools, when tools is present',
70
72
  'connector/name-length': 'connector names are 1-100 characters',
71
73
  'connector/undefined-server': 'a declared connector needs a matching mcp.json server',
72
74
  'connector/no-mcp': 'declaring a connector requires an mcp.json',
package/src/rules.mjs CHANGED
@@ -35,7 +35,7 @@ const CONTENT_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/i
35
35
 
36
36
  const MANIFEST_KEYS = ['$schema', 'name', 'version', 'description', 'author', 'homepage', 'repository', 'license', 'keywords', 'extensions']
37
37
  const NAMESPACE_KEYS = ['displayName', 'image', 'default', 'fields', 'automations', 'setup', 'connectors']
38
- const FIELD_KEYS = ['key', 'label', 'type', 'required', 'secret', 'public', 'pattern', 'options']
38
+ const FIELD_KEYS = ['key', 'label', 'description', 'type', 'required', 'secret', 'public', 'pattern', 'options']
39
39
  const AUTOMATION_KEYS = ['name', 'schedule', 'model']
40
40
  const AUTOMATION_MODELS = ['normal', 'intelligent-high']
41
41
  const SERVER_KEYS = ['type', 'url', 'headers']
@@ -458,6 +458,9 @@ function lintFields(report, file, at, check, ns, ext, slug) {
458
458
  }
459
459
 
460
460
  check.string('field/label', `${p}.label`, field.label, { min: 1, max: 100, required: true, label: `${label}.label` })
461
+ if (field.description !== undefined) {
462
+ check.string('field/description', `${p}.description`, field.description, { min: 1, max: 300, trim: true, label: `${label}.description` })
463
+ }
461
464
 
462
465
  if (field.type !== 'text' && field.type !== 'select') {
463
466
  report.error('field/type', file, `${label}.type: expected "text" or "select", received ${JSON.stringify(field.type)}`, at(`${p}.type`))
@@ -587,9 +590,9 @@ function lintConnectors(report, file, at, check, ns, ext) {
587
590
  report.error('connector/marker', file, `${label}: expected object, received ${typeOf(marker)}`, at(p))
588
591
  continue
589
592
  }
590
- check.strictKeys('connector/marker', p, marker, ['auth', 'tools'], label)
591
- if (marker.auth === undefined && marker.tools === undefined) {
592
- report.error('connector/marker', file, `${label}: declare auth, tools or both`, at(p))
593
+ check.strictKeys('connector/marker', p, marker, ['auth', 'tools', 'liveTools'], label)
594
+ if (marker.auth === undefined && marker.tools === undefined && marker.liveTools === undefined) {
595
+ report.error('connector/marker', file, `${label}: declare auth, tools or liveTools`, at(p))
593
596
  }
594
597
  if (marker.auth !== undefined && marker.auth !== 'oauth') {
595
598
  report.error('connector/auth', file, `${label}.auth: expected "oauth", received ${JSON.stringify(marker.auth)}`, at(`${p}.auth`))
@@ -597,6 +600,10 @@ function lintConnectors(report, file, at, check, ns, ext) {
597
600
  if (marker.tools !== undefined) {
598
601
  lintToolAllowlist(report, file, at, check, `${p}.tools`, `${label}.tools`, marker.tools)
599
602
  }
603
+ if (marker.liveTools !== undefined) {
604
+ lintToolAllowlist(report, file, at, check, `${p}.liveTools`, `${label}.liveTools`, marker.liveTools)
605
+ lintLiveToolsInTools(report, file, at, p, label, marker)
606
+ }
600
607
  names.push(name)
601
608
  }
602
609
  return names
@@ -624,6 +631,15 @@ function lintToolAllowlist(report, file, at, check, p, label, tools) {
624
631
  }
625
632
  }
626
633
 
634
+ function lintLiveToolsInTools(report, file, at, p, label, marker) {
635
+ if (!Array.isArray(marker.tools) || !Array.isArray(marker.liveTools)) return
636
+ for (const [index, tool] of marker.liveTools.entries()) {
637
+ if (typeof tool === 'string' && !marker.tools.includes(tool)) {
638
+ report.error('connector/live-tools', file, `${label}.liveTools[${index}]: live tool "${tool}" must also be in tools`, at(`${p}.liveTools.${index}`))
639
+ }
640
+ }
641
+ }
642
+
627
643
  // --- mcp.json --------------------------------------------------------------
628
644
 
629
645
  function lintMcp(root, entry, manifest, report) {