Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-License-Identifier: MPL-2.0
name: "Bug report"
description: "Report something that is broken or behaving unexpectedly."
labels: ["bug"]
body:
- type: textarea
id: what-happened
attributes:
label: "What happened?"
description: "Describe the problem. What did you see?"
validations:
required: true
- type: textarea
id: repro-steps
attributes:
label: "Steps to reproduce"
description: "Exact steps that trigger the problem, one per line."
placeholder: "1. ...\n2. ...\n3. ..."
validations:
required: true
- type: textarea
id: expected
attributes:
label: "Expected behaviour"
description: "What did you expect to happen instead?"
- type: input
id: version
attributes:
label: "Version"
placeholder: "e.g. 0.4.7 / commit SHA"
validations:
required: true
- type: dropdown
id: component
attributes:
label: "Component"
description: "Which part of boj-server is affected?"
options:
- "MCP bridge"
- "Elixir REST core"
- "Cartridges"
- "ABI / proofs"
- "Coordination (coord_*)"
- "Tray / TUI"
- "CI / workflows"
- "Other"
- type: textarea
id: logs
attributes:
label: "Relevant logs / stack trace"
description: "Paste any logs or stack traces."
render: shell
- type: textarea
id: environment
attributes:
label: "Environment"
description: "OS, runtime (Deno/Node/Bun) and versions, deployment shape (stdio/http/container)"
2 changes: 2 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: MPL-2.0
blank_issues_enabled: true
8 changes: 8 additions & 0 deletions .machine_readable/integrations/feedback-o-tron.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@
name = "feedback-o-tron"
type = "bug-reporter"
repository = "https://github.com/hyperpolymath/feedback-o-tron"
cartridge = "bug-filing-mcp"

[intake]
transport = "http"
endpoint = "http://127.0.0.1:7722"
enable-with = "FEEDBACK_A_TRON_HTTP=1"
tools = ["research_feedback", "synthesize_feedback", "submit_feedback"]

[reporting-config]
platforms = ["github", "gitlab", "bugzilla"]
deduplication = true
audit-logging = true
template-validation = "fail-closed"
auto-file-upstream = "on-external-dependency-failure"
111 changes: 111 additions & 0 deletions cartridges/bug-filing-mcp/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: CC-BY-SA-4.0
// Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
= bug-filing-mcp

Autonomous bug-report filing cartridge, v0.2. It is the boj-side packaging of the
real *feedback-o-tron* engine for the autonomous bug-reporting pipeline
(see `feedback-o-tron/docs/AUTONOMOUS-BUG-PIPELINE.adoc`).

[IMPORTANT]
====
*This is not `feedback-mcp`.* `feedback-mcp` is an unrelated in-memory *sentiment
counter*. Per owner decision *D0 = new wrapping cartridge*, that cartridge is left
untouched and this *new* cartridge wraps the real bug-filing engine. The two only
share the `127.0.0.1:7722` placeholder port; run one backend or the other.
====

== What it does

v0.2 exposes the engine's full interactive loop as three tools:

. `research_feedback` — *before filing anything*: searches the target forge for
similar existing issues (scored by title similarity), checks the engine's
local dedup index (with recurrence stats, so a recurring theme is visible as
a pattern rather than a one-off), and returns the target repo's issue-form
questions.
. `synthesize_feedback` — shapes raw feedback into a report fit for the
receiver: classifies intent, fetches the repo's own issue template, hydrates
its fields from context and system state, and returns a draft plus the *open
questions* still needed. The caller (an LLM agent, typically) resolves those
with its user and iterates.
. `submit_feedback` — files to one or more forges (GitHub, GitLab, Bitbucket,
Codeberg, Bugzilla, email). When `template_data` is supplied, answers are
validated against the fetched form schema (*fail closed*) and rendered as the
issue body. Dedup and audit logging are engine-side.

== The usefulness doctrine

The intent gate is *usefulness, not tone*:

* Harsh-but-substantive feedback keeps its signal — nothing is sanitized away.
* Zero-signal hostility is rejected with a stated reason (audit-logged
engine-side, never filed). A rejection is a *successful* tool result:
`{"rejected": true, "reason": ...}`.
* Mixed content is salvaged: the actionable core is extracted, the hostile
wrapping stripped, and both facts are reported back
(`salvaged: true`, `stripped_reason`).
* Useless-but-genuine feedback is never silently discarded — it comes back as
open questions that would make it fileable.

== Core status (Teranga)

This cartridge is Teranga-tier: bug filing is a core capability of the boj
system. It still requires the engine running locally (see below) — the manifest
does not claim `available: true`; availability is a deployment fact, not a
catalogue claim.

