Skip to content

Commit 42fec37

Browse files
feat(cartridges): bundle bug-filing-mcp v0.2 — core (Teranga) discoverability (#285)
<!-- SPDX-License-Identifier: CC-BY-SA-4.0 --> ## Summary Bundles the bug-filing-mcp v0.2 cartridge (the boj-side packaging of the real feedback-o-tron engine) and makes it discoverable as a **core (Teranga)** capability. Closes the boj-side wiring of the autonomous bug-reporting pipeline epic #274 (via #282/#283); companion PRs: hyperpolymath/feedback-o-tron#51 (engine) and hyperpolymath/boj-server-cartridges#85 (canonical cartridge). ## Changes - `cartridges/bug-filing-mcp/` — verbatim snapshot of the canonical v0.2 cartridge (flat-name convention, compatible with `scripts/refresh-bundled-cartridges.sh`). Tools: `research_feedback` (duplicate search + template questions), `synthesize_feedback` (usefulness-gated draft + open questions), `submit_feedback` (fail-closed `template_data` validation, multi-forge). `BojRest.Catalog` picks it up from the directory scan; the gateway reaches it via `POST /cartridge/bug-filing-mcp/invoke` (JsInvoker — the manifest claims no `ffi` block and no `available: true`). - `mcp-bridge/lib/offline-menu.js` — `tier_teranga` entry (owner decision) with an honest requires-local-engine note; `summary.total` reconciled to the generator's authoritative 127 (`ready` left at its legacy value, matching the #256 reconcile precedent). - `.machine_readable/integrations/feedback-o-tron.a2ml` — intake wire (`127.0.0.1:7722`, `FEEDBACK_A_TRON_HTTP=1`), the three tools, and the wrapping cartridge recorded. - `.github/ISSUE_TEMPLATE/{bug.yml,config.yml}` — issue forms so this repo's tracker is fetchable/hydratable by the pipeline it hosts. ## RSR Quality Checklist ### Required - [x] Tests pass (`boot_smoke` OK under Deno — 68 tools; `dispatch_test` 15/15) - [x] Code is formatted (JS menu entry matches file style) - [x] Linter is clean (no new warnings or errors) - [x] No banned language patterns (no TypeScript, no npm/bun, no Go/Python) - [x] No `unsafe` blocks without `// SAFETY:` comments - [x] No banned functions (`believe_me`, `unsafeCoerce`, `Obj.magic`, `Admitted`, `sorry`) — the bundled `SafeBugFiling.idr` is `%default total` with zero escape hatches - [x] SPDX license headers present on all new/modified source files - [x] No secrets, credentials, or `.env` files included ### As Applicable - [x] `.machine_readable/` integrations updated (`feedback-o-tron.a2ml`) - [x] Documentation updated for user-facing changes (bundled cartridge README) - [ ] `TOPOLOGY.md` updated — no architecture change (catalogue content only) - [ ] `CHANGELOG` — deferring to the squash-merge summary - [x] New dependencies reviewed — none added - [ ] ABI/FFI changes validated — no changes to `src/abi/` or `ffi/zig/` ## Testing - `deno run --allow-read mcp-bridge/lib/generate-offline-menu.js` → reports 127 cartridges, matching the reconciled `summary.total`. - `deno run mcp-bridge/tests/boot_smoke.js deno run --allow-read --allow-env mcp-bridge/main.js` → `OK: serverInfo.name=boj-server, tools=68, exit=0`. - `deno test mcp-bridge/tests/dispatch_test.js` → 15 passed / 0 failed. - Cartridge integration test ran 8/8 against a live engine in the canonical repo (see companion PR). ## Screenshots Not applicable (catalogue/menu change; terminal outputs quoted above). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017LDhR8gtnittEWSafunnrq --- _Generated by [Claude Code](https://claude.ai/code/session_017LDhR8gtnittEWSafunnrq)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent b85de60 commit 42fec37

14 files changed

Lines changed: 933 additions & 2 deletions

File tree

.github/ISSUE_TEMPLATE/bug.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
name: "Bug report"
3+
description: "Report something that is broken or behaving unexpectedly."
4+
labels: ["bug"]
5+
body:
6+
- type: textarea
7+
id: what-happened
8+
attributes:
9+
label: "What happened?"
10+
description: "Describe the problem. What did you see?"
11+
validations:
12+
required: true
13+
- type: textarea
14+
id: repro-steps
15+
attributes:
16+
label: "Steps to reproduce"
17+
description: "Exact steps that trigger the problem, one per line."
18+
placeholder: "1. ...\n2. ...\n3. ..."
19+
validations:
20+
required: true
21+
- type: textarea
22+
id: expected
23+
attributes:
24+
label: "Expected behaviour"
25+
description: "What did you expect to happen instead?"
26+
- type: input
27+
id: version
28+
attributes:
29+
label: "Version"
30+
placeholder: "e.g. 0.4.7 / commit SHA"
31+
validations:
32+
required: true
33+
- type: dropdown
34+
id: component
35+
attributes:
36+
label: "Component"
37+
description: "Which part of boj-server is affected?"
38+
options:
39+
- "MCP bridge"
40+
- "Elixir REST core"
41+
- "Cartridges"
42+
- "ABI / proofs"
43+
- "Coordination (coord_*)"
44+
- "Tray / TUI"
45+
- "CI / workflows"
46+
- "Other"
47+
- type: textarea
48+
id: logs
49+
attributes:
50+
label: "Relevant logs / stack trace"
51+
description: "Paste any logs or stack traces."
52+
render: shell
53+
- type: textarea
54+
id: environment
55+
attributes:
56+
label: "Environment"
57+
description: "OS, runtime (Deno/Node/Bun) and versions, deployment shape (stdio/http/container)"

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
blank_issues_enabled: true

.machine_readable/integrations/feedback-o-tron.a2ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
name = "feedback-o-tron"
66
type = "bug-reporter"
77
repository = "https://github.com/hyperpolymath/feedback-o-tron"
8+
cartridge = "bug-filing-mcp"
9+
10+
[intake]
11+
transport = "http"
12+
endpoint = "http://127.0.0.1:7722"
13+
enable-with = "FEEDBACK_A_TRON_HTTP=1"
14+
tools = ["research_feedback", "synthesize_feedback", "submit_feedback"]
815

916
[reporting-config]
1017
platforms = ["github", "gitlab", "bugzilla"]
1118
deduplication = true
1219
audit-logging = true
20+
template-validation = "fail-closed"
1321
auto-file-upstream = "on-external-dependency-failure"
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
// Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= bug-filing-mcp
4+
5+
Autonomous bug-report filing cartridge, v0.2. It is the boj-side packaging of the
6+
real *feedback-o-tron* engine for the autonomous bug-reporting pipeline
7+
(see `feedback-o-tron/docs/AUTONOMOUS-BUG-PIPELINE.adoc`).
8+
9+
[IMPORTANT]
10+
====
11+
*This is not `feedback-mcp`.* `feedback-mcp` is an unrelated in-memory *sentiment
12+
counter*. Per owner decision *D0 = new wrapping cartridge*, that cartridge is left
13+
untouched and this *new* cartridge wraps the real bug-filing engine. The two only
14+
share the `127.0.0.1:7722` placeholder port; run one backend or the other.
15+
====
16+
17+
== What it does
18+
19+
v0.2 exposes the engine's full interactive loop as three tools:
20+
21+
. `research_feedback` — *before filing anything*: searches the target forge for
22+
similar existing issues (scored by title similarity), checks the engine's
23+
local dedup index (with recurrence stats, so a recurring theme is visible as
24+
a pattern rather than a one-off), and returns the target repo's issue-form
25+
questions.
26+
. `synthesize_feedback` — shapes raw feedback into a report fit for the
27+
receiver: classifies intent, fetches the repo's own issue template, hydrates
28+
its fields from context and system state, and returns a draft plus the *open
29+
questions* still needed. The caller (an LLM agent, typically) resolves those
30+
with its user and iterates.
31+
. `submit_feedback` — files to one or more forges (GitHub, GitLab, Bitbucket,
32+
Codeberg, Bugzilla, email). When `template_data` is supplied, answers are
33+
validated against the fetched form schema (*fail closed*) and rendered as the
34+
issue body. Dedup and audit logging are engine-side.
35+
36+
== The usefulness doctrine
37+
38+
The intent gate is *usefulness, not tone*:
39+
40+
* Harsh-but-substantive feedback keeps its signal — nothing is sanitized away.
41+
* Zero-signal hostility is rejected with a stated reason (audit-logged
42+
engine-side, never filed). A rejection is a *successful* tool result:
43+
`{"rejected": true, "reason": ...}`.
44+
* Mixed content is salvaged: the actionable core is extracted, the hostile
45+
wrapping stripped, and both facts are reported back
46+
(`salvaged: true`, `stripped_reason`).
47+
* Useless-but-genuine feedback is never silently discarded — it comes back as
48+
open questions that would make it fileable.
49+
50+
== Core status (Teranga)
51+
52+
This cartridge is Teranga-tier: bug filing is a core capability of the boj
53+
system. It still requires the engine running locally (see below) — the manifest
54+
does not claim `available: true`; availability is a deployment fact, not a
55+
catalogue claim.
56+
57+
== How it wires
58+
59+
[source,text]
60+
----
61+
boj gateway :7700
62+
POST /cartridge/bug-filing-mcp/invoke {tool, arguments}
63+
-> bug-filing-mcp/mod.js (Deno JS worker)
64+
-> POST http://127.0.0.1:7722/api/v1/{research,synthesize,submit}_feedback
65+
-> feedback-o-tron Synthesis Engine + Submitter -> gh / glab / REST ...
66+
----
67+
68+
The cartridge is a thin HTTP client; classification, template hydration,
69+
validation, dedup, audit, rate limiting and dispatch all live in the engine.
70+
The `ffi/` and `abi/` directories carry the standard cartridge layout: the
71+
Idris2 module (`abi/BugFilingMcp/SafeBugFiling.idr`, compiled in CI-style
72+
checks) proves the pipeline's state machine — a rejected report has *no path*
73+
to submission, and submission is only reachable through validation. The Zig
74+
FFI answers with a structured delegation notice (the live path is mod.js);
75+
no `.so` is declared in the manifest.
76+
77+
== Prerequisites
78+
79+
Run the feedback-o-tron engine with its HTTP intake enabled:
80+
81+
[source,sh]
82+
----
83+
FEEDBACK_A_TRON_HTTP=1 feedback-a-tron # listens on 127.0.0.1:7722
84+
# override: FEEDBACK_A_TRON_HTTP_PORT, FEEDBACK_A_TRON_HTTP_BIND
85+
----
86+
87+
Point the cartridge elsewhere with `BUG_FILING_BACKEND_URL` if needed.
88+
89+
== The interactive loop, end to end
90+
91+
[source,text]
92+
----
93+
1. research_feedback {repo, title} -> similar issues + template questions
94+
2. synthesize_feedback {raw_feedback, repo} -> draft + open_questions (or rejected/salvaged)
95+
3. ... resolve open_questions with your user, repeat 2 if needed ...
96+
4. submit_feedback {title, body, repo, template, template_data, dry_run:true} -> preview
97+
5. submit_feedback {..., dry_run:false} -> filed, deduplicated, audit-logged
98+
----
99+
100+
== Tools
101+
102+
[cols="1,1,3",options="header"]
103+
|===
104+
| Tool | Required | Meaning
105+
| `research_feedback` | `repo`, `title` | Duplicate search + template questions; `limit` (≤20), `body`, `include_templates` optional
106+
| `synthesize_feedback` | `raw_feedback`, `repo` | Intent-gated, template-hydrated draft + open questions; `context`, `system_state`, `template`, `network_probe` optional
107+
| `submit_feedback` | `title`, `body`, `repo` | Multi-forge filing; `template`+`template_data` validated fail-closed; `platforms`, `labels`, `dry_run`, `skip_dedupe` optional
108+
|===
109+
110+
If the backend is not running, the cartridge returns a `503` with a hint to
111+
start the engine with `FEEDBACK_A_TRON_HTTP=1`.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
-- SPDX-License-Identifier: MPL-2.0
2+
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
||| BugFilingMcp.SafeBugFiling: Formally verified bug-filing pipeline states.
4+
|||
5+
||| Cartridge: bug-filing-mcp (wraps the feedback-o-tron engine)
6+
||| Matrix cell: Development domain x {MCP, REST} protocols
7+
|||
8+
||| This module defines the interactive filing pipeline as a state machine
9+
||| that prevents:
10+
||| - Submitting a report that was rejected by the usefulness gate
11+
||| - Submitting template answers that were never validated
12+
||| - Skipping research when a duplicate check is required
13+
|||
14+
||| Doctrine encoded (feedback-o-tron design intent):
15+
||| - the gate is usefulness, not tone: Salvaged is a live state, not an end
16+
||| - zero-signal hostility dead-ends in Rejected (no path to Submitted)
17+
||| - open questions loop Synthesized -> Synthesized until validation
18+
|||
19+
||| State machine:
20+
||| Draft -> Researched -> Synthesized -> Validated -> Submitted
21+
||| (Synthesized may be Salvaged first; Rejected is terminal)
22+
module BugFilingMcp.SafeBugFiling
23+
24+
import Data.List
25+
26+
%default total
27+
28+
-- ═══════════════════════════════════════════════════════════════════════════
29+
-- Filing Pipeline State Machine
30+
-- ═══════════════════════════════════════════════════════════════════════════
31+
32+
||| Pipeline lifecycle states for one piece of feedback.
33+
public export
34+
data FilingState
35+
= Draft -- raw feedback exists, nothing checked
36+
| Researched -- duplicates + template questions fetched
37+
| Salvaged -- hostile wrapping stripped; actionable core kept
38+
| Synthesized -- template hydrated; open questions may remain
39+
| Validated -- answers passed the form-schema boundary
40+
| Submitted -- dispatched to the forge(s)
41+
| Rejected -- zero-signal hostility: terminal, never filed
42+
43+
||| Equality for filing states.
44+
public export
45+
Eq FilingState where
46+
Draft == Draft = True
47+
Researched == Researched = True
48+
Salvaged == Salvaged = True
49+
Synthesized == Synthesized = True
50+
Validated == Validated = True
51+
Submitted == Submitted = True
52+
Rejected == Rejected = True
53+
_ == _ = False
54+
55+
||| Valid state transitions (enforced at the type level).
56+
||| Critically there is NO constructor targeting Submitted except from
57+
||| Validated, and NO constructor leaving Rejected.
58+
public export
59+
data ValidTransition : FilingState -> FilingState -> Type where
60+
Research : ValidTransition Draft Researched
61+
SalvageCore : ValidTransition Researched Salvaged
62+
Synthesize : ValidTransition Researched Synthesized
63+
Resynth : ValidTransition Synthesized Synthesized -- open-questions loop
64+
FromSalvage : ValidTransition Salvaged Synthesized
65+
Validate : ValidTransition Synthesized Validated
66+
Submit : ValidTransition Validated Submitted
67+
RejectR : ValidTransition Researched Rejected
68+
RejectD : ValidTransition Draft Rejected
69+
70+
||| Runtime transition validator (mirrors ValidTransition).
71+
public export
72+
canTransition : FilingState -> FilingState -> Bool
73+
canTransition Draft Researched = True
74+
canTransition Researched Salvaged = True
75+
canTransition Researched Synthesized = True
76+
canTransition Synthesized Synthesized = True
77+
canTransition Salvaged Synthesized = True
78+
canTransition Synthesized Validated = True
79+
canTransition Validated Submitted = True
80+
canTransition Researched Rejected = True
81+
canTransition Draft Rejected = True
82+
canTransition _ _ = False
83+
84+
||| Rejected is terminal: no transition out of it is ever valid.
85+
export
86+
rejectedIsTerminal : (s : FilingState) -> ValidTransition Rejected s -> Void
87+
rejectedIsTerminal _ Research impossible
88+
rejectedIsTerminal _ SalvageCore impossible
89+
rejectedIsTerminal _ Synthesize impossible
90+
rejectedIsTerminal _ Resynth impossible
91+
rejectedIsTerminal _ FromSalvage impossible
92+
rejectedIsTerminal _ Validate impossible
93+
rejectedIsTerminal _ Submit impossible
94+
rejectedIsTerminal _ RejectR impossible
95+
rejectedIsTerminal _ RejectD impossible
96+
97+
||| Submission only ever follows validation: the sole transition into
98+
||| Submitted starts at Validated.
99+
export
100+
submitRequiresValidation : (s : FilingState) -> ValidTransition s Submitted -> s = Validated
101+
submitRequiresValidation Validated Submit = Refl
102+
103+
-- ═══════════════════════════════════════════════════════════════════════════
104+
-- MCP Tool Definitions
105+
-- ═══════════════════════════════════════════════════════════════════════════
106+
107+
||| MCP tools exposed by this cartridge.
108+
public export
109+
data McpTool
110+
= ToolResearch -- research_feedback
111+
| ToolSynthesize -- synthesize_feedback
112+
| ToolSubmit -- submit_feedback
113+
114+
||| MCP tool name (must match cartridge.json).
115+
public export
116+
toolName : McpTool -> String
117+
toolName ToolResearch = "research_feedback"
118+
toolName ToolSynthesize = "synthesize_feedback"
119+
toolName ToolSubmit = "submit_feedback"
120+
121+
||| The pipeline state each tool drives toward.
122+
public export
123+
toolTarget : McpTool -> FilingState
124+
toolTarget ToolResearch = Researched
125+
toolTarget ToolSynthesize = Synthesized
126+
toolTarget ToolSubmit = Submitted
127+
128+
-- ═══════════════════════════════════════════════════════════════════════════
129+
-- C-ABI Exports
130+
-- ═══════════════════════════════════════════════════════════════════════════
131+
132+
||| Filing state to integer for the C ABI.
133+
public export
134+
filingStateToInt : FilingState -> Int
135+
filingStateToInt Draft = 0
136+
filingStateToInt Researched = 1
137+
filingStateToInt Salvaged = 2
138+
filingStateToInt Synthesized = 3
139+
filingStateToInt Validated = 4
140+
filingStateToInt Submitted = 5
141+
filingStateToInt Rejected = 6
142+
143+
||| Integer to filing state (unknown collapses to Rejected: fail closed).
144+
public export
145+
intToFilingState : Int -> FilingState
146+
intToFilingState 0 = Draft
147+
intToFilingState 1 = Researched
148+
intToFilingState 2 = Salvaged
149+
intToFilingState 3 = Synthesized
150+
intToFilingState 4 = Validated
151+
intToFilingState 5 = Submitted
152+
intToFilingState _ = Rejected
153+
154+
||| FFI: Validate a state transition.
155+
export
156+
bf_can_transition : Int -> Int -> Int
157+
bf_can_transition from to =
158+
if canTransition (intToFilingState from) (intToFilingState to) then 1 else 0
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- SPDX-License-Identifier: MPL-2.0
2+
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
-- Note: Idris2 package identifiers reject hyphens, hence "bugfilingmcp".
4+
package bugfilingmcp
5+
6+
authors = "Jonathan D.A. Jewell"
7+
version = 0.2.0
8+
sourcedir = "."
9+
10+
modules = BugFilingMcp.SafeBugFiling
11+
12+
depends = base

0 commit comments

Comments
 (0)