0b93a985d6
Read dev/issues/*.md and dev/flows/*.md as kanban cards via new
/api/boards/{issues|flows}/cards endpoints. PATCH writes status back
to the frontmatter atomically (tmp + rename), POST .../launch proxies
to agent_runner_api.
- issues_source.go: scan + parse frontmatter (yaml.v3) into IssueCard.
Skips README/INDEX/AGENT_GUIDE. Malformed YAML yields parse-error
cards (no crash). Description = first 5 body lines (no full body).
- flows_source.go: same shape, distinct status->column mapping
(pending/running/done/deferred -> Pending/Running/Done/Deferred).
- frontmatter_edit.go: PatchFrontmatterField — atomic, preserves the
rest of the file byte-for-byte, inserts key if missing.
- handlers_boards.go: list + patch + launch endpoints, taxonomy 0103
enforced. Cache 30s in memory, thread-safe (mutex), invalidated on
PATCH. Launch returns 502 with suggestion when runner is down.
- main.go: SkipPaths += "/api/boards/" so the C++ frontend hits the
read endpoints without a kanban_web session.
Smoke (FN_REGISTRY_ROOT pointed at the worktree, 87 issues + 9 flows
on disk):
GET /api/boards/issues/cards -> 200, 87 cards
GET /api/boards/flows/cards -> 200, 9 cards
PATCH /api/boards/issues/cards/0119 {status:en-curso} -> 200,
file mtime changes, frontmatter rewritten, rest preserved
POST /api/boards/issues/cards/0119/launch -> 502
agent_runner_unreachable (expected, runner not yet implemented)
Tests: issues_source_test (3 cases incl. malformed + missing status),
flows_source_test (3 cases), frontmatter_edit_test (4 cases incl.
atomic rename + no tmp leftovers). Pre-existing tools_test failure
on TestExecuteTool_MoveCard_BetweenColumns_OpensHistory is unrelated
(CardHistoryResponse type assert, not touched here).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
61 lines
1.7 KiB
Go
61 lines
1.7 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestLoadFlowCards_MapsStatuses(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFixture(t, dir, "INDEX.md", "skip")
|
|
writeFixture(t, dir, "0001-foo.md", "---\nid: 0001\nname: foo\nstatus: pending\n---\nbody\n")
|
|
writeFixture(t, dir, "0002-bar.md", "---\nid: 0002\nname: bar\nstatus: running\n---\nbody\n")
|
|
writeFixture(t, dir, "0003-baz.md", "---\nid: 0003\nname: baz\nstatus: done\n---\nbody\n")
|
|
writeFixture(t, dir, "0004-bop.md", "---\nid: 0004\nname: bop\nstatus: deferred\n---\nbody\n")
|
|
|
|
cards, err := loadFlowCards(dir)
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if len(cards) != 4 {
|
|
t.Fatalf("expected 4 cards, got %d", len(cards))
|
|
}
|
|
want := map[string]string{
|
|
"0001": "Pending",
|
|
"0002": "Running",
|
|
"0003": "Done",
|
|
"0004": "Deferred",
|
|
}
|
|
for _, c := range cards {
|
|
if want[c.ID] != c.ColumnID {
|
|
t.Fatalf("%s: expected column %s, got %s", c.ID, want[c.ID], c.ColumnID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadFlowCards_MissingStatusDefaultsPending(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFixture(t, dir, "0010-nostatus.md", "---\nid: 0010\nname: empty\n---\nbody\n")
|
|
cards, err := loadFlowCards(dir)
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if len(cards) != 1 {
|
|
t.Fatalf("expected 1 card")
|
|
}
|
|
if cards[0].ColumnID != "Pending" {
|
|
t.Fatalf("expected Pending column, got %q", cards[0].ColumnID)
|
|
}
|
|
}
|
|
|
|
func TestLoadFlowCards_MalformedDoesNotCrash(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFixture(t, dir, "0011-bad.md", "---\nid: 0011\nstatus: pending\n : malformed yaml\n---\nbody\n")
|
|
cards, err := loadFlowCards(dir)
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if len(cards) != 1 || cards[0].ParseError == "" {
|
|
t.Fatalf("expected 1 card with ParseError, got %#v", cards)
|
|
}
|
|
}
|