rulereceipt 0.1.43 → 0.1.44
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 +11 -0
- package/dist/report/gateOffer.d.ts +29 -0
- package/dist/report/gateOffer.js +58 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ import { runHook } from "./hook.js";
|
|
|
21
21
|
import { runGuard } from "./guard.js";
|
|
22
22
|
import { runJudgmentChecks } from "./checks/judgmentChecks.js";
|
|
23
23
|
import { generateReport, generateMarkdownReport } from "./report/generateReport.js";
|
|
24
|
+
import { gateOffer, hookIsInstalled } from "./report/gateOffer.js";
|
|
24
25
|
import { generateHtmlReport } from "./report/generateHtmlReport.js";
|
|
25
26
|
import { verifySessionHash } from "./verifyHash.js";
|
|
26
27
|
import { saveEmailConfig, loadEmailConfig, detectSmtpHost, isValidEmail } from "./emailConfig.js";
|
|
@@ -259,6 +260,16 @@ async function runCheck(opts) {
|
|
|
259
260
|
const meta = { sessionFilePath, ruleCount: results.length };
|
|
260
261
|
const reportText = markdown ? generateMarkdownReport(results, meta) : generateReport(results, meta);
|
|
261
262
|
console.log(reportText);
|
|
263
|
+
// Shown only to someone who has just read their own broken rules, and only
|
|
264
|
+
// if they have not already wired it up. See report/gateOffer.ts.
|
|
265
|
+
if (!markdown) {
|
|
266
|
+
const offer = gateOffer({
|
|
267
|
+
failures: results.filter((r) => r.status === "FAIL").length,
|
|
268
|
+
hookInstalled: hookIsInstalled(cwd),
|
|
269
|
+
});
|
|
270
|
+
if (offer)
|
|
271
|
+
console.log(`\n${offer}`);
|
|
272
|
+
}
|
|
262
273
|
// Written before --share/--email so that a failure to send something
|
|
263
274
|
// never costs the user the local artifact they explicitly asked for.
|
|
264
275
|
if (html !== false) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is a RuleReceipt hook already wired into this project or this machine?
|
|
3
|
+
*
|
|
4
|
+
* Read-only, and deliberately forgiving: an unreadable or malformed settings
|
|
5
|
+
* file means "assume not installed" rather than an error. A missing settings
|
|
6
|
+
* file is the normal case, not a problem.
|
|
7
|
+
*/
|
|
8
|
+
export declare function hookIsInstalled(cwd: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The line offering enforcement, or null when it should not be shown.
|
|
11
|
+
*
|
|
12
|
+
* The site describes the Stop hook to someone deciding whether to adopt the
|
|
13
|
+
* tool at all. This speaks to someone who has already run it and is looking
|
|
14
|
+
* at rules that were broken — which is the moment the offer is a consequence
|
|
15
|
+
* of what they just read rather than an advertisement.
|
|
16
|
+
*
|
|
17
|
+
* Three conditions, and each is a way of not nagging:
|
|
18
|
+
*
|
|
19
|
+
* - Only when something actually failed. On a clean report there is
|
|
20
|
+
* nothing to enforce and the line would be a pitch.
|
|
21
|
+
* - Never when the hook is already installed. Telling someone to do what
|
|
22
|
+
* they have already done is how a tool gets muted.
|
|
23
|
+
* - One line, no config block. A terminal report is not documentation, and
|
|
24
|
+
* pasting JSON into it would bury the findings it sits under.
|
|
25
|
+
*/
|
|
26
|
+
export declare function gateOffer(state: {
|
|
27
|
+
failures: number;
|
|
28
|
+
hookInstalled: boolean;
|
|
29
|
+
}): string | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Is a RuleReceipt hook already wired into this project or this machine?
|
|
6
|
+
*
|
|
7
|
+
* Read-only, and deliberately forgiving: an unreadable or malformed settings
|
|
8
|
+
* file means "assume not installed" rather than an error. A missing settings
|
|
9
|
+
* file is the normal case, not a problem.
|
|
10
|
+
*/
|
|
11
|
+
export function hookIsInstalled(cwd) {
|
|
12
|
+
const candidates = [
|
|
13
|
+
join(cwd, ".claude", "settings.json"),
|
|
14
|
+
join(cwd, ".claude", "settings.local.json"),
|
|
15
|
+
join(homedir(), ".claude", "settings.json"),
|
|
16
|
+
];
|
|
17
|
+
for (const path of candidates) {
|
|
18
|
+
if (!existsSync(path))
|
|
19
|
+
continue;
|
|
20
|
+
try {
|
|
21
|
+
// Matched as text rather than by walking the hook schema: the shape of
|
|
22
|
+
// that config has changed before, and a rename of one field should not
|
|
23
|
+
// make this start nagging someone who already installed it.
|
|
24
|
+
if (/rulereceipt\s+(hook|guard)/.test(readFileSync(path, "utf-8")))
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// unreadable settings file — treat as not installed
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The line offering enforcement, or null when it should not be shown.
|
|
35
|
+
*
|
|
36
|
+
* The site describes the Stop hook to someone deciding whether to adopt the
|
|
37
|
+
* tool at all. This speaks to someone who has already run it and is looking
|
|
38
|
+
* at rules that were broken — which is the moment the offer is a consequence
|
|
39
|
+
* of what they just read rather than an advertisement.
|
|
40
|
+
*
|
|
41
|
+
* Three conditions, and each is a way of not nagging:
|
|
42
|
+
*
|
|
43
|
+
* - Only when something actually failed. On a clean report there is
|
|
44
|
+
* nothing to enforce and the line would be a pitch.
|
|
45
|
+
* - Never when the hook is already installed. Telling someone to do what
|
|
46
|
+
* they have already done is how a tool gets muted.
|
|
47
|
+
* - One line, no config block. A terminal report is not documentation, and
|
|
48
|
+
* pasting JSON into it would bury the findings it sits under.
|
|
49
|
+
*/
|
|
50
|
+
export function gateOffer(state) {
|
|
51
|
+
if (state.failures === 0)
|
|
52
|
+
return null;
|
|
53
|
+
if (state.hookInstalled)
|
|
54
|
+
return null;
|
|
55
|
+
const n = state.failures;
|
|
56
|
+
return (`This report is after the fact. \`rulereceipt hook\` runs as a Claude Code Stop hook and\n` +
|
|
57
|
+
`refuses to let a session end on ${n === 1 ? "a broken rule" : "a broken rule"} — see the README for the four lines to add.`);
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulereceipt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
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",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"license": "SEE LICENSE IN LICENSE",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@anthropic-ai/sdk": "~0.
|
|
49
|
+
"@anthropic-ai/sdk": "~0.126.0",
|
|
50
50
|
"commander": "~15.0.0",
|
|
51
51
|
"nodemailer": "~10.0.1"
|
|
52
52
|
},
|