rulereceipt 0.1.63 → 0.1.64
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 +11 -0
- package/dist/card.d.ts +36 -0
- package/dist/card.js +76 -0
- package/dist/cli.js +35 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,17 @@ showing you exactly what it will add and asking. `protect --undo` restores the
|
|
|
49
49
|
file byte-for-byte. It's the only place RuleReceipt writes settings, and only
|
|
50
50
|
with your yes.
|
|
51
51
|
|
|
52
|
+
To share the result:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx rulereceipt card
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Writes a small SVG summary and prints ready-to-post links for X, LinkedIn,
|
|
59
|
+
Bluesky and Reddit, plus copy-text for Slack or a PR. It carries **counts
|
|
60
|
+
only** — no code, paths, or rule text (add rule names to the copy-text with
|
|
61
|
+
`--show-rules`). Nothing is posted for you and nothing is uploaded.
|
|
62
|
+
|
|
52
63
|
## Status
|
|
53
64
|
|
|
54
65
|
Published and live on npm, actively developed.
|
package/dist/card.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rulereceipt card` — a shareable image and pre-filled share links, built from
|
|
3
|
+
* history-mode counts. This is the "share it" step: someone runs RuleReceipt,
|
|
4
|
+
* sees their agent's mistakes, fixes them with `protect`, and posts the card.
|
|
5
|
+
*
|
|
6
|
+
* Privacy is the whole point of it being shareable: by default the card and the
|
|
7
|
+
* share text carry ONLY counts — never code, file paths, or rule text. A rule's
|
|
8
|
+
* name appears only when the user passes `--show-rules`, and even then only in
|
|
9
|
+
* the copy-text, never the image. Nothing is posted automatically and nothing
|
|
10
|
+
* is uploaded; the links open a compose window the user chooses to send.
|
|
11
|
+
*/
|
|
12
|
+
export interface CardData {
|
|
13
|
+
broken: number;
|
|
14
|
+
followed: number;
|
|
15
|
+
judgment: number;
|
|
16
|
+
sessions: number;
|
|
17
|
+
days: number;
|
|
18
|
+
/** "Claude" when the sessions are all Claude Code, else "the agent". */
|
|
19
|
+
who: string;
|
|
20
|
+
/** Titles of the broken rules — only used with --show-rules. */
|
|
21
|
+
brokenTitles?: string[];
|
|
22
|
+
}
|
|
23
|
+
/** The share caption. Counts only, unless showRules adds the broken rule names. */
|
|
24
|
+
export declare function shareText(d: CardData, showRules?: boolean): string;
|
|
25
|
+
export interface ShareLinks {
|
|
26
|
+
x: string;
|
|
27
|
+
linkedin: string;
|
|
28
|
+
bluesky: string;
|
|
29
|
+
reddit: string;
|
|
30
|
+
}
|
|
31
|
+
/** Compose-window links for each network. No auth, no posting — the user sends it. */
|
|
32
|
+
export declare function shareLinks(text: string): ShareLinks;
|
|
33
|
+
/** A self-contained SVG card. Counts only — never rule text, paths or code. */
|
|
34
|
+
export declare function cardSvg(d: CardData): string;
|
|
35
|
+
/** The terminal block: where the image went, the share links, and the copy-text. */
|
|
36
|
+
export declare function renderCardShare(d: CardData, outPath: string, showRules?: boolean): string;
|
package/dist/card.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rulereceipt card` — a shareable image and pre-filled share links, built from
|
|
3
|
+
* history-mode counts. This is the "share it" step: someone runs RuleReceipt,
|
|
4
|
+
* sees their agent's mistakes, fixes them with `protect`, and posts the card.
|
|
5
|
+
*
|
|
6
|
+
* Privacy is the whole point of it being shareable: by default the card and the
|
|
7
|
+
* share text carry ONLY counts — never code, file paths, or rule text. A rule's
|
|
8
|
+
* name appears only when the user passes `--show-rules`, and even then only in
|
|
9
|
+
* the copy-text, never the image. Nothing is posted automatically and nothing
|
|
10
|
+
* is uploaded; the links open a compose window the user chooses to send.
|
|
11
|
+
*/
|
|
12
|
+
const SITE = "rulereceipt.dev";
|
|
13
|
+
/** The share caption. Counts only, unless showRules adds the broken rule names. */
|
|
14
|
+
export function shareText(d, showRules = false) {
|
|
15
|
+
const base = d.broken > 0
|
|
16
|
+
? `${d.who} broke my written rules ${d.broken} time${d.broken === 1 ? "" : "s"} in ${d.days} days (${d.sessions} session${d.sessions === 1 ? "" : "s"}) — now it can't.`
|
|
17
|
+
: `RuleReceipt checked ${d.sessions} of my agent session${d.sessions === 1 ? "" : "s"} over ${d.days} days against my written rules: ${d.broken} broken.`;
|
|
18
|
+
const tail = `Checked with RuleReceipt — runs locally, nothing uploaded. ${SITE}`;
|
|
19
|
+
if (showRules && d.broken > 0 && d.brokenTitles && d.brokenTitles.length > 0) {
|
|
20
|
+
const list = d.brokenTitles.slice(0, 3).map((t) => `“${t.replace(/\s+/g, " ").trim().slice(0, 50)}”`).join(", ");
|
|
21
|
+
return `${base}\nBroke: ${list}.\n${tail}`;
|
|
22
|
+
}
|
|
23
|
+
return `${base}\n${tail}`;
|
|
24
|
+
}
|
|
25
|
+
/** Compose-window links for each network. No auth, no posting — the user sends it. */
|
|
26
|
+
export function shareLinks(text) {
|
|
27
|
+
const t = encodeURIComponent(text);
|
|
28
|
+
const url = encodeURIComponent(`https://${SITE}`);
|
|
29
|
+
const title = encodeURIComponent(text.split("\n")[0]);
|
|
30
|
+
return {
|
|
31
|
+
x: `https://twitter.com/intent/tweet?text=${t}`,
|
|
32
|
+
// LinkedIn's offsite share only takes a URL; the caption is added by the user.
|
|
33
|
+
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${url}`,
|
|
34
|
+
bluesky: `https://bsky.app/intent/compose?text=${t}`,
|
|
35
|
+
reddit: `https://www.reddit.com/submit?title=${title}&url=${url}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function esc(s) {
|
|
39
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
40
|
+
}
|
|
41
|
+
/** A self-contained SVG card. Counts only — never rule text, paths or code. */
|
|
42
|
+
export function cardSvg(d) {
|
|
43
|
+
const headline = d.broken > 0 ? `${d.who} broke your rules ${d.broken}×` : `0 rules broken`;
|
|
44
|
+
const sub = `${d.sessions} session${d.sessions === 1 ? "" : "s"} · last ${d.days} days`;
|
|
45
|
+
const stats = `${d.followed} followed · ${d.judgment} need judgment`;
|
|
46
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="800" height="418" viewBox="0 0 800 418" role="img" aria-label="RuleReceipt summary">
|
|
47
|
+
<rect width="800" height="418" fill="#0b0d10"/>
|
|
48
|
+
<rect x="0" y="0" width="800" height="6" fill="${d.broken > 0 ? "#e5534b" : "#3fb950"}"/>
|
|
49
|
+
<text x="56" y="86" fill="#8b949e" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="20">RuleReceipt</text>
|
|
50
|
+
<text x="56" y="196" fill="#e6edf3" font-family="-apple-system, Segoe UI, Roboto, sans-serif" font-size="52" font-weight="700">${esc(headline)}</text>
|
|
51
|
+
<text x="56" y="248" fill="#8b949e" font-family="-apple-system, Segoe UI, Roboto, sans-serif" font-size="24">${esc(sub)}</text>
|
|
52
|
+
<text x="56" y="300" fill="#8b949e" font-family="-apple-system, Segoe UI, Roboto, sans-serif" font-size="22">${esc(stats)}</text>
|
|
53
|
+
<text x="56" y="372" fill="#6e7681" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="18">proof from real sessions · local · nothing uploaded · ${SITE}</text>
|
|
54
|
+
</svg>`;
|
|
55
|
+
}
|
|
56
|
+
/** The terminal block: where the image went, the share links, and the copy-text. */
|
|
57
|
+
export function renderCardShare(d, outPath, showRules = false) {
|
|
58
|
+
const text = shareText(d, showRules);
|
|
59
|
+
const links = shareLinks(text);
|
|
60
|
+
const out = [];
|
|
61
|
+
out.push(`Saved a shareable card to ${outPath} (counts only — no code, paths or rule text).`);
|
|
62
|
+
out.push("");
|
|
63
|
+
out.push("Share it (opens a compose window — nothing is posted for you):");
|
|
64
|
+
out.push(` X / Twitter ${links.x}`);
|
|
65
|
+
out.push(` LinkedIn ${links.linkedin}`);
|
|
66
|
+
out.push(` Bluesky ${links.bluesky}`);
|
|
67
|
+
out.push(` Reddit ${links.reddit}`);
|
|
68
|
+
out.push("");
|
|
69
|
+
out.push("Copy text (for Slack / Discord / a PR):");
|
|
70
|
+
out.push(text.split("\n").map((l) => ` ${l}`).join("\n"));
|
|
71
|
+
if (!showRules && d.broken > 0) {
|
|
72
|
+
out.push("");
|
|
73
|
+
out.push("(Rule names are left out. Add them with --show-rules if you want them in the copy-text.)");
|
|
74
|
+
}
|
|
75
|
+
return out.join("\n");
|
|
76
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { buildWrongReport, findTarget } from "./wrong.js";
|
|
|
18
18
|
import { detectSelfEditedRuleFiles } from "./checks/selfEditedRules.js";
|
|
19
19
|
import { scanHistory, renderHistory } from "./historyReport.js";
|
|
20
20
|
import { planProtect, applyProtect, undoProtect } from "./protect.js";
|
|
21
|
+
import { cardSvg, renderCardShare } from "./card.js";
|
|
21
22
|
import { createInterface } from "node:readline";
|
|
22
23
|
import { loadOverrides, saveOverride, clearOverride, staleOverrides, ruleFingerprint, OVERRIDES_PATH } from "./overrides.js";
|
|
23
24
|
import { runHook } from "./hook.js";
|
|
@@ -1087,6 +1088,40 @@ program
|
|
|
1087
1088
|
console.log("The hooks call `rulereceipt` on your PATH (install once with `npm i -g rulereceipt`); they fail open if it's missing.");
|
|
1088
1089
|
console.log("Undo: rulereceipt protect --undo");
|
|
1089
1090
|
});
|
|
1091
|
+
program
|
|
1092
|
+
.command("card")
|
|
1093
|
+
.description("Make a shareable image and pre-filled share links from your last 30 days of sessions — counts only, no code, paths or rule text (add rule names to the copy-text with --show-rules). Saves an SVG locally and prints X/LinkedIn/Bluesky/Reddit compose links. Nothing is posted and nothing is uploaded.")
|
|
1094
|
+
.option("--out <path>", "where to write the SVG card", "rulereceipt-card.svg")
|
|
1095
|
+
.option("--show-rules", "include the broken rule names in the copy-text (never in the image)")
|
|
1096
|
+
.option("--days <n>", "how many days back to summarize", "30")
|
|
1097
|
+
.action(async (opts) => {
|
|
1098
|
+
const cwd = process.cwd();
|
|
1099
|
+
const rules = loadRules(cwd);
|
|
1100
|
+
if (rules.length === 0) {
|
|
1101
|
+
console.log("No rules file found for this project, so there's nothing to summarize. Add a CLAUDE.md or AGENTS.md first.");
|
|
1102
|
+
process.exitCode = 1;
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const days = Number.parseInt(opts.days ?? "30", 10);
|
|
1106
|
+
const s = await scanHistory(cwd, rules, Number.isFinite(days) && days > 0 ? days : 30);
|
|
1107
|
+
if (s.sessionsScanned === 0) {
|
|
1108
|
+
console.log("No sessions found for this project in the window, so there's nothing to put on a card yet. Run your agent here, then try again.");
|
|
1109
|
+
process.exitCode = 1;
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
const data = {
|
|
1113
|
+
broken: s.totalBrokenCount,
|
|
1114
|
+
followed: s.followedRules,
|
|
1115
|
+
judgment: s.judgmentRules,
|
|
1116
|
+
sessions: s.sessionsScanned,
|
|
1117
|
+
days: s.days,
|
|
1118
|
+
who: s.tools.length === 1 && s.tools[0] === "claude-code" ? "Claude" : "the agent",
|
|
1119
|
+
brokenTitles: s.breaks.map((b) => b.ruleTitle),
|
|
1120
|
+
};
|
|
1121
|
+
const outPath = resolve(cwd, opts.out ?? "rulereceipt-card.svg");
|
|
1122
|
+
writeFileSync(outPath, cardSvg(data));
|
|
1123
|
+
console.log(renderCardShare(data, outPath, Boolean(opts.showRules)));
|
|
1124
|
+
});
|
|
1090
1125
|
program
|
|
1091
1126
|
.command("history")
|
|
1092
1127
|
.description("Check EVERY session for this project in the last 30 days (this is what runs when you type `rulereceipt` with no arguments). Leads with the rules broken most, each with a count, the last date and one quoted line. Counts only proven breaks; judgment rules stay separate. No session to pick, no API key, nothing uploaded.")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulereceipt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64",
|
|
4
4
|
"description": "Checks whether your AI coding agent followed your rules, with evidence. Works with Claude Code (Codex in testing); reads CLAUDE.md, AGENTS.md, Cursor, Copilot and Windsurf rules.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|