proof-of-commitment 1.31.1 → 1.33.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/index.js +120 -9
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* proof-of-commitment CLI v1.
|
|
3
|
+
* proof-of-commitment CLI v1.33.0
|
|
4
4
|
* Scores npm/PyPI/Cargo/Go packages on behavioral commitment signals.
|
|
5
5
|
* Usage: npx proof-of-commitment [packages...] [options]
|
|
6
6
|
*/
|
|
@@ -313,6 +313,40 @@ function hasCritical(flags) {
|
|
|
313
313
|
return flags && flags.some(f => typeof f === 'string' && f.startsWith('CRITICAL'));
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Footer-calibration helper — diagnoses when the CRITICAL count is so dense
|
|
318
|
+
* that the label has lost actionable meaning, and produces a reframe.
|
|
319
|
+
*
|
|
320
|
+
* Why this exists. Lockfile audits on real Node projects routinely report
|
|
321
|
+
* 40–60% of scanned packages as CRITICAL — the threshold (sole publisher +
|
|
322
|
+
* >10M weekly downloads) is structurally widespread in npm. When the footer
|
|
323
|
+
* says "157 CRITICAL packages found" without context, a new user reads it as
|
|
324
|
+
* either alarmist noise (close the tab) or as an unbounded ask (157 things
|
|
325
|
+
* to investigate — uninstall the tool). Both kill activation.
|
|
326
|
+
*
|
|
327
|
+
* The dogfood signal: 2026-06-16 commit-landing-v2 lockfile, authenticated,
|
|
328
|
+
* 326 scanned, 157 CRITICAL (48%). Reflection: "label loses meaning when
|
|
329
|
+
* half the tree is red." Calibration reframes 157-of-326 as "typical npm
|
|
330
|
+
* baseline; act on the top 3-5 by impact" — preserves the data and gives
|
|
331
|
+
* the user a concrete next step. Threshold 20% chosen because that's the
|
|
332
|
+
* point at which per-package action stops being feasible.
|
|
333
|
+
*
|
|
334
|
+
* Returns null when calibration shouldn't fire (low ratio, no scan total,
|
|
335
|
+
* trivially small scan). Returns a `{ ratio, percent, baseline }` object
|
|
336
|
+
* otherwise — caller decides how to render it.
|
|
337
|
+
*/
|
|
338
|
+
function summarizeCriticalConcentration(criticalCount, totalScanned) {
|
|
339
|
+
if (!totalScanned || totalScanned < 10) return null;
|
|
340
|
+
if (!criticalCount || criticalCount <= 0) return null;
|
|
341
|
+
const ratio = criticalCount / totalScanned;
|
|
342
|
+
if (ratio < 0.20) return null;
|
|
343
|
+
return {
|
|
344
|
+
ratio,
|
|
345
|
+
percent: Math.round(ratio * 100),
|
|
346
|
+
baseline: true,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
316
350
|
function riskColor(flags, score) {
|
|
317
351
|
if (hasCritical(flags)) return c.red + c.bold;
|
|
318
352
|
if (score < 40) return c.yellow + c.bold;
|
|
@@ -592,9 +626,22 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
592
626
|
|
|
593
627
|
const effectiveCritical = totalCritical !== undefined ? totalCritical : criticalInDisplay;
|
|
594
628
|
if (effectiveCritical > 0) {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
629
|
+
const conc = summarizeCriticalConcentration(effectiveCritical, totalScanned);
|
|
630
|
+
if (conc) {
|
|
631
|
+
// High-density reframe: at ≥20% CRITICAL the per-package alarm framing
|
|
632
|
+
// loses meaning (the user can't audit a third of their tree). Recast as
|
|
633
|
+
// ecosystem baseline + actionable shortlist. Pinned by
|
|
634
|
+
// test/cli-critical-concentration.test.ts.
|
|
635
|
+
console.log('\n' + clr(c.red + c.bold, `⚠ ${effectiveCritical} CRITICAL packages (${conc.percent}% of ${totalScanned} scanned).`));
|
|
636
|
+
console.log(clr(c.dim, ' CRITICAL = sole npm publisher + >10M weekly downloads.'));
|
|
637
|
+
console.log(clr(c.dim, ' At this density you\'re seeing npm\'s baseline — most popular packages have'));
|
|
638
|
+
console.log(clr(c.dim, ' one publisher. Watch the top 3-5 above (highest impact); pin your lockfile'));
|
|
639
|
+
console.log(clr(c.dim, ' and audit deltas for the rest — concentration you can\'t avoid per-dep.'));
|
|
640
|
+
} else {
|
|
641
|
+
const suffix = totalScanned ? ` (in ${totalScanned} packages scanned)` : '';
|
|
642
|
+
console.log('\n' + clr(c.red + c.bold, `⚠ ${effectiveCritical} CRITICAL package${effectiveCritical > 1 ? 's' : ''} found${suffix}.`));
|
|
643
|
+
console.log(clr(c.dim, ' CRITICAL = sole npm publisher + >10M weekly downloads (publish-access concentration risk)'));
|
|
644
|
+
}
|
|
598
645
|
if (provenanceCount > 0 && provenanceCount < results.length) {
|
|
599
646
|
console.log(clr(c.cyan, ` 🔐 ${provenanceCount}/${results.length} use Trusted Publishing (OIDC provenance) — partial mitigation`));
|
|
600
647
|
}
|
|
@@ -609,7 +656,22 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
609
656
|
if (process.env.GITHUB_ACTIONS === 'true') {
|
|
610
657
|
if (effectiveCritical > 0) {
|
|
611
658
|
const critNames = results.filter(r => hasCritical(r.riskFlags)).slice(0, 5).map(r => r.name).join(', ');
|
|
612
|
-
|
|
659
|
+
// PR-check annotation is the single user-visible artifact outside the raw
|
|
660
|
+
// CI log. It must point at the conversion destination (/get-started with
|
|
661
|
+
// watchlist auto-seed), not the viewer (/audit). Pre-fix the annotation
|
|
662
|
+
// linked to /audit?packages= — same data the CI viewer already saw
|
|
663
|
+
// scrolled up in the log + an inline signup form one extra scroll away.
|
|
664
|
+
// Post-fix the link routes directly to /get-started?watch=…&eco=… which
|
|
665
|
+
// seeds the user's watchlist from this scan and arrives at an email
|
|
666
|
+
// capture as the primary form. Mirrors the same `?watch=…` URL contract
|
|
667
|
+
// (test/get-started-watch-param.test.ts) used by /audit's bottom CTA
|
|
668
|
+
// and the AUR-1579 / Snyk-comparison / Miasma / IronWorm blog posts.
|
|
669
|
+
// ref=cli-watch (not cli-ci-critical) because the latter is not in the
|
|
670
|
+
// backend's VALID_SOURCES and would get coerced to "web" — destroying
|
|
671
|
+
// attribution. cli-watch is already an accepted source.
|
|
672
|
+
const watchUrl = buildWatchUrl(results, 'cli-watch');
|
|
673
|
+
const ctaUrl = watchUrl || `https://getcommit.dev/audit?packages=${encodeURIComponent(critNames)}&utm_source=cli&utm_medium=ci-annotation`;
|
|
674
|
+
console.error(`::warning title=Commit: ${effectiveCritical} CRITICAL package${effectiveCritical > 1 ? 's' : ''}::Sole npm publisher + >10M downloads/week: ${critNames}. Watch + alert if scores change: ${ctaUrl}`);
|
|
613
675
|
}
|
|
614
676
|
if (compromisedCount > 0) {
|
|
615
677
|
const compNames = results.filter(r => r.compromised).slice(0, 5).map(r => r.name).join(', ');
|
|
@@ -650,10 +712,21 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
650
712
|
console.log(clr(c.dim, `\n 📊 Monitor ${effectiveCritical === 1 ? 'this package' : 'these packages'}: `) +
|
|
651
713
|
clr(c.cyan, `poc watch ${results.find(r => hasCritical(r.riskFlags))?.name || results[0]?.name}`));
|
|
652
714
|
} else if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
653
|
-
// Non-TTY (CI, piped): show
|
|
715
|
+
// Non-TTY (CI, piped): show both a clickable signup URL AND the one-step
|
|
716
|
+
// watch command. URL is for CI viewers reading log output in the browser
|
|
717
|
+
// (PR check tabs auto-link URLs); CLI command is for local-pipe / script
|
|
718
|
+
// contexts where copying a URL to a browser is friction. Two paths to
|
|
719
|
+
// the same outcome — see buildWatchUrl docstring for the rationale.
|
|
720
|
+
// Pre-fix this branch only emitted the CLI command, which requires the
|
|
721
|
+
// user to know the package name, run a second `npx`, and remember an
|
|
722
|
+
// email — a 3-decision ask in a CI log line that scrolls past.
|
|
654
723
|
const watchPkg = results.find(r => hasCritical(r.riskFlags))?.name || results[0]?.name;
|
|
724
|
+
const watchUrl = buildWatchUrl(results, 'cli-watch');
|
|
655
725
|
console.log(clr(c.dim, `\n 📊 Monitor ${effectiveCritical === 1 ? 'this' : 'these ' + effectiveCritical} CRITICAL ${effectiveCritical === 1 ? 'package' : 'packages'} — get alerted when scores change.`));
|
|
656
|
-
|
|
726
|
+
if (watchUrl) {
|
|
727
|
+
console.log(clr(c.dim, ' Web: ') + clr(c.cyan, watchUrl));
|
|
728
|
+
}
|
|
729
|
+
console.log(clr(c.dim, ' CLI: ') + clr(c.cyan, `poc watch ${watchPkg} --email you@company.com`));
|
|
657
730
|
console.log(clr(c.dim, ' Free: 3 packages, weekly digest. Developer $15/mo: 15 packages, daily scans.'));
|
|
658
731
|
}
|
|
659
732
|
// else: TTY mode — inlineSignup() will prompt interactively after printTable
|
|
@@ -664,8 +737,17 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
664
737
|
// text only in CI/piped output where interactive prompts can't fire.
|
|
665
738
|
// ref=audit-baseline distinguishes this funnel from audit-cli-429
|
|
666
739
|
// (rate-limit rescue) and from the static utm_source=cli help-line.
|
|
740
|
+
// Healthy-results non-TTY (CI, piped, no saved key) CTA. Mirror the
|
|
741
|
+
// CRITICAL branch's dual-path (Web + CLI). ref=audit-baseline keeps this
|
|
742
|
+
// funnel distinguishable from CRITICAL-driven cli-watch in api_keys.source
|
|
743
|
+
// breakdowns — important for measuring whether degradation-alert framing
|
|
744
|
+
// converts at all on clean results, separate from CRITICAL conversions.
|
|
745
|
+
const watchUrl = buildWatchUrl(results, 'audit-baseline');
|
|
667
746
|
console.log(clr(c.dim, '\n 📊 Get alerted if any package degrades:'));
|
|
668
|
-
|
|
747
|
+
if (watchUrl) {
|
|
748
|
+
console.log(clr(c.dim, ' Web: ') + clr(c.cyan, watchUrl));
|
|
749
|
+
}
|
|
750
|
+
console.log(clr(c.dim, ' CLI: ') + clr(c.cyan, `poc watch ${results[0]?.name || '<package>'} --email you@company.com`) + clr(c.dim, ' (free: 3 packages, weekly digest)'));
|
|
669
751
|
}
|
|
670
752
|
console.log();
|
|
671
753
|
}
|
|
@@ -703,6 +785,35 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
703
785
|
* Closes the second proposition-gap layer (CLI-side mirror of the
|
|
704
786
|
* 2026-06-11 audit-page watchlist auto-seed at abe53f1/df8a8be).
|
|
705
787
|
*/
|
|
788
|
+
/**
|
|
789
|
+
* Build a clickable /get-started URL that pre-seeds the watchlist from the
|
|
790
|
+
* scanned packages. Mirrors the `?watch=pkg1,pkg2,pkg3&eco=npm` URL contract
|
|
791
|
+
* pinned by test/get-started-watch-param.test.ts. The free-tier cap (3) is
|
|
792
|
+
* enforced via buildCliWatchSeeds(); /get-started.astro re-validates and the
|
|
793
|
+
* backend re-caps at PACKAGE_LIMITS.free (defense in depth across drift).
|
|
794
|
+
*
|
|
795
|
+
* Why this exists: the proven Snyk-comparison/AUR-1579 blog conversion pattern
|
|
796
|
+
* (mid-essay dual CTA + bottom dual CTA + `?watch=…` pre-seed) was sitting
|
|
797
|
+
* one repo over from the CLI for weeks. The CLI emits the highest-intent
|
|
798
|
+
* touchpoint after npm install — a CRITICAL finding in a real user's tree —
|
|
799
|
+
* but the only conversion paths it offered non-TTY users were a multi-token
|
|
800
|
+
* `poc watch X --email Y` CLI command (high friction) and a `getcommit.dev/
|
|
801
|
+
* audit?packages=…` link that drops users on the viewer page (not a converter).
|
|
802
|
+
* This helper produces the clickable conversion URL with packages pre-seeded.
|
|
803
|
+
*
|
|
804
|
+
* Returns null when no valid seeds exist (e.g. empty results, or all names
|
|
805
|
+
* fail the npm-name regex via buildCliWatchSeeds). Callers degrade gracefully
|
|
806
|
+
* to text-only CTAs without a URL.
|
|
807
|
+
*/
|
|
808
|
+
function buildWatchUrl(results, ref) {
|
|
809
|
+
const seeds = buildCliWatchSeeds(results);
|
|
810
|
+
if (seeds.length === 0) return null;
|
|
811
|
+
// All seeds from one scan share the same ecosystem; pick from the first.
|
|
812
|
+
const eco = seeds[0].ecosystem || 'npm';
|
|
813
|
+
const names = seeds.map(s => s.name).join(',');
|
|
814
|
+
return `https://getcommit.dev/get-started?watch=${encodeURIComponent(names)}&eco=${eco}&ref=${ref}&utm_source=cli`;
|
|
815
|
+
}
|
|
816
|
+
|
|
706
817
|
function buildCliWatchSeeds(results) {
|
|
707
818
|
if (!Array.isArray(results) || results.length === 0) return [];
|
|
708
819
|
const VALID_ECOS = new Set(['npm', 'pypi', 'cargo', 'golang']);
|
|
@@ -878,7 +989,7 @@ async function inlineSignup(results, opts = {}) {
|
|
|
878
989
|
|
|
879
990
|
function printHelp() {
|
|
880
991
|
console.log(`
|
|
881
|
-
${clr(c.bold, 'proof-of-commitment')} v1.
|
|
992
|
+
${clr(c.bold, 'proof-of-commitment')} v1.33.0 — supply chain risk scorer
|
|
882
993
|
|
|
883
994
|
${clr(c.bold, 'Usage:')}
|
|
884
995
|
npx proof-of-commitment Auto-detect manifest in current dir
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proof-of-commitment",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"mcpName": "io.github.piiiico/proof-of-commitment",
|
|
5
5
|
"description": "Supply chain security risk scorer for npm, PyPI, Cargo, and Go packages — behavioral signals that can't be faked",
|
|
6
6
|
"type": "module",
|