The canonical instruction file for any agent (human or AI) working in this repo.
CLAUDE.md / AGENTS.md are thin pointers here — edit this file. Read it before
writing code.
A standalone protoAgent plugin (ADR 0001/0019/0027): read and write GitHub
tools over the gh CLI, with per-agent write gating. The host loads it via the
register(registry) seam; the manifest (protoagent.plugin.yaml) is read as data.
| Layer | What |
|---|---|
| Runtime | Python ≥ 3.11; langchain-core (@tool) is provided by the host |
| Auth | gh ambient auth, or GITHUB_TOKEN/GH_TOKEN from the env |
| Repo | protoLabsAI/github-plugin, ships enabled: false (install ≠ enable ≠ trust) |
These must pass before a PR opens (host-free — no protoAgent needed):
pip install -r requirements-dev.txt ruff
ruff check . && ruff format --check . # lint + format
pytest -q # the suiteThere is no other runner. ruff + pytest are the sole gate.
protoagent.plugin.yaml # manifest (id: github, config_section: github; write/default_repo/repos)
__init__.py # register() — gating wiring (read always; write iff github.write) + /issue
gh_cli.py # vendored async `gh` runner (run_gh, check_gh_error, bad_repo)
read_tools.py # 8 read tools (6 ported core + read_file/repo_contents)
write_tools.py # 8 write tools (create/edit/merge/close/comment/labels/assignees) — gated
gh_issue.py # /issue chat command logic (user-only; gate-checked; configured repo)
api.py # routers — public PAGES (view/new-issue) + gated data routes (config/issues/prs/issue)
view.py # PAGE = read-only board (Issues/PRs tabs + repo picker); NEW_ISSUE_PAGE = file-an-issue form. --pl-* themed
tests/ # host-free pytest (gating + version coherence + routes via TestClient)
Console surfaces (ADR 0026/0038/0042/0057). register() mounts two routers when the
host exposes register_router (guarded — degrade-safe): the PAGES on the PUBLIC
/plugins/github prefix (iframe-loadable; a page-load can't carry a bearer) and the
DATA routes on the GATED /api/plugins/github prefix (pages fetch them with the DS
plugin-kit's apiFetch, which attaches the operator bearer from the postMessage
handshake). Pages are vanilla JS themed entirely from --pl-* tokens (no host build).
Two manifest views, three surfaces, deliberately split:
/view— the READ-ONLY board (Issues/PRs tabs + repo picker + state filter). Right dock + a ⌘K morph (palette: inline). No writes — it's a viewer./new-issue— the compact file-an-issue form. A util-bar widget pill (utility, opens its dialog) AND a distinct ⌘K page (palette: { path }).POST /issuereuses the SAMEfile_issuegate path as the/issuecommand, so they can't diverge. Filing lives in the widget + palette, NOT the board (keep the board read-only).
register() registers read tools unconditionally and write tools ONLY when
github.write is true (each agent's config decides — ADR 0019). A research/Lead
agent stays read-only; a coding/PM agent gets write. tests/test_register.py asserts
both halves — keep it green.
/issue is a user-only chat command, not an agent tool — creating an issue is a
write the model must not do autonomously, so register() registers it via the host's
register_chat_command seam (the logic lives in gh_issue.py). The call is guarded
by hasattr(registry, "register_chat_command"), so on an older host without the seam
/issue is skipped and the tools still load (degrade-safe). It routes to the
configured default_repo/repos (never a silent default). tests/test_issue_command.py
asserts both the seam-present and legacy-host paths — keep them green.
Each tool mocks run_gh in its test and asserts the exact argv + readable errors.
Read (always on) — 6 ported core tools (github_get_pr, github_get_issue,
github_list_issues, github_get_commit_diff, github_ci_runs, github_run_failure)
plus github_read_file (gh api .../contents/{path}, raw) and github_repo_contents
(directory listing).
Write (gated on github.write) —
github_create_issue / github_comment / github_create_pr (return the new URL),
github_edit_pr (gh pr edit + gh pr ready [--undo]),
github_merge_pr (gh pr merge — refuses without confirm=true, offers dry_run),
github_close (close/reopen issue|pr), and github_set_labels / github_set_assignees
(gh {issue,pr} edit --add/--remove-{label,assignee}). Issue-vs-PR ops take kind.
New write op? Validate bad_repo(), build argv, run_gh(), degrade to Error: ...,
add it to get_write_tools()'s return list and WRITE_TOOLS in test_register.py,
and mirror an existing test. Anything irreversible (merge) must be confirm-guarded.
- Host-free. NEVER import
graph.*/plugins.*at module top — the suite runs with onlyrequirements-dev.txt. Keep any host imports lazy (inside functions). @tooldocstrings must be PLAIN string literals — an f-string docstring makes__doc__None and the tool ships with no description (the model can't see it).- Every tool requires an explicit
owner/namerepo — validate withbad_repo(); there is no silent default (a forgotten repo must error, not hit the wrong repo). - DO NOT FABRICATE. Use real
ghinvocations; verify the actualgh apishape before relying on it. No placeholder/guessed command flags. - Don't add runtime pip deps. Test-only deps go in
requirements-dev.txt; real runtime deps would go in the manifest'srequires_pip(operator-installed).
.proto/ is the coding agent's own scratch — gitignored, never commit.