rulereceipt 0.1.45 → 0.1.47

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.
@@ -266,3 +266,45 @@ export function generateMarkdownReport(results, meta) {
266
266
  lines.push(hash ? `\`verify: sha256:${hash.slice(0, 16)}...\` · checked ${new Date().toISOString()}` : `demo data · checked ${new Date().toISOString()}`);
267
267
  return lines.join("\n");
268
268
  }
269
+ /**
270
+ * Machine-readable output for CI, a GitHub Action, or any other consumer.
271
+ *
272
+ * `schema` is versioned deliberately: this is a contract other tools will
273
+ * parse, so a breaking shape change must bump it rather than silently move
274
+ * fields under callers. The full (untruncated) sha256 is included so a
275
+ * consumer can `rulereceipt verify` the session independently — the human
276
+ * reports only show a prefix. Rule text and evidence are sanitized the same
277
+ * way as every other output: a hostile CLAUDE.md does not get to smuggle
278
+ * control characters through the JSON either.
279
+ */
280
+ export function generateJsonReport(results, meta, toolVersion) {
281
+ const clean = results.map(sanitize);
282
+ const count = (s) => clean.filter((r) => r.status === s).length;
283
+ const report = {
284
+ tool: "rulereceipt",
285
+ schema: 1,
286
+ version: toolVersion,
287
+ generatedAt: new Date().toISOString(),
288
+ session: {
289
+ path: meta.sessionFilePath,
290
+ sha256: computeTranscriptHash(meta.sessionFilePath),
291
+ },
292
+ summary: {
293
+ total: clean.length,
294
+ pass: count("PASS"),
295
+ fail: count("FAIL"),
296
+ unclear: count("UNCLEAR"),
297
+ },
298
+ results: clean.map((r) => ({
299
+ ruleId: r.ruleId,
300
+ ruleTitle: r.ruleTitle,
301
+ ruleSource: r.ruleSource,
302
+ status: r.status,
303
+ outcome: r.outcome ?? null,
304
+ method: r.method ?? null,
305
+ needsHuman: r.needsHuman ?? false,
306
+ evidence: r.evidence,
307
+ })),
308
+ };
309
+ return JSON.stringify(report, null, 2);
310
+ }
package/dist/types.d.ts CHANGED
@@ -64,7 +64,7 @@ export type CheckOutcome = "pass" | "fail"
64
64
  /** How a verdict was reached. A verdict with no method is a verdict with no standing. */
65
65
  export type CheckMethod = "text_scan" | "file_events" | "git_events" | "code_content" | "edit_test_pairing" | "claim_vs_evidence" | "model_judgment"
66
66
  /** Nothing ran. */
