pqcheck 0.16.3 → 0.16.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.
@@ -73,7 +73,11 @@ try {
73
73
  process.exit(0);
74
74
  }
75
75
 
76
- const { domain, score, grade, ship_decision, written_at, max_severity, unreachable } = state;
76
+ const {
77
+ domain, score, grade, ship_decision, written_at, max_severity, unreachable,
78
+ // v0.16.4 — preview-diff-specific fields for the 4-line render
79
+ kind, delta_count, diff_no_change, sector_ranking, verified_signal_categories, last_changed,
80
+ } = state;
77
81
  const age = ageHours(written_at);
78
82
 
79
83
  if (age > STALE_THRESHOLD_HOURS) {
@@ -87,6 +91,70 @@ if (age > STALE_THRESHOLD_HOURS) {
87
91
  process.exit(0);
88
92
  }
89
93
 
94
+ // v0.16.4 — preview-diff 4-line state.
95
+ // User direction: when the last scan was `preview-diff` (two URLs compared),
96
+ // render the high-yield block in the statusline. Other scan kinds keep the
97
+ // 1-line compact format below — the bigger block would be too noisy when
98
+ // it's just a routine domain scan with no diff context.
99
+ if (kind === "preview-diff" && !unreachable) {
100
+ // Line 1: brand header (hostname only).
101
+ const head = c(C.bold, "◆ Cipherwake") + c(C.dim, " — ") + c(C.bold, domain || "—");
102
+ // Line 3: decision pulse, diff-flavored copy.
103
+ let pulse;
104
+ if (ship_decision === "pass") {
105
+ pulse = c(C.green, "✓ PASS") + c(C.dim, " · no risky deploy-surface drift detected");
106
+ } else if (ship_decision === "review") {
107
+ pulse = c(C.yellow, "⚠ REVIEW") + c(C.dim, ` · ${delta_count || 0} public-surface change${delta_count === 1 ? "" : "s"}`);
108
+ } else if (ship_decision === "block") {
109
+ pulse = c(C.red, "⛔ BLOCK") + c(C.dim, ` · ${delta_count || 0} critical change${delta_count === 1 ? "" : "s"}`);
110
+ } else {
111
+ pulse = c(C.dim, "· no decision");
112
+ }
113
+ // Line 4: trust posture (DBR + percentile copy + stability).
114
+ function formatPercentile(percentile, sector) {
115
+ if (typeof percentile !== "number") return null;
116
+ const s = sector || "industry";
117
+ if (percentile <= 10) return `top 10% in ${s}`;
118
+ if (percentile <= 25) return `top 25% in ${s}`;
119
+ if (percentile <= 50) return `above median in ${s}`;
120
+ if (percentile <= 75) return `below median in ${s}`;
121
+ if (percentile <= 90) return `bottom 25% in ${s}`;
122
+ return `bottom 10% in ${s}`;
123
+ }
124
+ function formatStability(iso) {
125
+ if (!iso) return null;
126
+ const days = Math.floor((Date.now() - new Date(iso).getTime()) / 86400000);
127
+ if (days <= 0) return "drifted today";
128
+ if (days < 7) return `drifted ${days}d ago`;
129
+ return `stable ${days}d`;
130
+ }
131
+ const trustParts = [];
132
+ if (typeof score === "number") {
133
+ trustParts.push(`DBR ${score.toFixed(1)}${grade ? " " + grade : ""}`);
134
+ }
135
+ const pct = formatPercentile(sector_ranking?.percentile, sector_ranking?.sectorLabel || sector_ranking?.sectorName || sector_ranking?.sector);
136
+ if (pct) trustParts.push(pct);
137
+ const stab = formatStability(last_changed);
138
+ if (stab) trustParts.push(stab);
139
+ const trustLine = trustParts.length > 0
140
+ ? c(C.green, "✓ ") + c(C.bold, "Trust posture: ") + trustParts.join(c(C.dim, " · "))
141
+ : null;
142
+ // Line 5: verified signals (or finding line when there IS drift).
143
+ let verifiedLine = null;
144
+ if (Array.isArray(verified_signal_categories) && verified_signal_categories.length > 0) {
145
+ const head = diff_no_change === true
146
+ ? "Security-relevant surface unchanged"
147
+ : `Verified ${verified_signal_categories.length} signal${verified_signal_categories.length === 1 ? "" : "s"}`;
148
+ verifiedLine = c(C.green, "✓ ") + c(C.bold, head) + c(C.dim, " · " + verified_signal_categories.join(", "));
149
+ }
150
+ // Compose
151
+ const lines = [head, "", pulse];
152
+ if (trustLine) lines.push(trustLine);
153
+ if (verifiedLine) lines.push(verifiedLine);
154
+ process.stdout.write(lines.join("\n"));
155
+ process.exit(0);
156
+ }
157
+
90
158
  // Brand-anchored layout — "Cipherwake" is always the first word after the
91
159
  // diamond, so the customer (and their AI agent) can identify the status
92
160
  // line's source at a glance. Trailing segments depend on what we have:
package/bin/pqcheck.js CHANGED
@@ -24,7 +24,7 @@
24
24
  })();
