proof-of-commitment 1.4.0 → 1.6.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.
Files changed (3) hide show
  1. package/README.md +16 -4
  2. package/index.js +145 -18
  3. package/package.json +7 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # proof-of-commitment
2
2
 
3
- > Supply chain risk scorer for npm and PyPI packages. Behavioral signals that can't be faked.
3
+ > Supply chain risk scorer for npm, PyPI, Cargo (Rust), and Go modules. Behavioral signals that can't be faked.
4
4
 
5
5
  ```
6
6
  npx proof-of-commitment axios zod chalk
@@ -50,14 +50,26 @@ npx proof-of-commitment axios zod chalk lodash express
50
50
  # Score PyPI packages
51
51
  npx proof-of-commitment --pypi litellm langchain requests numpy
52
52
 
53
- # Auto-detect from package.json or requirements.txt
54
- npx proof-of-commitment --file package.json
55
- npx proof-of-commitment --file requirements.txt
53
+ # Score Rust crates
54
+ npx proof-of-commitment --cargo serde tokio reqwest
55
+
56
+ # Score Go modules (full module path required — host/owner/repo)
57
+ npx proof-of-commitment --golang github.com/gin-gonic/gin golang.org/x/net
58
+
59
+ # Auto-detect from manifest / lock / module file
60
+ npx proof-of-commitment --file package.json # npm
61
+ npx proof-of-commitment --file package-lock.json # full transitive
62
+ npx proof-of-commitment --file requirements.txt # PyPI
63
+ npx proof-of-commitment --file Cargo.toml # Rust direct deps
64
+ npx proof-of-commitment --file go.mod # Go direct + indirect
65
+ npx proof-of-commitment --file go.sum # Go full transitive set
56
66
 
57
67
  # Short alias
58
68
  npx poc axios zod chalk
59
69
  ```
60
70
 
71
+ **Go modules note:** Go has no centralized download counter and no publisher registry. Scoring is GitHub-primary: longevity, release cadence, GitHub contributor count, OpenSSF Scorecard, and stars (popularity proxy). The "publishers" column in Go output shows GitHub push-access contributors — the closest equivalent to npm publishers.
72
+
61
73
  ## Zero-install MCP server (for Claude, Cursor, Windsurf)
62
74
 
63
75
  Add to your AI tool's config:
package/index.js CHANGED
@@ -87,12 +87,23 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
87
87
  const label = riskLabel(pkg.riskFlags, pkg.score);
88
88
  if (pkg.riskFlags && pkg.riskFlags.includes('CRITICAL')) criticalInDisplay++;
89
89
 
90
+ // Go modules have no download data — show "—" instead. The maintainers
91
+ // column for Go shows GitHub contributor count (the closest equivalent
92
+ // to publish access since Go has no centralized publisher concept).
93
+ const isGo = pkg.ecosystem === 'golang';
94
+ const dlDisplay = isGo
95
+ ? '—'
96
+ : fmtDownloads(pkg.weeklyDownloads || 0);
97
+ const maintDisplay = pkg.maintainers === 35
98
+ ? '30+'
99
+ : String(pkg.maintainers || '?');
100
+
90
101
  const row = [
91
102
  padEnd(pkg.name, COL.name),
92
103
  padEnd(clr(rc, label), COL.risk),
93
104
  padEnd(String(pkg.score), COL.score),
94
- padEnd(String(pkg.maintainers || '?'), COL.maintainers),
95
- padEnd(fmtDownloads(pkg.weeklyDownloads || 0), COL.downloads),
105
+ padEnd(maintDisplay, COL.maintainers),
106
+ padEnd(dlDisplay, COL.downloads),
96
107
  padEnd((pkg.ageYears || '?').toString().replace(/(\.\d).*/, '$1') + 'y', COL.age),
97
108
  ].join(' ');
98
109
 
@@ -104,13 +115,18 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
104
115
  console.log(clr(c.dim, ` ↳ ${ghCount} GitHub contributors — publish-access concentration risk despite active community`));
105
116
  }
106
117
 
107
- // Score breakdown if available
118
+ // Score breakdown if available — keys differ across ecosystems
108
119
  if (pkg.scoreBreakdown) {
109
120
  const b = pkg.scoreBreakdown;
110
- const breakdown = clr(c.dim,
111
- ` └ longevity=${b.longevity} momentum=${b.downloadMomentum} ` +
112
- `releases=${b.releaseConsistency} publishers=${b.maintainerDepth} github=${b.githubBacking}`
113
- );
121
+ const breakdown = isGo
122
+ ? clr(c.dim,
123
+ ` └ longevity=${b.longevity} releases=${b.releaseConsistency} ` +
124
+ `contributors=${b.maintainerDepth} github=${b.githubBacking} stars=${b.popularityProxy}`
125
+ )
126
+ : clr(c.dim,
127
+ ` └ longevity=${b.longevity} momentum=${b.downloadMomentum} ` +
128
+ `releases=${b.releaseConsistency} publishers=${b.maintainerDepth} github=${b.githubBacking}`
129
+ );
114
130
  console.log(breakdown);
115
131
  }
