This repository has been archived on 2026-09-06. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
cad-atlas/internal/atlas/stuck.go
T
mathiasandClaude Sonnet 5 97d0f56383
CD / Build & Import (push) Failing after 3s
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 5s
CD / var-go/oath (push) Skipped
CD / Deploy via GitOps (push) Skipped
feat(stuck): stuck-state pipeline overlay (cad-atlas#11)
Consumes mathias/cad#14's cad-stuck query directly — cad's app/domain
packages are public Go modules, so this reuses the exact tested
classification logic (import, not re-implementation) rather than
duplicating the taxonomy across two repos.

ApplyStuckOverlay (internal/atlas/stuck.go, fixture-tested) maps each
finding's category onto the existing 9-stage pipeline: armed_never_fired to
the human dispatch gate, claimed_never_ran/ceiling_hit/refire_loop/
near_attempt_cap to the one Execute stage (there is no separate Claim
stage), done_not_closed to the loop-back stage. Adds a StuckCount/
AwaitingCount badge per stage plus one Node per finding, linking out to the
underlying issue/comment — read-only, no controls, matching cad#14's own
constraint.

Two visual classes, kept deliberately distinct per the issue's own framing:
a coral 'N stuck' badge (needs intervention) vs a green 'N awaiting' badge
(a functioning control waiting on a human) — done_not_closed can never
render as a bottleneck.

Verified against the live estate: STAGE 05 shows 2 stuck (cobalt-dingo#30
ceiling_hit, cad-atlas#10 near_attempt_cap), STAGE 08 shows 5 awaiting
(cobalt-dingo #3/#7/#12/#16/#19 done_not_closed) — screenshotted via a
koala k3s Playwright job against a live-token server, matching cad#14's own
proven query output exactly.

go.mod: added git.d-ma.be/mathias/cad v0.10.0 (app/domain/adapters-gitearead
only — no mcp-chassis pulled in, that's only imported by cad's own cmd/cad).
Dockerfile: added the same GOPRIVATE/insteadOf fetch config mathias/cad's
own Dockerfile uses, now needed for this new private-module dependency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 13:27:02 +02:00

101 lines
3.9 KiB
Go

// stuck.go overlays cad-atlas#11's stuck-state badges onto the pipeline: a
// count badge per stage (Node entries + Stage.StuckCount/AwaitingCount),
// sourced from mathias/cad#14's `cad stuck` query rather than re-implemented
// here. cad's app/domain packages are public Go modules (not internal/) —
// this reuses the exact tested classification logic instead of duplicating
// the taxonomy, per ADR-0002 ("a small coupling surface, not a monolith").
package atlas
import (
"fmt"
caddomain "git.d-ma.be/mathias/cad/domain"
)
// stageForCategory maps a cad-stuck taxonomy row onto the existing 9-stage
// pipeline (atlas.json), following the grouping cad-atlas#11 itself proposes:
// Trigger→armed-but-never-fired sits at the human dispatch gate (the label
// that authorizes dispatch is on, nothing has claimed it yet); Claim/Execute
// collapse into the one Execute stage this pipeline already has (there is no
// separate Claim stage); Close maps to the loop-back stage, where a human
// closing the issue is the action that actually completes the loop.
//
// CategoryDoneNotClosed is deliberately absent from the "stuck" class — see
// ApplyStuckOverlay, which reads Class rather than Category to decide the
// visual treatment. It IS present here for stage placement.
var stageForCategory = map[caddomain.StuckCategory]string{
caddomain.CategoryArmedNeverFired: "STAGE 04",
caddomain.CategoryClaimedNeverRan: "STAGE 05",
caddomain.CategoryCeilingHit: "STAGE 05",
caddomain.CategoryReFireLoop: "STAGE 05",
caddomain.CategoryNearAttemptCap: "STAGE 05",
caddomain.CategoryDoneNotClosed: "STAGE 08",
}
// stuckNodeCls / awaitingNodeCls are the CSS classes the static shell colours
// (cad-atlas.html: .node.stuck, .node.awaiting-human) — the two visual
// classes cad-atlas#11 asks for, kept visually distinct so "awaiting human"
// (a functioning control) never reads as "stuck" (needs intervention).
const (
stuckNodeCls = "stuck"
awaitingNodeCls = "awaiting-human"
)
// ApplyStuckOverlay assigns each finding to the stage stageForCategory names,
// and for every stage in stages marked `"generate":"stuck-badges"`, sets its
// StuckCount/AwaitingCount and prepends one Node per finding assigned there
// (title, class, repo tag, and a URL to the finding's first locator — this
// overlay never adds a control, only a link out to Gitea). Findings whose
// category has no stage mapping are silently omitted from the pipeline view
// rather than misplaced — cad-atlas#11 is a read of the query, not a second
// copy of its taxonomy, so an unmapped category is a gap to notice in
// stageForCategory, not something to guess a stage for.
//
// stages is returned unmodified aside from the stuck-badges stages; this is a
// pure function, no I/O, so it is fixture-testable without a live query.
func ApplyStuckOverlay(stages []Stage, findings []caddomain.StuckFinding) []Stage {
byStage := make(map[string][]caddomain.StuckFinding)
for _, f := range findings {
stageNo, ok := stageForCategory[f.Category]
if !ok {
continue
}
byStage[stageNo] = append(byStage[stageNo], f)
}
out := make([]Stage, len(stages))
copy(out, stages)
for i := range out {
if out[i].Generate != "stuck-badges" {
continue
}
assigned := byStage[out[i].No]
var stuckN, awaitingN int
nodes := make([]Node, 0, len(assigned))
for _, f := range assigned {
cls := stuckNodeCls
if f.Class == caddomain.ClassAwaitingHuman {
cls = awaitingNodeCls
awaitingN++
} else {
stuckN++
}
var url string
if len(f.Locators) > 0 {
url = f.Locators[0]
}
nodes = append(nodes, Node{
Title: fmt.Sprintf("#%d %s", f.Issue, f.Category),
Desc: f.Detail,
Cls: cls,
Tags: []string{"live · cad-stuck", f.Repo},
URL: url,
})
}
out[i].StuckCount = stuckN
out[i].AwaitingCount = awaitingN
out[i].Nodes = append(nodes, out[i].Nodes...)
}
return out
}