67
- | "none" | "emoji_output";
67
+ | "none" | "emoji_output" | "attribution_scan" | "approval_gate";
68
68
  export interface CheckResult {
69
69
  ruleId: string;
70
70
  ruleTitle: string;
@@ -0,0 +1,11 @@
1
+ export declare function isUpdateCheckEnabled(flag: boolean): boolean;
2
+ /** True if enough time has passed since the last check (or there was none). */
3
+ export declare function shouldCheckNow(now: number, lastCheck: number | null, intervalMs?: number): boolean;
4
+ /** The nudge, or null if the current version is already latest (or newer). */
5
+ export declare function renderUpdateNudge(current: string, latest: string): string | null;
6
+ /**
7
+ * Orchestration: only when enabled, only when due, fetch the latest version
8
+ * and nudge if newer. Records the check time even on failure so a flaky
9
+ * network does not turn into a ping on every single run.
10
+ */
11
+ export declare function maybeCheckUpdates(current: string, enabled: boolean, log?: (s: string) => void): Promise<void>;
@@ -0,0 +1,92 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { compareVersions } from "./whatsNew.js";
5
+ /**
6
+ * The online complement to the offline "what's new" footer: tell someone on
7
+ * an old global install that a newer version exists.
8
+ *
9
+ * OPT-IN, never by default. RuleReceipt's whole promise is that it makes no
10
+ * network call you did not ask for — telemetry is opt-in for the same reason,
11
+ * and an auditing tool that quietly phones a registry is the hypocrisy it
12
+ * exists to catch. Enabled only by --check-updates or RULERECEIPT_CHECK_UPDATES=1,
13
+ * cached so it pings at most once a day, and fails open on any error.
14
+ */
15
+ const REGISTRY = "https://registry.npmjs.org/rulereceipt/latest";
16
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
17
+ function stampPath() {
18
+ return join(homedir(), ".rulereceipt", "last-update-check");
19
+ }
20
+ export function isUpdateCheckEnabled(flag) {
21
+ if (flag)
22
+ return true;
23
+ const env = process.env.RULERECEIPT_CHECK_UPDATES;
24
+ return env === "1" || env === "true";
25
+ }
26
+ /** True if enough time has passed since the last check (or there was none). */
27
+ export function shouldCheckNow(now, lastCheck, intervalMs = CHECK_INTERVAL_MS) {
28
+ if (lastCheck === null)
29
+ return true;
30
+ return now - lastCheck >= intervalMs;
31
+ }
32
+ function readLastCheck() {
33
+ try {
34
+ const n = parseInt(readFileSync(stampPath(), "utf-8").trim(), 10);
35
+ return Number.isFinite(n) ? n : null;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ function writeLastCheck(now) {
42
+ try {
43
+ const dir = join(homedir(), ".rulereceipt");
44
+ if (!existsSync(dir))
45
+ mkdirSync(dir, { recursive: true });
46
+ writeFileSync(stampPath(), String(now), "utf-8");
47
+ }
48
+ catch {
49
+ // best-effort
50
+ }
51
+ }
52
+ /** The nudge, or null if the current version is already latest (or newer). */
53
+ export function renderUpdateNudge(current, latest) {
54
+ if (compareVersions(latest, current) <= 0)
55
+ return null;
56
+ return `\nA newer rulereceipt is available: v${latest} (you have v${current}). Update with: npx rulereceipt@latest`;
57
+ }
58
+ async function fetchLatestVersion() {
59
+ try {
60
+ const res = await fetch(REGISTRY, { signal: AbortSignal.timeout(2000) });
61
+ if (!res.ok)
62
+ return null;
63
+ const body = (await res.json());
64
+ return typeof body.version === "string" ? body.version : null;
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ /**
71
+ * Orchestration: only when enabled, only when due, fetch the latest version
72
+ * and nudge if newer. Records the check time even on failure so a flaky
73
+ * network does not turn into a ping on every single run.
74
+ */
75
+ export async function maybeCheckUpdates(current, enabled, log = console.log) {
76
+ try {
77
+ if (!enabled)
78
+ return;
79
+ if (!shouldCheckNow(Date.now(), readLastCheck()))
80
+ return;
81
+ writeLastCheck(Date.now());
82
+ const latest = await fetchLatestVersion();
83
+ if (!latest)
84
+ return;
85
+ const nudge = renderUpdateNudge(current, latest);
86
+ if (nudge)
87
+ log(nudge);
88
+ }
89
+ catch {
90
+ // never let an update check affect the run
91
+ }
92
+ }
package/dist/whatsNew.js CHANGED
@@ -14,6 +14,14 @@ import { join } from "node:path";
14
14
  * overstates is the exact failure this tool exists to catch.
15
15
  */
16
16
  export const RELEASES = [
17
+ {
18
+ version: "0.1.46",
19
+ highlights: [
20
+ "Run RuleReceipt in CI: `--json` output, `verify-receipt`, and a GitHub Action (uses: rulereceipt/rulereceipt).",
21
+ "`rulereceipt init` for guided setup, and per-rule warnings via .rulereceipt/config.json so CI gates on what matters.",
22
+ "A README status badge (`rulereceipt badge`), and opt-in `--check-updates`.",
23
+ ],
24
+ },
17
25
  { version: "0.1.45", highlights: ["The tool now shows what's improved since you last ran it, like this note."] },
18
26
  { version: "0.1.44", highlights: ["The report now offers to install enforcement, but only when a rule was actually broken."] },
19
27
  { version: "0.1.43", highlights: ["New check: a claim to have read or verified something, with nothing in the session behind it."] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulereceipt",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "Checks whether a Claude Code session actually followed your CLAUDE.md / AGENTS.md rules, with evidence.",
5
5
  "repository": {
6
6
  "type": "git",