From 5bdc9846748bb9966dca98592b81f10a47c6ca98 Mon Sep 17 00:00:00 2001 From: Oleks Date: Wed, 29 Jul 2026 17:50:20 +0300 Subject: [PATCH] Fix markdown lint in the Phase 5 docs --- agents/offload-analyst.md | 24 +- bin/filing-plan | 450 +++++++++++++++++++++++++++ commands/offload-audit.md | 24 ++ design/tasks.md | 12 +- references/issue-template.md | 164 ++++++++++ references/snapshot-schema.md | 13 +- skills/offload-audit/SKILL.md | 219 +++++++++++++ skills/offload-trend/SKILL.md | 2 +- tests/filing.test.sh | 178 +++++++++++ tests/fixtures/filing-judgments.json | 69 ++++ tests/fixtures/gitea-issues.json | 14 + 11 files changed, 1152 insertions(+), 17 deletions(-) create mode 100755 bin/filing-plan create mode 100644 commands/offload-audit.md create mode 100644 references/issue-template.md create mode 100644 skills/offload-audit/SKILL.md create mode 100755 tests/filing.test.sh create mode 100644 tests/fixtures/filing-judgments.json create mode 100644 tests/fixtures/gitea-issues.json diff --git a/agents/offload-analyst.md b/agents/offload-analyst.md index 2df8fe5..d81623b 100644 --- a/agents/offload-analyst.md +++ b/agents/offload-analyst.md @@ -184,9 +184,23 @@ what you looked at, and the conclusion that the boundary is already in the right place — with the `pure-inference` findings named, because they are the evidence that you looked rather than shrugged. -### 7. Filing (Phase 6 — not yet built) +### 7. Filing, and the run summary -Filing goes `offload-analyst → Agent(anxious:issuer-agent) → cluster:gitea-agent`. -This plugin holds no Gitea credentials and calls no Gitea tool directly. Until -Phase 6 lands, **report candidates to the user and file nothing** — including -candidates that `boundary-classify` returns with `"verdict": "file"`. +Both output paths are procedure, not judgment, and the `offload-audit` skill +carries them step by step. Read it rather than improvising: `skills/offload-audit/SKILL.md`. + +The four things that are yours to hold: + +- **The chain is fixed.** `offload-analyst → Agent(anxious:issuer-agent) → + cluster:gitea-agent → Gitea`. You hold no Gitea credentials and call no Gitea + tool directly. `issuer` decides repo, title, labels and milestone; you own the + issue body. +- **Filing is implicit** for a candidate that cleared the gate — do not ask + permission. Equally, never file one that did not, and never re-grade a verdict + `boundary-classify` already returned. +- **Never file without the marker search.** `bin/filing-plan` decides + file-vs-comment from the search results and renders both bodies; execute its + plan verbatim. A re-run that duplicates issues is the worst failure this + plugin has. +- **Path B always runs**, including when filing was unavailable (FR-6.4) and + when the answer was "nothing to offload here". diff --git a/bin/filing-plan b/bin/filing-plan new file mode 100755 index 0000000..73a67be --- /dev/null +++ b/bin/filing-plan @@ -0,0 +1,450 @@ +#!/usr/bin/env python3 +"""Decides, deterministically, what output path A does with each candidate: +open a new issue, comment on the one that already exists, or skip it — and +renders the exact body text for each (FR-6.1..FR-6.4, design/plan.md §10). + +Everything here is a `pure-script` step by the plugin's own rubric: the verdicts +were already graded by bin/boundary-classify, the marker match is a literal +string comparison, and rendering a template is a total function of its inputs. +None of it may be done by inference — an audit that decides duplicates by +"looking at" the tracker is exactly the shape this plugin reports on, and +getting it wrong spams a real human's tracker. + +This script never touches Gitea. It consumes the results of a search that +`anxious:issuer-agent` -> `cluster:gitea-agent` performed, and emits a plan that +the same chain executes. Two subcommands, in order: + + filing-plan queries --classified c.json --judgments j.json [--target-path P] + filing-plan plan --classified c.json --judgments j.json --existing e.json ... + +`queries` resolves the target repo (or reports that filing is unavailable) and +emits the marker searches to run. `plan` consumes their merged results. +""" + +import argparse +import json +import os +import re +import subprocess +import sys + +PLUGIN_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +DEFAULT_TEMPLATE = os.path.join(PLUGIN_ROOT, "references", "issue-template.md") +DEFAULT_WORKSPACE = os.path.expanduser("~/projects/claude-plugins") +DEFAULT_GITEA_HOST = "git.oleks.space" +DEFAULT_WIKI_REPO = "kotkan/claude-plugin-inference-arbitrage" +OFFLOAD_LABEL = "token-offload" + +TEMPLATE_SECTIONS = {"issue-title", "issue-body", "update-comment"} + + +# -------------------------------------------------------------------------- +# template loading and rendering +# -------------------------------------------------------------------------- + +def load_templates(path): + """Same contract as signals-catalog.md: one fenced block per recognized + `##
` heading, everything else prose.""" + with open(path, encoding="utf-8") as fh: + text = fh.read() + + out = {} + for m in re.finditer(r"^##\s+([a-z-]+)\s*$", text, re.MULTILINE): + name = m.group(1) + if name not in TEMPLATE_SECTIONS: + continue + block = re.search(r"```\S*\n(.*?)```", text[m.end():], re.DOTALL) + if block: + out[name] = block.group(1).strip("\n") + missing = TEMPLATE_SECTIONS - out.keys() + if missing: + sys.exit(f"error: {path} is missing section(s): {', '.join(sorted(missing))}") + return out + + +PLACEHOLDER = re.compile(r"\{\{([a-z_]+)\}\}") + + +def render(template, values): + """Strict substitution. An unsupplied placeholder is an error, never an + empty string — a half-filled issue body is worse than a crashed audit.""" + missing = sorted( + {k for k in PLACEHOLDER.findall(template) if values.get(k) in (None, "")} + ) + if missing: + sys.exit(f"error: template placeholder(s) unsupplied: {', '.join(missing)}") + return PLACEHOLDER.sub(lambda m: str(values[m.group(1)]), template) + + +# -------------------------------------------------------------------------- +# the marker (FR-6.3) +# -------------------------------------------------------------------------- + +def marker(candidate_id): + return f"" + + +def marker_re(candidate_id): + """Tolerates whitespace drift inside the comment, nothing else. The id + itself must match exactly — a fuzzy id match would let two genuinely + different candidates collapse onto one issue.""" + return re.compile(r"") + + +def find_existing(candidate_id, issues): + """Returns (issue_or_None, duplicates). Matches the body and every comment, + so an issue keeps its identity even if a human rewrites the body.""" + pat = marker_re(candidate_id) + hits = [] + for issue in issues: + haystacks = [issue.get("body") or ""] + haystacks += [c if isinstance(c, str) else (c.get("body") or "") + for c in issue.get("comments") or []] + if any(pat.search(h) for h in haystacks): + hits.append(issue) + if not hits: + return None, [] + hits.sort(key=lambda i: i.get("number", 0)) + return hits[0], hits[1:] + + +# -------------------------------------------------------------------------- +# target repo resolution (FR-6.4) +# -------------------------------------------------------------------------- + +def git_remote(path): + if not os.path.isdir(os.path.join(path, ".git")): + return None + try: + out = subprocess.run( + ["git", "-C", path, "remote", "get-url", "origin"], + capture_output=True, text=True, timeout=10, + ) + except (OSError, subprocess.SubprocessError): + return None + return out.stdout.strip() if out.returncode == 0 else None + + +def parse_remote(url, host): + """owner/repo if the remote points at our Gitea, else None.""" + m = re.match(r"^(?:git@|ssh://git@|https://)([^/:]+)[:/](.+?)(?:\.git)?/?$", url) + if not m or m.group(1) != host: + return None + parts = m.group(2).split("/") + return "/".join(parts[-2:]) if len(parts) >= 2 else None + + +def resolve_repo(args, target_name): + """(repo, filing, reason). `filing` is 'available' or 'unavailable'. + + Order: explicit flag, the target checkout's own remote, then the workspace + sibling checkout — because an audit normally runs against + ~/.claude/plugins/cache/... which carries no .git at all. + """ + if args.target_repo: + return args.target_repo, "available", None + + tried = [] + candidates = [] + if args.target_path: + candidates.append(os.path.abspath(args.target_path)) + if target_name and args.workspace: + sibling = os.path.join(args.workspace, target_name) + if sibling not in candidates: + candidates.append(sibling) + + for path in candidates: + if not os.path.isdir(path): + continue + url = git_remote(path) + if url is None: + tried.append(f"{path}: no git remote") + continue + repo = parse_remote(url, args.gitea_host) + if repo: + return repo, "available", None + tried.append(f"{path}: remote {url} is not on {args.gitea_host}") + + if not candidates: + detail = "no --target-repo, --target-path, or workspace checkout to resolve from" + else: + detail = "; ".join(tried) or "no checkout found at " + ", ".join(candidates) + return None, "unavailable", ( + f"no writable repo for target {target_name!r} ({detail}). " + "Path A is skipped; the candidate is reported in the run summary only (FR-6.4)." + ) + + +# -------------------------------------------------------------------------- +# rendering values +# -------------------------------------------------------------------------- + +TICKS = {True: "✓", False: "✗"} + + +def fmt_tests(tests): + return " ".join(f"{t} {TICKS[tests.get(t) is True]}" for t in ("T1", "T2", "T3", "T4", "T5")) + + +def fmt_tokens(n): + if n >= 1_000_000: + return f"{n / 1_000_000:.1f}M" + if n >= 1_000: + return f"{n / 1_000:.1f}k" + return str(n) + + +def fmt_examples(examples): + return "\n".join(f"- {e}" for e in examples) + + +def fmt_evidence(strength, m): + inv, turns = m.get("invocations", 0), m.get("attributed_turns", 0) + if strength == "measured": + return f"`measured` — {inv} invocations, {turns} attributed turns." + if strength == "thin": + return ( + f"`thin` — only {inv} invocation(s) and {turns} attributed turns in this " + "window. **The cost figure above is arithmetic, not evidence.** The " + "correctness case stands on the rubric; the value claim is unproven." + ) + return ( + "`unmeasured` — no transcript history for this target in the window. This " + "candidate rests on the plugin definition alone (FR-2.4)." + ) + + +def fmt_cost(cand, cls, window): + """Only states what was actually measured. Absent signals are omitted, never + guessed at — a fabricated MTR in a filed issue is unrecoverable.""" + m = cand.get("measurement") or {} + bits = [] + value = cls.get("offload_value") or 0 + share = cls.get("share_of_audited_spend") or 0 + inv = m.get("invocations", 0) + head = f"{fmt_tokens(value)} weighted tokens" + if inv: + head += f" over {inv} invocation{'s' if inv != 1 else ''}" + if window: + head += f" ({window})" + if share: + head += f", {share:.0%} of this plugin's audited spend" + bits.append(head + ".") + + stats = [] + if m.get("mtr") is not None: + stats.append(f"MTR {m['mtr']:.2f}") + if m.get("judgment_density") is not None: + stats.append(f"judgment density {m['judgment_density']:.2f}") + if stats: + bits.append(" ".join(stats) + ".") + + ngram = m.get("ngram") or {} + if ngram.get("sig") and ngram.get("recurrences"): + seq = " -> ".join(ngram["sig"]) + bits.append(f"Recurring sequence observed {ngram['recurrences']}x: {seq}.") + return " ".join(bits) + + +def headline(cand): + if cand.get("headline"): + return cand["headline"] + # Last path segment of the candidate id is its slug by construction + # (design/plan.md §6: "//"). + return cand["candidate_id"].rsplit("/", 1)[-1].replace("-", " ") + + +# -------------------------------------------------------------------------- +# the plan +# -------------------------------------------------------------------------- + +def index_judgments(doc): + return {c["candidate_id"]: c for c in doc.get("candidates", [])} + + +def build(args, cls_doc, jud_doc, existing, templates): + target = cls_doc.get("target") or jud_doc.get("target") + repo, filing, reason = resolve_repo(args, target) + judgments = index_judgments(jud_doc) + + actions, warnings = [], [] + for cls in cls_doc.get("candidates", []): + cid = cls["candidate_id"] + verdict = cls.get("verdict") + if verdict != "file": + actions.append({ + "candidate_id": cid, + "action": "skip", + "reason": f"verdict {verdict!r} — path A files only 'file' verdicts " + "(FR-4.2 gate, enforced by boundary-classify)", + }) + continue + + if filing == "unavailable": + actions.append({ + "candidate_id": cid, + "action": "skip", + "filing": "unavailable", + "reason": reason, + }) + continue + + cand = judgments.get(cid) + if cand is None: + sys.exit(f"error: candidate {cid!r} has verdict 'file' but no judgments entry") + + f = cand.get("falsifiability") or {} + values = { + "candidate_id": cid, + "headline": headline(cand), + "skill": cand.get("skill") or cls.get("skill"), + "measured_cost": fmt_cost(cand, cls, args.window), + "evidence": fmt_evidence(cls.get("measurement_strength"), cand.get("measurement") or {}), + "position": cls.get("position"), + "boundary_confidence": cls.get("boundary_confidence"), + "determinism_tests": fmt_tests(cls.get("determinism_tests") or {}), + "signature": f.get("signature"), + "examples": fmt_examples(f.get("examples") or []), + "overrule_case": f.get("overrule_case"), + "escalation_path": cand.get("escalation_path"), + "digest_schema": cand.get("digest_schema") or "n/a — position 1, the whole step moves.", + "audit_page": args.audit_page, + "wiki_repo": args.wiki_repo, + "rubric_version": cls_doc.get("rubric_version", "1.0.0"), + "run_date": args.run_date, + "window": args.window, + "status": args.status, + } + + found, dupes = find_existing(cid, existing) + if dupes: + numbers = ", ".join("#%s" % d.get("number") for d in [found] + dupes) + warnings.append( + f"{cid}: marker found on {1 + len(dupes)} issues ({numbers}) — " + "commenting on the lowest-numbered one; the rest want a human merge" + ) + + if found: + actions.append({ + "candidate_id": cid, + "action": "comment", + "issue": f"{repo}#{found['number']}", + "issue_number": found["number"], + "issue_state": found.get("state", "unknown"), + "marker": marker(cid), + "body": render(templates["update-comment"], values), + "note": "existing issue is closed; issuer decides whether to reopen" + if found.get("state") == "closed" else None, + }) + else: + actions.append({ + "candidate_id": cid, + "action": "file", + "marker": marker(cid), + "proposed_title": render(templates["issue-title"], values), + "body": render(templates["issue-body"], values), + "required_labels": [OFFLOAD_LABEL], + "labels_note": "the four-axis anxious taxonomy is issuer's call; " + f"{OFFLOAD_LABEL} is required and issuer creates it if absent", + }) + + counts = {a: sum(1 for x in actions if x["action"] == a) for a in ("file", "comment", "skip")} + return { + "target": target, + "repo": repo, + "filing": filing, + "filing_unavailable_reason": reason, + "run_date": args.run_date, + "window": args.window, + "audit_page": args.audit_page, + "delegation": "offload-analyst -> Agent(anxious:issuer-agent) -> cluster:gitea-agent -> Gitea", + "summary": { + "to_file": counts["file"], + "to_comment": counts["comment"], + "skipped": counts["skip"], + "searched_issues": len(existing), + }, + "warnings": warnings, + "actions": actions, + } + + +def build_queries(args, cls_doc, jud_doc): + target = cls_doc.get("target") or jud_doc.get("target") + repo, filing, reason = resolve_repo(args, target) + ids = [c["candidate_id"] for c in cls_doc.get("candidates", []) if c.get("verdict") == "file"] + doc = { + "target": target, + "repo": repo, + "filing": filing, + "filing_unavailable_reason": reason, + "candidates_to_search": ids, + } + if filing == "available" and ids: + doc["queries"] = { + "full_text": [ + {"tool": "search_issues", "repo": repo, "state": "all", + "q": f"ia-candidate: {cid}"} for cid in ids + ], + "label_scan": {"tool": "list_issues", "repo": repo, "state": "all", + "labels": [OFFLOAD_LABEL]}, + } + doc["note"] = ( + "Run BOTH and merge into one array of " + "{number, state, title, body, comments[]} for `filing-plan plan --existing`. " + "The label scan is authoritative; the full-text query is the fast path and " + "may miss on indexer tokenization. See references/issue-template.md." + ) + return doc + + +def main(): + ap = argparse.ArgumentParser(prog="filing-plan") + sub = ap.add_subparsers(dest="cmd", required=True) + + def common(p): + p.add_argument("--classified", required=True, help="bin/boundary-classify output") + p.add_argument("--judgments", required=True, help="the analyst's judgments JSON") + p.add_argument("--target-repo", help="owner/repo, if already known") + p.add_argument("--target-path", help="the audited plugin's directory") + p.add_argument("--workspace", default=DEFAULT_WORKSPACE) + p.add_argument("--gitea-host", default=DEFAULT_GITEA_HOST) + p.add_argument("--json", action="store_true", help="compact single-line JSON") + + q = sub.add_parser("queries", help="resolve the repo and emit the marker searches") + common(q) + + p = sub.add_parser("plan", help="decide file/comment/skip per candidate") + common(p) + p.add_argument("--existing", required=True, + help="JSON array of issues from the marker search. Required even when " + "empty ([]): defaulting to 'nothing exists' would duplicate on re-run.") + p.add_argument("--audit-page", required=True, help="e.g. Audits/anxious/2026-07-29") + p.add_argument("--window", required=True, help="e.g. 2026-07-22 -> 2026-07-29") + p.add_argument("--run-date", required=True) + p.add_argument("--wiki-repo", default=DEFAULT_WIKI_REPO) + p.add_argument("--status", default="persisting", + help="snapshot-diff status for the update comment (audit-snapshot diff)") + p.add_argument("--template", default=DEFAULT_TEMPLATE) + + args = ap.parse_args() + with open(args.classified) as fh: + cls_doc = json.load(fh) + with open(args.judgments) as fh: + jud_doc = json.load(fh) + + if args.cmd == "queries": + out = build_queries(args, cls_doc, jud_doc) + else: + with open(args.existing) as fh: + existing = json.load(fh) + if not isinstance(existing, list): + sys.exit("error: --existing must be a JSON array of issues") + out = build(args, cls_doc, jud_doc, existing, load_templates(args.template)) + + json.dump(out, sys.stdout, indent=None if args.json else 2, ensure_ascii=False) + sys.stdout.write("\n") + + +if __name__ == "__main__": + main() diff --git a/commands/offload-audit.md b/commands/offload-audit.md new file mode 100644 index 0000000..8e78475 --- /dev/null +++ b/commands/offload-audit.md @@ -0,0 +1,24 @@ +--- +description: Audit a Claude Code plugin for steps that should be a deterministic script instead of raw inference — measure, classify, file the well-evidenced candidates, and write the run summary. Usage — /offload-audit [--days N] +argument-hint: [--days N] +allowed-tools: ["Bash", "Read", "Grep", "Glob", "Skill", "Agent"] +--- + +# /offload-audit + +Run the full audit on **$ARGUMENTS**. + +1. Parse `$ARGUMENTS` into a target (a plugin name or a directory path) and an + optional `--days N` window, defaulting to 30. +2. If no target was given, ask which plugin to audit. Do not guess one, and do + not default to the current directory — auditing the wrong plugin wastes a + full transcript scan. +3. Invoke the `offload-audit` skill and follow it exactly, including the filing + gate and the marker search. For a heavy target, or when the report should not + consume this session's context, hand the work to + `Agent(subagent_type="inference-arbitrage:offload-analyst")` instead — one + agent for the whole audit, never one per skill (spec §7). + +Filing is implicit: a candidate that clears the rubric gate and the value +threshold is filed without asking. Everything below the gate is reported as a +boundary question and filed nowhere. diff --git a/design/tasks.md b/design/tasks.md index 1787df3..dae91db 100644 --- a/design/tasks.md +++ b/design/tasks.md @@ -122,16 +122,16 @@ These block design, not just code. Answers go into `Methodology/Calibration.md`. ## Phase 6 — Output paths -- [ ] **6.1** `references/issue-template.md` per PLAN §10. -- [ ] **6.2** `anxious:issuer` delegation path; create the `token-offload` label +- [x] **6.1** `references/issue-template.md` per PLAN §10. +- [x] **6.2** `anxious:issuer` delegation path; create the `token-offload` label on a target repo on first audit (implicitly, via `issuer`). -- [ ] **6.3** **Idempotency (FR-6.3)** — `ia-candidate` marker search before +- [x] **6.3** **Idempotency (FR-6.3)** — `ia-candidate` marker search before filing; comment on the existing issue instead of duplicating. Verify by running the same audit twice (S5). This is the highest-consequence correctness detail in the whole plugin. -- [ ] **6.4** Summary page generation → `Audits//` + `Latest`. -- [ ] **6.5** Graceful `filing: unavailable` for repo-less targets (FR-6.4). -- [ ] **6.6** `skills/offload-audit/SKILL.md` and `commands/offload-audit.md` +- [x] **6.4** Summary page generation → `Audits//` + `Latest`. +- [x] **6.5** Graceful `filing: unavailable` for repo-less targets (FR-6.4). +- [x] **6.6** `skills/offload-audit/SKILL.md` and `commands/offload-audit.md` tying the whole procedure together. ## Phase 7 — Acceptance diff --git a/references/issue-template.md b/references/issue-template.md new file mode 100644 index 0000000..62d118a --- /dev/null +++ b/references/issue-template.md @@ -0,0 +1,164 @@ +# issue-template.md — the issue body contract + +The shape of every issue this plugin causes to be filed on a **target plugin's +own repo** (output path A, `design/plan.md` §10, FR-6). Loaded at runtime by +`bin/filing-plan`, which renders it; it is kept here rather than in the script +body so that a change to what an issue says is a reviewable diff. + +Format is the same as `signals-catalog.md`: each `##
` heading below is +followed by **exactly one fenced block**, and the fenced block is the only part +the parser reads. Prose outside the fences is for humans. + +Placeholders are `{{name}}`. Every placeholder in a template must be supplied — +`filing-plan` fails loudly on an unknown or missing one rather than rendering a +half-filled issue. + +## Who decides what + +`filing-plan` renders the **body**, because the body is the contract: the marker, +the measurement, the rubric verdict, and the falsifiability triple are all +already-computed values and rendering them is a `pure-script` step. + +It does **not** decide where the issue goes. Repo, final title, labels beyond +`token-offload`, and milestone are `anxious:issuer-agent`'s call, per its own +taxonomy. The `issue-title` section below is a *proposal* passed to `issuer`, +not an instruction. + +## The marker is load-bearing + +The first line of every body — and of every update comment — is + +```text + +``` + +It is how a re-run recognizes its own prior work (FR-6.3). It renders invisible +in Gitea, is stable across runs because `candidate_id` is, and must never be +edited by hand on a filed issue. Removing it from an issue causes the next audit +to file a duplicate — which the spec names as the single most likely way for +this plugin to become hated. + +## issue-title + +A proposal. `issuer` may rewrite it. + +```text +token-offload: {{headline}} ({{skill}}) +``` + +## issue-body + +`{{examples}}` renders as a markdown list, one input→output pair per line, with +the edge case marked. `{{determinism_tests}}` renders as `T1 ✓ T2 ✓ …`, using ✗ +for a failed test. `{{evidence}}` always renders — it states whether the cost +claim is `measured`, `thin`, or `unmeasured`, so a reader never has to guess +whether the number is evidence or arithmetic. + +```markdown + + +**Measured cost.** {{measured_cost}} + +**Evidence strength.** {{evidence}} + +**Boundary position.** {{position}} — confidence {{boundary_confidence}}. +Determinism tests: {{determinism_tests}} + +**Proposed signature.** +`{{signature}}` + +**Examples.** + +{{examples}} + +**When a human would overrule this.** {{overrule_case}} + +**Escalation path.** {{escalation_path}} + +**What crosses the boundary.** {{digest_schema}} + +--- + +This is a proposal from an audit, not an accepted design. It is deliberately +falsifiable: if the overrule case above is *common*, the cut belongs earlier in +the pipeline than proposed, and this issue should be reshaped rather than +implemented. Closing this as `wontfix` is a useful outcome and is tracked as a +calibration signal. + +Audit: `{{audit_page}}` on the {{wiki_repo}} wiki. Rubric v{{rubric_version}}. +``` + +## update-comment + +Posted instead of a new issue when the marker is already present on the repo +(FR-6.3). It carries the marker again so that the search finds this issue +whether the marker lives in the body or only in a comment. + +```markdown + + +**Re-measured {{run_date}}** — window {{window}}. Status: {{status}}. + +{{measured_cost}} + +Evidence strength: {{evidence}} + +Boundary position {{position}}, confidence {{boundary_confidence}} (rubric +v{{rubric_version}}). + +Audit: `{{audit_page}}` on the {{wiki_repo}} wiki. +``` + +## marker-search + +How the pre-filing search is actually performed (FR-6.3). This is not +hand-waved: both queries below run against the **target** repo, through +`anxious:issuer-agent` → `cluster:gitea-agent`, and their merged results are +what `filing-plan plan --existing` consumes. + +**Three steps, not one.** The obvious one-query design does not work, and the +reason was established empirically against the live Gitea (2026-07-29, probe +issue `kotkan/claude-plugin-inference-arbitrage#9`, since closed): + +> **Neither `search_issues` nor `list_issues` returns the issue `body`.** Both +> return metadata only — number, title, state, labels, timestamps. A marker +> match therefore *cannot* be made from a search result. The body must be +> fetched per issue with `issue_read(method="get")`, and comments with +> `issue_read(method="get_comments")`. + +Also established by the same probe: `search_issues` **does** full-text match on +body content, including text inside an HTML comment (a query for a string that +appeared only in the body, never in the title, returned the issue). So the +full-text path works — it just cannot be trusted to *confirm* a match, only to +suggest one. + +```text +1. candidates list_issues(owner, repo, labels=["token-offload"], state="all") + search_issues(query="ia-candidate: ", owner, state="all") + -> union of issue numbers; bodies are NOT in these responses +2. fetch issue_read(method="get", index=N) -> body + issue_read(method="get_comments", index=N) -> comment bodies + (comments only needed when the body has no marker) +3. match filing-plan plan --existing -> local literal match +``` + +Step 1's label scan is the authoritative half: every issue this plugin files +carries `token-offload`, so it is a bounded, exact superset of the issues that +can carry a marker. The full-text query is the safety net for an issue whose +label a human removed. Step 3's match is a literal string comparison and is the +only thing that decides identity — the search engine never does. + +If step 1 finds no `token-offload` label on the target repo, that is the +first-audit case: there are no prior issues, `issuer` creates the label with the +first filing, and the empty result is correct. + +The merged shape handed to `--existing` is a JSON array of +`{number, state, title, body, comments: [...]}`. `filing-plan` matches +`` against the body **and each comment**, so an +issue keeps its identity even if a human rewrites the body entirely, as long as +one update comment survives — also verified against the live probe issue. + +**`--existing` is mandatory, even when empty.** `filing-plan plan` refuses to +run without it rather than defaulting to "nothing exists", because that default +turns a failed search into a duplicate-filing spree — the exact outcome FR-6.3 +exists to prevent. diff --git a/references/snapshot-schema.md b/references/snapshot-schema.md index 8c5b8af..a9a723e 100644 --- a/references/snapshot-schema.md +++ b/references/snapshot-schema.md @@ -16,19 +16,22 @@ plan §6 is the source, with the two additions noted at the bottom. }, "window": {"since": "...", "until": "...", "days": 7}, "coverage": {"ratio": 0.735, "attributed_turns": 812}, - "totals": {"weighted_tokens": 41200000, "invocations": 60, "share_mechanical": 0.61}, + "totals": {"weighted_tokens": 41200000, "invocations": 60, + "share_mechanical": 0.61}, "candidates": [{ "candidate_id": "anxious/steward/label-derivation", "signature": ["list_issues()", "issue_read()"], "signature_source": "explicit | scan-ngram | slug", - "signature_hash": "b41f...", // sha256 of the joined signature, 16 hex + "signature_hash": "b41f...", // sha256 of the signature, 16 hex "skill": "anxious:steward-agent", "position": "llm-over-script-digest", "boundary_confidence": "high", "determinism_tests": {"T1": true, "...": true}, - "falsifiability": {"signature": true, "examples": true, "overrule_case": true}, - "measurement": {"invocations": 22, "offload_waste": 6900000, "offload_value": 8400000, - "share_of_plugin": 0.22, "cost_per_invocation": 381818}, + "falsifiability": {"signature": true, "examples": true, + "overrule_case": true}, + "measurement": {"invocations": 22, "offload_waste": 6900000, + "offload_value": 8400000, "share_of_plugin": 0.22, + "cost_per_invocation": 381818}, "measurement_strength": "measured | thin | unmeasured", "digest_schema": "...", "escalation_path": "...", "issue": "oleks/claude-plugin-anxious#41", diff --git a/skills/offload-audit/SKILL.md b/skills/offload-audit/SKILL.md new file mode 100644 index 0000000..ed537e8 --- /dev/null +++ b/skills/offload-audit/SKILL.md @@ -0,0 +1,219 @@ +--- +name: offload-audit +description: Run a full offload audit on a Claude Code plugin — measure what its skills and agents actually cost, classify each candidate against the boundary rubric, file the well-evidenced ones as issues on the target's own repo, and write the run summary to this plugin's wiki. Trigger on "audit this plugin for token optimization", "can any of this be a script", "offload analysis", "where is this plugin wasting inference", "check for scripting opportunities", "what is this plugin paying inference for", "run an offload audit". Read-only with respect to the audited plugin. +allowed-tools: Bash, Read, Grep, Glob, Skill, Agent +--- + +# Audit a plugin for work that should be a script + +Six steps, in order. Steps 1–3 are scripts, step 4 is the only judgment, steps +5–6 are delegation. **Do not do by hand anything a `bin/` tool does** — this +plugin is subject to its own thesis (FR-8), and an audit that counts things by +inference is the finding, not the method. + +Full rubric: the `boundary-rubric` skill. Deeper reporting discipline: +`agents/offload-analyst.md`. Issue-body contract and the marker-search +mechanics: `${CLAUDE_PLUGIN_ROOT}/references/issue-template.md`. + +--- + +## 1. Resolve and measure + +```bash +IA=${CLAUDE_PLUGIN_ROOT} +$IA/bin/plugin-inventory --json > inv.json # static pass +$IA/bin/offload-scan --plugin --days 30 --json > scan.json # dynamic pass +``` + +`plugin-inventory` refuses a directory that is not a plugin rather than guessing +(FR-1.3). A target with no transcript history is still auditable — the static +pass stands alone and every candidate is marked `unmeasured` (FR-2.4). + +## 2. Read the measurement honestly, before looking for candidates + +Open the report with window, **absolute** evidence counts, the named structural +blind spots (hooks, agents, direct `commands/`), and only then the coverage +ratio — **stated as dilution, not completeness.** + +> Coverage answers "how much of these sessions was about something else", not +> "how much of this plugin's work did we see." + +A real run against `anxious` measured 0.021. That is not a failed scan; it is +long multi-topic sessions. Reporting it as completeness is the easiest way to +mislead the reader. Then give the mechanical-share headline — the number that +says whether there is anything here at all. + +## 3. Gather candidates + +From `plugin-inventory`: high verb ratio **with no `bin/` script behind it** +(a conjunction — never the ratio alone), duplicated command blocks, rule tables, +low script coverage. From `offload-scan`: high MTR with real spend, recurring +n-grams, read amplification, retry density, fan-out. `judgment_density` is a +**brake** — high judgment density with high spend means the plugin is healthy +and must be reported that way. + +`aggregate.hook_prose_drift` entries are **not offload candidates.** Pass them +with `"category": "hook-prose-drift"` so they come back as `drift-note`. + +## 4. Judge, then grade mechanically + +This is the only step that is inference. Invoke the `boundary-rubric` skill per +candidate; write the five determinism tests, the falsifiability triple, the +escalation path and digest schema into a judgments JSON (shape: +`tests/calibration/*.judgments.json`), then: + +```bash +$IA/bin/boundary-classify judgments.json > classified.json +``` + +**Never grade by hand and never override the verdict it returns.** The filing +threshold (`high` confidence **and** ≥2% of audited spend **and** the triple +complete including the overrule case) lives in that script. + +> **No overrule case → boundary question, reported, never filed.** Hard gate, +> FR-4.2. When genuinely torn, do not file. + +## 5. Output path A — file the issues + +**Filing is implicit.** Do not ask permission to file a candidate that cleared +the gate — this matches the environment's standing bug-handling convention. What +you must not do is file anything the gate rejected. + +**This plugin holds no Gitea credentials and calls no Gitea MCP tool.** The chain +is fixed: + +```text +offload-audit → Agent(anxious:issuer-agent) → cluster:gitea-agent → Gitea +``` + +### 5a. Resolve the repo and get the searches + +```bash +$IA/bin/filing-plan queries --classified classified.json --judgments judgments.json \ + --target-path > queries.json +``` + +If `filing` is `unavailable` (FR-6.4 — a third-party or marketplace plugin with +no repo this environment can write to), **skip the rest of path A entirely**, +carry the reason into the report and into the run summary, and go to step 6. +Path B still runs. This is a normal outcome, not an error. + +### 5b. Run the marker search — the step that prevents duplicates + +Delegate the searches in `queries.json`. Both queries, merged; **the label scan +is the authoritative one.** Bodies are not in listing responses, so each +candidate issue needs an `issue_read`: + +```text +Agent(subagent_type="anxious:issuer-agent", prompt=...) + "Read-only lookup on , no writes. For an inference-arbitrage + audit I need to know whether these candidates already have issues. + 1. list_issues(owner, repo, labels=['token-offload'], state='all') + 2. search_issues(query='ia-candidate: ', owner, state='all') [per id] + 3. For each issue number found: issue_read(method='get') → body + and issue_read(method='get_comments') → comment bodies + Return a JSON array of {number, state, title, body, comments:[{body}]}. + Do not create, edit, or label anything." +``` + +Write the returned array to `existing.json`. **If the search fails or you cannot +get bodies, stop path A and report it** — do not proceed with an empty array. +`filing-plan` requires `--existing` precisely so that a failed search cannot +silently become a duplicate-filing spree (FR-6.3). + +### 5c. Build the plan + +```bash +$IA/bin/filing-plan plan --classified classified.json --judgments judgments.json \ + --target-path --existing existing.json \ + --audit-page "Audits//" \ + --window " -> " --run-date \ + --status > filing.json +``` + +Each action is `file`, `comment`, or `skip`, with the exact body already +rendered. **Execute the plan verbatim — do not rewrite a body, and never turn a +`comment` into a `file`.** Surface any `warnings` to the user. + +### 5d. Execute through `issuer` + +One `Agent` call per action (or one call carrying all of them): + +```text +Agent(subagent_type="anxious:issuer-agent", prompt=...) + # for action == "file" + "File this on . It is an inference-arbitrage audit finding. + Proposed title: (yours to rewrite per your taxonomy) + Body — file VERBATIM, the first line is an idempotency marker that a later + audit searches for and MUST NOT be altered or reformatted: + + Labels: 'token-offload' is required — create it on this repo if absent + (colour #8b5cf6, 'A step paying for inference where a deterministic script + would do'). Add the four-axis taxonomy labels and any milestone per your own + rules; typically kind/chore or kind/capability-gap, area/agent-behavior, + domain/agents, activity/automate. + Return the issue reference in full owner/repo#num form." + + # for action == "comment" + "Add this comment to , an existing inference-arbitrage finding being + re-measured. Do not open a new issue. Post the body VERBATIM (the first line + is an idempotency marker). If the issue is closed, use your judgement about + reopening; the audit does not require it. + " +``` + +`issuer` owns repo, final title, labels beyond `token-offload`, and milestone. +You own the body. + +Collect the returned refs into `issue-map.json` — `{"": "owner/repo#N"}`. + +## 6. Output path B — the run summary + +Path B runs **after** path A so the summary records real issue numbers, and it +runs **even when path A was skipped**. + +```bash +$IA/bin/audit-snapshot write --target --from inv.json scan.json \ + --classified classified.json --issue-map issue-map.json --repo +$IA/bin/audit-snapshot pages --target > pages.json +``` + +`audit-snapshot` renders `Audits//`, regenerates +`Audits//Latest`, and appends the machine-readable record to +`Data//snapshots.jsonl`. It does not write to Gitea either — delegate +each page in `pages.json` to `cluster:gitea-agent` for the wiki of +`kotkan/claude-plugin-inference-arbitrage`, respecting `append_only` on the +JSONL. + +When filing was unavailable, say so on the page: pass the reason through +`audit-snapshot write --note ""` if that flag exists, and state it in +the report regardless. + +## 7. Report to the user + +In this order (FR-7.3): coverage and window → mechanical-share headline → +ranked candidate table with position, confidence and value → diff against the +previous snapshot → boundary questions, as questions → drift notes, with their +documentation fix → issues filed or updated, in full `owner/repo#num` form → +the wiki page path. + +Rank by measured `offload_value` and say that you are doing so. Name skills and +agents, **never UUIDs or session ids**. Emit **only shapes and counts** — no +transcript content, file contents, command arguments, or user prose ever enters +a report, an issue, or a wiki page (FR-3.5). + +### "Nothing to offload here" is a complete answer + +If nothing clears the bar, say so plainly and stop. Do not pad the report to +look thorough. `token-budget` is expected to produce exactly this result, and a +run against it that yields confident candidates is **a bug in this plugin**, not +a finding about `token-budget`. Report the `pure-inference` findings by name — +they are the evidence that you looked rather than shrugged. + +### Never + +- Never edit the plugin you audit, and never implement your own recommendation. +- Never call a Gitea tool directly from this skill. +- Never file a candidate the gate rejected, or one whose repo is unavailable. +- Never re-run an audit "to be safe" without the marker search. That is the one + mistake that makes this plugin hated. diff --git a/skills/offload-trend/SKILL.md b/skills/offload-trend/SKILL.md index d442e7f..0a51bf2 100644 --- a/skills/offload-trend/SKILL.md +++ b/skills/offload-trend/SKILL.md @@ -73,7 +73,7 @@ list and the unaddressed list are all in the output. ### The statuses | Status | Means | -|---|---| +| --- | --- | | `new` | Not present in the compared snapshot. | | `persisting` | Both volume-normalized metrics inside the ±10% noise band. | | `grown` / `shrunk` | Cost per invocation moved beyond the band; share of spend breaks ties. | diff --git a/tests/filing.test.sh b/tests/filing.test.sh new file mode 100755 index 0000000..f5653bc --- /dev/null +++ b/tests/filing.test.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# bin/filing-plan: output path A — the marker-based idempotency contract +# (FR-6.3), template rendering (FR-6.1), and repo-less degradation (FR-6.4). +# +# The load-bearing case is "run 1 files, run 2 comments". It is proved here the +# only way that means anything: run 1's ACTUAL rendered body is fed back as the +# tracker's state for run 2. Nothing is asserted about the marker by hand. +# +# tests/fixtures/gitea-issues.json is copied verbatim from real Gitea API +# responses (probe issue kotkan/claude-plugin-inference-arbitrage#9, since +# closed), so the shape this parses is the shape the API returns. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +BIN=bin/filing-plan +JUD=tests/fixtures/filing-judgments.json +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +fail=0 + +check() { + local what=$1 got=$2 want=$3 + if [ "$got" = "$want" ]; then + echo " ok $what = $want" + else + echo " FAIL $what = $got (want $want)" >&2 + fail=1 + fi +} +# q +q() { python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print($2)" "$1"; } + +bin/boundary-classify "$JUD" >"$tmp/cls.json" + +plan() { # plan [extra args...] + local existing=$1 out=$2 + shift 2 + $BIN plan --classified "$tmp/cls.json" --judgments "$JUD" \ + --target-repo oleks/claude-plugin-fixture --existing "$existing" \ + --audit-page Audits/fixture-plugin/2026-07-29 \ + --window "2026-07-22 -> 2026-07-29" --run-date 2026-07-29 "$@" >"$out" +} + +echo "== the fixture's two candidates grade as expected (guards the rest) ==" +check "A verdict" "$(q "$tmp/cls.json" "d['candidates'][0]['verdict']")" "file" +check "B verdict" "$(q "$tmp/cls.json" "d['candidates'][1]['verdict']")" "no-offload" + +echo "== RUN 1: empty tracker -> files, with the marker as line 1 ==" +echo '[]' >"$tmp/empty.json" +plan "$tmp/empty.json" "$tmp/plan1.json" +check "to_file" "$(q "$tmp/plan1.json" "d['summary']['to_file']")" "1" +check "to_comment" "$(q "$tmp/plan1.json" "d['summary']['to_comment']")" "0" +check "action" "$(q "$tmp/plan1.json" "d['actions'][0]['action']")" "file" +check "marker is line 1" \ + "$(q "$tmp/plan1.json" "d['actions'][0]['body'].splitlines()[0]")" \ + "" +check "token-offload label required" \ + "$(q "$tmp/plan1.json" "d['actions'][0]['required_labels']")" "['token-offload']" +check "non-file verdict skipped" "$(q "$tmp/plan1.json" "d['actions'][1]['action']")" "skip" + +echo "== the body carries every element of the FR-6 contract ==" +body=$(q "$tmp/plan1.json" "d['actions'][0]['body']") +for needle in \ + "**Measured cost.**" "**Evidence strength.**" "**Boundary position.**" \ + "Determinism tests: T1 ✓ T2 ✓ T3 ✓ T4 ✓ T5 ✓" \ + "**Proposed signature.**" "derive_labels(" "**Examples.**" \ + "**When a human would overrule this.**" "**Escalation path.**" \ + "**What crosses the boundary.**" \ + "Audits/fixture-plugin/2026-07-29" "Rubric v1.0.0"; do + if grep -qF -- "$needle" <<<"$body"; then + echo " ok body contains: $needle" + else + echo " FAIL body missing: $needle" >&2 + fail=1 + fi +done +check "measurement is quoted, not invented" \ + "$(grep -cF 'Recurring sequence observed 17x' <<<"$body")" "1" +check "3 examples incl. the edge case" "$(grep -c '^- ' <<<"$body")" "3" + +echo "== RUN 2 (FR-6.3, S5): run 1's own body is now on the tracker -> comment ==" +python3 - "$tmp/plan1.json" "$tmp/tracker.json" <<'PY' +import json, sys +plan = json.load(open(sys.argv[1])) +filed = next(a for a in plan["actions"] if a["action"] == "file") +json.dump([{"number": 41, "state": "open", "title": filed["proposed_title"], + "body": filed["body"], "comments": []}], open(sys.argv[2], "w")) +PY +plan "$tmp/tracker.json" "$tmp/plan2.json" --status grown +check "files nothing on re-run" "$(q "$tmp/plan2.json" "d['summary']['to_file']")" "0" +check "comments instead" "$(q "$tmp/plan2.json" "d['summary']['to_comment']")" "1" +check "on the right issue" \ + "$(q "$tmp/plan2.json" "d['actions'][0]['issue']")" "oleks/claude-plugin-fixture#41" +check "comment re-carries the marker" \ + "$(q "$tmp/plan2.json" "d['actions'][0]['body'].splitlines()[0]")" \ + "" +check "comment reports the diff status" \ + "$(q "$tmp/plan2.json" "'Status: grown' in d['actions'][0]['body']")" "True" + +echo "== RUN 3: the comment from run 2 is the only marker left (body rewritten) ==" +python3 - "$tmp/plan2.json" "$tmp/tracker3.json" <<'PY' +import json, sys +plan = json.load(open(sys.argv[1])) +c = next(a for a in plan["actions"] if a["action"] == "comment") +json.dump([{"number": 41, "state": "closed", "title": "a human retitled this", + "body": "and rewrote the body, dropping the marker", + "comments": [{"body": c["body"]}]}], open(sys.argv[2], "w")) +PY +plan "$tmp/tracker3.json" "$tmp/plan3.json" +check "still no duplicate" "$(q "$tmp/plan3.json" "d['summary']['to_file']")" "0" +check "found via the comment" "$(q "$tmp/plan3.json" "d['actions'][0]['action']")" "comment" +check "closed state surfaced for issuer" \ + "$(q "$tmp/plan3.json" "d['actions'][0]['issue_state']")" "closed" + +echo "== real Gitea API shapes parse, and a DIFFERENT id does not false-match ==" +plan tests/fixtures/gitea-issues.json "$tmp/plan4.json" +check "unrelated markers -> files" "$(q "$tmp/plan4.json" "d['summary']['to_file']")" "1" +check "searched the fixture" "$(q "$tmp/plan4.json" "d['summary']['searched_issues']")" "1" + +echo "== a marker on two issues warns and takes the lowest number ==" +python3 - "$tmp/plan1.json" "$tmp/dupes.json" <<'PY' +import json, sys +filed = next(a for a in json.load(open(sys.argv[1]))["actions"] if a["action"] == "file") +json.dump([{"number": 77, "state": "open", "title": "later dupe", "body": filed["body"], "comments": []}, + {"number": 41, "state": "open", "title": "original", "body": filed["body"], "comments": []}], + open(sys.argv[2], "w")) +PY +plan "$tmp/dupes.json" "$tmp/plan5.json" +check "lowest issue wins" "$(q "$tmp/plan5.json" "d['actions'][0]['issue_number']")" "41" +check "warns a human" "$(q "$tmp/plan5.json" "len(d['warnings'])")" "1" + +echo "== --existing is mandatory: a skipped search must never mean 'file it all' ==" +if $BIN plan --classified "$tmp/cls.json" --judgments "$JUD" \ + --target-repo oleks/claude-plugin-fixture --audit-page A --window w \ + --run-date 2026-07-29 >/dev/null 2>&1; then + echo " FAIL plan ran without --existing" >&2 + fail=1 +else + echo " ok refuses to plan without a search result" +fi + +echo "== FR-6.4: a target with no writable repo degrades to summary-only ==" +# Real, not synthetic: third-party/baoyu-design is a vendored plugin whose only +# remote is github.com, i.e. nothing this environment can file to. +THIRD=../third-party/baoyu-design +if [ -d "$THIRD" ]; then + bin/plugin-inventory "$THIRD" --json >"$tmp/tp-inv.json" + check "inventory still resolves it" "$(q "$tmp/tp-inv.json" "d['plugin']['name']")" "baoyu-design" + $BIN queries --classified "$tmp/cls.json" --judgments "$JUD" \ + --target-path "$THIRD" --workspace /nonexistent >"$tmp/tp-q.json" + check "filing" "$(q "$tmp/tp-q.json" "d['filing']")" "unavailable" + check "reason names the remote" \ + "$(q "$tmp/tp-q.json" "'github.com' in d['filing_unavailable_reason']")" "True" + check "no queries emitted" "$(q "$tmp/tp-q.json" "'queries' in d")" "False" + + $BIN plan --classified "$tmp/cls.json" --judgments "$JUD" \ + --target-path "$THIRD" --workspace /nonexistent --existing "$tmp/empty.json" \ + --audit-page A --window w --run-date 2026-07-29 >"$tmp/tp-plan.json" + check "nothing filed" "$(q "$tmp/tp-plan.json" "d['summary']['to_file']")" "0" + check "candidate marked unavailable" \ + "$(q "$tmp/tp-plan.json" "d['actions'][0]['filing']")" "unavailable" +else + echo " skip (no third-party/baoyu-design checkout)" +fi + +echo "== a writable target resolves through its own checkout's remote ==" +if [ -d ../worktree-discipline/.git ]; then + $BIN queries --classified "$tmp/cls.json" --judgments "$JUD" \ + --target-path ../worktree-discipline >"$tmp/wd-q.json" + check "repo" "$(q "$tmp/wd-q.json" "d['repo']")" "kotkan/claude-plugin-worktree-discipline" + check "filing" "$(q "$tmp/wd-q.json" "d['filing']")" "available" + check "label scan is the authoritative query" \ + "$(q "$tmp/wd-q.json" "d['queries']['label_scan']['labels']")" "['token-offload']" +else + echo " skip (no sibling worktree-discipline checkout)" +fi + +exit "$fail" diff --git a/tests/fixtures/filing-judgments.json b/tests/fixtures/filing-judgments.json new file mode 100644 index 0000000..e0612e4 --- /dev/null +++ b/tests/fixtures/filing-judgments.json @@ -0,0 +1,69 @@ +{ + "target": "fixture-plugin", + "note": "Synthetic. Two candidates: one that clears the FR-4.2 gate and the 2% filing threshold, one that is correctly left to inference. Shapes match tests/calibration/*.judgments.json.", + "audited_spend_weighted_tokens": 1000000, + "candidates": [ + { + "candidate_id": "fixture-plugin/skill-a/label-derivation", + "skill": "fixture-plugin:skill-a", + "headline": "label derivation is a documented lookup table applied by inference", + "category": "usage", + "position": "llm-over-script-digest", + "determinism_tests": { + "T1": true, + "T2": true, + "T3": true, + "T4": true, + "T5": true + }, + "falsifiability": { + "signature": "derive_labels(repo: str, issue: Issue) -> tuple[Labels, list[Ambiguity]]", + "examples": [ + "repo='x', issue.title='fix crash' -> (['kind/bug'], [])", + "repo='x', issue.body has a checklist -> (['kind/epic'], [])", + "EDGE: repo='x', no signal at all -> ([], [Ambiguity('no axis-1 signal, defer to human')])" + ], + "edge_case_index": 2, + "overrule_case": "An issue whose title reads as a bug but which the maintainer is deliberately tracking as a capability gap. The script returns kind/bug; a human says no. It cannot see that intent — it is not in the issue text." + }, + "escalation_path": "Rows the table cannot resolve are emitted as Ambiguity and go to the model, never defaulted. The script proposes labels and never applies them; application stays behind the judgment gate (P4).", + "digest_schema": "One typed row per issue: (number, title, has_checklist, proposed_labels, ambiguities). Replaces reading every issue whole.", + "measurement": { + "offload_value": 200000, + "invocations": 12, + "attributed_turns": 200, + "mtr": 0.68, + "judgment_density": 0.11, + "ngram": { + "sig": ["list_issues()", "issue_read()", "label_read()"], + "recurrences": 17 + } + } + }, + { + "candidate_id": "fixture-plugin/skill-b/naming", + "skill": "fixture-plugin:skill-b", + "category": "usage", + "position": "pure-inference", + "determinism_tests": { + "T1": false, + "T2": false, + "T3": true, + "T4": false, + "T5": false + }, + "falsifiability": { + "signature": null, + "examples": [], + "overrule_case": null + }, + "escalation_path": null, + "digest_schema": null, + "measurement": { + "offload_value": 90000, + "invocations": 8, + "attributed_turns": 120 + } + } + ] +} diff --git a/tests/fixtures/gitea-issues.json b/tests/fixtures/gitea-issues.json new file mode 100644 index 0000000..19c285b --- /dev/null +++ b/tests/fixtures/gitea-issues.json @@ -0,0 +1,14 @@ +[ + { + "number": 9, + "state": "closed", + "title": "[test artifact] Phase 6 idempotency probe — ia-candidate marker search", + "body": "\n\nScratch issue created to verify, for real, that the FR-6.3 marker search finds an\n`ia-candidate` marker through the Gitea API. Not a real candidate; will be closed\nimmediately. See kotkan/claude-plugin-inference-arbitrage#6.", + "comments": [ + { + "id": 37761, + "body": "\n\nSecond marker, in a comment only, to verify the comment-carried identity path\n(an issue whose body a human rewrote must still be found)." + } + ] + } +]