@subbly/plugin-lint 0.0.2 → 0.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @subbly/plugin-lint
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 3a07f70: The `repository` field is gone from `package.json`. It pointed at a repo that does not exist. No rule changes.
8
+
9
+ ## 0.0.3
10
+
11
+ ### Patch Changes
12
+
13
+ - A connector marker may carry a `tools` allowlist beside `auth`, and `auth` becomes optional. `tools` is a non-empty array of unique tool names, 1-100 characters each. A marker with neither field is rejected, as the builder refresh rejects it. Rule ids `connector/marker` and `connector/tools` join `connector/auth`.
14
+
3
15
  ## 0.0.2
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,16 +1,11 @@
1
1
  {
2
2
  "name": "@subbly/plugin-lint",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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.",
7
7
  "license": "MIT",
8
8
  "author": "Subbly LTD <oleg@subbly.co> (https://www.subbly.co)",
9
- "repository": {
10
- "type": "git",
11
- "url": "https://github.com/subbly/subbly-plugin-examples.git",
12
- "directory": "packages/plugin-lint"
13
- },
14
9
  "publishConfig": {
15
10
  "access": "public"
16
11
  },
@@ -36,10 +31,10 @@
36
31
  "README.md",
37
32
  "CHANGELOG.md"
38
33
  ],
39
- "scripts": {
40
- "test": "node test/lint.test.mjs"
41
- },
42
34
  "engines": {
43
35
  "node": ">=20"
36
+ },
37
+ "scripts": {
38
+ "test": "node test/lint.test.mjs"
44
39
  }
45
- }
40
+ }
package/src/report.mjs CHANGED
@@ -64,7 +64,9 @@ export const RULES = {
64
64
  'setup/missing-install-skill': 'setup: true requires a skills/install skill',
65
65
 
66
66
  // connectors and mcp.json
67
- 'connector/auth': 'a connector marker is exactly { "auth": "oauth" }',
67
+ 'connector/marker': 'a connector marker holds auth, tools or both, and nothing else',
68
+ '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',
68
70
  'connector/name-length': 'connector names are 1-100 characters',
69
71
  'connector/undefined-server': 'a declared connector needs a matching mcp.json server',
70
72
  'connector/no-mcp': 'declaring a connector requires an mcp.json',
package/src/rules.mjs CHANGED
@@ -564,7 +564,7 @@ function lintSchedule(report, file, pos, label, schedule) {
564
564
  function lintConnectors(report, file, at, check, ns, ext) {
565
565
  if (ext.connectors === undefined) return []
566
566
  if (!isObject(ext.connectors)) {
567
- report.error('connector/auth', file, `${ns}.connectors: expected object, received ${typeOf(ext.connectors)}`, at(`${ns}.connectors`))
567
+ report.error('connector/marker', file, `${ns}.connectors: expected object, received ${typeOf(ext.connectors)}`, at(`${ns}.connectors`))
568
568
  return []
569
569
  }
570
570
 
@@ -577,18 +577,46 @@ function lintConnectors(report, file, at, check, ns, ext) {
577
577
  report.error('connector/name-length', file, `${label}: connector name must be 1-100 characters`, at(p, { key: true }))
578
578
  }
579
579
  if (!isObject(marker)) {
580
- report.error('connector/auth', file, `${label}: expected object, received ${typeOf(marker)}`, at(p))
580
+ report.error('connector/marker', file, `${label}: expected object, received ${typeOf(marker)}`, at(p))
581
581
  continue
582
582
  }
583
- check.strictKeys('connector/auth', p, marker, ['auth'], label)
584
- if (marker.auth !== 'oauth') {
583
+ check.strictKeys('connector/marker', p, marker, ['auth', 'tools'], label)
584
+ if (marker.auth === undefined && marker.tools === undefined) {
585
+ report.error('connector/marker', file, `${label}: declare auth, tools or both`, at(p))
586
+ }
587
+ if (marker.auth !== undefined && marker.auth !== 'oauth') {
585
588
  report.error('connector/auth', file, `${label}.auth: expected "oauth", received ${JSON.stringify(marker.auth)}`, at(`${p}.auth`))
586
589
  }
590
+ if (marker.tools !== undefined) {
591
+ lintToolAllowlist(report, file, at, check, `${p}.tools`, `${label}.tools`, marker.tools)
592
+ }
587
593
  names.push(name)
588
594
  }
589
595
  return names
590
596
  }
591
597
 
598
+ function lintToolAllowlist(report, file, at, check, p, label, tools) {
599
+ if (!Array.isArray(tools)) {
600
+ report.error('connector/tools', file, `${label}: expected array, received ${typeOf(tools)}`, at(p))
601
+ return
602
+ }
603
+ if (tools.length === 0) {
604
+ report.error('connector/tools', file, `${label}: must name at least one tool`, at(p))
605
+ return
606
+ }
607
+ const seen = new Set()
608
+ for (const [index, tool] of tools.entries()) {
609
+ if (!check.string('connector/tools', `${p}.${index}`, tool, { min: 1, max: 100, label: `${label}[${index}]` })) {
610
+ continue
611
+ }
612
+ if (seen.has(tool)) {
613
+ report.error('connector/tools', file, `${label}[${index}]: duplicate tool name "${tool}"`, at(`${p}.${index}`))
614
+ continue
615
+ }
616
+ seen.add(tool)
617
+ }
618
+ }
619
+
592
620
  // --- mcp.json --------------------------------------------------------------
593
621
 
594
622
  function lintMcp(root, entry, manifest, report) {