6.5 KiB
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 ## <section> 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
<!-- ia-candidate: <candidate_id> -->
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.
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.
<!-- ia-candidate: {{candidate_id}} -->
**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.
<!-- ia-candidate: {{candidate_id}} -->
**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_issuesnorlist_issuesreturns the issuebody. 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 withissue_read(method="get"), and comments withissue_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.
1. candidates list_issues(owner, repo, labels=["token-offload"], state="all")
search_issues(query="ia-candidate: <candidate_id>", 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 <merged.json> -> 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
<!--\s*ia-candidate:\s*<id>\s*--> 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.