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.
- package/README.md +46 -0
- package/dist/badge.d.ts +22 -0
- package/dist/badge.js +22 -0
- package/dist/checkability.d.ts +31 -0
- package/dist/checkability.js +68 -0
- package/dist/checks/approvalGate.d.ts +3 -0
- package/dist/checks/approvalGate.js +105 -0
- package/dist/checks/attribution.d.ts +3 -0
- package/dist/checks/attribution.js +105 -0
- package/dist/checks/classify.d.ts +14 -1
- package/dist/checks/classify.js +86 -0
- package/dist/cli.js +158 -9
- package/dist/evaluate.js +4 -0
- package/dist/guard.js +6 -0
- package/dist/init.d.ts +15 -0
- package/dist/init.js +55 -0
- package/dist/projectConfig.d.ts +25 -0
- package/dist/projectConfig.js +31 -0
- package/dist/receipt.d.ts +68 -0
- package/dist/receipt.js +91 -0
- package/dist/report/generateReport.d.ts +12 -0
- package/dist/report/generateReport.js +42 -0
- package/dist/types.d.ts +1 -1
- package/dist/updateCheck.d.ts +11 -0
- package/dist/updateCheck.js +92 -0
- package/dist/whatsNew.js +8 -0
- package/package.json +1 -1
|
@@ -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."] },
|