== How it wires

[source,text]
----
boj gateway :7700
POST /cartridge/bug-filing-mcp/invoke {tool, arguments}
-> bug-filing-mcp/mod.js (Deno JS worker)
-> POST http://127.0.0.1:7722/api/v1/{research,synthesize,submit}_feedback
-> feedback-o-tron Synthesis Engine + Submitter -> gh / glab / REST ...
----

The cartridge is a thin HTTP client; classification, template hydration,
validation, dedup, audit, rate limiting and dispatch all live in the engine.
The `ffi/` and `abi/` directories carry the standard cartridge layout: the
Idris2 module (`abi/BugFilingMcp/SafeBugFiling.idr`, compiled in CI-style
checks) proves the pipeline's state machine — a rejected report has *no path*
to submission, and submission is only reachable through validation. The Zig
FFI answers with a structured delegation notice (the live path is mod.js);
no `.so` is declared in the manifest.

== Prerequisites

Run the feedback-o-tron engine with its HTTP intake enabled:

[source,sh]
----
FEEDBACK_A_TRON_HTTP=1 feedback-a-tron # listens on 127.0.0.1:7722
# override: FEEDBACK_A_TRON_HTTP_PORT, FEEDBACK_A_TRON_HTTP_BIND
----

Point the cartridge elsewhere with `BUG_FILING_BACKEND_URL` if needed.

== The interactive loop, end to end

[source,text]
----
1. research_feedback {repo, title} -> similar issues + template questions
2. synthesize_feedback {raw_feedback, repo} -> draft + open_questions (or rejected/salvaged)
3. ... resolve open_questions with your user, repeat 2 if needed ...
4. submit_feedback {title, body, repo, template, template_data, dry_run:true} -> preview
5. submit_feedback {..., dry_run:false} -> filed, deduplicated, audit-logged
----

== Tools

[cols="1,1,3",options="header"]
|===
| Tool | Required | Meaning
| `research_feedback` | `repo`, `title` | Duplicate search + template questions; `limit` (≤20), `body`, `include_templates` optional
| `synthesize_feedback` | `raw_feedback`, `repo` | Intent-gated, template-hydrated draft + open questions; `context`, `system_state`, `template`, `network_probe` optional
| `submit_feedback` | `title`, `body`, `repo` | Multi-forge filing; `template`+`template_data` validated fail-closed; `platforms`, `labels`, `dry_run`, `skip_dedupe` optional
|===

If the backend is not running, the cartridge returns a `503` with a hint to
start the engine with `FEEDBACK_A_TRON_HTTP=1`.
158 changes: 158 additions & 0 deletions cartridges/bug-filing-mcp/abi/BugFilingMcp/SafeBugFiling.idr
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
-- SPDX-License-Identifier: MPL-2.0
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
||| BugFilingMcp.SafeBugFiling: Formally verified bug-filing pipeline states.
|||
||| Cartridge: bug-filing-mcp (wraps the feedback-o-tron engine)
||| Matrix cell: Development domain x {MCP, REST} protocols
|||
||| This module defines the interactive filing pipeline as a state machine
||| that prevents:
||| - Submitting a report that was rejected by the usefulness gate
||| - Submitting template answers that were never validated
||| - Skipping research when a duplicate check is required
|||
||| Doctrine encoded (feedback-o-tron design intent):
||| - the gate is usefulness, not tone: Salvaged is a live state, not an end
||| - zero-signal hostility dead-ends in Rejected (no path to Submitted)
||| - open questions loop Synthesized -> Synthesized until validation
|||
||| State machine:
||| Draft -> Researched -> Synthesized -> Validated -> Submitted
||| (Synthesized may be Salvaged first; Rejected is terminal)
module BugFilingMcp.SafeBugFiling

import Data.List

%default total

-- ═══════════════════════════════════════════════════════════════════════════
-- Filing Pipeline State Machine
-- ═══════════════════════════════════════════════════════════════════════════

||| Pipeline lifecycle states for one piece of feedback.
public export
data FilingState
= Draft -- raw feedback exists, nothing checked
| Researched -- duplicates + template questions fetched
| Salvaged -- hostile wrapping stripped; actionable core kept
| Synthesized -- template hydrated; open questions may remain
| Validated -- answers passed the form-schema boundary
| Submitted -- dispatched to the forge(s)
| Rejected -- zero-signal hostility: terminal, never filed