25
25
 
26
26
  const API_BASE = process.env.PQCHECK_API_BASE || "https://cipherwake.io";
27
- const VERSION = "0.16.3";
27
+ const VERSION = "0.16.4";
28
28
 
29
29
  // API-key support — paid tiers (Starter $29 / Growth $79 / Scale $199) get
30
30
  // per-account monthly quotas instead of the per-IP rate limit. Set via:
@@ -3390,15 +3390,30 @@ async function runPreviewDiffCommand(args) {
3390
3390
  }));
3391
3391
  console.log("");
3392
3392
 
3393
+ // v0.16.4 — write the data the statusline needs to render its
3394
+ // 4-line preview-diff state (brand header + decision pulse + trust
3395
+ // posture + verified signals). For non-preview-diff kinds we keep
3396
+ // the 1-line statusline; only preview-diff gets the bigger format.
3397
+ const verifiedSignalCats = (() => {
3398
+ try { return countVerifiedSignals(reportLike).categories; } catch { return null; }
3399
+ })();
3393
3400
  await writeLastScanFile({
3394
- domain: result?.production?.domain || productionUrl,
3401
+ domain: result?.production?.hostname || result?.production?.domain || productionUrl,
3395
3402
  kind: "preview-diff",
3396
3403
  preview_url: previewUrl,
3397
3404
  production_url: productionUrl,
3398
- score: typeof prevScore === "number" ? prevScore : null,
3405
+ // v0.16.4 — score/grade come from the PRODUCTION side so the trust
3406
+ // posture line reflects the live customer site, not the in-flight
3407
+ // preview. (Was prevScore before — wrong reference.)
3408
+ score: typeof prodSig?.score === "number" ? prodSig.score : (typeof prevScore === "number" ? prevScore : null),
3409
+ grade: prodSig?.grade || null,
3399
3410
  max_severity: maxSev,
3400
3411
  ship_decision: shipDecision,
3401
3412
  delta_count: summaryLines.filter((l) => !/no meaningful/i.test(l)).length,
3413
+ diff_no_change: diffNoChange,
3414
+ sector_ranking: prodSig?.sectorRanking || null,
3415
+ verified_signal_categories: verifiedSignalCats,
3416
+ last_changed: prodSig?.lastChanged || null,
3402
3417
  });
3403
3418
 
3404
3419
  process.exit(shipDecisionExitCode(shipDecision));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pqcheck",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "Deploy gate for AI-coded web apps. `pqcheck deploy-check --ai` returns ship_decision=pass|review|block for Claude Code / Cursor / Copilot / Aider to gate deploys before they ship. Anonymous, no signup, free for first use.",
5
5
  "keywords": [
6
6
  "ai-coder",