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
5 changes: 3 additions & 2 deletions .github/ISSUE_TEMPLATE/bounty.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: MRWK bounty
description: Propose work that may receive an MRWK bounty.
description: Draft bounty text for maintainer proposal and execution.
title: "MRWK bounty: <amount> MRWK - <short scope>"
labels: ["mrwk:bounty"]
body:
- type: markdown
attributes:
value: |
Do not add the live bounty label from this template. A GitHub issue is not claimable until the treasury proposal executes, the public bounty page exists, and maintainers post the reserved bounty comment.

Use the stable bounty shape from `docs/bounty-rules.md` so humans, GitHub search, API clients, and MCP agents can parse the reward, scope, evidence, and exclusions.
Keep public MRWK wording work-based: no price, investment, exchange, liquidity, bridge, cash-out, or fabricated payout claims.
- type: textarea
Expand Down
83 changes: 83 additions & 0 deletions .github/ISSUE_TEMPLATE/proposed-work.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Proposed work request
description: Suggest work for maintainer review before any MRWK bounty exists.
title: "Proposed work: <short scope>"
labels: ["proposed-work"]
body:
- type: markdown
attributes:
value: |
This is not a live MRWK bounty. Opening this issue does not reserve MRWK, create a payout, or make work claimable.

Do not submit `/claim` for this issue unless maintainers later post a live reserved bounty comment and the public bounty page exists.
- type: textarea
id: problem
attributes:
label: Problem
description: Describe the bug, missing docs, confusing workflow, UX issue, test gap, or improvement opportunity.
validations:
required: true
- type: textarea
id: evidence
attributes:
label: Evidence
description: Link the exact page, API route, file, PR, issue, command output, screenshot, or reproduction path.
validations:
required: true
- type: textarea
id: proposed_work
attributes:
label: Proposed work
description: Describe the smallest useful fix, verification, documentation, or implementation work.
validations:
required: true
- type: textarea
id: expected_value
attributes:
label: Expected value
description: Explain who this helps and what gets easier, safer, clearer, or more reliable.
validations:
required: true
- type: dropdown
id: reference_tier
attributes:
label: Reference tier
description: Non-binding guidance only. Maintainers decide whether any bounty proposal is opened.
options:
- "25-100 MRWK: small docs, typo, reproduction, triage"
- "100-500 MRWK: useful issue, test, docs page, small bugfix"
- "500-2,500 MRWK: normal feature, verified bugfix, agent integration"
- "2,500-10,000 MRWK: security fix, major feature, infrastructure work"
validations:
required: true
- type: textarea
id: acceptance
attributes:
label: Possible acceptance criteria
description: List what would need to be true before maintainers could accept the work.
validations:
required: true
- type: textarea
id: tests
attributes:
label: Evidence or tests required
description: List commands, URLs, screenshots, logs, reproduction steps, or review evidence.
validations:
required: true
- type: textarea
id: duplicate_search
attributes:
label: Duplicate search
description: Link related issues or PRs and explain why this is not already covered.
validations:
required: true
- type: textarea
id: out_of_scope
attributes:
label: Out of scope
description: List work that should not be included.
placeholder: |
- No broad rewrites.
- No speculative price, liquidity, bridge, exchange, cash-out, or payout claims.
- No private secrets or security exploit details in public artifacts.
validations:
required: true
11 changes: 11 additions & 0 deletions docs/agent-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,17 @@ Use this checklist before opening a PR for `mrwk:bounty` issues:
Common rejection reasons: duplicate scope, style-only changes without user
impact, missing evidence, or ignoring issue-specific acceptance criteria.

## Proposed Work Requests

Proposed work requests are intake issues, not live bounties. They may describe a
bug, docs gap, UX issue, verification task, or possible future bounty scope, but
they do not reserve MRWK and they do not make work claimable.

Do not submit `/claim` for a proposed work request. You may add concise evidence,
duplicate-search notes, reproduction steps, or a suggested reference tier, but
wait for `mrwk:bounty`, a `Reserved on MergeWork` comment, and a public bounty
page before treating the issue as bounty work.

### Recovering from Rejection

A `mrwk:rejected` label does not mean the entire contribution is worthless. Use rejection as diagnostic feedback:
Expand Down
22 changes: 22 additions & 0 deletions docs/bounty-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,30 @@ Accepted work can include:

MRWK uses work-based tiers at launch. The project does not publish a fiat peg.

## Proposed Work Requests

A proposed work request is not a live MRWK bounty. It is an intake issue for
ideas, bugs, docs gaps, verification needs, and possible future bounty scopes.
Opening one does not reserve MRWK, create a payout, or make work claimable.

The normal lifecycle is:

```text
proposed issue -> maintainer review -> optional create_bounty proposal -> 24-hour delay -> execution -> mrwk:bounty + Reserved on MergeWork -> claims open
```

Reference tiers are guidance, not entitlement. Contributors and agents may
suggest a tier to help maintainers size the work, but maintainers decide whether
to reject, ask for more information, point to an existing live bounty, accept
non-bounty work, or create a public treasury proposal.

Do not submit `/claim` for a proposed work request unless maintainers later make
the issue live by executing the treasury proposal, adding `mrwk:bounty`, and
posting the `Reserved on MergeWork` comment with the public bounty URL.

## Labels

