Beagle SCM
CODE-020: be/ JS duplication — extract shared primitives
An 8-agent quality review of be/ (JS jab; 61 files, ~15kloc; 2026-07-03) found pervasive duplication: ~a dozen low-level primitives re-implemented per file, several with SILENT divergence (hex-length gates in hashlet resolution, three isMeta rule-sets) — a latent-bug surface, not mere untidiness. Tracks behavior-preserving extraction into be/shared/ (and be/view/) via the work loop, one worktree per group, repro-first where reconciling a divergent copy could shift behavior. JS-only; C twin CODE-017/CODE-010, modularity JS-090.
Input
Context
- 8 agents partitioned be/ by subsystem (core/shared/verbs/views/view), quality-only (reuse/readability/duplication/patterns); each returned file:line dup-candidates this ticket synthesizes.
- Silent-divergence evidence A: hashlet→sha resolution uses 5 different hex gates (≥4 / ≥6 / ≤39 / ≤40 / 6–40) across diff.js:373 commit.js:110 log.js:110 blob.js:68 tree.js:118 sha1.js:96 size.js:59 type.js:63.
- Silent-divergence evidence B: three
isMeta with different rules AND different empty-input returns at classify.js:435, stage.js:51, util/ignore.js:143.
- Load-order trap: ambient globals (hex/git/io/sha1/utf8/ron/URI) are injected not imported; store.js:52 derefs
hex at module load.
Goals
- Extract each duplicated primitive to ONE shared home; unify divergent copies onto a single reconciled implementation; keep behavior identical.
- Reconcile the bug-risk divergences (hashlet gates,
isMeta) FIRST — dedup there changes behavior and needs a repro test.
- Let new verbs/views compose helpers (fs probes, tok32 codec, hunk-span accumulator) instead of copy-paste.
Constraints
- be/ JS only; never touch beagle/ (C twin); drive via jab, never native be.
- Behavior-preserving; keep the full ctest suite green; add a repro only where behavior could shift (abc refactor policy, repro-first).
- Comments ≤2 lines; resist churn — skip-and-note any marginal swap (the CODE-001..015 bullshit-quality lesson).
- One worktree per TODO group; groups are independent and parallelize.
WIP
Design decisions
- Target homes:
shared/util/fs.js (stat/kind/exists), shared/util/wtblob.js (readWtBytes/hashWtBytes), shared/store.js (treeWalk/descendPath/loadTreeLeaves + blobBytes), shared/util/path.js (join/normRel/isMount/extOf/pathExt/trimSlash).
- Target homes:
shared/uriarg.js (parseVerbArg), shared/resolve.js (one hashlet resolver), view/theme.js (tok32 codec + TAG_* + palette), view/hunkbuild.js (span accumulator + concatBytes/writeFd).
- resolveHashlet: one resolver with an explicit length-policy param; document why any gate must differ, else make them equal.
- Palette:
view/theme.js is the single source (its header already claims to supersede view/bro.js THEME16); derive bro.js ansi64 from it, drop THEME16/cellSGR.
TODOs
- reconcile hashlet→sha resolution into shared/resolve.js (one hex-gate policy); fold diff.js:373 commit.js:110 log.js:110 blob.js:68 tree.js:118 sha1.js:96 size.js:59 type.js:63 — repro-gated.
- unify
isMeta onto one predicate (fix divergent empty-input return); fold classify.js:435 stage.js:51 util/ignore.js:143 — repro-gated.
- shared/util/fs.js stat/kind/exists probes; fold discover.js:30 recurse.js:35 classify.js:104 checkout.js:24 subs.js:41 stage.js:321 submount.js:39 store.js:61 delete.js:52 get.js:111.
- v wtblob group — LANDED
57ff502a as shared/wtread.js readFileBytes ("shared readFileBytes wt read, 5 sites deduped"); verified consumers stage.js:59/74, checkout.js:47-48.
- tok32 codec + TAG_* letters → view/theme.js; 2026-07-07: only diff.js + log.js still define
tok32Pack (the JAB-003 hunk-output conversion retired the other copies), but view/theme.js still has NO codec — fold the last two.
- view/hunkbuild.js span accumulator (textParts+spans→row) + concatBytes/writeFd; fold log.js:350 commit.js:181 status.js:140 tree.js:51 ls.js:43 view/render.js:25.
- tree walk / descend-by-path → shared/store.js (treeWalk/descendPath/loadTreeLeaves); fold store.js:500 changedpaths.js:97 classify.js:214 get.js:260 patch.js:59 diff.js:552 log.js:312 cat.js:99.
- shared/uriarg.js parseVerbArg (strip
verb: → URI → path/query/frag); fold cat.js:122 blob.js:105 tree.js:167 sha1.js:123 size.js:127 type.js:83.
- path helpers → shared/util/path.js (join/normRel/isMount/extOf/pathExt/trailing-slash-strip); fold put.js:214 delete.js:51 get.js:859 patch.js:82 diff.js:295 view/bro.js:631 ext.js:53 discover.js:39.
- palette single-source: view/theme.js supersedes view/bro.js THEME16/cellSGR; derive ansi64, drop the dead map.
- v wire.js internal triples — LANDED
69e56b20 ("CODE-020 wire dedup"): drainAdvert (shared/wire.js:226), pkt.decodeAt reuse (:211), curlPost (:195); verified in-tree.
- ingest.js refs-ULOG write helper (83/108/134) + origin-normalize; share ron60 pack/unpack+epoch (ulog.js:70/85, post.js:83, dag.js).
- weave-merge + conflict-fence render: one helper for get.js:812 vs patch.js:278 (two whole algorithms) — behavior-sensitive, repro-gated.
Blockers and bummers
- CODE-001..015 produced bullshit-quality "improvements" (see CODE board); keep every change behavior-preserving and gate the diff before landing.
- Divergent copies (resolveHashlet, isMeta, weave-merge) cannot be blind-deduped — reconciling semantics may change behavior, so those are repro-first, not mechanical.
- Ambient-global injection means moving code between files can hit load-order traps; preserve the binding contract when relocating.
- Spin-off (NOT this ticket, file separately per todo own-ticket rule): put.js:336 dead ternary (both arms return
hexish); eval() on address-bar input at views/bro/bro.js:76; dead code fold-decide.js:232 libDir and loop.js _nullSink (stale, drops flush); stale comment post.js:457.
Outcome
- PARTIAL (2026-07-07 audit): landed
57ff502a (shared/wtread.js readFileBytes, 5 sites) + 69e56b20 (wire dedup: drainAdvert/decodeAt/curlPost).
- Still open, re-verified in-tree: resolveHashlet copies persist (commit.js:129 vs log.js:121, differing gates); isMeta still 3-way divergent (classify.js:435, stage.js:51, util/ignore.js:143); no shared/resolve.js, shared/uriarg.js, view/hunkbuild.js, shared/util/fs.js; view/theme.js still lacks the tok32 codec.
- Note: the JAB-003/JAB-004 reworks (hunk-output conversion
aa61c49c/1e564190, plain-args verbs 11e4dddc) retired several listed copies as a side effect — re-scan fold-site line numbers before starting a group.