coderifts 4.0.0 → 4.1.0
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 +24 -0
- package/README.md +12 -1
- package/dist/cli.js +133 -29
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.1.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`coderifts claude-hook` JSONL GuardEvent log.** When `CODERIFTS_GUARD_EVENT_LOG` is
|
|
8
|
+
set to an absolute file path, the hook appends one JSON object per line:
|
|
9
|
+
`preflight_start` / `preflight_result` / `preflight_unavailable` / `detection_skip`
|
|
10
|
+
plus terminal `hook_blocked` (exit 2) / `hook_continue` (exit 0) /
|
|
11
|
+
`hook_advisory_passthrough` (ADVISORY exit 0). Extra fields: `tool`, `path`,
|
|
12
|
+
`trigger_source:"claude_hook"`, `fingerprint` when the authorize envelope
|
|
13
|
+
returns one, `sessionId` (`CODERIFTS_SESSION_ID` or per-process). Unset env
|
|
14
|
+
→ no file. Log write errors are swallowed and printed to stderr; **exit
|
|
15
|
+
codes are unchanged**. The hook does not claim `enforced` (no
|
|
16
|
+
receipt-verified execution path).
|
|
17
|
+
|
|
3
18
|
## 4.0.0
|
|
4
19
|
|
|
5
20
|
**Breaking** — `coderifts claude-hook` is fail-closed by default. The CLI now
|
|
@@ -28,6 +43,15 @@ depends on `@coderifts/agent-guard` ^8.1.1.
|
|
|
28
43
|
(never widen).
|
|
29
44
|
- **`MIN_GUARD_MAJOR`:** 6 → 8 (`scripts/assert-guard-major.js`).
|
|
30
45
|
|
|
46
|
+
### Added
|
|
47
|
+
- **`coderifts agent-setup` writes `.claude/settings.json`.** PreToolUse hook
|
|
48
|
+
(`matcher: Write|Edit|MultiEdit` → `coderifts claude-hook`, timeout 60)
|
|
49
|
+
beside the six rule files. Fail-closed default (P0-3/P0-4). Explicit
|
|
50
|
+
opt-out documented on stdout: `CODERIFTS_ADVISORY=1`. `--check` is
|
|
51
|
+
byte-stable against the embed (same drift-gate as the six rule files).
|
|
52
|
+
Existing `.claude/settings.json` is skipped (no merge); `--force`
|
|
53
|
+
replaces the **entire** file.
|
|
54
|
+
|
|
31
55
|
### Fixed
|
|
32
56
|
- **Installer copy (P0 #9 / e76e846):** `agent-setup` / `init` teach
|
|
33
57
|
guard@8 execution-state **DEFAULT ON**, not the old OFF.
|
package/README.md
CHANGED
|
@@ -301,7 +301,8 @@ coderifts enforce --repo owner/repo --apply
|
|
|
301
301
|
|
|
302
302
|
### Agent host files (`coderifts agent-setup`)
|
|
303
303
|
|
|
304
|
-
Writes the six CodeRifts agent-host rule files
|
|
304
|
+
Writes the six CodeRifts agent-host rule files **plus** the Claude Code
|
|
305
|
+
PreToolUse hook settings into the current repo (or `--out <dir>`):
|
|
305
306
|
|
|
306
307
|
| File | Host |
|
|
307
308
|
|------|------|
|
|
@@ -311,6 +312,16 @@ Writes the six CodeRifts agent-host rule files into the current repo (or `--out
|
|
|
311
312
|
| `.github/copilot-instructions.md` | GitHub Copilot |
|
|
312
313
|
| `coderifts-langgraph-policy.js` | LangGraph |
|
|
313
314
|
| `openai-agent-instructions.md` | OpenAI Agents SDK |
|
|
315
|
+
| `.claude/settings.json` | Claude Code PreToolUse → `coderifts claude-hook` (fail-closed) |
|
|
316
|
+
|
|
317
|
+
The generated `.claude/settings.json` matches the recipe above (`matcher`
|
|
318
|
+
`Write|Edit|MultiEdit`, `command` `coderifts claude-hook`, `timeout` 60).
|
|
319
|
+
Default is **fail-closed** (missing key / API down → exit 2). Explicit
|
|
320
|
+
opt-out: `CODERIFTS_ADVISORY=1|true`. `CODERIFTS_STRICT=1` cannot be
|
|
321
|
+
weakened by ADVISORY. Existing files are skipped unless `--force`.
|
|
322
|
+
`--force` **replaces the entire** `.claude/settings.json` (no merge into
|
|
323
|
+
permissions / other hooks). If the file already exists, add the PreToolUse
|
|
324
|
+
recipe by hand or accept a full replace.
|
|
314
325
|
|
|
315
326
|
```bash
|
|
316
327
|
coderifts agent-setup # write (skip existing files)
|
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.
|
|
3010
|
+
version: "4.1.0",
|
|
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",
|
|
@@ -100059,7 +100059,8 @@ var require_agent_host_files_embedded = __commonJS({
|
|
|
100059
100059
|
".cursor/rules/coderifts.mdc",
|
|
100060
100060
|
".github/copilot-instructions.md",
|
|
100061
100061
|
"coderifts-langgraph-policy.js",
|
|
100062
|
-
"openai-agent-instructions.md"
|
|
100062
|
+
"openai-agent-instructions.md",
|
|
100063
|
+
".claude/settings.json"
|
|
100063
100064
|
]);
|
|
100064
100065
|
var AGENT_HOST_FILES = Object.freeze({
|
|
100065
100066
|
"AGENTS.md": '<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts agent rules\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n',
|
|
@@ -100067,7 +100068,8 @@ var require_agent_host_files_embedded = __commonJS({
|
|
|
100067
100068
|
".cursor/rules/coderifts.mdc": '---\ndescription: CodeRifts API governance \u2014 when to preflight and how to branch\nglobs:\nalwaysApply: true\n---\n\n<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n',
|
|
100068
100069
|
".github/copilot-instructions.md": '<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts instructions for GitHub Copilot\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n',
|
|
100069
100070
|
"coderifts-langgraph-policy.js": '// GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js\n// System policy string for a LangGraph (or similar) agent. Content is generated;\n// identical rule sentences to AGENTS.md / other formats.\n\'use strict\';\n\nmodule.exports = "Call `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\\n\\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\\n\\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\\n\\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\\n\\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not \\"proceed with caution\\" without monitoring.\\n\\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\\n\\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\\n\\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\\n\\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\\n\\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\\n\\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.";\n',
|
|
100070
|
-
"openai-agent-instructions.md": '<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts agent instructions\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n'
|
|
100071
|
+
"openai-agent-instructions.md": '<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts agent instructions\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n',
|
|
100072
|
+
".claude/settings.json": '{\n "hooks": {\n "PreToolUse": [\n {\n "matcher": "Write|Edit|MultiEdit",\n "hooks": [\n {\n "type": "command",\n "command": "coderifts claude-hook",\n "timeout": 60\n }\n ]\n }\n ]\n }\n}\n'
|
|
100071
100073
|
});
|
|
100072
100074
|
module2.exports = { AGENT_HOST_FILES, AGENT_HOST_PATHS };
|
|
100073
100075
|
}
|
|
@@ -100207,6 +100209,15 @@ ${USAGE}` };
|
|
|
100207
100209
|
for (const line of AGENT_SETUP_WARN_TELEMETRY_LINES) {
|
|
100208
100210
|
log(line);
|
|
100209
100211
|
}
|
|
100212
|
+
if (summary.skipped.includes(".claude/settings.json")) {
|
|
100213
|
+
for (const line of AGENT_SETUP_HOOK_SKIP_LINES) {
|
|
100214
|
+
log(line);
|
|
100215
|
+
}
|
|
100216
|
+
} else if (summary.written.includes(".claude/settings.json")) {
|
|
100217
|
+
for (const line of AGENT_SETUP_HOOK_WIRE_LINES) {
|
|
100218
|
+
log(line);
|
|
100219
|
+
}
|
|
100220
|
+
}
|
|
100210
100221
|
}
|
|
100211
100222
|
if (doExit) process.exit(0);
|
|
100212
100223
|
return { exitCode: 0, code: "OK", outDir, ...summary };
|
|
@@ -100222,13 +100233,28 @@ ${USAGE}` };
|
|
|
100222
100233
|
chalk.dim(" \xB7 missing artifacts/fingerprint \u2192 execution_state_unmeasurable (quiet)"),
|
|
100223
100234
|
chalk.dim(" Merge gate block is separate: coderifts setup-required-check --apply")
|
|
100224
100235
|
]);
|
|
100236
|
+
var AGENT_SETUP_HOOK_WIRE_LINES = Object.freeze([
|
|
100237
|
+
chalk.bold(" Claude Code hook (.claude/settings.json): fail-closed PreToolUse"),
|
|
100238
|
+
chalk.dim(" matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK)."),
|
|
100239
|
+
chalk.dim(" Missing key / API down / unreadable spec \u2192 exit 2 (enforce_indeterminate), not ALLOW."),
|
|
100240
|
+
chalk.dim(" Opt-out: CODERIFTS_ADVISORY=1|true restores soft-allow when governance could not run."),
|
|
100241
|
+
chalk.dim(" CODERIFTS_STRICT=1 cannot be weakened by ADVISORY.")
|
|
100242
|
+
]);
|
|
100243
|
+
var AGENT_SETUP_HOOK_SKIP_LINES = Object.freeze([
|
|
100244
|
+
chalk.yellow(" .claude/settings.json exists \u2014 left unchanged (installer does not merge)."),
|
|
100245
|
+
chalk.dim(" --force replaces the ENTIRE file (not just the PreToolUse hook)."),
|
|
100246
|
+
chalk.dim(" To wire fail-closed: add PreToolUse \u2192 coderifts claude-hook by hand, or --force."),
|
|
100247
|
+
chalk.dim(" Opt-out if wired: CODERIFTS_ADVISORY=1|true. CODERIFTS_STRICT cannot weaken.")
|
|
100248
|
+
]);
|
|
100225
100249
|
module2.exports = {
|
|
100226
100250
|
runAgentSetup,
|
|
100227
100251
|
parseAgentSetupArgs,
|
|
100228
100252
|
AGENT_HOST_FILES,
|
|
100229
100253
|
AGENT_HOST_PATHS,
|
|
100230
100254
|
USAGE,
|
|
100231
|
-
AGENT_SETUP_WARN_TELEMETRY_LINES
|
|
100255
|
+
AGENT_SETUP_WARN_TELEMETRY_LINES,
|
|
100256
|
+
AGENT_SETUP_HOOK_WIRE_LINES,
|
|
100257
|
+
AGENT_SETUP_HOOK_SKIP_LINES
|
|
100232
100258
|
};
|
|
100233
100259
|
}
|
|
100234
100260
|
});
|
|
@@ -101248,6 +101274,33 @@ var require_claude_hook = __commonJS({
|
|
|
101248
101274
|
if (dr && typeof dr === "object" && dr.monitoringSinkWired === true) return true;
|
|
101249
101275
|
return false;
|
|
101250
101276
|
}
|
|
101277
|
+
var HOOK_TRIGGER_SOURCE = "claude_hook";
|
|
101278
|
+
function extractFingerprint(result) {
|
|
101279
|
+
const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
101280
|
+
const dr = d.decision_result && typeof d.decision_result === "object" ? d.decision_result : {};
|
|
101281
|
+
const fp = dr.fingerprint || d.fingerprint || d.verdict_fingerprint || d.input_fingerprint;
|
|
101282
|
+
return typeof fp === "string" && fp.length > 0 ? fp : void 0;
|
|
101283
|
+
}
|
|
101284
|
+
function hookSessionId(env) {
|
|
101285
|
+
const fromEnv = env && env.CODERIFTS_SESSION_ID && String(env.CODERIFTS_SESSION_ID).trim();
|
|
101286
|
+
if (fromEnv) return fromEnv;
|
|
101287
|
+
if (!hookSessionId._gen) hookSessionId._gen = `hook-${process.pid}-${Date.now()}`;
|
|
101288
|
+
return hookSessionId._gen;
|
|
101289
|
+
}
|
|
101290
|
+
function appendGuardEventLog(env, event, deps = {}) {
|
|
101291
|
+
const p = env && env.CODERIFTS_GUARD_EVENT_LOG;
|
|
101292
|
+
if (p == null || String(p).trim() === "") return;
|
|
101293
|
+
const errLog = deps.errLog || ((m) => console.error(String(m)));
|
|
101294
|
+
const append = deps.appendFileSync || ((file, data) => fs.appendFileSync(file, data));
|
|
101295
|
+
try {
|
|
101296
|
+
append(String(p).trim(), JSON.stringify(event) + "\n");
|
|
101297
|
+
} catch (err) {
|
|
101298
|
+
try {
|
|
101299
|
+
errLog(`CodeRifts claude-hook: CODERIFTS_GUARD_EVENT_LOG write failed (${err && err.message})`);
|
|
101300
|
+
} catch {
|
|
101301
|
+
}
|
|
101302
|
+
}
|
|
101303
|
+
}
|
|
101251
101304
|
function failClosedOrAdvisory({ advisory, strict, site, softMsg, why, errLog }) {
|
|
101252
101305
|
if (advisory && !strict) {
|
|
101253
101306
|
errLog(`${softMsg} (CODERIFTS_ADVISORY)`);
|
|
@@ -101443,32 +101496,65 @@ var require_claude_hook = __commonJS({
|
|
|
101443
101496
|
const env = deps.env || process.env;
|
|
101444
101497
|
const strict = isStrictMode(env);
|
|
101445
101498
|
const advisory = isAdvisoryMode(env);
|
|
101499
|
+
const sessionId = hookSessionId(env);
|
|
101500
|
+
const logEv = (partial) => {
|
|
101501
|
+
appendGuardEventLog(env, {
|
|
101502
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
101503
|
+
sessionId,
|
|
101504
|
+
trigger_source: HOOK_TRIGGER_SOURCE,
|
|
101505
|
+
...partial
|
|
101506
|
+
}, { errLog, appendFileSync: deps.appendFileSync });
|
|
101507
|
+
};
|
|
101508
|
+
let tool = null;
|
|
101509
|
+
let filePathKnown = null;
|
|
101510
|
+
const emitTerminal = (r) => {
|
|
101511
|
+
const extra = {
|
|
101512
|
+
tool: tool || void 0,
|
|
101513
|
+
path: filePathKnown || void 0,
|
|
101514
|
+
decision: r.decision != null ? r.decision : void 0,
|
|
101515
|
+
decisionId: r.decisionId != null ? r.decisionId : void 0
|
|
101516
|
+
};
|
|
101517
|
+
if (r.exitCode === 2) {
|
|
101518
|
+
logEv({ type: "hook_blocked", exit: 2, ...extra, cause: r.reason || r.site });
|
|
101519
|
+
} else if (r.exitCode === 0 && r.site && advisory && !strict) {
|
|
101520
|
+
logEv({ type: "hook_advisory_passthrough", exit: 0, decision: r.decision || null, ...extra, cause: r.site });
|
|
101521
|
+
} else {
|
|
101522
|
+
logEv({ type: "hook_continue", exit: 0, ...extra, cause: r.reason });
|
|
101523
|
+
}
|
|
101524
|
+
return r;
|
|
101525
|
+
};
|
|
101446
101526
|
const apiKey = resolveApiKey({ ...deps, cwd, env });
|
|
101447
101527
|
if (!apiKey) {
|
|
101448
|
-
|
|
101528
|
+
logEv({ type: "preflight_unavailable", cause: "missing_api_key" });
|
|
101529
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
101449
101530
|
advisory,
|
|
101450
101531
|
strict,
|
|
101451
101532
|
site: "missing_api_key",
|
|
101452
101533
|
why: "no API key",
|
|
101453
101534
|
softMsg: "CodeRifts claude-hook: no API key (coderifts login / CODERIFTS_API_KEY / git config coderifts.apiKey) \u2014 allowing",
|
|
101454
101535
|
errLog
|
|
101455
|
-
});
|
|
101536
|
+
}));
|
|
101456
101537
|
}
|
|
101457
101538
|
const raw = typeof options.stdin === "string" ? options.stdin : readStdin();
|
|
101458
101539
|
const parsed = parseStdinJson(raw);
|
|
101459
101540
|
if (!parsed.ok) {
|
|
101460
101541
|
errLog(`CodeRifts claude-hook: ${parsed.reason} \u2014 allowing (soft; never block on parse gap)`);
|
|
101461
|
-
|
|
101542
|
+
logEv({ type: "detection_skip", signals: ["stdin_unparseable"] });
|
|
101543
|
+
return emitTerminal({ exitCode: 0, reason: "stdin_unparseable" });
|
|
101462
101544
|
}
|
|
101463
101545
|
const { toolName, toolInput } = parsed;
|
|
101546
|
+
tool = toolName;
|
|
101464
101547
|
const filePath = toolInput.file_path || toolInput.filePath || toolInput.path;
|
|
101465
101548
|
if (!filePath || typeof filePath !== "string") {
|
|
101466
101549
|
errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (soft)");
|
|
101467
|
-
|
|
101550
|
+
logEv({ type: "detection_skip", signals: ["missing_file_path"], tool });
|
|
101551
|
+
return emitTerminal({ exitCode: 0, reason: "missing_file_path" });
|
|
101468
101552
|
}
|
|
101553
|
+
filePathKnown = filePath;
|
|
101469
101554
|
const specPath = resolveSpecPath({ ...deps, cwd });
|
|
101470
101555
|
if (!isSpecPath(filePath, specPath)) {
|
|
101471
|
-
|
|
101556
|
+
logEv({ type: "detection_skip", signals: ["not_spec_path"], tool, path: filePath });
|
|
101557
|
+
return emitTerminal({ exitCode: 0, reason: "not_spec_path" });
|
|
101472
101558
|
}
|
|
101473
101559
|
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
101474
101560
|
let diskBefore = "";
|
|
@@ -101476,30 +101562,34 @@ var require_claude_hook = __commonJS({
|
|
|
101476
101562
|
try {
|
|
101477
101563
|
diskBefore = readFile(absPath, "utf8");
|
|
101478
101564
|
} catch (e) {
|
|
101479
|
-
|
|
101565
|
+
logEv({ type: "preflight_unavailable", cause: "disk_unreadable", tool, path: filePath });
|
|
101566
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
101480
101567
|
advisory,
|
|
101481
101568
|
strict,
|
|
101482
101569
|
site: "disk_unreadable",
|
|
101483
101570
|
why: `cannot read contract file ${absPath}`,
|
|
101484
101571
|
softMsg: `CodeRifts claude-hook: cannot read ${absPath} \u2014 allowing (soft)`,
|
|
101485
101572
|
errLog
|
|
101486
|
-
});
|
|
101573
|
+
}));
|
|
101487
101574
|
}
|
|
101488
101575
|
}
|
|
101489
101576
|
const derived = deriveAfterContent(toolName, toolInput, diskBefore);
|
|
101490
101577
|
if (!derived.ok) {
|
|
101491
|
-
|
|
101578
|
+
logEv({ type: "preflight_unavailable", cause: "edit_apply_failed", tool, path: filePath });
|
|
101579
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
101492
101580
|
advisory,
|
|
101493
101581
|
strict,
|
|
101494
101582
|
site: "edit_apply_failed",
|
|
101495
101583
|
why: derived.reason,
|
|
101496
101584
|
softMsg: `CodeRifts claude-hook: ${derived.reason} \u2014 allowing (soft; never guess edit apply)`,
|
|
101497
101585
|
errLog
|
|
101498
|
-
});
|
|
101586
|
+
}));
|
|
101499
101587
|
}
|
|
101500
101588
|
if (diskBefore === derived.after) {
|
|
101501
|
-
|
|
101589
|
+
logEv({ type: "detection_skip", signals: ["identical"], tool, path: filePath });
|
|
101590
|
+
return emitTerminal({ exitCode: 0, reason: "identical" });
|
|
101502
101591
|
}
|
|
101592
|
+
logEv({ type: "preflight_start", tool, path: filePath });
|
|
101503
101593
|
let result;
|
|
101504
101594
|
try {
|
|
101505
101595
|
result = await authorize(diskBefore, derived.after, apiKey, {
|
|
@@ -101508,54 +101598,65 @@ var require_claude_hook = __commonJS({
|
|
|
101508
101598
|
});
|
|
101509
101599
|
} catch (e) {
|
|
101510
101600
|
const msg = e && e.message ? String(e.message) : "request failed";
|
|
101511
|
-
|
|
101601
|
+
logEv({ type: "preflight_unavailable", cause: "api_unreachable", tool, path: filePath });
|
|
101602
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
101512
101603
|
advisory,
|
|
101513
101604
|
strict,
|
|
101514
101605
|
site: "api_unreachable",
|
|
101515
101606
|
why: `API unreachable: ${msg}`,
|
|
101516
101607
|
softMsg: `CodeRifts claude-hook: API unreachable (${msg}) \u2014 allowing (soft; availability must not brick the editor)`,
|
|
101517
101608
|
errLog
|
|
101518
|
-
});
|
|
101609
|
+
}));
|
|
101519
101610
|
}
|
|
101520
101611
|
const mapped = mapDecisionSeverity(result);
|
|
101612
|
+
const fingerprint = extractFingerprint(result);
|
|
101521
101613
|
const idPart = mapped.decisionId ? ` decision_id=${mapped.decisionId}` : "";
|
|
101522
101614
|
const decPart = mapped.decision ? ` decision=${mapped.decision}` : "";
|
|
101523
101615
|
const eaPart = mapped.executionAction ? ` execution_action=${mapped.executionAction}` : "";
|
|
101616
|
+
const actionForLog = mapped.executionAction || (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN" || mapped.severity === "INDETERMINATE" ? "STOP" : mapped.severity === "REQUIRE_APPROVAL" ? "REQUEST_APPROVAL" : mapped.severity === "MONITOR" || mapped.severity === "WARN" ? "CONTINUE_WITH_MONITORING" : "CONTINUE");
|
|
101617
|
+
logEv({
|
|
101618
|
+
type: "preflight_result",
|
|
101619
|
+
action: actionForLog,
|
|
101620
|
+
decisionId: mapped.decisionId || void 0,
|
|
101621
|
+
fingerprint,
|
|
101622
|
+
tool,
|
|
101623
|
+
path: filePath
|
|
101624
|
+
});
|
|
101524
101625
|
if (mapped.severity === "INDETERMINATE") {
|
|
101525
101626
|
errLog(
|
|
101526
101627
|
"CodeRifts claude-hook: BLOCKED (indeterminate response \u2014 no execution_action and no decision; not permission)"
|
|
101527
101628
|
);
|
|
101528
|
-
return {
|
|
101629
|
+
return emitTerminal({
|
|
101529
101630
|
exitCode: 2,
|
|
101530
101631
|
reason: "indeterminate",
|
|
101531
101632
|
severity: "INDETERMINATE",
|
|
101532
101633
|
decision: mapped.decision,
|
|
101533
101634
|
decisionId: mapped.decisionId
|
|
101534
|
-
};
|
|
101635
|
+
});
|
|
101535
101636
|
}
|
|
101536
101637
|
if (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN") {
|
|
101537
101638
|
errLog(
|
|
101538
101639
|
`CodeRifts claude-hook: BLOCKED${decPart}${eaPart}${idPart}` + (mapped.severity === "UNKNOWN" ? " (unrecognised execution_action)" : "")
|
|
101539
101640
|
);
|
|
101540
|
-
return {
|
|
101641
|
+
return emitTerminal({
|
|
101541
101642
|
exitCode: 2,
|
|
101542
101643
|
reason: mapped.severity === "UNKNOWN" ? "unknown_action" : "block",
|
|
101543
101644
|
severity: mapped.severity,
|
|
101544
101645
|
decision: mapped.decision,
|
|
101545
101646
|
decisionId: mapped.decisionId
|
|
101546
|
-
};
|
|
101647
|
+
});
|
|
101547
101648
|
}
|
|
101548
101649
|
if (mapped.severity === "REQUIRE_APPROVAL") {
|
|
101549
101650
|
errLog(
|
|
101550
101651
|
`CodeRifts claude-hook: BLOCKED approval_required${decPart}${eaPart}${idPart}`
|
|
101551
101652
|
);
|
|
101552
|
-
return {
|
|
101653
|
+
return emitTerminal({
|
|
101553
101654
|
exitCode: 2,
|
|
101554
101655
|
reason: "approval_required",
|
|
101555
101656
|
severity: mapped.severity,
|
|
101556
101657
|
decision: mapped.decision,
|
|
101557
101658
|
decisionId: mapped.decisionId
|
|
101558
|
-
};
|
|
101659
|
+
});
|
|
101559
101660
|
}
|
|
101560
101661
|
if (mapped.severity === "MONITOR" || mapped.severity === "WARN") {
|
|
101561
101662
|
const sinkWired = isMonitoringSinkWired({ env, deps, result });
|
|
@@ -101563,29 +101664,29 @@ var require_claude_hook = __commonJS({
|
|
|
101563
101664
|
errLog(
|
|
101564
101665
|
`CodeRifts claude-hook: BLOCKED monitoring_unwired${decPart}${eaPart}${idPart} \u2014 CONTINUE_WITH_MONITORING requires CODERIFTS_MONITORING_SINK_WIRED=1 (host assertion; not delivery proof)`
|
|
101565
101666
|
);
|
|
101566
|
-
return {
|
|
101667
|
+
return emitTerminal({
|
|
101567
101668
|
exitCode: 2,
|
|
101568
101669
|
reason: "monitoring_unwired",
|
|
101569
101670
|
severity: mapped.severity === "WARN" ? "MONITOR" : mapped.severity,
|
|
101570
101671
|
decision: mapped.decision,
|
|
101571
101672
|
decisionId: mapped.decisionId
|
|
101572
|
-
};
|
|
101673
|
+
});
|
|
101573
101674
|
}
|
|
101574
|
-
return {
|
|
101675
|
+
return emitTerminal({
|
|
101575
101676
|
exitCode: 0,
|
|
101576
101677
|
reason: "allow",
|
|
101577
101678
|
severity: mapped.severity === "WARN" ? "MONITOR" : mapped.severity,
|
|
101578
101679
|
decision: mapped.decision,
|
|
101579
101680
|
decisionId: mapped.decisionId
|
|
101580
|
-
};
|
|
101681
|
+
});
|
|
101581
101682
|
}
|
|
101582
|
-
return {
|
|
101683
|
+
return emitTerminal({
|
|
101583
101684
|
exitCode: 0,
|
|
101584
101685
|
reason: "allow",
|
|
101585
101686
|
severity: "ALLOW",
|
|
101586
101687
|
decision: mapped.decision,
|
|
101587
101688
|
decisionId: mapped.decisionId
|
|
101588
|
-
};
|
|
101689
|
+
});
|
|
101589
101690
|
}
|
|
101590
101691
|
module2.exports = {
|
|
101591
101692
|
runClaudeHook,
|
|
@@ -101604,7 +101705,10 @@ var require_claude_hook = __commonJS({
|
|
|
101604
101705
|
softOrStrictBlock,
|
|
101605
101706
|
DEFAULT_SPEC_PATH,
|
|
101606
101707
|
CLOSED_ACTIONS,
|
|
101607
|
-
USAGE
|
|
101708
|
+
USAGE,
|
|
101709
|
+
appendGuardEventLog,
|
|
101710
|
+
extractFingerprint,
|
|
101711
|
+
HOOK_TRIGGER_SOURCE
|
|
101608
101712
|
};
|
|
101609
101713
|
}
|
|
101610
101714
|
});
|
package/package.json
CHANGED