scripts/transcript-digest.ts:182 opens turns with isTurnTrigger (isMeta !== true) and classifies wake sources off that entry. Routine wakes ([hermit-routine:<id>] …) and inbound channel envelopes are isMeta: true with string content, so they never open a turn — their tool calls fold into the preceding turn instead.
Content shape, not isMeta, is the discriminator. The cost-attribution path already established this (see the isSkillInjection comment in scripts/lib/cc-compat.ts): measured on live transcripts, 247 routine and 192 channel signals live in isMeta entries.
Effect
wakes undercounts.
productive_wakes is wrong in both directions — a channel turn's Write/Edit calls currently fold into the preceding routine wake's turn and can mark it productive.
Fix
Promote the predicate turnPromptText already uses into a shared export and use it for segmentation in the digest:
// lib/cc-compat.ts
function isPromptBoundary(entry: Json): boolean {
return entry.type === 'user' && !isToolResult(entry) && !isSkillInjection(entry);
}
turnPromptText calls it; transcript-digest.ts:182 swaps isTurnTrigger → isPromptBoundary. That leaves isTurnTrigger with zero production callers (only transcript-digest.ts and one test comment reference it — cost-tracker.ts uses a deliberately looser inline predicate, see the warning at cost-tracker.ts:156-161), so it should be deleted in the same change with its isMeta rationale folded into a comment on the new predicate.
Gate before merging
This moves an operator-facing counter. The defer-loop rule in skills/reflect/SKILL.md:54 thresholds on wakes >= 20 && productive_wakes / wakes < 0.25, calibrated against the current undercount. Routine wakes are disproportionately unproductive by nature, so the ratio should drop and the rule fire more readily — likely the correct direction, but it needs before/after transcript-digest.ts numbers on live transcripts to confirm 0.25 still separates healthy from pathological.
Wants its own PR rather than riding along with a cost-attribution change.
scripts/transcript-digest.ts:182opens turns withisTurnTrigger(isMeta !== true) and classifies wake sources off that entry. Routine wakes ([hermit-routine:<id>] …) and inbound channel envelopes areisMeta: truewith string content, so they never open a turn — their tool calls fold into the preceding turn instead.Content shape, not
isMeta, is the discriminator. The cost-attribution path already established this (see theisSkillInjectioncomment inscripts/lib/cc-compat.ts): measured on live transcripts, 247 routine and 192 channel signals live inisMetaentries.Effect
wakesundercounts.productive_wakesis wrong in both directions — a channel turn'sWrite/Editcalls currently fold into the preceding routine wake's turn and can mark it productive.Fix
Promote the predicate
turnPromptTextalready uses into a shared export and use it for segmentation in the digest:turnPromptTextcalls it;transcript-digest.ts:182swapsisTurnTrigger→isPromptBoundary. That leavesisTurnTriggerwith zero production callers (onlytranscript-digest.tsand one test comment reference it —cost-tracker.tsuses a deliberately looser inline predicate, see the warning atcost-tracker.ts:156-161), so it should be deleted in the same change with itsisMetarationale folded into a comment on the new predicate.Gate before merging
This moves an operator-facing counter. The defer-loop rule in
skills/reflect/SKILL.md:54thresholds onwakes >= 20 && productive_wakes / wakes < 0.25, calibrated against the current undercount. Routine wakes are disproportionately unproductive by nature, so the ratio should drop and the rule fire more readily — likely the correct direction, but it needs before/aftertranscript-digest.tsnumbers on live transcripts to confirm 0.25 still separates healthy from pathological.Wants its own PR rather than riding along with a cost-attribution change.