Skip to content

Commit eff5b1c

Browse files
chore: add optional Beads issue queue guidance (#86)
* chore: add optional Beads issue queue guidance * chore: address PR-86 review feedback (BEADS doc + template + CI-script compile gate) Applies the actionable items from the PR-86 review: - docs/BEADS.md: lead with a one-sentence "what Beads is" + upstream link; state the stance explicitly (optional/additive, recommended for agent-driven flows, GitHub remains authoritative); add a YAML example block under Recommended Bead fields; replace the duplicated Closure checklist with a Bead-specific narrowing that cites the PR template + CONTRIBUTING; call out that .beads/ is wiped by git clean -fdx. - .github/pull_request_template.md: collapse the "Local Beads" section into an HTML-commented opt-in block so it is invisible in the rendered preview until a Beads-using team uncomments it. - CONTRIBUTING.md: document the one-shot git renormalisation step for Windows clones after the .gitattributes change lands. - tests/test_scripts_compile.py: regression gate that py_compiles every .github/scripts/*.py. The "scripts unparseable" review finding was based on an older local Python — PEP 758 (3.14) makes the unparenthesised except clauses valid, so the scripts ARE fine on the project pin. The test guards against an actual syntax error landing in future. * chore: bump version to 0.2.11 --------- Co-authored-by: const.koutsakis@aurecongroup.com <constantinos.koutsakis@gmail.com>
1 parent 8be4b73 commit eff5b1c

16 files changed

Lines changed: 243 additions & 13 deletions

.gitattributes

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# The pre-commit hook stack enforces LF line endings. Keep checkout behavior
2+
# aligned across Windows, macOS, and Linux so `pre-commit run --all-files` does
3+
# not rewrite the working tree on Windows clones with global autocrlf enabled.
4+
* text=auto eol=lf
5+
6+
*.png binary
7+
*.jpg binary
8+
*.jpeg binary
9+
*.gif binary
10+
*.ico binary
11+
*.pdf binary

.github/pull_request_template.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@
4040
<!-- Required for UI change. Delete this section for non-UI PRs. -->
4141

4242

43+
<!--
44+
## Local Beads
45+
46+
Optional opt-in. Only add this section if your team uses a local Beads queue
47+
(see docs/BEADS.md). Uncomment the heading and replace this comment with the
48+
Bead id. GitHub issue linkage below is still required either way.
49+
-->
50+
51+
4352
## Linked issue
4453

4554
Closes #

.github/scripts/check_aspirational_tickets.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from pathlib import Path
5858

5959
INVARIANTS_DOC = Path("docs/INVARIANTS.md")
60+
GITHUB_API_ERRORS = (urllib.error.URLError, TimeoutError, json.JSONDecodeError)
6061

6162
# A marker line *starts* with one or two asterisks immediately followed by
6263
# `Aspirational` and a word boundary. Avoids picking up mid-sentence prose
@@ -88,7 +89,7 @@ def _issue_state(repo: str, number: str, token: str) -> str | None:
8889
try:
8990
with urllib.request.urlopen(req, timeout=5) as response: # noqa: S310
9091
payload = json.loads(response.read().decode("utf-8"))
91-
except urllib.error.URLError, TimeoutError, json.JSONDecodeError:
92+
except GITHUB_API_ERRORS:
9293
return None
9394
state = payload.get("state")
9495
return state if isinstance(state, str) else None

.github/scripts/check_pin_freshness.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def _load_pin_module() -> ModuleType:
8484

8585
_pins = _load_pin_module()
8686
_API_BASE = "https://api.github.com"
87+
GITHUB_API_ERRORS = (urllib.error.URLError, TimeoutError, json.JSONDecodeError)
8788

8889

8990
def _fetch_json(url: str, token: str) -> dict[str, object] | None:
@@ -104,7 +105,7 @@ def _fetch_json(url: str, token: str) -> dict[str, object] | None:
104105
try:
105106
with urllib.request.urlopen(req, timeout=10) as response: # noqa: S310
106107
payload = json.loads(response.read().decode("utf-8"))
107-
except urllib.error.URLError, TimeoutError, json.JSONDecodeError:
108+
except GITHUB_API_ERRORS:
108109
return None
109110
return payload if isinstance(payload, dict) else None
110111

.github/scripts/check_tests_present.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import sys
4444
from pathlib import Path
4545

46+
EVENT_READ_ERRORS = (OSError, json.JSONDecodeError)
47+
4648
# Prefixes that declare a behaviour change → tests required.
4749
BLOCKING_PREFIXES: frozenset[str] = frozenset({"feat", "fix"})
4850

@@ -59,7 +61,7 @@ def pr_title_from_event() -> str | None:
5961
return None
6062
try:
6163
data = json.loads(Path(event_path).read_text(encoding="utf-8"))
62-
except OSError, json.JSONDecodeError:
64+
except EVENT_READ_ERRORS:
6365
return None
6466
pr = data.get("pull_request")
6567
if not isinstance(pr, dict):

.github/scripts/check_version_bump.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
PYPROJECT = Path("pyproject.toml")
4040
UV_LOCK = Path("uv.lock")
4141
PACKAGE_NAME = "harness-python-react"
42+
EVENT_READ_ERRORS = (OSError, json.JSONDecodeError)
4243

4344
# Match the project's self-version block in uv.lock:
4445
#
@@ -105,7 +106,7 @@ def pr_title_from_event() -> str | None:
105106
return None
106107
try:
107108
data = json.loads(Path(event_path).read_text(encoding="utf-8"))
108-
except OSError, json.JSONDecodeError:
109+
except EVENT_READ_ERRORS:
109110
return None
110111
pr = data.get("pull_request")
111112
if not isinstance(pr, dict):

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
.claude/bash-log.txt
44
.claude/worktrees/
55

6+
# Optional local Beads queue state
7+
.beads/
8+
beads/
9+
610
# Node / Frontend
711
node_modules/
812
frontend/dist/

CONTRIBUTING.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ The subject is **lowercase** after the colon. Title Case prose (`Add the thing`)
3434

3535
1. Open the issue first. Use a feature/bug template; fill every section.
3636
2. Branch off `develop` with the matching name.
37-
3. Land one logical change per PR. Stack PRs if the work is naturally split.
38-
4. The PR template asks five things — answer each (`None` is valid where applicable):
37+
3. If your team uses Beads, mirror or claim the linked issue in the local Beads queue after the issue exists. Beads track local ready/blocked execution only; GitHub Issues remain canonical for scope, discussion, PR linkage, and closure.
38+
4. Land one logical change per PR. Stack PRs if the work is naturally split.
39+
5. The PR template asks five things — answer each (`None` is valid where applicable):
3940
- **What & why** (1–3 lines)
4041
- **Test plan** (checkbox list; CI covers most of it)
4142
- **Invariants affected** — cite numbered rules from `docs/INVARIANTS.md`
4243
- **New deps / actions / external surface** (anchor for supply-chain review)
4344
- **Screenshots** (UI changes only)
44-
5. Wait for green CI + a code-owner review before merging.
45+
6. Wait for green CI + a code-owner review before merging.
4546

4647
### Solo-owner merge policy
4748

@@ -55,6 +56,21 @@ gh pr merge <N> --admin --squash --delete-branch
5556

5657
When a second collaborator joins, drop the `--admin` flag and adopt standard PR review. Update this section + `CODEOWNERS` in the same PR.
5758

59+
## Line endings (Windows clones)
60+
61+
This repo enforces LF line endings via `.gitattributes` (`* text=auto eol=lf`)
62+
and the pre-commit hygiene hook. If you cloned on Windows with
63+
`core.autocrlf=true`, the first checkout after pulling the `.gitattributes`
64+
change can leave the working tree out of sync with the index. Renormalise
65+
once:
66+
67+
```sh
68+
git add --renormalize .
69+
git commit -m "chore: renormalise line endings"
70+
```
71+
72+
After that, day-to-day work is unaffected.
73+
5874
## Local pre-push gate
5975

6076
```sh

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
- **Backend:** Python 3.14, FastAPI, Pydantic v2 (`StrictModel` base), `uv` deps, OpenTelemetry SDK + OTLP exporter, structured JSON logs, generic tool-registry pattern.
1616
- **Frontend:** Node 24 LTS, React 19.2, Vite 8, TypeScript strict, ESLint 10 flat config, Prettier, Vitest + jsdom + Testing Library.
1717
- **Eval harness:** provider-agnostic runner + LLM-judge `Protocol`, three tolerance modes (exact / numeric / semantic), one example golden case, nightly workflow (disabled by default).
18-
- **CI:** 15 required status checks across `ci.yml` (lint/format, mypy strict, unit tests, coverage ≥75%, import-linter architecture, pre-commit, frontend build, frontend quality, branch-protection sync, commit-type sync) + `security.yml` (gitleaks, pip-audit, npm audit, trivy) + PR-title lint.
18+
- **CI:** 21 required status checks across `ci.yml` (lint/format, mypy strict, unit tests, coverage, import-linter architecture, pre-commit, frontend build, frontend quality, branch-protection sync, commit-type sync, version/action/tests/docs audits) + `security.yml` (gitleaks, pip-audit, npm audit, trivy) + PR-title lint.
1919
- **Release:** tag-triggered workflow that builds the image, pushes to `ghcr.io`, generates a CycloneDX SBOM, and publishes the GitHub Release.
2020
- **Agent integration:** `.claude/hooks/` (forbidden-flag blocker, secret scan, formatter dispatch, SessionStart context) + six auto-activating skills (architect / code-reviewer / devops / frontend / qa-engineer / technical-writer).
21+
- **Issue execution:** GitHub Issues remain the external source of truth; optional Beads guidance adds a local dependency-aware execution queue without changing issue closure authority.
2122
- **Docker:** multi-stage Dockerfile (non-root, healthcheck), `docker compose up` boots app + frontend + Jaeger.
2223

2324
## Quickstart
@@ -114,6 +115,7 @@ See [`docs/HARNESS.md`](docs/HARNESS.md) for the full umbrella. Highlights:
114115
| [`docs/BOUNDARIES.md`](docs/BOUNDARIES.md) | Module layering + the import-linter contracts |
115116
| [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) | Local setup, branching, justfile, CI |
116117
| [`docs/EVAL_HARNESS.md`](docs/EVAL_HARNESS.md) | Eval flywheel + opt-in for the nightly workflow |
118+
| [`docs/BEADS.md`](docs/BEADS.md) | Optional local Beads queue layered under GitHub Issues |
117119
| [`docs/SECURITY.md`](docs/SECURITY.md) | Threat model + defence-in-depth map |
118120
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Scaffold-level component view |
119121
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Branching, commit format, PR flow |

docs/BEADS.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Optional Beads execution queue
2+
3+
[Beads](https://github.com/steveyegge/beads) is an open-source
4+
dependency-aware issue tracker designed for AI coding agents — it gives an
5+
agent a local ready/blocked view of work, a dependency graph, and restart-safe
6+
task claims that GitHub Issues alone do not.
7+
8+
This document is **optional and additive**. The base harness does not assume
9+
Beads; if your team has no agent or multi-actor execution concern, GitHub
10+
Issues plus the PR template is sufficient and you can skip this doc entirely.
11+
Beads is recommended specifically when you are coordinating an LLM agent (or
12+
several) against this repo and want dependency planning the public issue
13+
tracker does not provide. The README and `docs/HARNESS.md` references describe
14+
Beads as optional infrastructure, not part of the standard contributor flow.
15+
16+
Wherever Beads is used, GitHub Issues remain the external source of truth and
17+
the authority for issue closure.
18+
19+
## Review of existing GitHub issue guidance
20+
21+
The current harness already treats GitHub as the public planning and merge
22+
record:
23+
24+
- `.github/ISSUE_TEMPLATE/bug.md`, `feature.md`, and `eval-regression.md`
25+
define the supported intake paths, and blank issues are disabled in
26+
`.github/ISSUE_TEMPLATE/config.yml`.
27+
- `CONTRIBUTING.md` requires one issue per branch, short-lived branches named
28+
`<type>/<issue-number>-<kebab-title>`, and green CI plus review before merge.
29+
- `.github/pull_request_template.md` requires What & why, Test plan,
30+
Invariants affected, supply-chain surface, Screenshots when relevant, and a
31+
linked issue.
32+
- `CLAUDE.md` and `docs/DEVELOPMENT.md` describe the same one-issue,
33+
one-branch, `develop` to `main` release flow for agent and human operators.
34+
- `docs/TASKS.md` is a project-local planning map cross-referenced with GitHub
35+
issues and the project board.
36+
37+
There is no Beads-specific policy in the base harness today. Any Beads addition
38+
must therefore be additive and must not make GitHub issue state ambiguous.
39+
40+
## GitHub Issues vs Beads
41+
42+
| System | Owns | Does not own |
43+
|---|---|---|
44+
| GitHub Issues | Public backlog, user-facing requirements, labels, project board state, discussion, acceptance criteria, links from PRs, and final issue closure. | Local agent claims, transient execution notes, or dependency scheduling that would be noisy in the public issue. |
45+
| Beads | Local execution queue, ready/blocked views, dependency graph, implementation notes, reviewer handoff notes, and restart-safe task claims. | The canonical requirement, public status, release notes, or authority to close a GitHub issue. |
46+
47+
The rule is simple: **GitHub answers what work exists and whether it is
48+
externally done; Beads answers what the local execution system should pick up
49+
next.**
50+
51+
## Sync contract
52+
53+
When using Beads with this harness:
54+
55+
1. Create or confirm the GitHub issue first.
56+
2. Mirror the issue into Beads with an immutable external reference:
57+
- GitHub repository owner/name.
58+
- GitHub issue number.
59+
- GitHub issue URL.
60+
- Original issue title.
61+
3. Use Beads for local status only: `ready`, `in_progress`, `blocked`,
62+
`review`, or `done` are execution states, not replacements for the GitHub
63+
issue state.
64+
4. Put the Bead id in local notes, branch notes, or PR body when useful, but
65+
keep `Closes #<issue>` pointing at the GitHub issue.
66+
5. Do not close a GitHub issue because a Bead is marked done. Close only after
67+
the PR is merged, required checks are green, any required manual or browser
68+
validation is recorded, and a human-readable note has been added to the
69+
issue or PR.
70+
71+
If the GitHub issue changes after import, update the Bead from GitHub before
72+
continuing. GitHub wins on scope, acceptance criteria, and user-visible status.
73+
74+
## Recommended Bead fields
75+
76+
A Bead should carry enough information for a new agent or contributor to resume
77+
without reopening every browser tab:
78+
79+
| Field | Purpose |
80+
|---|---|
81+
| `external_ref` | GitHub issue URL, for example `https://github.com/owner/repo/issues/123`. |
82+
| `github_issue` | Numeric issue id used by branches and PRs. |
83+
| `acceptance` | The current acceptance criteria copied or summarized from GitHub. |
84+
| `dependencies` | Other Beads or GitHub issues that must land first. |
85+
| `status` | Local execution state. |
86+
| `owner` | Optional local agent or human claim. |
87+
| `evidence` | Paths or URLs for test output, review notes, screenshots, or deploy checks. |
88+
| `closeout` | Merge SHA, PR URL, and verification notes once complete. |
89+
90+
A short YAML example:
91+
92+
```yaml
93+
external_ref: https://github.com/owner/repo/issues/123
94+
github_issue: 123
95+
acceptance: |
96+
/api/v1/echo rejects payloads >1KiB with HTTP 413.
97+
dependencies: [122] # other Bead ids or GitHub issues
98+
status: ready
99+
owner: agent-a
100+
evidence:
101+
- tests/test_api.py::test_echo_size_cap
102+
closeout: null
103+
```
104+
105+
Avoid storing secrets, tokens, credentials, private customer data, or raw
106+
production payloads in Beads. Treat Beads data as local operational metadata.
107+
Note that `.beads/` is gitignored, so anything Beads stores locally — including
108+
agent-action audit logs — is wiped by `git clean -fdx`; commit deliberate
109+
summaries to the repo if you need them to survive workspace resets.
110+
111+
## PR discipline when Beads are used
112+
113+
The existing PR template still applies. Add Beads information without deleting
114+
any required section:
115+
116+
- `Linked issue` remains `Closes #<issue>`.
117+
- Mention the Bead id or local queue reference under `What & why` or the
118+
optional Beads section.
119+
- Include Beads-derived evidence paths in `Test plan` only when they are useful
120+
to a reviewer.
121+
- If the Bead changed scope, update the GitHub issue before asking for review.
122+
- If the Bead was blocked by an external dependency, note that in the PR or
123+
issue rather than hiding it in the local queue.
124+
125+
## Local artifact hygiene
126+
127+
Beads state is usually local execution metadata. Do not commit raw Beads
128+
databases, scratch exports, or agent logs by default. Commit only intentional
129+
summaries or docs that reviewers need.
130+
131+
If a downstream project decides to version Beads state, document that policy in
132+
that project and make sure secret scanning, review, and retention expectations
133+
are explicit.
134+
135+
## Closure checklist
136+
137+
The PR-merge and issue-closure gates already live in
138+
`.github/pull_request_template.md` and `CONTRIBUTING.md` — don't duplicate them
139+
here. The Bead-specific closure rule is narrower:
140+
141+
- Do not mark a Bead done until the GitHub issue's closure conditions (per the
142+
PR template and `CONTRIBUTING.md`) are met. Beads track the local execution
143+
state of work GitHub already authorised; they don't grant new closure
144+
authority.
145+
- If the Bead and the GitHub issue disagree on scope, acceptance, or status,
146+
stop and reconcile against GitHub before continuing.
147+
148+
Beads improve local throughput only if they reduce ambiguity. If a Bead and a
149+
GitHub issue disagree, the GitHub issue wins.

0 commit comments

Comments
 (0)