rulereceipt 0.1.85 → 0.1.87
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 +9 -0
- package/dist/cli.js +8 -0
- package/dist/guard.d.ts +11 -0
- package/dist/guard.js +43 -2
- package/dist/teamPlan.d.ts +27 -0
- package/dist/teamPlan.js +42 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -538,6 +538,15 @@ rm -rf .rulereceipt/ # the local reports/receipts folder, if you want
|
|
|
538
538
|
`protect --undo` is only needed if you ran `protect`. Nothing else is
|
|
539
539
|
installed anywhere on your system.
|
|
540
540
|
|
|
541
|
+
## Team plan
|
|
542
|
+
|
|
543
|
+
The CLI is free and runs locally, forever. A separate **Team plan** — an
|
|
544
|
+
org-wide, hosted view with trends over time, history, cross-repo dashboards and
|
|
545
|
+
compliance-ready exports — is a genuinely different product from the local
|
|
546
|
+
check. It's in **early access** while we finish setting it up; email
|
|
547
|
+
hello@rulereceipt.dev if you want it early. The local check (and the free
|
|
548
|
+
`rulereceipt team <folder>` snapshot of exports you already have) stays free.
|
|
549
|
+
|
|
541
550
|
## Contact
|
|
542
551
|
|
|
543
552
|
Questions, bugs, or anything else — hello@rulereceipt.dev.
|
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ import { runGuard } from "./guard.js";
|
|
|
32
32
|
import { generateReport, generateMarkdownReport, generateJsonReport, computeTranscriptHash } from "./report/generateReport.js";
|
|
33
33
|
import { buildTeamExport, parseExport, mergeTeamExports, renderTeamHtml } from "./teamExport.js";
|
|
34
34
|
import { applyVisibility } from "./visibility.js";
|
|
35
|
+
import { teamPlanNote, activateNote } from "./teamPlan.js";
|
|
35
36
|
import { gateOffer, hookIsInstalled } from "./report/gateOffer.js";
|
|
36
37
|
import { generateHtmlReport } from "./report/generateHtmlReport.js";
|
|
37
38
|
import { verifySessionHash } from "./verifyHash.js";
|
|
@@ -1411,6 +1412,13 @@ program
|
|
|
1411
1412
|
const outPath = opts.out ? resolve(process.cwd(), opts.out) : join(dir, "team-report.html");
|
|
1412
1413
|
writeFileSync(outPath, renderTeamHtml(merged));
|
|
1413
1414
|
console.log(`team preview: merged ${merged.exportsRead} export(s) from ${merged.devs.length} dev(s), ${merged.totalBroken} break(s) — wrote ${outPath}`);
|
|
1415
|
+
console.log(`\n${teamPlanNote()}`);
|
|
1416
|
+
});
|
|
1417
|
+
program
|
|
1418
|
+
.command("activate <key>")
|
|
1419
|
+
.description("activate a Team plan seat. (Early access while the paid tier is being set up.)")
|
|
1420
|
+
.action((key) => {
|
|
1421
|
+
console.log(activateNote(key));
|
|
1414
1422
|
});
|
|
1415
1423
|
// Bare `rulereceipt` (no subcommand, no flags) runs history mode — the first-run
|
|
1416
1424
|
// "wait, what?" screen across the last 30 days of sessions. Anything with a
|
package/dist/guard.d.ts
CHANGED
|
@@ -74,5 +74,16 @@ export interface GuardDecision {
|
|
|
74
74
|
export declare function guardDecision(cwd: string, toolName: string, toolInput: {
|
|
75
75
|
command?: unknown;
|
|
76
76
|
} & Record<string, unknown>, events?: TranscriptEvent[], permissionMode?: string): GuardDecision;
|
|
77
|
+
/** One line for the guard receipt log — every block (or would-block) is recorded. */
|
|
78
|
+
export declare function guardReceiptLine(decision: GuardDecision, command: string, shadow: boolean): {
|
|
79
|
+
ts: string;
|
|
80
|
+
mode: "shadow" | "enforce";
|
|
81
|
+
action: "deny" | "would-deny" | "ask" | "would-ask";
|
|
82
|
+
command: string;
|
|
83
|
+
rules: {
|
|
84
|
+
id: string;
|
|
85
|
+
title: string;
|
|
86
|
+
}[];
|
|
87
|
+
};
|
|
77
88
|
export declare function runGuard(): Promise<void>;
|
|
78
89
|
export {};
|
package/dist/guard.js
CHANGED
|
@@ -25,9 +25,9 @@ function gitCurrentBranch(cwd) {
|
|
|
25
25
|
return undefined;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
import { readFileSync } from "node:fs";
|
|
28
|
+
import { readFileSync, appendFileSync, mkdirSync } from "node:fs";
|
|
29
29
|
import { homedir } from "node:os";
|
|
30
|
-
import { join } from "node:path";
|
|
30
|
+
import { join, dirname } from "node:path";
|
|
31
31
|
/**
|
|
32
32
|
* Modes where Claude Code shows NO permission prompt, so a hook's "ask" is
|
|
33
33
|
* ignored and the call just runs (Claude Code #89561; "ask" also drops bypass
|
|
@@ -301,6 +301,38 @@ export function guardDecision(cwd, toolName, toolInput, events = [], permissionM
|
|
|
301
301
|
}
|
|
302
302
|
return allow;
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Shadow mode (live-blocking rollout, vs Failproof): set RULERECEIPT_GUARD_SHADOW=1
|
|
306
|
+
* and the guard never blocks — it LOGS what it WOULD have blocked and allows the
|
|
307
|
+
* call. The honest way to turn on enforcement: run shadow first, read the log,
|
|
308
|
+
* then drop the flag. Enforcement is the default (no env needed).
|
|
309
|
+
*/
|
|
310
|
+
function guardShadowEnabled() {
|
|
311
|
+
const v = process.env.RULERECEIPT_GUARD_SHADOW;
|
|
312
|
+
return v === "1" || v === "true" || v === "yes";
|
|
313
|
+
}
|
|
314
|
+
/** One line for the guard receipt log — every block (or would-block) is recorded. */
|
|
315
|
+
export function guardReceiptLine(decision, command, shadow) {
|
|
316
|
+
const base = decision.deny ? "deny" : "ask";
|
|
317
|
+
return {
|
|
318
|
+
ts: new Date().toISOString(),
|
|
319
|
+
mode: shadow ? "shadow" : "enforce",
|
|
320
|
+
action: (shadow ? `would-${base}` : base),
|
|
321
|
+
command: command.replace(/\s+/g, " ").trim().slice(0, 120),
|
|
322
|
+
rules: decision.blocks.map((b) => ({ id: b.rule.id, title: b.rule.title })),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/** Append a receipt line to .rulereceipt/guard-log.jsonl. Fail-open: never breaks the guard. */
|
|
326
|
+
function writeGuardReceipt(cwd, line) {
|
|
327
|
+
try {
|
|
328
|
+
const p = join(cwd, ".rulereceipt", "guard-log.jsonl");
|
|
329
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
330
|
+
appendFileSync(p, `${JSON.stringify(line)}\n`);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
/* logging must never stop (or crash) the guard */
|
|
334
|
+
}
|
|
335
|
+
}
|
|
304
336
|
export async function runGuard() {
|
|
305
337
|
const allow = () => {
|
|
306
338
|
process.stdout.write(JSON.stringify({}));
|
|
@@ -321,6 +353,15 @@ export async function runGuard() {
|
|
|
321
353
|
}
|
|
322
354
|
}
|
|
323
355
|
const decision = guardDecision(cwd, tool, toolInput, events, input.permission_mode);
|
|
356
|
+
const shadow = guardShadowEnabled();
|
|
357
|
+
const commandStr = typeof toolInput.command === "string" ? toolInput.command : tool;
|
|
358
|
+
// Every block (or would-block) writes a receipt line — the audit trail of
|
|
359
|
+
// what the guard stopped, in both shadow and enforce mode.
|
|
360
|
+
if (decision.deny || decision.ask)
|
|
361
|
+
writeGuardReceipt(cwd, guardReceiptLine(decision, commandStr, shadow));
|
|
362
|
+
// Shadow mode: log above, then ALLOW — never block, never ask.
|
|
363
|
+
if (shadow)
|
|
364
|
+
return allow();
|
|
324
365
|
if (!decision.deny && decision.ask) {
|
|
325
366
|
// "ask" is not a refusal: no exit 2. Claude Code shows its permission
|
|
326
367
|
// prompt with this reason; the user's click decides.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Team plan — the ONE place the checkout link and its feature flag live, read by
|
|
3
|
+
* the CLI (the landing pages mirror these exact values; keep them in sync).
|
|
4
|
+
*
|
|
5
|
+
* OPEN-CORE: this file holds only a LINK, a flag, and copy — never any paid-tier
|
|
6
|
+
* LOGIC. Real licence/key validation and the hosted team service live in the
|
|
7
|
+
* separate PRIVATE repo. `activate` here does not validate or unlock anything;
|
|
8
|
+
* while the flag is off it only points at early access.
|
|
9
|
+
*
|
|
10
|
+
* TEAM_CHECKOUT_LIVE stays `false` until Polar setup is done and a test purchase
|
|
11
|
+
* works. While false, every surface shows "early access, contact …" and no
|
|
12
|
+
* checkout/activate link is shown. Flip the flag AND fill `checkoutUrl`/
|
|
13
|
+
* `portalUrl` together (and mirror them on the landing pages), then rebuild.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TEAM_PLAN: {
|
|
16
|
+
readonly live: false;
|
|
17
|
+
/** Polar checkout URL — filled in when going live. Empty while not live. */
|
|
18
|
+
readonly checkoutUrl: "";
|
|
19
|
+
/** Polar customer portal (manage subscription) — shown on /thanks when live. */
|
|
20
|
+
readonly portalUrl: "";
|
|
21
|
+
readonly contact: "hello@rulereceipt.dev";
|
|
22
|
+
readonly trialDays: 14;
|
|
23
|
+
};
|
|
24
|
+
/** The one-line Team-plan note for CLI output, flag-aware. */
|
|
25
|
+
export declare function teamPlanNote(): string;
|
|
26
|
+
/** What `rulereceipt activate <key>` prints. No validation here — that is the private tier. */
|
|
27
|
+
export declare function activateNote(key: string): string;
|
package/dist/teamPlan.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Team plan — the ONE place the checkout link and its feature flag live, read by
|
|
3
|
+
* the CLI (the landing pages mirror these exact values; keep them in sync).
|
|
4
|
+
*
|
|
5
|
+
* OPEN-CORE: this file holds only a LINK, a flag, and copy — never any paid-tier
|
|
6
|
+
* LOGIC. Real licence/key validation and the hosted team service live in the
|
|
7
|
+
* separate PRIVATE repo. `activate` here does not validate or unlock anything;
|
|
8
|
+
* while the flag is off it only points at early access.
|
|
9
|
+
*
|
|
10
|
+
* TEAM_CHECKOUT_LIVE stays `false` until Polar setup is done and a test purchase
|
|
11
|
+
* works. While false, every surface shows "early access, contact …" and no
|
|
12
|
+
* checkout/activate link is shown. Flip the flag AND fill `checkoutUrl`/
|
|
13
|
+
* `portalUrl` together (and mirror them on the landing pages), then rebuild.
|
|
14
|
+
*/
|
|
15
|
+
export const TEAM_PLAN = {
|
|
16
|
+
live: false,
|
|
17
|
+
/** Polar checkout URL — filled in when going live. Empty while not live. */
|
|
18
|
+
checkoutUrl: "",
|
|
19
|
+
/** Polar customer portal (manage subscription) — shown on /thanks when live. */
|
|
20
|
+
portalUrl: "",
|
|
21
|
+
contact: "hello@rulereceipt.dev",
|
|
22
|
+
trialDays: 14,
|
|
23
|
+
};
|
|
24
|
+
/** The one-line Team-plan note for CLI output, flag-aware. */
|
|
25
|
+
export function teamPlanNote() {
|
|
26
|
+
if (TEAM_PLAN.live && TEAM_PLAN.checkoutUrl) {
|
|
27
|
+
return (`Team plan (trends over time, history, cross-repo dashboards): ` +
|
|
28
|
+
`start a ${TEAM_PLAN.trialDays}-day free trial — ${TEAM_PLAN.checkoutUrl}\n` +
|
|
29
|
+
`Already bought a seat? Run \`rulereceipt activate <key>\`.`);
|
|
30
|
+
}
|
|
31
|
+
return `Team plan (trends over time, history, cross-repo dashboards): early access — contact ${TEAM_PLAN.contact}.`;
|
|
32
|
+
}
|
|
33
|
+
/** What `rulereceipt activate <key>` prints. No validation here — that is the private tier. */
|
|
34
|
+
export function activateNote(key) {
|
|
35
|
+
const masked = key.length > 6 ? `${key.slice(0, 3)}…${key.slice(-2)}` : "(key)";
|
|
36
|
+
if (!TEAM_PLAN.live) {
|
|
37
|
+
return `Team plan is in early access — nothing to activate yet. Contact ${TEAM_PLAN.contact} and we'll set you up.`;
|
|
38
|
+
}
|
|
39
|
+
return (`Thanks for subscribing. Key ${masked} noted.\n` +
|
|
40
|
+
`Activation and the hosted team features are handled by the Team service (see rulereceipt.dev/thanks).\n` +
|
|
41
|
+
`Manage your subscription: ${TEAM_PLAN.portalUrl || TEAM_PLAN.contact}`);
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulereceipt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.87",
|
|
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",
|