swarmdo 1.12.0 → 1.13.0
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/.claude/helpers/statusline.cjs +1 -1
- package/README.md +2 -1
- package/package.json +1 -1
- package/v3/@swarmdo/cli/dist/src/commands/index.js +7 -3
- package/v3/@swarmdo/cli/dist/src/commands/license.d.ts +16 -0
- package/v3/@swarmdo/cli/dist/src/commands/license.js +124 -0
- package/v3/@swarmdo/cli/dist/src/license/license.d.ts +55 -0
- package/v3/@swarmdo/cli/dist/src/license/license.js +81 -0
- package/v3/@swarmdo/cli/dist/src/mcp-client.js +2 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/index.js +1 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/license-tools.d.ts +11 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/license-tools.js +57 -0
- package/v3/@swarmdo/cli/package.json +1 -1
|
@@ -515,7 +515,7 @@ function getCostFromStdin() {
|
|
|
515
515
|
// misses, the displayed version is meaningful (matches what the user
|
|
516
516
|
// installed), not a stale hard-coded string.
|
|
517
517
|
function getPkgVersion() {
|
|
518
|
-
let ver = '1.
|
|
518
|
+
let ver = '1.13.0';
|
|
519
519
|
try {
|
|
520
520
|
const home = os.homedir();
|
|
521
521
|
const pkgPaths = [
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://swarmdo.com)
|
|
4
4
|
|
|
5
|
-
[](https://img.shields.io/badge/npx%20swarmdo-v1.13.0-cb3837?style=for-the-badge&logo=npm&logoColor=white)](https://github.com/SwarmDo/swarmdo/releases)
|
|
6
6
|
[](https://github.com/SwarmDo/swarmdo/blob/main/LICENSE)
|
|
7
7
|
[](https://swarmdo.com)
|
|
8
8
|
[](https://github.com/SwarmDo/swarmdo)
|
|
@@ -281,6 +281,7 @@ The recent release train added a full day-to-day operations layer around the swa
|
|
|
281
281
|
| `swarmdo redact` | **Mask secrets before they reach an LLM/log/memory** — detect API keys, tokens, and private keys (gitleaks-style rule catalog + Shannon-entropy fallback) and redact them. Stdin filter (`cat deploy.log \| swarmdo redact`), command-wrap (`swarmdo redact -- npm run deploy`), or `--scan` to fail CI on any secret. Also MCP tools (`redact_text`/`redact_scan`). Deterministic, zero tokens |
|
|
282
282
|
| `swarmdo pack` | **Bundle a repo into one AI-context blob** — walk the tree (respects `.gitignore` + glob `--include`/`--exclude`, skips binaries/node_modules), emit markdown/xml/json/plain with a directory tree and per-file + total token estimates. `swarmdo pack --tokens` for a budget breakdown; `--redact` masks secrets first. Deterministic |
|
|
283
283
|
| `swarmdo env` | **Catch env-var drift before deploy** — statically scan code for `process.env.X` / `import.meta.env.X` / `Deno.env.get` / `os.getenv` references and reconcile against `.env` and `.env.example`: reports **missing** (used but undeclared), **unused**, and **undocumented**. `--ci` exits 1 on missing vars. Also an MCP tool (`env_check`). Deterministic |
|
|
284
|
+
| `swarmdo license` | **Audit dependency licenses** — walk `node_modules`, resolve each package's SPDX license, and gate on an allow/deny policy so a GPL or unknown license can't slip into a permissive tree. `--allow MIT,Apache-2.0 --ci` fails the build; distinct from `security` (CVEs). Also an MCP tool (`license_check`). Deterministic |
|
|
284
285
|
| `swarmdo integrations` (alias `integrate`) | **Use swarmdo from Codex CLI, GitHub Copilot CLI, and pi** — one command wires AGENTS.md + each CLI's MCP config (idempotent, dry-run first, never touches your Claude Code setup) |
|
|
285
286
|
| OpenRouter model pool | **Let swarms pick from any models you configure** — declare tier-mapped OpenRouter models in `swarmdo.config.json`; the router Thompson-samples among them per task and the execution layer dispatches the winner |
|
|
286
287
|
| `swarmdo changelog` (alias `notes`) | **Release notes from conventional commits** — `--out NOTES.md` feeds `gh release create --notes-file` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarmdo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Swarmdo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,10 @@ const commandLoaders = {
|
|
|
31
31
|
// Env-var drift checker (dotenv-safe/dotenv-scan demand) — reconcile code
|
|
32
32
|
// references against .env / .env.example (missing/unused/undocumented).
|
|
33
33
|
env: () => import('./env.js'),
|
|
34
|
+
// Dependency license audit + policy gate (license-checker demand) — catch
|
|
35
|
+
// GPL/unknown licenses in the tree, fail CI on a forbidden one.
|
|
36
|
+
license: () => import('./license.js'),
|
|
37
|
+
licenses: () => import('./license.js'), // alias via loader key
|
|
34
38
|
// Queryable exported-symbol index (codegraph demand) — where things are
|
|
35
39
|
// defined without grep+read round-trips.
|
|
36
40
|
codegraph: () => import('./codegraph.js'),
|
|
@@ -264,7 +268,7 @@ export async function getCommandsByCategory() {
|
|
|
264
268
|
// three slots from 'statusline' onward (completionsCmd received the
|
|
265
269
|
// statusline module, analyzeCmd received completions, …), scrambling the
|
|
266
270
|
// categorized help. Every load now has a named slot.
|
|
267
|
-
const [daemonCmd, doctorCmd, embeddingsCmd, neuralCmd, performanceCmd, securityCmd, swarmvectorCmd, hiveMindCmd, configCmd, statuslineCmd, compressCmd, efficiencyCmd, completionsCmd, migrateCmd, workflowCmd, analyzeCmd, routeCmd, progressCmd, providersCmd, pluginsCmd, deploymentCmd, claimsCmd, issuesCmd, updateCmd, processCmd, guidanceCmd, applianceCmd, cleanupCmd, autopilotCmd, demoCmd, usageCmd, repairCmd, hudCmd, compactCmd, codegraphCmd, redactCmd, packCmd, envCmd,] = await Promise.all([
|
|
271
|
+
const [daemonCmd, doctorCmd, embeddingsCmd, neuralCmd, performanceCmd, securityCmd, swarmvectorCmd, hiveMindCmd, configCmd, statuslineCmd, compressCmd, efficiencyCmd, completionsCmd, migrateCmd, workflowCmd, analyzeCmd, routeCmd, progressCmd, providersCmd, pluginsCmd, deploymentCmd, claimsCmd, issuesCmd, updateCmd, processCmd, guidanceCmd, applianceCmd, cleanupCmd, autopilotCmd, demoCmd, usageCmd, repairCmd, hudCmd, compactCmd, codegraphCmd, redactCmd, packCmd, envCmd, licenseCmd,] = await Promise.all([
|
|
268
272
|
loadCommand('daemon'), loadCommand('doctor'), loadCommand('embeddings'), loadCommand('neural'),
|
|
269
273
|
loadCommand('performance'), loadCommand('security'), loadCommand('swarmvector'), loadCommand('hive-mind'),
|
|
270
274
|
loadCommand('config'), loadCommand('statusline'), loadCommand('compress'), loadCommand('efficiency'),
|
|
@@ -272,7 +276,7 @@ export async function getCommandsByCategory() {
|
|
|
272
276
|
loadCommand('analyze'), loadCommand('route'), loadCommand('progress'), loadCommand('providers'),
|
|
273
277
|
loadCommand('plugins'), loadCommand('deployment'), loadCommand('claims'), loadCommand('issues'),
|
|
274
278
|
loadCommand('update'), loadCommand('process'), loadCommand('guidance'), loadCommand('appliance'),
|
|
275
|
-
loadCommand('cleanup'), loadCommand('autopilot'), loadCommand('demo'), loadCommand('usage'), loadCommand('repair'), loadCommand('hud'), loadCommand('compact'), loadCommand('codegraph'), loadCommand('redact'), loadCommand('pack'), loadCommand('env'),
|
|
279
|
+
loadCommand('cleanup'), loadCommand('autopilot'), loadCommand('demo'), loadCommand('usage'), loadCommand('repair'), loadCommand('hud'), loadCommand('compact'), loadCommand('codegraph'), loadCommand('redact'), loadCommand('pack'), loadCommand('env'), loadCommand('license'),
|
|
276
280
|
]);
|
|
277
281
|
return {
|
|
278
282
|
primary: [
|
|
@@ -288,7 +292,7 @@ export async function getCommandsByCategory() {
|
|
|
288
292
|
utility: [
|
|
289
293
|
configCmd, doctorCmd, daemonCmd, completionsCmd,
|
|
290
294
|
migrateCmd, workflowCmd, demoCmd,
|
|
291
|
-
statuslineCmd, compressCmd, compactCmd, redactCmd, packCmd, envCmd, efficiencyCmd,
|
|
295
|
+
statuslineCmd, compressCmd, compactCmd, redactCmd, packCmd, envCmd, licenseCmd, efficiencyCmd,
|
|
292
296
|
].filter(Boolean),
|
|
293
297
|
analysis: [
|
|
294
298
|
analyzeCmd, routeCmd, progressCmd, usageCmd, hudCmd, codegraphCmd,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `swarmdo license` — audit installed dependency licenses against an allow/deny
|
|
3
|
+
* policy. Reads each package's own package.json under node_modules and checks
|
|
4
|
+
* the SPDX license, so a GPL or unknown license in a permissive tree is caught
|
|
5
|
+
* (and can fail CI) before it ships.
|
|
6
|
+
*
|
|
7
|
+
* swarmdo license # list licenses in the tree
|
|
8
|
+
* swarmdo license --allow MIT,Apache-2.0,ISC # fail on anything else
|
|
9
|
+
* swarmdo license --deny GPL-3.0 --ci # exit 1 on a forbidden license
|
|
10
|
+
*
|
|
11
|
+
* Engine (../license/license.ts) is pure + tested; this walks node_modules.
|
|
12
|
+
*/
|
|
13
|
+
import type { Command } from '../types.js';
|
|
14
|
+
export declare const licenseCommand: Command;
|
|
15
|
+
export default licenseCommand;
|
|
16
|
+
//# sourceMappingURL=license.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `swarmdo license` — audit installed dependency licenses against an allow/deny
|
|
3
|
+
* policy. Reads each package's own package.json under node_modules and checks
|
|
4
|
+
* the SPDX license, so a GPL or unknown license in a permissive tree is caught
|
|
5
|
+
* (and can fail CI) before it ships.
|
|
6
|
+
*
|
|
7
|
+
* swarmdo license # list licenses in the tree
|
|
8
|
+
* swarmdo license --allow MIT,Apache-2.0,ISC # fail on anything else
|
|
9
|
+
* swarmdo license --deny GPL-3.0 --ci # exit 1 on a forbidden license
|
|
10
|
+
*
|
|
11
|
+
* Engine (../license/license.ts) is pure + tested; this walks node_modules.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import { output } from '../output.js';
|
|
16
|
+
import { classifyLicense, auditLicenses, formatLicenseSummary } from '../license/license.js';
|
|
17
|
+
/** Collect installed packages from a node_modules dir (incl. one level of @scope). */
|
|
18
|
+
function collectDeps(nodeModules) {
|
|
19
|
+
const deps = [];
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
let top;
|
|
22
|
+
try {
|
|
23
|
+
top = fs.readdirSync(nodeModules, { withFileTypes: true });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return deps;
|
|
27
|
+
}
|
|
28
|
+
const pkgDirs = [];
|
|
29
|
+
for (const e of top) {
|
|
30
|
+
if (!e.isDirectory() || e.name === '.bin' || e.name === '.cache')
|
|
31
|
+
continue;
|
|
32
|
+
if (e.name.startsWith('@')) {
|
|
33
|
+
// scoped: descend one level
|
|
34
|
+
let scoped;
|
|
35
|
+
try {
|
|
36
|
+
scoped = fs.readdirSync(path.join(nodeModules, e.name), { withFileTypes: true });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const s of scoped)
|
|
42
|
+
if (s.isDirectory())
|
|
43
|
+
pkgDirs.push(path.join(nodeModules, e.name, s.name));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
pkgDirs.push(path.join(nodeModules, e.name));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const dir of pkgDirs) {
|
|
50
|
+
let pkg;
|
|
51
|
+
try {
|
|
52
|
+
pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const name = pkg.name ?? path.basename(dir);
|
|
58
|
+
if (seen.has(name))
|
|
59
|
+
continue;
|
|
60
|
+
seen.add(name);
|
|
61
|
+
deps.push({ name, version: pkg.version ?? '0.0.0', license: classifyLicense(pkg) });
|
|
62
|
+
}
|
|
63
|
+
return deps.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
|
+
}
|
|
65
|
+
function csv(v) {
|
|
66
|
+
if (typeof v !== 'string' || !v)
|
|
67
|
+
return undefined;
|
|
68
|
+
return v.split(',').map((s) => s.trim()).filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
async function run(ctx) {
|
|
71
|
+
const repoRoot = ctx.cwd || process.cwd();
|
|
72
|
+
const nodeModules = path.resolve(repoRoot, ctx.args[0] ?? 'node_modules');
|
|
73
|
+
if (!fs.existsSync(nodeModules)) {
|
|
74
|
+
output.printError(`no node_modules at ${path.relative(repoRoot, nodeModules) || nodeModules} (install deps first, or pass a path)`);
|
|
75
|
+
return { success: false, exitCode: 1 };
|
|
76
|
+
}
|
|
77
|
+
const deps = collectDeps(nodeModules);
|
|
78
|
+
if (deps.length === 0) {
|
|
79
|
+
output.printError('no packages found under node_modules');
|
|
80
|
+
return { success: false, exitCode: 1 };
|
|
81
|
+
}
|
|
82
|
+
const policy = {
|
|
83
|
+
allow: csv(ctx.flags.allow),
|
|
84
|
+
deny: csv(ctx.flags.deny),
|
|
85
|
+
allowUnknown: ctx.flags['allow-unknown'] === true,
|
|
86
|
+
};
|
|
87
|
+
const report = auditLicenses(deps, policy);
|
|
88
|
+
if (ctx.flags.json === true) {
|
|
89
|
+
output.printJson(report);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
if (report.violations.length > 0) {
|
|
93
|
+
output.writeln(output.bold(`Violations (${report.violations.length})`));
|
|
94
|
+
output.printList(report.violations.map((v) => `${v.name}@${v.version} ${output.dim(`[${v.license}]`)} ${v.reason}`));
|
|
95
|
+
}
|
|
96
|
+
// license breakdown, most common first
|
|
97
|
+
output.writeln(output.bold('Licenses'));
|
|
98
|
+
output.printList(Object.entries(report.byLicense).sort((a, b) => b[1] - a[1]).map(([lic, n]) => `${lic}: ${n}`));
|
|
99
|
+
output.writeln(output.dim(formatLicenseSummary(report)));
|
|
100
|
+
}
|
|
101
|
+
const gate = ctx.flags.ci === true || policy.allow || policy.deny;
|
|
102
|
+
const code = gate && report.violations.length > 0 ? 1 : 0;
|
|
103
|
+
return { success: code === 0, exitCode: code };
|
|
104
|
+
}
|
|
105
|
+
export const licenseCommand = {
|
|
106
|
+
name: 'license',
|
|
107
|
+
aliases: ['licenses'],
|
|
108
|
+
description: 'Audit dependency licenses against an allow/deny policy — catch GPL/unknown licenses before they ship',
|
|
109
|
+
options: [
|
|
110
|
+
{ name: 'allow', description: 'comma-separated SPDX allowlist; anything else is a violation', type: 'string' },
|
|
111
|
+
{ name: 'deny', description: 'comma-separated SPDX denylist; any match is a violation', type: 'string' },
|
|
112
|
+
{ name: 'allow-unknown', description: 'treat UNKNOWN licenses as allowed (default: violation under an allowlist)', type: 'boolean' },
|
|
113
|
+
{ name: 'ci', description: 'exit 1 if there are any violations', type: 'boolean' },
|
|
114
|
+
{ name: 'json', description: 'machine-readable report', type: 'boolean' },
|
|
115
|
+
],
|
|
116
|
+
examples: [
|
|
117
|
+
{ command: 'swarmdo license', description: 'List the licenses present in node_modules' },
|
|
118
|
+
{ command: 'swarmdo license --allow MIT,Apache-2.0,ISC,BSD-3-Clause --ci', description: 'Fail CI on any non-permissive license' },
|
|
119
|
+
{ command: 'swarmdo license --deny GPL-3.0,AGPL-3.0', description: 'Fail on specific copyleft licenses' },
|
|
120
|
+
],
|
|
121
|
+
action: run,
|
|
122
|
+
};
|
|
123
|
+
export default licenseCommand;
|
|
124
|
+
//# sourceMappingURL=license.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* license.ts — audit dependency licenses against an allow/deny policy. Supply-
|
|
3
|
+
* chain hygiene for agent-built projects: catch a copyleft (GPL) or unknown
|
|
4
|
+
* license slipping into a tree that must stay permissive, and fail CI on it.
|
|
5
|
+
*
|
|
6
|
+
* Distinct from `security` (CVE/vulnerability scanning) — this is license
|
|
7
|
+
* COMPLIANCE. Pure + deterministic: the engine takes a list of
|
|
8
|
+
* {name, version, license} plus a policy and returns violations. The fs walk
|
|
9
|
+
* over node_modules lives in ../commands/license.ts.
|
|
10
|
+
*/
|
|
11
|
+
export interface DepLicense {
|
|
12
|
+
name: string;
|
|
13
|
+
version: string;
|
|
14
|
+
/** SPDX id/expression, or 'UNKNOWN' */
|
|
15
|
+
license: string;
|
|
16
|
+
}
|
|
17
|
+
export interface LicensePolicy {
|
|
18
|
+
/** if non-empty, a dep must match at least one to pass */
|
|
19
|
+
allow?: string[];
|
|
20
|
+
/** any match fails the dep */
|
|
21
|
+
deny?: string[];
|
|
22
|
+
/** treat UNKNOWN as a pass even when an allowlist is set (default false) */
|
|
23
|
+
allowUnknown?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export type ViolationReason = 'denied' | 'not-allowed' | 'unknown';
|
|
26
|
+
export interface Violation {
|
|
27
|
+
name: string;
|
|
28
|
+
version: string;
|
|
29
|
+
license: string;
|
|
30
|
+
reason: ViolationReason;
|
|
31
|
+
}
|
|
32
|
+
export interface LicenseReport {
|
|
33
|
+
total: number;
|
|
34
|
+
violations: Violation[];
|
|
35
|
+
/** license id → count across all deps */
|
|
36
|
+
byLicense: Record<string, number>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Normalize a package.json `license`/`licenses` field to an SPDX string.
|
|
40
|
+
* Handles the modern string, the legacy `{type}` object, and the legacy
|
|
41
|
+
* `licenses: [{type}]` array (→ `A OR B`). Missing → 'UNKNOWN'.
|
|
42
|
+
*/
|
|
43
|
+
export declare function classifyLicense(pkg: {
|
|
44
|
+
license?: unknown;
|
|
45
|
+
licenses?: unknown;
|
|
46
|
+
}): string;
|
|
47
|
+
/** Split an SPDX expression into its atomic license ids (strips OR/AND/WITH/parens/+). */
|
|
48
|
+
export declare function spdxComponents(expr: string): string[];
|
|
49
|
+
/** Evaluate one dep against the policy; null if it passes. Pure. */
|
|
50
|
+
export declare function evaluateDep(dep: DepLicense, policy: LicensePolicy): Violation | null;
|
|
51
|
+
/** Audit a dependency set against a policy. Pure. */
|
|
52
|
+
export declare function auditLicenses(deps: DepLicense[], policy?: LicensePolicy): LicenseReport;
|
|
53
|
+
/** One-line human summary. */
|
|
54
|
+
export declare function formatLicenseSummary(r: LicenseReport): string;
|
|
55
|
+
//# sourceMappingURL=license.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* license.ts — audit dependency licenses against an allow/deny policy. Supply-
|
|
3
|
+
* chain hygiene for agent-built projects: catch a copyleft (GPL) or unknown
|
|
4
|
+
* license slipping into a tree that must stay permissive, and fail CI on it.
|
|
5
|
+
*
|
|
6
|
+
* Distinct from `security` (CVE/vulnerability scanning) — this is license
|
|
7
|
+
* COMPLIANCE. Pure + deterministic: the engine takes a list of
|
|
8
|
+
* {name, version, license} plus a policy and returns violations. The fs walk
|
|
9
|
+
* over node_modules lives in ../commands/license.ts.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a package.json `license`/`licenses` field to an SPDX string.
|
|
13
|
+
* Handles the modern string, the legacy `{type}` object, and the legacy
|
|
14
|
+
* `licenses: [{type}]` array (→ `A OR B`). Missing → 'UNKNOWN'.
|
|
15
|
+
*/
|
|
16
|
+
export function classifyLicense(pkg) {
|
|
17
|
+
const l = pkg.license;
|
|
18
|
+
if (typeof l === 'string' && l.trim())
|
|
19
|
+
return l.trim();
|
|
20
|
+
if (l && typeof l === 'object' && typeof l.type === 'string') {
|
|
21
|
+
return l.type.trim() || 'UNKNOWN';
|
|
22
|
+
}
|
|
23
|
+
const arr = pkg.licenses;
|
|
24
|
+
if (Array.isArray(arr) && arr.length) {
|
|
25
|
+
const types = arr
|
|
26
|
+
.map((e) => (typeof e === 'string' ? e : e?.type))
|
|
27
|
+
.filter((t) => typeof t === 'string' && !!t.trim());
|
|
28
|
+
if (types.length)
|
|
29
|
+
return types.join(' OR ');
|
|
30
|
+
}
|
|
31
|
+
return 'UNKNOWN';
|
|
32
|
+
}
|
|
33
|
+
/** Split an SPDX expression into its atomic license ids (strips OR/AND/WITH/parens/+). */
|
|
34
|
+
export function spdxComponents(expr) {
|
|
35
|
+
return expr
|
|
36
|
+
.replace(/[()]/g, ' ')
|
|
37
|
+
.split(/\s+(?:OR|AND|WITH)\s+/i)
|
|
38
|
+
.map((s) => s.trim().replace(/\+$/, ''))
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
/** Evaluate one dep against the policy; null if it passes. Pure. */
|
|
42
|
+
export function evaluateDep(dep, policy) {
|
|
43
|
+
const allow = new Set(policy.allow ?? []);
|
|
44
|
+
const deny = new Set(policy.deny ?? []);
|
|
45
|
+
const isUnknown = dep.license === 'UNKNOWN' || !dep.license;
|
|
46
|
+
const components = isUnknown ? [] : spdxComponents(dep.license);
|
|
47
|
+
if (components.some((c) => deny.has(c))) {
|
|
48
|
+
return { name: dep.name, version: dep.version, license: dep.license, reason: 'denied' };
|
|
49
|
+
}
|
|
50
|
+
if (isUnknown) {
|
|
51
|
+
if (allow.size > 0 && !policy.allowUnknown) {
|
|
52
|
+
return { name: dep.name, version: dep.version, license: 'UNKNOWN', reason: 'unknown' };
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (allow.size > 0 && !components.some((c) => allow.has(c))) {
|
|
57
|
+
return { name: dep.name, version: dep.version, license: dep.license, reason: 'not-allowed' };
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/** Audit a dependency set against a policy. Pure. */
|
|
62
|
+
export function auditLicenses(deps, policy = {}) {
|
|
63
|
+
const violations = [];
|
|
64
|
+
const byLicense = {};
|
|
65
|
+
for (const dep of deps) {
|
|
66
|
+
const lic = dep.license || 'UNKNOWN';
|
|
67
|
+
byLicense[lic] = (byLicense[lic] ?? 0) + 1;
|
|
68
|
+
const v = evaluateDep(dep, policy);
|
|
69
|
+
if (v)
|
|
70
|
+
violations.push(v);
|
|
71
|
+
}
|
|
72
|
+
violations.sort((a, b) => a.name.localeCompare(b.name));
|
|
73
|
+
return { total: deps.length, violations, byLicense };
|
|
74
|
+
}
|
|
75
|
+
/** One-line human summary. */
|
|
76
|
+
export function formatLicenseSummary(r) {
|
|
77
|
+
if (r.violations.length === 0)
|
|
78
|
+
return `license: ${r.total} deps, 0 violations`;
|
|
79
|
+
return `license: ${r.violations.length} violation${r.violations.length === 1 ? '' : 's'} across ${r.total} deps`;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=license.js.map
|
|
@@ -22,6 +22,7 @@ import { analyzeTools } from './mcp-tools/analyze-tools.js';
|
|
|
22
22
|
import { codegraphTools } from './mcp-tools/codegraph-tools.js';
|
|
23
23
|
import { redactTools } from './mcp-tools/redact-tools.js';
|
|
24
24
|
import { envTools } from './mcp-tools/env-tools.js';
|
|
25
|
+
import { licenseTools } from './mcp-tools/license-tools.js';
|
|
25
26
|
import { progressTools } from './mcp-tools/progress-tools.js';
|
|
26
27
|
import { embeddingsTools } from './mcp-tools/embeddings-tools.js';
|
|
27
28
|
import { claimsTools } from './mcp-tools/claims-tools.js';
|
|
@@ -108,6 +109,7 @@ const TOOL_GROUPS = {
|
|
|
108
109
|
codegraph: () => codegraphTools,
|
|
109
110
|
redact: () => redactTools,
|
|
110
111
|
env: () => envTools,
|
|
112
|
+
license: () => licenseTools,
|
|
111
113
|
progress: () => progressTools,
|
|
112
114
|
embeddings: () => embeddingsTools,
|
|
113
115
|
claims: () => claimsTools,
|
|
@@ -18,6 +18,7 @@ export { analyzeTools } from './analyze-tools.js';
|
|
|
18
18
|
export { codegraphTools } from './codegraph-tools.js';
|
|
19
19
|
export { redactTools } from './redact-tools.js';
|
|
20
20
|
export { envTools } from './env-tools.js';
|
|
21
|
+
export { licenseTools } from './license-tools.js';
|
|
21
22
|
export { progressTools } from './progress-tools.js';
|
|
22
23
|
export { transferTools } from './transfer-tools.js';
|
|
23
24
|
export { securityTools } from './security-tools.js';
|
|
@@ -17,6 +17,7 @@ export { analyzeTools } from './analyze-tools.js';
|
|
|
17
17
|
export { codegraphTools } from './codegraph-tools.js';
|
|
18
18
|
export { redactTools } from './redact-tools.js';
|
|
19
19
|
export { envTools } from './env-tools.js';
|
|
20
|
+
export { licenseTools } from './license-tools.js';
|
|
20
21
|
export { progressTools } from './progress-tools.js';
|
|
21
22
|
export { transferTools } from './transfer-tools.js';
|
|
22
23
|
export { securityTools } from './security-tools.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License MCP Tool
|
|
3
|
+
*
|
|
4
|
+
* Let an agent audit dependency licenses in-session — e.g. before adding a
|
|
5
|
+
* package or approving a release — against an allow/deny policy. Deterministic;
|
|
6
|
+
* shares the pure engine in ../license/license.ts with the CLI command. The
|
|
7
|
+
* caller passes the dependency list (name/version/license), so no fs access.
|
|
8
|
+
*/
|
|
9
|
+
import type { MCPTool } from './types.js';
|
|
10
|
+
export declare const licenseTools: MCPTool[];
|
|
11
|
+
//# sourceMappingURL=license-tools.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License MCP Tool
|
|
3
|
+
*
|
|
4
|
+
* Let an agent audit dependency licenses in-session — e.g. before adding a
|
|
5
|
+
* package or approving a release — against an allow/deny policy. Deterministic;
|
|
6
|
+
* shares the pure engine in ../license/license.ts with the CLI command. The
|
|
7
|
+
* caller passes the dependency list (name/version/license), so no fs access.
|
|
8
|
+
*/
|
|
9
|
+
import { auditLicenses } from '../license/license.js';
|
|
10
|
+
const licenseCheckTool = {
|
|
11
|
+
name: 'license_check',
|
|
12
|
+
description: 'Audit a dependency list against a license allow/deny policy. Returns violations (denied, not-allowed, or unknown license) plus a license breakdown. Use before adding a dependency or approving a release to keep a permissive tree clean. Deterministic; pass the deps directly.',
|
|
13
|
+
category: 'license',
|
|
14
|
+
tags: ['license', 'compliance', 'supply-chain', 'ci'],
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
deps: {
|
|
19
|
+
type: 'array',
|
|
20
|
+
description: 'dependencies to audit',
|
|
21
|
+
items: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
name: { type: 'string' },
|
|
25
|
+
version: { type: 'string' },
|
|
26
|
+
license: { type: 'string', description: 'SPDX id/expression (e.g. MIT, "(MIT OR Apache-2.0)", or UNKNOWN)' },
|
|
27
|
+
},
|
|
28
|
+
required: ['name', 'license'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
allow: { type: 'array', items: { type: 'string' }, description: 'SPDX allowlist; a dep must match at least one' },
|
|
32
|
+
deny: { type: 'array', items: { type: 'string' }, description: 'SPDX denylist; any match is a violation' },
|
|
33
|
+
allowUnknown: { type: 'boolean', description: 'treat UNKNOWN as allowed even under an allowlist', default: false },
|
|
34
|
+
},
|
|
35
|
+
required: ['deps'],
|
|
36
|
+
},
|
|
37
|
+
handler: async (params) => {
|
|
38
|
+
if (!Array.isArray(params.deps))
|
|
39
|
+
return { error: true, message: 'deps[] is required' };
|
|
40
|
+
const deps = params.deps
|
|
41
|
+
.filter((d) => !!d && typeof d === 'object')
|
|
42
|
+
.map((d) => ({
|
|
43
|
+
name: String(d.name ?? '?'),
|
|
44
|
+
version: String(d.version ?? '0.0.0'),
|
|
45
|
+
license: String(d.license ?? 'UNKNOWN'),
|
|
46
|
+
}));
|
|
47
|
+
const policy = {
|
|
48
|
+
allow: Array.isArray(params.allow) ? params.allow.filter((x) => typeof x === 'string') : undefined,
|
|
49
|
+
deny: Array.isArray(params.deny) ? params.deny.filter((x) => typeof x === 'string') : undefined,
|
|
50
|
+
allowUnknown: params.allowUnknown === true,
|
|
51
|
+
};
|
|
52
|
+
const report = auditLicenses(deps, policy);
|
|
53
|
+
return { clean: report.violations.length === 0, total: report.total, violations: report.violations, byLicense: report.byLicense };
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
export const licenseTools = [licenseCheckTool];
|
|
57
|
+
//# sourceMappingURL=license-tools.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmdo/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Swarmdo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|