Versions: cli-3.43.0, fw-4.42.0 (also present on cli-3.40.0, first observed 2026-07-26)
Summary
straymark charter refresh-suggest reports (missing) for every closed Charter, in any repo that uses the CLI, because the writer and the reader disagree on the telemetry filename.
The heuristic therefore never activates. In our repo it prints Chain length (closed Charters in window): 0 with 29 telemetry files on disk and 7 closed Charters in the chain.
Root cause
Two telemetry_path_for functions that build different names:
cli/src/commands/charter/close.rs:308-321 — writes CHARTER-NN.telemetry.yaml, deliberately stripping the slug:
// Strip optional slug suffix to keep the telemetry filename stable across
// Charter renames: CHARTER-01-foo → CHARTER-01.telemetry.yaml.
cli/src/commands/charter/refresh_suggest.rs:133-138 — reads <file_stem>.telemetry.yaml, where the stem is the Charter's own filename:
/// Charters live at `.straymark/charters/<NN-slug>.md`; telemetry sits next to
/// them as `<NN-slug>.telemetry.yaml` per the convention in charter.rs.
fn telemetry_path_for(_project_root: &Path, charter: &Charter) -> Option<PathBuf> {
let stem = charter.path.file_stem()?.to_str()?;
So for a Charter at .straymark/charters/22-fundacion-expedientes.md:
close writes .straymark/charters/CHARTER-22.telemetry.yaml ✅ (exists)
refresh-suggest looks for .straymark/charters/22-fundacion-expedientes.telemetry.yaml ❌ (never written)
Note the doc comment in refresh_suggest.rs states the <NN-slug> convention as fact — it's the stale one. STRAYMARK.md §10 and §15 document the same stale pattern, so the docs corroborate the reader, not the writer.
Reproduction
Any repo with at least one closed Charter that has telemetry:
$ ls .straymark/charters/*.telemetry.yaml | wc -l
29
$ straymark charter refresh-suggest expedientes
charter_id closed_at R<N+1> telemetry
---------- ---------- ------ ---------
CHARTER-22-fundacion-expedientes — — (missing)
CHARTER-23-expedientes-us1 — — (missing)
CHARTER-24-expedientes-seguimiento — — (missing)
...
Heuristic
Chain length (closed Charters in window): 0
ℹ Chain shorter than 3 closed Charters with telemetry — heuristic not yet meaningful.
Why it matters
The refresh heuristic is precisely the mechanism that detects accumulated spec drift across a Charter chain — the problem CHARTER-CHAIN-EVOLUTION.md was written to address. It has been silently inert since the two names diverged, and the failure mode is indistinguishable from "you don't have enough closed Charters yet", so an adopter reads the message as normal and never suspects a defect. We ran a seven-Charter chain past it without the heuristic ever firing.
Suggested fix
close.rs has the better rule — a filename stable across Charter renames — so align the reader to the writer rather than the reverse: have refresh_suggest.rs derive the name from charter.frontmatter.charter_id the same way close.rs does. Factoring the canonical-name derivation into one shared helper would prevent them drifting apart again.
Falling back to the legacy <NN-slug>.telemetry.yaml when the canonical name is absent would also pick up telemetry written by older CLI versions, if any adopter has that on disk.
STRAYMARK.md §10 and §15 need the same correction — they document the name that is never produced.
Secondary observation
The heuristic block prints two different thresholds for the same condition:
Threshold for rolling mean: > 6
ℹ Chain shorter than 3 closed Charters with telemetry — heuristic not yet meaningful.
> 6 and 3 may well be two distinct gates, but as rendered they read as contradictory. Worth a label that distinguishes them.
Reported from ri-ceiba (private adopter repo, ASP.NET Core / Blazor). Tracked there as FU-033.
Versions: cli-3.43.0, fw-4.42.0 (also present on cli-3.40.0, first observed 2026-07-26)
Summary
straymark charter refresh-suggestreports(missing)for every closed Charter, in any repo that uses the CLI, because the writer and the reader disagree on the telemetry filename.The heuristic therefore never activates. In our repo it prints
Chain length (closed Charters in window): 0with 29 telemetry files on disk and 7 closed Charters in the chain.Root cause
Two
telemetry_path_forfunctions that build different names:cli/src/commands/charter/close.rs:308-321— writesCHARTER-NN.telemetry.yaml, deliberately stripping the slug:cli/src/commands/charter/refresh_suggest.rs:133-138— reads<file_stem>.telemetry.yaml, where the stem is the Charter's own filename:So for a Charter at
.straymark/charters/22-fundacion-expedientes.md:closewrites.straymark/charters/CHARTER-22.telemetry.yaml✅ (exists)refresh-suggestlooks for.straymark/charters/22-fundacion-expedientes.telemetry.yaml❌ (never written)Note the doc comment in
refresh_suggest.rsstates the<NN-slug>convention as fact — it's the stale one.STRAYMARK.md§10 and §15 document the same stale pattern, so the docs corroborate the reader, not the writer.Reproduction
Any repo with at least one closed Charter that has telemetry:
Why it matters
The refresh heuristic is precisely the mechanism that detects accumulated spec drift across a Charter chain — the problem
CHARTER-CHAIN-EVOLUTION.mdwas written to address. It has been silently inert since the two names diverged, and the failure mode is indistinguishable from "you don't have enough closed Charters yet", so an adopter reads the message as normal and never suspects a defect. We ran a seven-Charter chain past it without the heuristic ever firing.Suggested fix
close.rshas the better rule — a filename stable across Charter renames — so align the reader to the writer rather than the reverse: haverefresh_suggest.rsderive the name fromcharter.frontmatter.charter_idthe same wayclose.rsdoes. Factoring the canonical-name derivation into one shared helper would prevent them drifting apart again.Falling back to the legacy
<NN-slug>.telemetry.yamlwhen the canonical name is absent would also pick up telemetry written by older CLI versions, if any adopter has that on disk.STRAYMARK.md§10 and §15 need the same correction — they document the name that is never produced.Secondary observation
The heuristic block prints two different thresholds for the same condition:
> 6and3may well be two distinct gates, but as rendered they read as contradictory. Worth a label that distinguishes them.Reported from ri-ceiba (private adopter repo, ASP.NET Core / Blazor). Tracked there as FU-033.