Give the agent loop a board, and keep it in step with Forgejo #129

Merged
cruelacid merged 4 commits from worktree-agent-queue into main 2026-09-23 19:02:17 +01:00
Owner

The agent work queue's board, and the tools that filled it.

Forgejo has no Projects API, so an agent can read an issue but never move it between columns. Work is moving to a self-hosted Vikunja board (projectron), where an agent can.

  • scripts/agent/lib/vikunja.mjs: a small v2 client that enforces Markdown in and out (format=markdown), refuses a PATCH that carries Markdown, reads whole buckets (they page inside), and compares round trips by what renders.
  • scripts/agent/lib/reconcile.mjs: a pure planner that keeps the board in step with Forgejo until the cut. It never overwrites a card edited on the board, and never mistakes an issue closed for the move for finished work.
  • scripts/agent/migrate-forgejo.mjs: migrate, --reconcile, --verify, and --close-forgejo --apply.
  • CLAUDE.md and .forgejo/pull_request_template.md: work is now tracked on the board, not in Forgejo issues. The template's Closes # becomes Task: NEC-. The cut was made on 23 September 2026: the 75 open issues were closed, each with a comment naming its card, and only the Dependency Dashboard (#18) remains open.
  • docs/changes/README.md: where change specs go, and which Markdown survives the board.

Tests: node --test scripts/agent/, 22 tests. Every rule was mutation-checked, and each broken rule failed its own test. Nothing here is mirrored; build-mirror --check stages cleanly.

Changelog

NONE

The agent work queue's board, and the tools that filled it. Forgejo has no Projects API, so an agent can read an issue but never move it between columns. Work is moving to a self-hosted Vikunja board (projectron), where an agent can. - `scripts/agent/lib/vikunja.mjs`: a small v2 client that enforces Markdown in and out (`format=markdown`), refuses a PATCH that carries Markdown, reads whole buckets (they page inside), and compares round trips by what renders. - `scripts/agent/lib/reconcile.mjs`: a pure planner that keeps the board in step with Forgejo until the cut. It never overwrites a card edited on the board, and never mistakes an issue closed *for the move* for finished work. - `scripts/agent/migrate-forgejo.mjs`: migrate, `--reconcile`, `--verify`, and `--close-forgejo --apply`. - `CLAUDE.md` and `.forgejo/pull_request_template.md`: work is now tracked on the board, not in Forgejo issues. The template's `Closes #` becomes `Task: NEC-`. The cut was made on 23 September 2026: the 75 open issues were closed, each with a comment naming its card, and only the Dependency Dashboard (#18) remains open. - `docs/changes/README.md`: where change specs go, and which Markdown survives the board. Tests: `node --test scripts/agent/`, 22 tests. Every rule was mutation-checked, and each broken rule failed its own test. Nothing here is mirrored; `build-mirror --check` stages cleanly. ## Changelog NONE
Forgejo has no Projects API, so an agent can read an issue but never move
it between columns. The queue moves to a self-hosted Vikunja, which has
kanban buckets and an API that places a task in one.

scripts/agent/lib/vikunja.mjs is the client, and it enforces the one rule
that is easy to forget: descriptions and comments go in and out as
Markdown. Vikunja stores HTML and only converts when a request carries
format=markdown; a request without it speaks HTML and looks fine in
Vikunja's own UI. PATCH does not accept the parameter, so a PATCH carrying
Markdown is refused before it is sent. Both rules and the round-trip
normaliser are mutation-checked.

scripts/agent/migrate-forgejo.mjs copies the open issues across without
rewriting anybody's text, dry-runs by default, is safe to re-run, and
closes nothing on Forgejo unless asked to separately.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k9tkUtbU3wBpCHAHq7Qas
Two things the migration's own read-back caught before anything on
Forgejo was touched.

The kanban endpoint pages the tasks inside each bucket, fifty at a time,
while its total counts buckets and total_pages is absent. Read as an
ordinary list it looks complete, and every bucket is cut at fifty: 34 of
the 84 migrated cards were missing from Backlog on first reading. The
claim step would never have seen a Ready card past the fiftieth.

All 84 bodies first compared unequal, for six reasons, five of which
render identically (joined wraps, re-spelt thematic breaks, re-padded
tables, joined quote lines, escaped punctuation). The comparison now
treats exactly those as equal, each with a test that fails without it.
The sixth is real: emphasis ending on code gains a space before adjacent
punctuation. docs/changes/README.md tells spec authors to avoid it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k9tkUtbU3wBpCHAHq7Qas
Keep the board in step with Forgejo until the switch-over
Some checks failed
Release note / release-note (pull_request) Successful in 11s
CI / promote (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
459c2a1c06
Between the first migration and the cut, Forgejo moved: one issue filed,
ten closed by the pull-request work, four comments added. Draining the
board and migrating again would renumber every card to fix twelve
differences, so --reconcile applies just the differences instead.

The rules live in lib/reconcile.mjs as a pure planner, each with a test
that fails when the rule is removed. Two of them exist to refuse:

- A card edited on the board is never overwritten from Forgejo. The
  footer now records a hash of the body this code wrote; a body that no
  longer hashes to it is reported as a conflict and left alone.
- An issue this script closed for the move is not finished work. After
  the cut every issue is closed, by us, and without that rule the next
  reconcile would sweep every live card into Done.

--close-forgejo now also needs --apply, and closes only issues whose card
read back as written.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k9tkUtbU3wBpCHAHq7Qas
Say where work is tracked now: the board, not Forgejo issues
All checks were successful
Release note / release-note (pull_request) Successful in 13s
CI / build (pull_request) Successful in 4m22s
CI / e2e (pull_request) Successful in 4m48s
CI / promote (pull_request) Has been skipped
CI / build (push) Successful in 4m34s
CI / e2e (push) Successful in 6m32s
CI / promote (push) Successful in 31s
9dbfb1f3ea
The open Forgejo issues were moved to the Vikunja board on 23 September
2026 and closed, each naming its card, because Forgejo has no Projects
API and an agent could not move an issue between columns. CLAUDE.md says
so where a session looks before starting work, and the pull request
template names a card (Task: NEC-) instead of an issue to close.

The Dependency Dashboard (#18) stays open on Forgejo; Renovate needs it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k9tkUtbU3wBpCHAHq7Qas
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Nectenda/nectenda!129
No description provided.