Files
Oleks c4389d8bb1
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
Add bin/candidate-digest: script the inv.json/scan.json join
The offload-analyst agent was hand-reshaping bin/plugin-inventory's
PluginInventory and bin/offload-scan's OffloadScan into a candidate
table on every audit run — 40.09% of this plugin's own audited spend
over 26 invocations (kotkan/claude-plugin-inference-arbitrage#24).

candidate_digest(inv, scan) -> list[CandidateRow] joins the two JSON
documents and computes shape: share_of_spend, mtr, judgment_density,
read_amplification, retry_density, fanout_multiplier,
dominant_ngram_recurrences, has_bin_script, and an advisory `flags`
list. It reuses boundary-classify's MIN_INVOCATIONS/
MIN_ATTRIBUTED_TURNS and ia_store's MIN_SHARE_OF_SPEND rather than
reinventing thresholds, and applies the judgment_density brake
(new JUDGMENT_DENSITY_BRAKE=0.3) so a row with real judgment density
never gets flagged even if every mechanical threshold is crossed.

This is a digest, not an auto-filing script (position 3, never
position 1): flags are advisory input to the analyst's own reading of
the actual n-gram/tool-call shape, never a verdict on their own.
bin/boundary-classify remains the only place a verdict is computed.

Wired into skills/offload-audit/SKILL.md step 3 and
agents/offload-analyst.md section 2 so future audit runs call the
script instead of reshaping by hand.

Tests: tests/candidate-digest.test.sh covers the issue's three worked
examples (scripted skill suppressed via has_bin_script, a flagged
agent row, and the judgment_density-brake-suppressed edge case) plus
a below-MIN_SHARE_OF_SPEND case and a full CandidateRow schema check.
Also verified against a real matched inv.json/scan.json pair from a
prior anxious audit in /tmp.
2026-07-30 19:27:09 +03:00

199 lines
8.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Joins bin/plugin-inventory's PluginInventory and bin/offload-scan's
OffloadScan into a candidate table -- the reshaping the offload-analyst agent
used to do by hand on every single audit run (40.09% of this plugin's own
audited spend, kotkan/claude-plugin-inference-arbitrage#24).
This is a DIGEST, not an auto-filing script: position 3 of the rubric
(references/boundary-rubric.md), never position 1. It computes SHAPE -- which
rows cross the codified mechanical thresholds -- and leaves intent to a
human/the analyst. A row can cross every mechanical threshold (share, mtr,
read-amplification, a recurring n-gram) and still turn out to be this audit's
own necessary two-pass discipline (read the static inventory, then the
dynamic scan, then reshape both) rather than a genuinely wasteful loop. The
digest cannot see intent, only shape, so `flags` is advisory input to a
human's judgment call -- never a verdict, and never wired to filing.
`bin/boundary-classify` remains the only place a verdict is computed; this
script does not import it for grading, only for its two evidence-strength
constants (see below).
Usage: candidate-digest <inv.json|-> <scan.json> [--json]
"""
import argparse
import importlib.machinery
import importlib.util
import json
import os
import sys
BIN_DIR = os.path.dirname(os.path.abspath(__file__))
# ia_store.py has no hyphen, so a normal import works once bin/ is on
# sys.path -- which it already is (sys.path[0]) when this file is run as
# `bin/candidate-digest`, per ia_store.py's own docstring.
from ia_store import MIN_SHARE_OF_SPEND, scan_rows # noqa: E402
def _load_bin_module(filename):
"""boundary-classify has a hyphenated filename and can't be `import`ed by
name -- loaded the same way offload-scan loads token-budget's cc-tokens
(resolve_cc_tokens there), so this plugin has exactly one pattern for
"import a sibling bin/ script as a module"."""
path = os.path.join(BIN_DIR, filename)
mod_name = filename.replace("-", "_")
spec = importlib.util.spec_from_loader(
mod_name, importlib.machinery.SourceFileLoader(mod_name, path))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
_boundary_classify = _load_bin_module("boundary-classify")
# Reused, not reinvented (kotkan/claude-plugin-inference-arbitrage#24 says to
# reuse these): the same bar boundary-classify already applies to call a
# candidate's evidence `measured` rather than `thin`.
MIN_INVOCATIONS = _boundary_classify.MIN_INVOCATIONS
MIN_ATTRIBUTED_TURNS = _boundary_classify.MIN_ATTRIBUTED_TURNS
# New in this script -- no existing catalog threshold covers "how much
# judgment_density is enough to call a recurring shape genuine work rather
# than waste". The issue's own worked example put a real non-flag case at
# judgment_density=0.55 and a real flag case at 0.034; 0.3 sits well clear of
# both and reads naturally as "under a third of this row's turns were
# judgment calls". This is the judgment_density BRAKE
# (agents/offload-analyst.md: "a brake, not an accelerator") -- it can only
# suppress a flag, never add one.
JUDGMENT_DENSITY_BRAKE = 0.3
def _has_bin_script(name, kind, inv):
"""True only when plugin-inventory's OWN static evidence shows this row's
documented procedure already delegates to a bin/ script.
Skills carry `command_blocks` (the fenced code in their SKILL.md); a
block whose first line names a `bin/` path means the skill is *already*
a thin wrapper around an existing script -- the
"verb_ratio=0.71 backed by a 606-LOC script" example from the issue,
where the conjunction (high verb ratio AND no script) fails and nothing
should be surfaced.
Agents carry no body or command-block text in plugin-inventory's JSON at
all -- only skills do -- so this is conservatively False for every agent
row. That is a real limit on what this script can see, not a judgment
call it is dodging: an analyst who knows a given agent already delegates
to bin/ overrides this field by hand, same as any other digest output.
"""
if kind != "skill":
return False
for skill in inv.get("skills", []):
if skill.get("name") == name:
return any("bin/" in b.get("sig", "")
for b in skill.get("command_blocks", []))
return False
def _dominant_ngram_recurrences(row):
ngrams = row.get("ngrams") or []
if not ngrams:
return None
return max(g.get("recurrences", 0) for g in ngrams)
def _flags(share, invocations, turns, judgment_density, dominant_ngram,
has_bin_script):
"""The escalation path documented in kotkan/claude-plugin-inference-
arbitrage#24: flag a row when it crosses the same thresholds
boundary-classify already uses for evidence strength and filing share
(MIN_INVOCATIONS, MIN_ATTRIBUTED_TURNS, MIN_SHARE_OF_SPEND), shows a
dominant recurring n-gram, AND judgment_density sits below the brake.
`judgment_density` is a brake, not an accelerator: a row that crosses
every other threshold but carries real judgment density is the healthy
case (this audit's own two-pass read-then-reshape discipline, not a
wasteful loop) and MUST come back with flags=[] -- that is the low_judgment
check below, and it is the whole reason this is a digest and not an
auto-filing script.
A row already backed by a bin/ script (has_bin_script) is never flagged
at all: whatever mechanical share it carries is already offloaded, so
there is nothing left here to propose.
"""
if has_bin_script:
return []
measured = invocations >= MIN_INVOCATIONS and turns >= MIN_ATTRIBUTED_TURNS
significant_share = share >= MIN_SHARE_OF_SPEND
recurring = dominant_ngram is not None and dominant_ngram > 1
low_judgment = judgment_density < JUDGMENT_DENSITY_BRAKE
flags = []
if measured and significant_share and recurring and low_judgment:
flags.append("high-share-low-judgment-recurring-ngram")
return flags
def candidate_digest(inv, scan):
"""PluginInventory, OffloadScan -> list[CandidateRow]
(kotkan/claude-plugin-inference-arbitrage#24).
Pure function of its two already-computed JSON documents -- no
filesystem access beyond what the caller already read, no network, no
re-derivation of anything plugin-inventory or offload-scan already
computed. This is the entire join the offload-analyst agent used to
reshape by hand every audit run.
CandidateRow = {row_kind: 'skill'|'agent', name: str,
share_of_spend: float, mtr: float, judgment_density: float,
read_amplification: float, retry_density: float,
fanout_multiplier: float, dominant_ngram_recurrences: int|None,
has_bin_script: bool, flags: list[str]}
"""
rows = []
for name, row in scan_rows(scan):
# scan_rows() (ia_store.py) yields every scan["skills"] row keyed by
# row["skill"] and every scan["agents"] row keyed by row["agent"];
# the row itself still carries whichever of those two keys it came
# from, which is the only place kind is recoverable from here.
kind = "agent" if "agent" in row else "skill"
has_bin = _has_bin_script(name, kind, inv)
dominant = _dominant_ngram_recurrences(row)
share = row.get("share_of_plugin", 0.0)
judgment_density = row.get("judgment_density", 0.0)
rows.append({
"row_kind": kind,
"name": name,
"share_of_spend": share,
"mtr": row.get("mtr", 0.0),
"judgment_density": judgment_density,
"read_amplification": row.get("read_amplification", 0.0),
"retry_density": row.get("retry_density", 0.0),
"fanout_multiplier": row.get("fanout_multiplier", 1.0),
"dominant_ngram_recurrences": dominant,
"has_bin_script": has_bin,
"flags": _flags(share, row.get("invocations", 0),
row.get("turns", 0), judgment_density,
dominant, has_bin),
})
rows.sort(key=lambda r: -r["share_of_spend"])
return rows
def main():
ap = argparse.ArgumentParser(prog="candidate-digest")
ap.add_argument("inventory",
help="path to plugin-inventory --json output, or - for stdin")
ap.add_argument("scan", help="path to offload-scan --json output")
ap.add_argument("--json", action="store_true", help="compact single-line JSON")
args = ap.parse_args()
with (sys.stdin if args.inventory == "-" else open(args.inventory)) as fh:
inv = json.load(fh)
with open(args.scan) as fh:
scan = json.load(fh)
rows = candidate_digest(inv, scan)
json.dump(rows, sys.stdout, indent=None if args.json else 2)
sys.stdout.write("\n")
if __name__ == "__main__":
main()