@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 +12 -0
- package/package.json +1 -1
- package/src/report.mjs +4 -2
- package/src/rules.mjs +20 -4
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.
|
|
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
|
|
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
|
|
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
|
|
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) {
|