||| Equality for filing states.
public export
Eq FilingState where
Draft == Draft = True
Researched == Researched = True
Salvaged == Salvaged = True
Synthesized == Synthesized = True
Validated == Validated = True
Submitted == Submitted = True
Rejected == Rejected = True
_ == _ = False

||| Valid state transitions (enforced at the type level).
||| Critically there is NO constructor targeting Submitted except from
||| Validated, and NO constructor leaving Rejected.
public export
data ValidTransition : FilingState -> FilingState -> Type where
Research : ValidTransition Draft Researched
SalvageCore : ValidTransition Researched Salvaged
Synthesize : ValidTransition Researched Synthesized
Resynth : ValidTransition Synthesized Synthesized -- open-questions loop
FromSalvage : ValidTransition Salvaged Synthesized
Validate : ValidTransition Synthesized Validated
Submit : ValidTransition Validated Submitted
RejectR : ValidTransition Researched Rejected
RejectD : ValidTransition Draft Rejected

||| Runtime transition validator (mirrors ValidTransition).
public export
canTransition : FilingState -> FilingState -> Bool
canTransition Draft Researched = True
canTransition Researched Salvaged = True
canTransition Researched Synthesized = True
canTransition Synthesized Synthesized = True
canTransition Salvaged Synthesized = True
canTransition Synthesized Validated = True
canTransition Validated Submitted = True
canTransition Researched Rejected = True
canTransition Draft Rejected = True
canTransition _ _ = False

||| Rejected is terminal: no transition out of it is ever valid.
export
rejectedIsTerminal : (s : FilingState) -> ValidTransition Rejected s -> Void
rejectedIsTerminal _ Research impossible
rejectedIsTerminal _ SalvageCore impossible
rejectedIsTerminal _ Synthesize impossible
rejectedIsTerminal _ Resynth impossible
rejectedIsTerminal _ FromSalvage impossible
rejectedIsTerminal _ Validate impossible
rejectedIsTerminal _ Submit impossible
rejectedIsTerminal _ RejectR impossible
rejectedIsTerminal _ RejectD impossible

||| Submission only ever follows validation: the sole transition into
||| Submitted starts at Validated.
export
submitRequiresValidation : (s : FilingState) -> ValidTransition s Submitted -> s = Validated
submitRequiresValidation Validated Submit = Refl

-- ═══════════════════════════════════════════════════════════════════════════
-- MCP Tool Definitions
-- ═══════════════════════════════════════════════════════════════════════════

||| MCP tools exposed by this cartridge.
public export
data McpTool
= ToolResearch -- research_feedback
| ToolSynthesize -- synthesize_feedback
| ToolSubmit -- submit_feedback

||| MCP tool name (must match cartridge.json).
public export
toolName : McpTool -> String
toolName ToolResearch = "research_feedback"
toolName ToolSynthesize = "synthesize_feedback"
toolName ToolSubmit = "submit_feedback"

||| The pipeline state each tool drives toward.
public export
toolTarget : McpTool -> FilingState
toolTarget ToolResearch = Researched
toolTarget ToolSynthesize = Synthesized
toolTarget ToolSubmit = Submitted

-- ═══════════════════════════════════════════════════════════════════════════
-- C-ABI Exports
-- ═══════════════════════════════════════════════════════════════════════════

||| Filing state to integer for the C ABI.
public export
filingStateToInt : FilingState -> Int
filingStateToInt Draft = 0
filingStateToInt Researched = 1
filingStateToInt Salvaged = 2
filingStateToInt Synthesized = 3
filingStateToInt Validated = 4
filingStateToInt Submitted = 5
filingStateToInt Rejected = 6

||| Integer to filing state (unknown collapses to Rejected: fail closed).
public export
intToFilingState : Int -> FilingState
intToFilingState 0 = Draft
intToFilingState 1 = Researched
intToFilingState 2 = Salvaged
intToFilingState 3 = Synthesized
intToFilingState 4 = Validated
intToFilingState 5 = Submitted
intToFilingState _ = Rejected

||| FFI: Validate a state transition.
export
bf_can_transition : Int -> Int -> Int
bf_can_transition from to =
if canTransition (intToFilingState from) (intToFilingState to) then 1 else 0
12 changes: 12 additions & 0 deletions cartridges/bug-filing-mcp/abi/bug-filing-mcp.ipkg
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- SPDX-License-Identifier: MPL-2.0
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
-- Note: Idris2 package identifiers reject hyphens, hence "bugfilingmcp".
package bugfilingmcp

authors = "Jonathan D.A. Jewell"
version = 0.2.0
sourcedir = "."

modules = BugFilingMcp.SafeBugFiling

depends = base
Loading
Loading