Code Generation
Hook
Hooks
What ripwire runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
$ npx -y skills add redhat-et/ripwire --agent claude-codeShips with ripwire. Installing the plugin gets these hooks.
Where it lives
- hooks/ripwire-claude-route.shGitHub
Read the script
#!/usr/bin/env bash # hooks/ripwire-claude-route.sh — OPT-IN Claude Code UserPromptSubmit router. Ask the deterministic # --help-task classifier BEFORE the first tool is chosen, inject ONE paste-ready command only when that # classifier says `recommend`, and instrument the decision without ever retaining prompt text. The # --observe arm is called by hooks/ripwire-nudge.sh on PreToolUse and closes the adoption-within-two # loop pre-registered in docs/EVALS.md §4. Advisory-only: any missing dependency or error degrades to # silence, never to a blocked prompt. # # WHY THIS EXISTS, AND WHY IT IS NOT THE RETIRED NUDGE WEARING A HAT (2026-09-02). The PreToolUse nudge # in hooks/ripwire-nudge.sh was measured inert by a randomized A/B and retired — see §RETIRED there and # the readout in docs/EVALS.md §4. Three things differ here, and they are the hypothesis under test: # 1. THE MOMENT. This runs before the agent has chosen a tool, not after it already reached for one. # 2. THE PAYLOAD. A runnable command with its arguments already filled in from the prompt, not a verb # name and an ellipsis the agent has to finish. # 3. THE GATE. `--help-task` has a measured precision (1.000 / harmful 0.000 on its tuning corpus, # test/taskroutecheck.sh), so this is silent on the prompts it cannot route rather than firing on # everything. Silence is the common case and is not a failure. # The round cannot separate the three, and the registration says so. # # THE ARM IS THE METER'S ARM, NOT A SECOND COIN FLIP. It resolves `arm` exactly as meter_init() in # hooks/ripwire-nudge.sh does — env over `meter.conf` over the `auto` default (issue #381; it was `treatment`), with the literal # `auto` selecting the same stable session-id hash — so a session lands on the SAME side in both # instruments and the two logs join on session_hash. A control session runs the classifier, writes the # identical row and the identical pending file, and injects NOTHING; its adoption-within-two number is # "how often would the agent have run that verb anyway", which is the quantity the retired nudge never # had and the reason its readouts were uninterpretable. # # PROMPT-INJECTION POSTURE (a contract, gated by test/routehookcheck.sh). The injected context is # assembled from exactly two sources: the compile-time constant framing string below, and the # classifier's own XML, whose intent/skill/reason strings come from the binary's route table and whose # only variable parts are a symbol or literal taken FROM THE USER'S OWN PROMPT and XML-escaped by the # binary. NO REPOSITORY CONTENT REACHES THE OUTPUT — not a file name, not a match, not a body. The # whole thing is then passed through `jq --arg`, so a prompt carrying XML or JSON structure breakers # produces well-formed JSON with the breakers inert inside a string. A router steerable by the thing it # is reading would be a worse failure than a router that does not work. # # NEVER EXIT 2. For UserPromptSubmit, exit 2 BLOCKS the prompt and erases it. This hook is advisory and # has no business doing that under any condition, including its own bugs — every path below exits 0. # # Contract (verified against https://code.claude.com/docs/en/hooks, 2026-09-02): UserPromptSubmit JSON # arrives on stdin with {session_id, transcript_path, cwd, permission_mode, hook_event_name, prompt}. # Advisory context = exit 0 plus this JSON on stdout: # {"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"..."}} # Plain stdout would also be added as context; JSON is used because it is unambiguous and because the # codex sibling already emits this exact shape. set -u command -v jq >/dev/null 2>&1 || exit 0 input="$( cat )" || exit 0 meter_home() { [ "${RIPWIRE_ROUTE_METER:-1}" != 0 ] || return 1 meterHome="${RIPWIRE_HOME:-${HOME:+$HOME/.ripwire}}" [ -n "$meterHome" ] || return 1 mkdir -p "$meterHome/routing-pending" 2>/dev/null || return 1 routingLog="$meterHome/routing.jsonl" # A session that ends before two ripwire calls leaves its pending file behind; expire the strays so # routing-pending/ never accumulates unboundedly. Best-effort, like everything else here. find "$meterHome/routing-pending" -type f -name '*.json' -mtime +7 -delete 2>/dev/null || true } hash_text() { printf '%s' "$1" | cksum 2>/dev/null | cut -d' ' -f1 } # ---- resolve_arm SESSION — the meter's arm resolution, reimplemented here rather than sourced. # hooks/ripwire-nudge.sh is a hook, not a library: sourcing it would run its whole PreToolUse path. # The rules are the ones meter_init() applies and they must not drift — `control` and `treatment` # force an arm, `auto` selects the stable cksum split (low two decimal digits, <50 is control), and # ANY other value, including a hash that does not come back as a plain integer, reads as # `treatment`. That last clause is why a broken `cksum` cannot silently invent a third population. route_arm="treatment" resolve_arm() { _ra_conf="" _ra_home="${RIPWIRE_HOME:-${HOME:+$HOME/.ripwire}}" if [ -n "$_ra_home" ] && [ -f "$_ra_home/meter.conf" ] then # `|| [ -n "$_ra_k" ]` keeps a final line that has no newline (read returns 1 on it but has filled # the variables); `${_ra_v%$'\r'}` drops the CR of a CRLF file so `arm=control\r` reads as `control`. while IFS='=' read -r _ra_k _ra_v || [ -n "$_ra_k" ] do _ra_v="${_ra_v%$'\r'}" case "$_ra_k" in arm) _ra_conf="$_ra_v" ;; esac done < "$_ra_home/meter.conf" fi # UNSET (no env, no `arm=` in meter.conf) is `auto`, not `treatment` (issue #381): the shipped default # used to put every session on treatment, so the pre-registered treatment-minus-control difference could # never be computed. An explicit `treatment` stays the opt-out; any OTHER value still reads as treatment. case "${RIPWIRE_METER_ARM:-${_ra_conf:-auto}}" in control) route_arm="control - hooks/ripwire-claude-toolroute.shGitHub
Read the script
#!/usr/bin/env bash # hooks/ripwire-claude-toolroute.sh — OPT-IN Claude Code PreToolUse SECOND ROUTER ARM, pre-registered # in docs/EVALS.md ("A second router arm — route on the agent's FIRST TOOL CALL, not the prompt", # 2026-09-03). Routes on the SHAPE of the tool call itself rather than the prompt that led to it: # a recursive grep/rg on a source path names `--grep=<pattern>`; a Read of a source file names # `--expand=<symbol>` (only when the symbol resolves on the root) or `--for` with the file already # known. Advisory-only, exactly like hooks/ripwire-claude-route.sh (the prompt arm) and the retired # hooks/ripwire-nudge.sh nudge tiers: never blocks, never denies, degrades to silence on any error. # # WHY A SECOND ARM. hooks/ripwire-claude-route.sh's own instrument (docs/EVALS.md §4) found the prompt # classifier blind to 98% of labelled ripwire moments — most tasks never say what they need in the # words `--help-task` reads. The agent's first `Bash`/`Read` call is a cleaner signal: `grep -rn NAME # src/` already names the verb (`--grep`) and its argument; a `Read` of a source file already names # the file `--for` would take, or the symbol `--expand` would take once it resolves. The retired nudge # (hooks/ripwire-nudge.sh, §RETIRED) fired on the same moment with generic text and no argument; this # arm pastes the exact command instead — the same difference the prompt router made over grep, applied # one moment earlier. # # THE ARM IS THE METER'S ARM, NOT A SECOND COIN FLIP. `resolve_arm` below reimplements the exact rule # meter_init() (hooks/ripwire-nudge.sh) and hooks/ripwire-claude-route.sh's own `resolve_arm` apply — # env over `meter.conf` over the `auto` default (issue #381; it was `treatment`), `auto` selecting the same stable session-id hash # — so a session lands on the same side across all three instruments. A control session runs the same # classification and writes the identical row; it injects nothing. Reimplemented rather than sourced: # sourcing either sibling script would run its own top-level PreToolUse/UserPromptSubmit body. # # ROUTER=TOOLCALL, NEVER POOLED WITH router=prompt. Both routers append to the SAME # ~/.ripwire/routing.jsonl (one file, one contamination surface, matching the substitution meter's own # "one global log" posture) but every row here carries `router:"toolcall"`; hooks/ripwire-claude-route.sh # was updated in the same round to carry `router:"prompt"` on its own rows so the two populations are # distinguishable by a single field rather than by absence-of-field. bench/routing_ab_report.py reads # `router` and reports each router SEPARATELY — the pre-registered n>=40-per-arm floor applies per # router per arm, and a reader that pooled them would be comparing two different questions. # # WHAT THIS ROW DOES NOT DO: adoption tracking. Unlike the prompt router's `RouteObservation` (which # needs hooks/ripwire-nudge.sh's PreToolUse path to see the NEXT tool call after a prompt), this hook # sees every tool call already — but a live adoption-within-two loop for THIS arm was deliberately left # out of THIS build (see LANE_REPORT.md): the substitution meter (hooks/ripwire-nudge.sh's §METER, # ~/.ripwire/substitution.jsonl) already logs every observed tool call in session+seq order, so the # control arm's counterfactual ("how often does a routable event get followed by a ripwire call # anyway", measured at 0.086 from the local meter) is computable by correlating THIS log's decision # rows against THAT log after the fact, without this hook needing its own pending-file chain. Building # that correlator is follow-up work, named as such in docs/SUBSTITUTION_METER.md and LANE_REPORT.md. # # ONE ROW PER DECISION, NEVER RAW TEXT. A row is written for every ROUTABLE EVENT — a Bash command that # looks like a recursive grep/rg, a `Grep` tool call, or a `Read` tool call — in BOTH arms, with # `status` (`recommend`/`abstain`), `reason` (why an abstain happened), and `recommended` (the bare # verb, e.g. `--grep`, never the argument). Anything else (Edit/Write/Task/Glob/mcp__*, or a Bash # command that is not a recursive grep — `cat`, `sed`, `ps`, `tail -f`, a build/test invocation, a # plain `git` command) is not a routable event at all and exits before any row is written or any # subprocess is spawned, mirroring hooks/ripwire-nudge.sh's own fast-bail discipline. # # NOTIFICATION-SHAPED INPUT NEVER ROUTES. A command, pattern, or path carrying the literal substrings # `[SYSTEM NOTIFICATION` or `<task-notification>` (the harness's own wake-up markers, per # bench/mine_traces.py's §2.1 origin-kind filter) is treated as instrumentation noise, not a real # retrieval need, and abstains with reason="notification" before any other classification runs. # # PER-SESSION CAP OF THREE RECOMMENDATIONS, same reasoning as the retired nudge's own design note: an # uncapped hint on every grep is noise. A marker file under $TMPDIR, keyed by the raw session id, # counts recommendations already made (one byte appended per recommend, same atomic-append trick # hooks/ripwire-nudge.sh's meter_log uses for `seq`). The 4th-and-later routable event in a session # still gets a row — `status="abstain" reason="cap"` — but injects nothing, in either arm. # # THE RESOLVED_SYMBOLS GUARD (unchanged from the prompt router / --help-task's own rule): a `Read` of a # source file is recommended as `--expand=<symbol>` only when a guessed symbol name actually resolves # on the root (`ripwire <root> --expand=<candidate>` exits 0). It almost always will not — most files # are not named exactly after the one symbol inside them — and the deliberate, honest fallback is # `--for="<file>"`, which never needs a resolved symbol and is always a sound thing to ask for a known # file. Guessing a symbol that does not resolve and recommending it anyway is exactly the harmful shape # this arm's own gate (test/toolcallroutecheck.sh) is written to catch. # # PROMPT-INJECTION / PRIVACY POSTURE, same contract as hooks/ripwire - hooks/ripwire-codex-nudge.shGitHub
Read the script
#!/usr/bin/env bash # Convert the shared advisory hook's Claude-compatible output into Codex's native hook shape. # Classification, deduplication, routing suggestions, and metering stay in ripwire-nudge.sh. set -u dir="$( cd "$( dirname "$0" )" && pwd )" # §METER v3: rows written through this wrapper are Codex's, and the shared hook's `agent` field # defaults to claude — name the runner unless the operator already did. export RIPWIRE_METER_AGENT="${RIPWIRE_METER_AGENT:-codex}" input="$( cat )" || exit 0 out="$( printf '%s' "$input" | "$dir/ripwire-nudge.sh" "$@" )" rc=$? [ "$rc" -eq 0 ] || exit "$rc" [ "${1:-}" = "--session-start" ] || printf '%s' "$input" | "$dir/ripwire-codex-route.sh" --observe >/dev/null 2>&1 || true [ -n "$out" ] || exit 0 if command -v jq >/dev/null 2>&1; then # Codex accepts additionalContext directly, but `permissionDecision: allow` is valid only when # accompanied by updatedInput. The hook is advisory, so remove that Claude compatibility field. printf '%s\n' "$out" | jq 'if .hookSpecificOutput.permissionDecision == "allow" and (.hookSpecificOutput.updatedInput | not) then del(.hookSpecificOutput.permissionDecision) else . end' else # Never block the triggering tool merely because the optional output adapter is unavailable. exit 0 fi - hooks/ripwire-codex-route.shGitHub
Read the script
#!/usr/bin/env bash # Codex UserPromptSubmit router: ask the deterministic --help-task classifier before the first tool # choice, inject one CLI recommendation only at high confidence, and instrument the decision without # retaining prompt text. The --observe arm is called by ripwire-codex-nudge.sh on PreToolUse and closes # the registered adoption-within-two loop. Advisory-only: any missing dependency/error degrades to silence. set -u command -v jq >/dev/null 2>&1 || exit 0 input="$( cat )" || exit 0 meter_home() { [ "${RIPWIRE_ROUTE_METER:-1}" != 0 ] || return 1 meterHome="${RIPWIRE_HOME:-${HOME:+$HOME/.ripwire}}" [ -n "$meterHome" ] || return 1 mkdir -p "$meterHome/routing-pending" 2>/dev/null || return 1 routingLog="$meterHome/routing.jsonl" # A session that ends before two Ripwire calls leaves its pending file behind; expire the strays so # routing-pending/ never accumulates unboundedly. Best-effort, like everything else in the meter. find "$meterHome/routing-pending" -type f -name '*.json' -mtime +7 -delete 2>/dev/null || true } hash_text() { printf '%s' "$1" | cksum 2>/dev/null | cut -d' ' -f1 } # ---- BEGIN MIRRORED BLOCK rw_is_ripwire_call (PR #215 review item 6) ------------------------------------- # KEEP BYTE-IDENTICAL in hooks/ripwire-claude-route.sh, hooks/ripwire-codex-route.sh and hooks/ripwire-nudge.sh. # test/routehookcheck.sh extracts the three copies and diffs them, the kIngestParserVerMirror pattern: three # files answering one question must answer it in one text, or the meter and the hooks disagree about the very # same command line — which is exactly what happened, and it makes the adoption numbers unreadable. # # WHAT ROUND 1 REPLACED. A regex that looked for `ripwire` after a separator. It said NO to every WRAPPED # invocation an agent actually types — `time ./build/ripwire .`, `sudo ripwire`, `env RIPWIRE_BIN=x ripwire`, # `xargs ripwire`, `exec ripwire`, `nohup ripwire`, `if ripwire … ; then`, `{ ripwire … ; }` — and still said # YES to `git commit -m "fix; ripwire hook"`, where the word sits inside a quoted string and no ripwire runs. # Both errors corrupt the same measurement in opposite directions. # # WHAT ROUND 2 REPLACES (CodeRabbit, PR #215). Round 1 asked the SHELL to split the line — `set -- $1` with # globbing off — and then walked the words. Word splitting is not lexing: it never separates a control # operator from the word it is attached to. `true; ripwire .` split into `true;` and `ripwire`; `true;` was # read as an ordinary command word, so the `ripwire` behind it was no longer in command position and the call # was missed. Every `a; ripwire`, `a&&ripwire`, `a|ripwire`, `(ripwire .)` shape went the same way — and # those are the shapes an agent's one-liner is actually made of. Round 1 also got the quoted-string case # right for the WRONG reason (`-m "fix;` happened not to end a command), which is not a property to rest a # published ratio on. # # WHAT IT DOES NOW. It LEXES the line itself, one character at a time, and never expands, evaluates or # executes any part of it: backslash escapes, 'single' and "double" quotes, and the unquoted control # operators `;` `&` `|` `(` `)` and newline, each of which ends the current word AND puts the next word in # command position. An unquoted `#` starting a word ends the scan — the rest is a comment. `<` and `>` end a # word and consume the next one as a redirection target, leaving command position where it was. On top of # that sits the same command-position rule round 1 used: the wrapper words below do not consume the command, # `cd DIR`, `rtk proxy` and `VAR=value` prefixes are stepped over with their operand, and the word is # basename'd, so `./build/ripwire` and `/opt/rw/ripwire` count while `/opt/ripwire/bin/other` does not. # Quoted text can no longer reach command position by construction, so `git commit -m "fix; ripwire hook"` # and `grep -r 'ripwire;' src/` read as what they are: appearances that run nothing. # # KNOWN LIMIT, disclosed rather than papered over: a redirection written `2>&1` sends its `&` through the # control-operator branch, so the digit behind it is read as a command word. That can only ever cost a # MISSED call, in a line where `ripwire` sits in exactly that position, and never a false one. # # COST (issue #327). Each character read rebuilds the rest of the line (`${rw_line#?}`, and the suffix match # around it), so the scan grows with the cube of the line's length: 20 s for a 4,000-character line under macOS # bash 3.2, in front of the tool call it only counts. Two guards come first. A line that does not contain the # word as written holds no call, and that check ends the scan for nearly every command. It reads the raw text, # before quote removal, so a command word the shell assembles from quoted or escaped fragments (`'rip''wire'`, # `rip\wire`, `"rip""wire"`) reads as no call: a MISSED call, never a false one. A line longer than 1,024 # characters is not scanned and reads as no call — a MISSED call, the same direction as the limit above; 1,024 # costs under half a second at worst. test/routehookcheck.sh O10 holds the cost, O9 pins the assembled words. # # POSIX sh only, no bashisms: routehookcheck.sh extracts this block and runs it under `sh`. rw_cmd_word() { # One completed word, offered to the command-position rule. Returns 0 only for a call. if [ "$rw_rtk" = 1 ] then rw_rtk=0 if [ "$1" = "proxy" ]; then return 1; fi fi if [ "$rw_skip" -gt 0 ] then rw_skip=$(( rw_skip - 1 )) return 1 fi if [ "$rw_at_cmd" != 1 ] then return 1 fi case "$1" in if|while|until|do|then|else|elif|done|fi|esac|'!'|'{'|'}') return 1 ;; *=*) return 1 ;; sudo|command|env|time|nice|nohup|exec|builtin|xargs) return 1 ;; cd|pushd) rw_skip=1; return 1 - hooks/ripwire-nudge.shGitHub
Read the script
#!/usr/bin/env bash # hooks/ripwire-nudge.sh — OPT-IN Claude Code PreToolUse + SessionStart hook. # # READ §RETIRED FIRST. As of 2026-09-02 the PreToolUse path SAYS NOTHING: a randomized A/B measured # both nudge tiers inert and the registered consequence was applied. What survives on that path is the # SUBSTITUTION METER (§METER) plus the eligibility bookkeeping that tells a later readout which calls # the retired advice would have landed on. The SessionStart primer still speaks, and is still the one # thing the control arm does not get. The design record below describes a mechanism that is GONE; it # is kept because a measured negative whose reasoning is deleted gets rebuilt by the next person. # # The original two-jobs description follows. Historically: two jobs in one script, the ADVISORY-ONLY # nudge (below) and the SUBSTITUTION METER (see the §METER block further down). # # Nudged an agent from raw grep/rg, from whole-file Read / candidate-Glob, and from raw `git diff`/ # `git log`/`git show --stat`, toward the matching ripwire verb. Ships INACTIVE — only registered via # `skills/install.sh --hook`, never automatically (see the Phase B5.2 / R5 agent-context-science # design: "with grep available, the agent defaults to it... a PreToolUse hook is the high-leverage # lever" — passive skill-description triggering alone is measured ~30-50% reliable). Phase B9 # (Part 3) extends the trigger set to the git-information moments where raw # output is weakest vs ripwire's structured answers: `git diff` → `--situ`/`--pr-context`, `git log` → # `--rank-by=churn`/`--map-diff`, `git show --stat` → `--map-diff`. Deliberately excluded: `git status` # and state-changing commands (add/commit/push/pull/checkout/branch) — nudging those is spam. # # 2026-08-10 (skill-orientation audit): Read and Glob added. They were previously excluded by the fast # bail, which left the LARGEST token sink in the loop — the whole-file read — as the one default this # hook could not see. A skill description only fires if the agent first RECOGNIZES a moment and then # spends a call to load the skill; `Read` needs neither. The read nudge is therefore the one that # matters most, and it is why the Read case is deliberately NOT deduped against the grep case: they # are different habits and each gets its own one-time nudge. # # The read nudge fires ONCE per session and deliberately does NOT try to name the symbol involved: # suggesting `--expand=<guess>` when the guess is wrong teaches an agent the tool is unreliable, which # costs more than the nudge gains. It names the verb and lets the agent supply the argument. # # 2026-08-29 (P4.2): the grep/rg base nudge no longer fires on a short single-literal pattern (the # case our own docs concede to `rg`) — only on a multi-pattern/OR-chain search, where the bundle # genuinely wins. See §CEDE, near where `category` is demoted, for the full rationale; the sweep # escalation below is untouched and still escalates on a same-class run of literal greps. # # Design posture (non-negotiable — do not "improve" this into a blocker): # - NEVER blocks, denies, or rewrites the tool call. Always permissionDecision "allow" when it # speaks at all; never "deny"/"ask"; never "updatedInput". Any internal failure (missing jq, # malformed JSON, no git, no ripwire) degrades to silent allow — exit 0, no stdout. # - Fires at MOST once per session per pattern (Grep, Bash-grep, git-diff, git-log, and git-show-stat # are separate patterns), via a marker file under $TMPDIR keyed by session_id (falls back to PPID # if the payload has none) — an agent sees each nudge once, not on every call. # - Only fires when the target directory is inside a git repo AND a `ripwire` binary is on PATH. # - Exits fast: the common case (any tool other than Grep/Bash, or a Bash command that matches none # of recursive grep/rg / `git diff` / `git log` / `git show --stat`) bails after one or two greps # over stdin — no subprocess spawned. # # Contract (verified against https://code.claude.com/docs/en/hooks, 2026-07-13): PreToolUse JSON # arrives on stdin with at least {session_id, cwd, tool_name, tool_input}. Advisory feedback that does # NOT block the call = exit 0 + this JSON on stdout: # {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow", # "additionalContext":"..."}} # # Usage (wired by the installer, not meant to be run by hand): # printf '%s' "$PRETOOLUSE_JSON" | hooks/ripwire-nudge.sh set -u # ═══════════════════════════════════════════════════════════════════════════════════════════════════ # §METER — THE SUBSTITUTION METER (Track B §S2, 2026-08-11) # # WHAT IT MEASURES, AND WHY THE UNIT IS A TOOL CALL. The question this project actually wants answered # is "does an agent with ripwire installed reach for it instead of grep/read?". The task-success # outcome eval cannot answer it: the power arithmetic puts the minimum detectable effect at 8.5-26pp # against a pilot that measured parity, so that instrument is dead no matter how long it runs. A # PreToolUse hook, on the other hand, sits at the exact decision point — the moment a default is # chosen — and every session produces hundreds of those. So the unit of observation is one TOOL CALL, # and the metric is a rate over calls: ripwire calls / (ripwire + native retrieval calls). # # ONE GLOBAL LOG. Rows append to $RIPWIRE_HOME/substitution.jsonl (default ~/.ripwire/), created # lazily, one row per line, each tagged with the repo it came from. The hook is registered per-user # but runs in whatever repo the session is in, so a single global file with a `tag`/`repo` field is # what makes both cross-repo and per-repo analysis possible from the same data. bench/ # substitution_report.py reads it; docs/SUBSTITUTION_METER.md is the schema and the classifier's # rule table. One log means one contamination risk, so the §FIXTURE guard in meter_init() is part of # the design and not a test detail: a gate run must neve
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withripwire
The ripgrep of AI context. A map before your agent reads the repo — and a check on what it writes. Ranked, deterministic call graph: what to touch, what it breaks, which tests to run.
Get the whole plugin
Stats
2,449
Stars
155
Forks
Active
Maintenance
C++
Language
Apache-2.0
License
1h ago
Last commit
2mo ago
Created
4h ago
Added
Repo: redhat-et/ripwire