116
132
  }
@@ -138,24 +154,33 @@ function printHelp() {
138
154
  ${clr(c.bold, 'proof-of-commitment')} — supply chain risk scorer
139
155
 
140
156
  ${clr(c.bold, 'Usage:')}
141
- npx proof-of-commitment [packages...] Score npm packages
142
- npx proof-of-commitment --pypi [pkgs...] Score PyPI packages
143
- npx proof-of-commitment --file package.json Audit direct dependencies
144
- npx proof-of-commitment --file package-lock.json Audit ALL dependencies (lock file)
145
- npx proof-of-commitment --file yarn.lock Audit from yarn lock file
146
- npx proof-of-commitment --file pnpm-lock.yaml Audit from pnpm lock file
147
- npx proof-of-commitment --file requirements.txt Audit Python packages
157
+ npx proof-of-commitment [packages...] Score npm packages
158
+ npx proof-of-commitment --pypi [pkgs...] Score PyPI packages
159
+ npx proof-of-commitment --cargo [crates...] Score Rust crates
160
+ npx proof-of-commitment --golang [modules...] Score Go modules (full path required)
161
+ npx proof-of-commitment --file package.json Audit direct dependencies
162
+ npx proof-of-commitment --file package-lock.json Audit ALL dependencies (lock file)
163
+ npx proof-of-commitment --file yarn.lock Audit from yarn lock file
164
+ npx proof-of-commitment --file pnpm-lock.yaml Audit from pnpm lock file
165
+ npx proof-of-commitment --file requirements.txt Audit Python packages
166
+ npx proof-of-commitment --file Cargo.toml Audit Rust direct dependencies
167
+ npx proof-of-commitment --file go.mod Audit Go direct + indirect deps
168
+ npx proof-of-commitment --file go.sum Audit Go full transitive set
148
169
 
149
170
  ${clr(c.bold, 'Options:')}
150
171
  --json Output results as JSON (exits 1 if any CRITICAL found — useful in CI)
151
172
  --pypi Score PyPI packages instead of npm
152
- --file, -f Read packages from package.json, lock file, or requirements.txt
173
+ --cargo Score Rust crates from crates.io
174
+ --golang Score Go modules from proxy.golang.org (use full path: github.com/owner/repo)
175
+ --file, -f Read packages from package.json, lock file, requirements.txt, Cargo.toml, or go.mod/go.sum
153
176
 
154
177
  ${clr(c.bold, 'Examples:')}
155
178
  npx proof-of-commitment axios zod chalk
156
179
  npx proof-of-commitment --pypi litellm langchain requests
157
- npx proof-of-commitment --file package.json
180
+ npx proof-of-commitment --cargo serde tokio reqwest
181
+ npx proof-of-commitment --golang github.com/gin-gonic/gin golang.org/x/net
158
182
  npx proof-of-commitment --file package-lock.json # scans ALL transitive deps
183
+ npx proof-of-commitment --file go.sum # scans full Go module graph
159
184
  npx proof-of-commitment axios chalk --json | jq '.criticalCount'
160
185
 
161
186
  ${clr(c.bold, 'Score meaning:')}
@@ -165,7 +190,8 @@ ${clr(c.bold, 'Score meaning:')}
165
190
  🟡 GOOD Score 60–74
166
191
  🟢 HEALTHY Score 75+
167
192
 
168
- ${clr(c.bold, 'Score dimensions:')} longevity · download momentum · release consistency · publisher depth · GitHub backing
193
+ ${clr(c.bold, 'Score dimensions (npm/PyPI/Cargo):')} longevity · download momentum · release consistency · publisher depth · GitHub backing
194
+ ${clr(c.bold, 'Score dimensions (Go):')} longevity · release consistency · maintainer depth · GitHub backing · stars (Go has no centralized download counter)
169
195
 
170
196
  ${clr(c.bold, 'Web:')} ${WEB}
171
197
  ${clr(c.bold, 'MCP:')} ${clr(c.dim, 'Add to Claude Desktop / Cursor for AI-assisted auditing')}
@@ -274,7 +300,106 @@ async function readPackagesFromFile(filePath) {
274
300
  return { packages: pkgs, ecosystem: 'pypi', lockfile: false };
275
301
  }
276
302
 
277
- throw new Error(`Unsupported file: ${basename}. Supported: package.json, package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt`);
303
+ // Cargo.toml
304
+ if (basename === 'cargo.toml') {
305
+ // Crude TOML parse — extract [dependencies] section keys
306
+ const pkgs = parseCargoToml(content);
307
+ return { packages: pkgs, ecosystem: 'cargo', lockfile: false };
308
+ }
309
+
310
+ // go.mod — Go module file
311
+ if (basename === 'go.mod') {
312
+ const pkgs = parseGoMod(content);
313
+ return { packages: pkgs, ecosystem: 'golang', lockfile: false, totalInFile: pkgs.length };
314
+ }
315
+
316
+ // go.sum — Go module checksum file (lockfile-equivalent — captures full transitive set)
317
+ if (basename === 'go.sum') {
318
+ const pkgs = parseGoSum(content);
319
+ return { packages: pkgs, ecosystem: 'golang', lockfile: true, totalInFile: pkgs.length };
320
+ }
321
+
322
+ throw new Error(`Unsupported file: ${basename}. Supported: package.json, package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Cargo.toml, go.mod, go.sum`);
323
+ }
324
+
325
+ /**
326
+ * Parse a Cargo.toml — extract direct deps from [dependencies] / [dev-dependencies].
327
+ * Crude — only handles the common "name = version" / "name = { ... }" lines under
328
+ * [dependencies] / [dev-dependencies]. Doesn't expand workspace members.
329
+ */
330
+ function parseCargoToml(content) {
331
+ const pkgs = new Set();
332
+ const lines = content.split('\n');
333
+ let inDeps = false;
334
+ for (const raw of lines) {
335
+ const line = raw.trim();
336
+ if (line.startsWith('[')) {
337
+ inDeps = /^\[(dev-)?dependencies\]/.test(line);
338
+ continue;
339
+ }
340
+ if (!inDeps || !line || line.startsWith('#')) continue;
341
+ const match = line.match(/^([a-zA-Z0-9_-]+)\s*=/);
342
+ if (match) pkgs.add(match[1]);
343
+ }
344
+ return [...pkgs];
345
+ }
346
+
347
+ /**
348
+ * Parse a go.mod file — extract direct + indirect dependencies from require blocks.
349
+ * Format:
350
+ * require (
351
+ * github.com/gin-gonic/gin v1.9.1
352
+ * golang.org/x/net v0.20.0 // indirect
353
+ * )
354
+ * require github.com/foo/bar v1.0.0
355
+ */
356
+ function parseGoMod(content) {
357
+ const pkgs = new Set();
358
+ const lines = content.split('\n');
359
+ let inRequireBlock = false;
360
+
361
+ for (const raw of lines) {
362
+ const line = raw.trim();
363
+ if (!line || line.startsWith('//')) continue;
364
+
365
+ // Block-form require: "require ("
366
+ if (/^require\s*\(\s*$/.test(line)) {
367
+ inRequireBlock = true;
368
+ continue;
369
+ }
370
+ if (inRequireBlock && line === ')') {
371
+ inRequireBlock = false;
372
+ continue;
373
+ }
374
+
375
+ // Inside a block: "<modulepath> <version>" optionally followed by comments
376
+ if (inRequireBlock) {
377
+ const match = line.match(/^([^\s]+)\s+v[^\s]+/);
378
+ if (match) pkgs.add(match[1]);
379
+ continue;
380
+ }
381
+
382
+ // Single-line require: "require <modulepath> <version>"
383
+ const single = line.match(/^require\s+([^\s]+)\s+v[^\s]+/);
384
+ if (single) pkgs.add(single[1]);
385
+ }
386
+
387
+ return [...pkgs];
388
+ }
389
+
390
+ /**
391
+ * Parse a go.sum file — module path is the first column, version + suffix follow.
392
+ * Each module appears multiple times (once per artifact); dedupe.
393
+ */
394
+ function parseGoSum(content) {
395
+ const pkgs = new Set();
396
+ for (const raw of content.split('\n')) {
397
+ const line = raw.trim();
398
+ if (!line) continue;
399
+ const match = line.match(/^([^\s]+)\s+v[^\s/]+/);
400
+ if (match) pkgs.add(match[1]);
401
+ }
402
+ return [...pkgs];
278
403
  }
279
404
 
280
405
  /**
@@ -340,6 +465,8 @@ async function main() {
340
465
  const a = args[i];
341
466
  if (a === '--pypi') { ecosystem = 'pypi'; i++; }
342
467
  else if (a === '--npm') { ecosystem = 'npm'; i++; }
468
+ else if (a === '--cargo') { ecosystem = 'cargo'; i++; }
469
+ else if (a === '--golang' || a === '--go') { ecosystem = 'golang'; i++; }
343
470
  else if (a === '--json') { jsonOutput = true; i++; }
344
471
  else if (a === '--file' || a === '-f') {
345
472
  filePath = args[++i];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "proof-of-commitment",
3
- "version": "1.4.0",
4
- "description": "Supply chain risk scorer for npm and PyPI packages — behavioral signals that can't be faked",
3
+ "version": "1.6.0",
4
+ "description": "Supply chain risk scorer for npm, PyPI, Cargo, and Go packages — behavioral signals that can't be faked",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "proof-of-commitment": "./index.js",
@@ -17,6 +17,11 @@
17
17
  "security",
18
18
  "npm",
19
19
  "pypi",
20
+ "cargo",
21
+ "rust",
22
+ "golang",
23
+ "go",
24
+ "go-modules",
20
25
  "dependencies",
21
26
  "audit",
22
27
  "risk",