coderifts 4.1.0 → 4.1.1
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/CHANGELOG.md +17 -0
- package/dist/cli.js +94 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.1.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **P0-2: `mapDecisionSeverity` never maps missing `execution_action` to ALLOW.**
|
|
8
|
+
Missing EA in any body shape → `INDETERMINATE` (exit 2). Legacy decision→severity
|
|
9
|
+
map is allowed only when `decision_spec_version === "1.0"` on a non-v2 body
|
|
10
|
+
(no `decision_result`, no `preflight_mode`). Top-level-only `{decision:"ALLOW"}`
|
|
11
|
+
without that pin is not permission.
|
|
12
|
+
- **P0-3: parse-gap is fail-closed by default.** Unparseable stdin and missing
|
|
13
|
+
`tool_input.file_path` → **exit 2**, stderr
|
|
14
|
+
`unparseable input — refusing (fail-closed); set CODERIFTS_ADVISORY=1 to soften`,
|
|
15
|
+
JSONL `hook_blocked` with `cause: stdin_unparseable|missing_file_path`.
|
|
16
|
+
`CODERIFTS_ADVISORY=1` restores exit 0. API-key-before-parse order is unchanged
|
|
17
|
+
(garbage stdin + no key → `missing_api_key`). Non-spec Write stays exit 0.
|
|
18
|
+
Unknown-tool-on-spec was already exit 2.
|
|
19
|
+
|
|
3
20
|
## 4.1.0
|
|
4
21
|
|
|
5
22
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -3007,7 +3007,7 @@ var require_package = __commonJS({
|
|
|
3007
3007
|
"package.json"(exports2, module2) {
|
|
3008
3008
|
module2.exports = {
|
|
3009
3009
|
name: "coderifts",
|
|
3010
|
-
version: "4.1.
|
|
3010
|
+
version: "4.1.1",
|
|
3011
3011
|
description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
|
|
3012
3012
|
author: "CodeRifts <hello@coderifts.com>",
|
|
3013
3013
|
license: "MIT",
|
|
@@ -101231,8 +101231,10 @@ var require_claude_hook = __commonJS({
|
|
|
101231
101231
|
" no API key / API unreachable / disk unreadable / edit-apply failure on the spec path",
|
|
101232
101232
|
" Absence of a key is not permission. Explicit opt-out: CODERIFTS_ADVISORY=1|true.",
|
|
101233
101233
|
"",
|
|
101234
|
-
"
|
|
101235
|
-
"
|
|
101234
|
+
"Parse-gap DEFAULT (exit 2): unparseable stdin / missing file_path \u2014 refusing (fail-closed);",
|
|
101235
|
+
" set CODERIFTS_ADVISORY=1 to soften. JSONL hook_blocked cause stdin_unparseable|missing_file_path.",
|
|
101236
|
+
"",
|
|
101237
|
+
"Still exit 0 (nothing to govern): non-spec path, identical content.",
|
|
101236
101238
|
"",
|
|
101237
101239
|
"CONTINUE_WITH_MONITORING: allow only if the host asserts a sink",
|
|
101238
101240
|
" (CODERIFTS_MONITORING_SINK_WIRED=1|true or git config coderifts.monitoringSinkWired).",
|
|
@@ -101431,13 +101433,22 @@ var require_claude_hook = __commonJS({
|
|
|
101431
101433
|
}
|
|
101432
101434
|
return { ok: false, reason: `unsupported tool_name for content derive: ${name}` };
|
|
101433
101435
|
}
|
|
101436
|
+
function isV2DecisionBody(d, dr) {
|
|
101437
|
+
if (dr && typeof dr === "object") return true;
|
|
101438
|
+
if (d.preflight_mode != null && d.preflight_mode !== "") return true;
|
|
101439
|
+
const ver = d.decision_spec_version;
|
|
101440
|
+
return typeof ver === "string" && ver.startsWith("2.");
|
|
101441
|
+
}
|
|
101442
|
+
function allowLegacyDecisionMap(d, dr) {
|
|
101443
|
+
return d.decision_spec_version === "1.0" && !isV2DecisionBody(d, dr);
|
|
101444
|
+
}
|
|
101434
101445
|
function mapDecisionSeverity(result) {
|
|
101435
101446
|
const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
101436
101447
|
let ea = null;
|
|
101437
101448
|
const dr = d.decision_result;
|
|
101438
|
-
if (dr && typeof dr === "object" && typeof dr.execution_action === "string") {
|
|
101449
|
+
if (dr && typeof dr === "object" && typeof dr.execution_action === "string" && dr.execution_action !== "") {
|
|
101439
101450
|
ea = dr.execution_action;
|
|
101440
|
-
} else if (typeof d.execution_action === "string") {
|
|
101451
|
+
} else if (typeof d.execution_action === "string" && d.execution_action !== "") {
|
|
101441
101452
|
ea = d.execution_action;
|
|
101442
101453
|
}
|
|
101443
101454
|
const hasDecision = d.omega_decision != null && d.omega_decision !== "" || d.decision != null && d.decision !== "" || dr && typeof dr === "object" && dr.execution_action;
|
|
@@ -101462,7 +101473,7 @@ var require_claude_hook = __commonJS({
|
|
|
101462
101473
|
} else {
|
|
101463
101474
|
severity = "REQUIRE_APPROVAL";
|
|
101464
101475
|
}
|
|
101465
|
-
} else {
|
|
101476
|
+
} else if (allowLegacyDecisionMap(d, dr)) {
|
|
101466
101477
|
const od = d.omega_decision || d.decision;
|
|
101467
101478
|
if (od == null || od === "") {
|
|
101468
101479
|
severity = "INDETERMINATE";
|
|
@@ -101470,6 +101481,8 @@ var require_claude_hook = __commonJS({
|
|
|
101470
101481
|
else if (od === "REQUIRE_APPROVAL") severity = "REQUIRE_APPROVAL";
|
|
101471
101482
|
else if (od === "WARN") severity = "WARN";
|
|
101472
101483
|
else severity = "ALLOW";
|
|
101484
|
+
} else {
|
|
101485
|
+
severity = "INDETERMINATE";
|
|
101473
101486
|
}
|
|
101474
101487
|
const decision = dr && dr.decision || d.decision || d.omega_decision || null;
|
|
101475
101488
|
const decisionId = dr && dr.decision_id || d.decision_id || null;
|
|
@@ -101480,6 +101493,45 @@ var require_claude_hook = __commonJS({
|
|
|
101480
101493
|
executionAction: ea
|
|
101481
101494
|
};
|
|
101482
101495
|
}
|
|
101496
|
+
function renderDecisionWhy(result, opts = {}) {
|
|
101497
|
+
const maxFixes = Number.isInteger(opts.maxFixes) ? opts.maxFixes : 5;
|
|
101498
|
+
const maxLines = Number.isInteger(opts.maxLines) ? opts.maxLines : 13;
|
|
101499
|
+
const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
101500
|
+
const dr = d.decision_result && typeof d.decision_result === "object" ? d.decision_result : {};
|
|
101501
|
+
const pick = (k) => dr[k] !== void 0 ? dr[k] : d[k];
|
|
101502
|
+
const arr = (v) => Array.isArray(v) ? v : [];
|
|
101503
|
+
const str = (v) => typeof v === "string" ? v.trim() : "";
|
|
101504
|
+
const lines = [];
|
|
101505
|
+
const reasonCodes = [];
|
|
101506
|
+
for (const r of arr(pick("blocking_reasons"))) {
|
|
101507
|
+
if (!r || typeof r !== "object") continue;
|
|
101508
|
+
const code = str(r.code);
|
|
101509
|
+
const message = str(r.message);
|
|
101510
|
+
if (code) reasonCodes.push(code);
|
|
101511
|
+
if (!code && !message) continue;
|
|
101512
|
+
lines.push(`- ${code || "REASON"}${message ? `: ${message}` : ""}`);
|
|
101513
|
+
}
|
|
101514
|
+
for (const r of arr(pick("degraded_reasons"))) {
|
|
101515
|
+
if (!r || typeof r !== "object") continue;
|
|
101516
|
+
const code = str(r.code);
|
|
101517
|
+
const message = str(r.message);
|
|
101518
|
+
if (!code && !message) continue;
|
|
101519
|
+
lines.push(`- degraded: ${[code, message].filter(Boolean).join(": ")}`);
|
|
101520
|
+
}
|
|
101521
|
+
const action = str(pick("required_action"));
|
|
101522
|
+
if (action) lines.push(`- action: ${action}`);
|
|
101523
|
+
const rt = pick("remediation_transaction");
|
|
101524
|
+
const changes = arr(rt && typeof rt === "object" ? rt.required_changes : null).filter((c) => c && typeof c === "object");
|
|
101525
|
+
const shown = changes.slice(0, maxFixes);
|
|
101526
|
+
for (const c of shown) {
|
|
101527
|
+
const target = str(c.target);
|
|
101528
|
+
const instruction = str(c.instruction);
|
|
101529
|
+
if (!instruction && !target) continue;
|
|
101530
|
+
lines.push(`- fix${target ? ` (${target})` : ""}: ${instruction || str(c.precise_label)}`);
|
|
101531
|
+
}
|
|
101532
|
+
if (changes.length > shown.length) lines.push(`- fix: +${changes.length - shown.length} more`);
|
|
101533
|
+
return { lines: lines.slice(0, maxLines), reasonCodes };
|
|
101534
|
+
}
|
|
101483
101535
|
async function runClaudeHook(options = {}, deps = {}) {
|
|
101484
101536
|
const errLog = deps.errLog || ((m) => console.error(String(m)));
|
|
101485
101537
|
const readStdin = deps.readStdin || (() => {
|
|
@@ -101515,7 +101567,14 @@ var require_claude_hook = __commonJS({
|
|
|
101515
101567
|
decisionId: r.decisionId != null ? r.decisionId : void 0
|
|
101516
101568
|
};
|
|
101517
101569
|
if (r.exitCode === 2) {
|
|
101518
|
-
|
|
101570
|
+
const reasons = Array.isArray(r.reasons) && r.reasons.length ? r.reasons : void 0;
|
|
101571
|
+
logEv({
|
|
101572
|
+
type: "hook_blocked",
|
|
101573
|
+
exit: 2,
|
|
101574
|
+
...extra,
|
|
101575
|
+
reasons,
|
|
101576
|
+
cause: r.reason || r.site
|
|
101577
|
+
});
|
|
101519
101578
|
} else if (r.exitCode === 0 && r.site && advisory && !strict) {
|
|
101520
101579
|
logEv({ type: "hook_advisory_passthrough", exit: 0, decision: r.decision || null, ...extra, cause: r.site });
|
|
101521
101580
|
} else {
|
|
@@ -101535,20 +101594,27 @@ var require_claude_hook = __commonJS({
|
|
|
101535
101594
|
errLog
|
|
101536
101595
|
}));
|
|
101537
101596
|
}
|
|
101597
|
+
const PARSE_GAP_STDERR = "unparseable input \u2014 refusing (fail-closed); set CODERIFTS_ADVISORY=1 to soften";
|
|
101538
101598
|
const raw = typeof options.stdin === "string" ? options.stdin : readStdin();
|
|
101539
101599
|
const parsed = parseStdinJson(raw);
|
|
101540
101600
|
if (!parsed.ok) {
|
|
101541
|
-
|
|
101542
|
-
|
|
101543
|
-
|
|
101601
|
+
if (advisory && !strict) {
|
|
101602
|
+
errLog(`CodeRifts claude-hook: ${parsed.reason} \u2014 allowing (CODERIFTS_ADVISORY)`);
|
|
101603
|
+
return emitTerminal({ exitCode: 0, reason: "stdin_unparseable", site: "stdin_unparseable" });
|
|
101604
|
+
}
|
|
101605
|
+
errLog(`CodeRifts claude-hook: ${PARSE_GAP_STDERR}`);
|
|
101606
|
+
return emitTerminal({ exitCode: 2, reason: "stdin_unparseable" });
|
|
101544
101607
|
}
|
|
101545
101608
|
const { toolName, toolInput } = parsed;
|
|
101546
101609
|
tool = toolName;
|
|
101547
101610
|
const filePath = toolInput.file_path || toolInput.filePath || toolInput.path;
|
|
101548
101611
|
if (!filePath || typeof filePath !== "string") {
|
|
101549
|
-
|
|
101550
|
-
|
|
101551
|
-
|
|
101612
|
+
if (advisory && !strict) {
|
|
101613
|
+
errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (CODERIFTS_ADVISORY)");
|
|
101614
|
+
return emitTerminal({ exitCode: 0, reason: "missing_file_path", site: "missing_file_path" });
|
|
101615
|
+
}
|
|
101616
|
+
errLog(`CodeRifts claude-hook: ${PARSE_GAP_STDERR}`);
|
|
101617
|
+
return emitTerminal({ exitCode: 2, reason: "missing_file_path" });
|
|
101552
101618
|
}
|
|
101553
101619
|
filePathKnown = filePath;
|
|
101554
101620
|
const specPath = resolveSpecPath({ ...deps, cwd });
|
|
@@ -101635,27 +101701,37 @@ var require_claude_hook = __commonJS({
|
|
|
101635
101701
|
});
|
|
101636
101702
|
}
|
|
101637
101703
|
if (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN") {
|
|
101704
|
+
const why = renderDecisionWhy(result);
|
|
101638
101705
|
errLog(
|
|
101639
|
-
|
|
101706
|
+
[
|
|
101707
|
+
`CodeRifts claude-hook: BLOCKED${decPart}${eaPart}${idPart}` + (mapped.severity === "UNKNOWN" ? " (unrecognised execution_action)" : ""),
|
|
101708
|
+
...why.lines
|
|
101709
|
+
].join("\n")
|
|
101640
101710
|
);
|
|
101641
101711
|
return emitTerminal({
|
|
101642
101712
|
exitCode: 2,
|
|
101643
101713
|
reason: mapped.severity === "UNKNOWN" ? "unknown_action" : "block",
|
|
101644
101714
|
severity: mapped.severity,
|
|
101645
101715
|
decision: mapped.decision,
|
|
101646
|
-
decisionId: mapped.decisionId
|
|
101716
|
+
decisionId: mapped.decisionId,
|
|
101717
|
+
reasons: why.reasonCodes
|
|
101647
101718
|
});
|
|
101648
101719
|
}
|
|
101649
101720
|
if (mapped.severity === "REQUIRE_APPROVAL") {
|
|
101721
|
+
const why = renderDecisionWhy(result);
|
|
101650
101722
|
errLog(
|
|
101651
|
-
|
|
101723
|
+
[
|
|
101724
|
+
`CodeRifts claude-hook: BLOCKED approval_required${decPart}${eaPart}${idPart}`,
|
|
101725
|
+
...why.lines
|
|
101726
|
+
].join("\n")
|
|
101652
101727
|
);
|
|
101653
101728
|
return emitTerminal({
|
|
101654
101729
|
exitCode: 2,
|
|
101655
101730
|
reason: "approval_required",
|
|
101656
101731
|
severity: mapped.severity,
|
|
101657
101732
|
decision: mapped.decision,
|
|
101658
|
-
decisionId: mapped.decisionId
|
|
101733
|
+
decisionId: mapped.decisionId,
|
|
101734
|
+
reasons: why.reasonCodes
|
|
101659
101735
|
});
|
|
101660
101736
|
}
|
|
101661
101737
|
if (mapped.severity === "MONITOR" || mapped.severity === "WARN") {
|
|
@@ -101694,6 +101770,7 @@ var require_claude_hook = __commonJS({
|
|
|
101694
101770
|
isSpecPath,
|
|
101695
101771
|
deriveAfterContent,
|
|
101696
101772
|
mapDecisionSeverity,
|
|
101773
|
+
renderDecisionWhy,
|
|
101697
101774
|
resolveApiKey,
|
|
101698
101775
|
resolveSpecPath,
|
|
101699
101776
|
readGitConfig,
|
package/package.json
CHANGED