Skip to content

sd-3wp.1: epic buckets, the what's-next cascade, and a change gate that doesn't defeat itself - #112

Merged
dkoosis merged 5 commits into
mainfrom
bead/sd-3wp.1
Aug 19, 2026
Merged

sd-3wp.1: epic buckets, the what's-next cascade, and a change gate that doesn't defeat itself#112
dkoosis merged 5 commits into
mainfrom
bead/sd-3wp.1

Conversation

@dkoosis

@dkoosis dkoosis commented Aug 19, 2026

Copy link
Copy Markdown
Owner

counts.json rows gain three additive fields — epics (per-live-roadmap-epic ◆○◐● buckets, roadmap order), next {id,title,reason} via the four-rung cascade (claimed → current-epic top ready → waiting-on-dk → next epic), claimed — consumed next by the session-open banner (sd-3wp.2).

Perf: the 35.7s bare run was a self-defeating change gate — changeKey keyed on the dolt noms-manifest MTIME, which a pure bd read rewrites with identical bytes, so the refresher's own reads marked every repo changed forever. Now keys on manifest CONTENT (bd.StoreContentKey, FNV-1a). Measured after: bare warm 3.0s, single-repo warm 1.1s (<2s acceptance).

Plan: sdlc docs/plans/'Strand counts epic buckets, next pick, fast writer.md' (approved). Reviews: plan-adherence pass · concurrency pass (5 hunts refuted; one pre-existing multi-manifest caveat noted). make check green.

One acceptance criterion is verified-by: human — statusline render unchanged; eyeball the badge in a live session after merge.

Closes sd-3wp.1

…ignal

FNV-1a over the noms manifest bytes instead of its mtime. StoreMTime moves on
every bd read (including a pure one), so gating on it never lets a repo
settle. Content only moves on a genuine write, preserving st-nm5's
out-of-band detection. Diagnosis re-verified live before coding: 21/23
counts-mtimes rows pending=1, bd stats rewrites the manifest mtime with
byte-identical content (sha256 unchanged), ~0.4s/exec x 4 reads per repo.
… section

Roadmap() now resolves $root/ROADMAP.md first (the sdlc standard), falling
back to the legacy NORTH_STAR.md ## roadmap parse only when it's absent. New
format: numbered arrow lines inside ## Epics (legacy ## Milestones/## Route
tolerated), id = first token after the first arrow, multi-id lines keep the
first, wrapped continuation lines fold into their numbered row (sd-r59). Go
twin of roadmap-epics.sh's roadmap_file/roadmap_parse, reduced to its two
non-kg branches per this bead's non-goals.
Row gains three additive fields (epics/next/claimed, JSON null when absent).
computeRow now derives insight.Lanes once and shares it across the repo-level
buckets, the per-epic buckets, and pickNext — zero new bd execs.

- liveRoadmapEpics/currentEpicID: roadmap-ordered live (non-closed) epics;
  a deferred epic still counts as current (deliberately, station() keeps its
  own open/in-progress rule); ghost ids skipped.
- epicBuckets: per-live-epic ◆○◐● partition over direct children, nested
  epics excluded, bw/bh overlap on a gated in-progress child.
- pickNext: 4-rung cascade (repo-wide claimed -> current-epic open child ->
  repo-wide waiting -> next live epic's open child), epics excluded from
  every rung, no repo-wide ready-bead catch-all (bead-verbatim exclusion,
  pinned by test). Tie-break: lowest priority (nil->P2) then id.

laneCounts signature changed to take the shared lanes map.
changeKey() now calls bd.StoreContentKey instead of bd.StoreMTime, falling
back to lastTouched exactly as before. This is the actual perf fix: the
refresher's own 4 bd reads/repo rewrote the manifest mtime with identical
content, so the old mtime-keyed gate never let a repo settle — every cycle
re-armed the st-3p8 pending bit for every repo it had just visited.

refresh_test.go: setStoreMTime -> setStoreState(content, mtime);
TestRefreshOutOfBandStoreChangeRefreshes now varies CONTENT in its
out-of-band step (mtime-only would no longer trip a content-keyed gate).
New TestRefreshConvergesDespiteReadSelfChurn is the bug's own regression
test: a source that self-churns the manifest mtime every read must still
let the repo settle after the guaranteed st-3p8 follow-up (calls==2 after
3 runs) — confirmed red against the old gate before the fix, green after.
… work

goconst: one issueTypeEpic constant replaces 5 of the 6 "epic" literals in
counts.go (the cascade reason string "epic" stays literal — different
meaning). gocritic rangeValCopy/hugeParam: pickNext's filter/pick chain and
epicBuckets now index/pointer into []bd.Issue instead of copying the
~240-byte struct per iteration; refresh.go's writeRowsAtomic loop switched
to the same pattern (flagged only because Row grew past the threshold with
this bead's new fields). modernize: strandmd.epicLineID uses strings.Cut
instead of strings.Index+slicing. gofmt: counts_test.go table alignment.

make check green: vet, lint (0 issues), test, build, conform.
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bf565aec-2244-4874-b1b3-c301e61d4aeb


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dkoosis
dkoosis merged commit ebec3a1 into main Aug 19, 2026
2 checks passed
@dkoosis
dkoosis deleted the bead/sd-3wp.1 branch August 19, 2026 21:58
dkoosis added a commit that referenced this pull request Aug 20, 2026
…lot (#115)

* feat(counts): sd-3wp.2 — epic titles and the current epic's roadmap slot

wrap's rewritten session banner names epics and states "epic 2 of 7". counts.json
could supply neither: epics[] carried ids without titles, and its index counts
only live epics, so it cannot express a position among all of them.

Two additive fields, from reads computeRow already makes:
- EpicRow.title — bd's own epic title, via the EpicStatus read the buckets
  derive from (bd.EpicRef gains Title to carry it).
- Row.roadmap {k, n} — the current epic's 1-based slot among ALL roadmap ids,
  closed included, so k/n reads as progress through the project. Explicit null
  when there is no roadmap or no live epic — the same two conditions that null
  epics.

Additive only, same rule as #112: every consumer decodes by name into its own
struct, so a key it never mentions cannot affect it.

Verified live against sdlc: roadmap {k:2,n:7}, seven epic titles rendered.

* feat(counts): carry bd's closed/total child roll-up per epic

The four lane buckets partition only LIVE children — bd list omits closed — so
epics[] could not answer "how far through this epic are we", which the banner's
✓ tally and progress bar both need.

EpicRow gains bcl (closed children) and n (total children), taken verbatim from
the EpicStatus read epicBuckets already walks. They are bd's own roll-up, not a
re-derivation: a nested epic child counts there and not in the buckets, and the
comment says so.

epicTitles becomes epicMeta — the lookup now carries the whole EpicStatus row
rather than just the title.

Live against sdlc: sd-ev2 bcl 12 / n 13, matching bd epic status.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant