rulereceipt 0.1.44 → 0.1.45
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/dist/cli.js +7 -0
- package/dist/whatsNew.d.ts +41 -0
- package/dist/whatsNew.js +112 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -27,6 +27,7 @@ import { verifySessionHash } from "./verifyHash.js";
|
|
|
27
27
|
import { saveEmailConfig, loadEmailConfig, detectSmtpHost, isValidEmail } from "./emailConfig.js";
|
|
28
28
|
import { sendReportEmail } from "./sendReport.js";
|
|
29
29
|
import { appendHistory, readHistorySince } from "./history.js";
|
|
30
|
+
import { maybeShowWhatsNew } from "./whatsNew.js";
|
|
30
31
|
import { generateDigest } from "./digest.js";
|
|
31
32
|
import { enableSchedule, disableSchedule, scheduleStatus } from "./schedule.js";
|
|
32
33
|
import { findSplitBrainConflicts } from "./checks/splitBrain.js";
|
|
@@ -319,6 +320,12 @@ async function runCheck(opts) {
|
|
|
319
320
|
console.log(`\n(${stale.length} saved correction${stale.length === 1 ? "" : "s"} no longer match any rule in this project — the rule was probably reworded. Run \`rulereceipt rules --list\` to see them.)`);
|
|
320
321
|
}
|
|
321
322
|
appendHistory(results, sessionFilePath);
|
|
323
|
+
// A once-per-update footer so a returning user sees the tool improved and
|
|
324
|
+
// comes back. Offline (notes ship in the package), fails open, and never
|
|
325
|
+
// on --markdown (that output is meant to be pasted into a PR/Slack).
|
|
326
|
+
if (!markdown) {
|
|
327
|
+
maybeShowWhatsNew(pkg.version);
|
|
328
|
+
}
|
|
322
329
|
if (share) {
|
|
323
330
|
await shareResults(results);
|
|
324
331
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface Release {
|
|
2
|
+
version: string;
|
|
3
|
+
highlights: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* User-facing release highlights, newest first.
|
|
7
|
+
*
|
|
8
|
+
* This is NOT the internal CHANGELOG (developer/business-facing, never
|
|
9
|
+
* shipped). It is the short, plain "here is what got better" note a user
|
|
10
|
+
* sees once, the first time they run a new version. Add ONE entry at the
|
|
11
|
+
* top on each release: what changed, in a sentence, honest, no marketing.
|
|
12
|
+
*
|
|
13
|
+
* The whole point: someone who bounced off an early version sees the tool
|
|
14
|
+
* is improving and comes back. So keep it truthful — a highlight that
|
|
15
|
+
* overstates is the exact failure this tool exists to catch.
|
|
16
|
+
*/
|
|
17
|
+
export declare const RELEASES: Release[];
|
|
18
|
+
export declare function readLastSeen(): string | null;
|
|
19
|
+
export declare function writeLastSeen(version: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Numeric dotted-version compare: <0 if a<b, 0 if equal, >0 if a>b.
|
|
22
|
+
* Numeric per segment, so 0.1.9 < 0.1.10 (not lexical). Junk gives 0, which
|
|
23
|
+
* makes the caller show nothing rather than guess.
|
|
24
|
+
*/
|
|
25
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Releases strictly newer than lastSeen and no newer than current, newest
|
|
28
|
+
* first. A null lastSeen (first run ever) yields nothing on purpose: a
|
|
29
|
+
* first-timer should see their report, not a changelog.
|
|
30
|
+
*/
|
|
31
|
+
export declare function highlightsBetween(lastSeen: string | null, current: string, releases?: Release[]): Release[];
|
|
32
|
+
export declare function renderWhatsNew(releases: Release[], current: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Prints the "what's new" note once per new version, then records the
|
|
35
|
+
* current version so it never repeats for that version.
|
|
36
|
+
*
|
|
37
|
+
* Fails open, always: any error here must never affect the report the user
|
|
38
|
+
* actually ran for, and there is no network call — the notes ship inside
|
|
39
|
+
* the package, so the tool stays true to "nothing leaves your machine".
|
|
40
|
+
*/
|
|
41
|
+
export declare function maybeShowWhatsNew(current: string, log?: (s: string) => void): void;
|
package/dist/whatsNew.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* User-facing release highlights, newest first.
|
|
6
|
+
*
|
|
7
|
+
* This is NOT the internal CHANGELOG (developer/business-facing, never
|
|
8
|
+
* shipped). It is the short, plain "here is what got better" note a user
|
|
9
|
+
* sees once, the first time they run a new version. Add ONE entry at the
|
|
10
|
+
* top on each release: what changed, in a sentence, honest, no marketing.
|
|
11
|
+
*
|
|
12
|
+
* The whole point: someone who bounced off an early version sees the tool
|
|
13
|
+
* is improving and comes back. So keep it truthful — a highlight that
|
|
14
|
+
* overstates is the exact failure this tool exists to catch.
|
|
15
|
+
*/
|
|
16
|
+
export const RELEASES = [
|
|
17
|
+
{ version: "0.1.45", highlights: ["The tool now shows what's improved since you last ran it, like this note."] },
|
|
18
|
+
{ version: "0.1.44", highlights: ["The report now offers to install enforcement, but only when a rule was actually broken."] },
|
|
19
|
+
{ version: "0.1.43", highlights: ["New check: a claim to have read or verified something, with nothing in the session behind it."] },
|
|
20
|
+
{ version: "0.1.41", highlights: ["Emoji rules are checked properly now (Unicode properties, not a hand-written list)."] },
|
|
21
|
+
{ version: "0.1.39", highlights: ["You can mark which clause in a rule is the actual prohibition, so only that blocks."] },
|
|
22
|
+
{ version: "0.1.36", highlights: ["Enforcement arrives: the tool can act on a broken rule with a hook, not just report it."] },
|
|
23
|
+
];
|
|
24
|
+
function stateDir() {
|
|
25
|
+
return join(homedir(), ".rulereceipt");
|
|
26
|
+
}
|
|
27
|
+
function lastSeenPath() {
|
|
28
|
+
return join(stateDir(), "last-seen-version");
|
|
29
|
+
}
|
|
30
|
+
export function readLastSeen() {
|
|
31
|
+
try {
|
|
32
|
+
const v = readFileSync(lastSeenPath(), "utf-8").trim();
|
|
33
|
+
return v.length > 0 ? v : null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function writeLastSeen(version) {
|
|
40
|
+
try {
|
|
41
|
+
const dir = stateDir();
|
|
42
|
+
if (!existsSync(dir))
|
|
43
|
+
mkdirSync(dir, { recursive: true });
|
|
44
|
+
writeFileSync(lastSeenPath(), version, "utf-8");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// best-effort; a run that cannot persist this just shows the note again
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Numeric dotted-version compare: <0 if a<b, 0 if equal, >0 if a>b.
|
|
52
|
+
* Numeric per segment, so 0.1.9 < 0.1.10 (not lexical). Junk gives 0, which
|
|
53
|
+
* makes the caller show nothing rather than guess.
|
|
54
|
+
*/
|
|
55
|
+
export function compareVersions(a, b) {
|
|
56
|
+
const pa = a.split(".").map((n) => parseInt(n, 10));
|
|
57
|
+
const pb = b.split(".").map((n) => parseInt(n, 10));
|
|
58
|
+
const len = Math.max(pa.length, pb.length);
|
|
59
|
+
for (let i = 0; i < len; i++) {
|
|
60
|
+
const x = pa[i] ?? 0;
|
|
61
|
+
const y = pb[i] ?? 0;
|
|
62
|
+
if (Number.isNaN(x) || Number.isNaN(y))
|
|
63
|
+
return 0;
|
|
64
|
+
if (x !== y)
|
|
65
|
+
return x - y;
|
|
66
|
+
}
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Releases strictly newer than lastSeen and no newer than current, newest
|
|
71
|
+
* first. A null lastSeen (first run ever) yields nothing on purpose: a
|
|
72
|
+
* first-timer should see their report, not a changelog.
|
|
73
|
+
*/
|
|
74
|
+
export function highlightsBetween(lastSeen, current, releases = RELEASES) {
|
|
75
|
+
if (!lastSeen)
|
|
76
|
+
return [];
|
|
77
|
+
return releases.filter((r) => compareVersions(r.version, lastSeen) > 0 && compareVersions(r.version, current) <= 0);
|
|
78
|
+
}
|
|
79
|
+
export function renderWhatsNew(releases, current) {
|
|
80
|
+
const lines = [];
|
|
81
|
+
lines.push(`\n✨ What's new since you last ran rulereceipt (you're on v${current}):`);
|
|
82
|
+
for (const r of releases) {
|
|
83
|
+
for (const h of r.highlights) {
|
|
84
|
+
lines.push(` • v${r.version} ${h}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
lines.push(`\nThis note shows once per update. To stay current: npx rulereceipt@latest`);
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Prints the "what's new" note once per new version, then records the
|
|
92
|
+
* current version so it never repeats for that version.
|
|
93
|
+
*
|
|
94
|
+
* Fails open, always: any error here must never affect the report the user
|
|
95
|
+
* actually ran for, and there is no network call — the notes ship inside
|
|
96
|
+
* the package, so the tool stays true to "nothing leaves your machine".
|
|
97
|
+
*/
|
|
98
|
+
export function maybeShowWhatsNew(current, log = console.log) {
|
|
99
|
+
try {
|
|
100
|
+
const lastSeen = readLastSeen();
|
|
101
|
+
const news = highlightsBetween(lastSeen, current, RELEASES);
|
|
102
|
+
if (news.length > 0)
|
|
103
|
+
log(renderWhatsNew(news, current));
|
|
104
|
+
// Record current even on the first run and even when nothing showed, so
|
|
105
|
+
// the next update is measured from here.
|
|
106
|
+
if (lastSeen !== current)
|
|
107
|
+
writeLastSeen(current);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// never let a footer break the run
|
|
111
|
+
}
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulereceipt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
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",
|
|
@@ -57,6 +57,6 @@
|
|
|
57
57
|
"tsx": "^4.19.0",
|
|
58
58
|
"typescript": "^5.6.0",
|
|
59
59
|
"typescript-eslint": "^8.68.0",
|
|
60
|
-
"vitest": "^
|
|
60
|
+
"vitest": "^5.0.1"
|
|
61
61
|
}
|
|
62
62
|
}
|