- `proposed-work`: intake issue for possible future work; not a live bounty.
- `mrwk:bounty`: issue has a posted MRWK reward.
- `mrwk:claimed`: someone is actively working on the issue.
- `mrwk:submitted`: work is ready for review.
Expand Down
58 changes: 58 additions & 0 deletions scripts/docs_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
"docs/agent-guide.md": [
("Public reads such as `GET /api/v1/bounties/{id}/attempts` do not require login"),
("creating or releasing an attempt requires the GitHub-authenticated browser session"),
"Proposed work requests are intake issues, not live bounties",
"wait for `mrwk:bounty`",
],
"docs/paid-bounties.md": [
"This page is not manually updated for every payout.",
Expand All @@ -65,6 +67,9 @@
"Smoke-check or bug-report claim:",
"Discussion or decision-support claim:",
"Do not describe work as accepted, merged, or paid until the public GitHub label",
"## Proposed Work Requests",
"proposed issue -> maintainer review -> optional create_bounty proposal",
"Reference tiers are guidance, not entitlement",
],
"docs/api-examples.md": [
"Internal ledger accounts use the same account response shape",
Expand Down Expand Up @@ -101,6 +106,36 @@ def _template_field_is_required(template: str, field_id: str) -> bool:
return "validations:" in block and "required: true" in block


def _issue_template_labels(template: str) -> set[str]:
labels: set[str] = set()
lines = template.splitlines()

def add_labels(raw_value: str) -> None:
value = raw_value.split("#", 1)[0].strip()
if not value:
return
value = value.strip("[]")
for part in value.split(","):
label = part.strip().strip("\"'")
if label:
labels.add(label.lower())

for index, line in enumerate(lines):
if not line.startswith("labels:"):
continue
add_labels(line.split(":", 1)[1])
for continuation in lines[index + 1 :]:
if not continuation.strip():
continue
if not continuation.startswith((" ", "\t")):
break
stripped = continuation.strip()
if stripped.startswith("- "):
add_labels(stripped[2:])
break
return labels


def main() -> int:
ok = True
for relative in REQUIRED:
Expand Down Expand Up @@ -151,6 +186,7 @@ def main() -> int:
bounty_template = bounty_issue_template.read_text(encoding="utf-8").lower()
for phrase in [
"mrwk bounty: <amount> mrwk - <short scope>",
"do not add the live bounty label from this template",
"id: evidence",
"evidence or tests required",
"id: out_of_scope",
Expand All @@ -159,10 +195,32 @@ def main() -> int:
if phrase not in bounty_template:
print(f"bounty issue template missing required phrase: {phrase}")
ok = False
if "mrwk:bounty" in _issue_template_labels(bounty_template):
print("bounty issue template must not auto-apply mrwk:bounty")
ok = False
for field_id in ("evidence", "out_of_scope", "duplicate_stale_rules"):
if not _template_field_is_required(bounty_template, field_id):
print(f"bounty issue template {field_id} field must be required")
ok = False
proposed_work_template = ROOT / ".github/ISSUE_TEMPLATE/proposed-work.yml"
if not proposed_work_template.exists():
print("missing proposed work issue template: .github/ISSUE_TEMPLATE/proposed-work.yml")
ok = False
else:
proposed_template = proposed_work_template.read_text(encoding="utf-8").lower()
for phrase in [
'title: "proposed work: <short scope>"',
'labels: ["proposed-work"]',
"not a live mrwk bounty",
"do not submit `/claim`",
"id: duplicate_search",
]:
if phrase not in proposed_template:
print(f"proposed work issue template missing required phrase: {phrase}")
ok = False
if "mrwk:bounty" in proposed_template:
print("proposed work issue template must not mention or apply mrwk:bounty")
ok = False
if ok:
print("docs smoke ok")
return 0
Expand Down
53 changes: 52 additions & 1 deletion tests/test_docs_public_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from pathlib import Path

from scripts.docs_smoke import REQUIRED, _template_field_is_required
from scripts.docs_smoke import REQUIRED, _issue_template_labels, _template_field_is_required


def test_readme_lists_live_ltclab_urls() -> None:
Expand Down Expand Up @@ -114,6 +114,57 @@ def test_admin_runbook_documents_webhook_event_limit_cap() -> None:
assert "/api/v1/admin/webhook-events?status=missing_submitter&limit=100" not in runbook


def test_proposed_work_template_is_not_a_live_bounty_template() -> None:
template = Path(".github/ISSUE_TEMPLATE/proposed-work.yml").read_text(encoding="utf-8")
lowered = template.lower()

assert 'title: "proposed work: <short scope>"' in lowered
assert 'labels: ["proposed-work"]' in lowered
assert "mrwk:bounty" not in lowered
assert "not a live mrwk bounty" in lowered
assert "do not submit `/claim`" in lowered
assert "reference tier" in lowered


def test_bounty_issue_template_does_not_auto_mark_issue_live() -> None:
template = Path(".github/ISSUE_TEMPLATE/bounty.yml").read_text(encoding="utf-8")

assert "mrwk:bounty" not in _issue_template_labels(template)
assert "Do not add the live bounty label from this template" in template


def test_issue_template_labels_parse_inline_and_block_styles() -> None:
assert _issue_template_labels('labels: ["proposed-work", "docs"]') == {
"proposed-work",
"docs",
}
assert _issue_template_labels("labels:\n - proposed-work\n - docs\nbody: []") == {
"proposed-work",
"docs",
}


def test_bounty_rules_document_proposed_work_lifecycle() -> None:
rules = Path("docs/bounty-rules.md").read_text(encoding="utf-8")
squashed = " ".join(rules.split())

assert "## Proposed Work Requests" in rules
assert "proposed work request is not a live MRWK bounty" in rules
assert (
"proposed issue -> maintainer review -> optional create_bounty proposal -> "
"24-hour delay -> execution -> mrwk:bounty"
) in squashed
assert "Reference tiers are guidance, not entitlement" in rules


def test_agent_guide_tells_agents_not_to_claim_proposed_work() -> None:
guide = Path("docs/agent-guide.md").read_text(encoding="utf-8")

assert "Proposed work requests are intake issues, not live bounties" in guide
assert "Do not submit `/claim`" in guide
assert "wait for `mrwk:bounty`" in guide


def test_api_examples_document_bounty_list_response_shape() -> None:
examples = Path("docs/api-examples.md").read_text(encoding="utf-8")

Expand Down
Loading