Skip to content

Commit dea01e0

Browse files
committed
address review (claude): trim prior-issue prose tail from the head so the closing survives
trimContextToBudget keeps the head of the string, so on a small-context local model the prior-issue prose tail lost its literal closing lines — the exact seam the prose template tells the model to open from. Size the tail via extractProseTail (which keeps the end) capped at the budget instead, and pin it with a regression assertion that the trimmed tail contains the closing marker, not the opening.
1 parent 2384fc1 commit dea01e0

2 files changed

Lines changed: 17 additions & 4 deletions

File tree

server/services/pipeline/textStages.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,9 @@ async function buildProseContextAugment(series, issue, options = {}) {
274274
const empty = { priorIssueProseTail: '', nextIssueBeats: '', hasNeighborContinuity: false };
275275

276276
const { prior, next } = await resolveVolumeNeighbors(series, issue);
277-
const rawTail = prior ? extractProseTail(stageContentOf(prior.stages?.prose)) : '';
277+
const priorProse = prior ? stageContentOf(prior.stages?.prose) : '';
278278
const rawBeats = extractNextIssueBeats(next);
279-
if (!rawTail && !rawBeats) return empty;
279+
if (!priorProse && !rawBeats) return empty;
280280

281281
// Token-budget both blocks so they degrade by trimming on a small window
282282
// rather than blowing the prompt. Resolve the prose stage's planning window
@@ -294,8 +294,15 @@ async function buildProseContextAugment(series, issue, options = {}) {
294294
const continuityChars = Math.max(0, Math.floor(usableTokens * CONTINUITY_BUDGET_FRACTION)) * CHARS_PER_TOKEN;
295295

296296
// Prose tail keeps priority; next beats reclaim whatever the tail doesn't use.
297+
// The tail must be trimmed from its HEAD (keep the actual CLOSING of the prior
298+
// issue — the seam the template tells the model to flow from), so size it via
299+
// extractProseTail (which keeps the end) rather than trimContextToBudget (which
300+
// keeps the head and would discard the literal final lines on a small window).
301+
// Never grow past PRIOR_PROSE_TAIL_CHARS just because the window is large.
297302
const tailBudget = rawBeats ? Math.floor(continuityChars * PRIOR_TAIL_SHARE) : continuityChars;
298-
const priorIssueProseTail = rawTail ? trimContextToBudget(rawTail, tailBudget) : '';
303+
const priorIssueProseTail = priorProse
304+
? extractProseTail(priorProse, Math.min(PRIOR_PROSE_TAIL_CHARS, tailBudget))
305+
: '';
299306
const beatsBudget = continuityChars - priorIssueProseTail.length;
300307
const nextIssueBeats = rawBeats ? trimContextToBudget(rawBeats, Math.max(0, beatsBudget)) : '';
301308

server/services/pipeline/textStages.test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,9 @@ describe('pipeline text stage generator', () => {
634634
});
635635
const { series } = await seed();
636636
const sea = await seasonsSvc.createSeason(series.id, { title: 'V1' });
637-
const bigTail = 'The tide rolls in. '.repeat(2000); // ~38K chars — far over the window
637+
// Distinct opening vs closing markers so we can prove the trim keeps the END
638+
// (the actual close the model must flow from), not the head.
639+
const bigTail = `PRIOR_OPENING. ${'The tide rolls in. '.repeat(2000)}PRIOR_CLOSING.`;
638640
await issuesSvc.createIssue({
639641
seriesId: series.id, title: 'A', seasonId: sea.id, arcPosition: 1,
640642
stages: { prose: { status: 'ready', output: bigTail } },
@@ -646,6 +648,10 @@ describe('pipeline text stage generator', () => {
646648
// Present but trimmed below the raw ~2000-char tail cap — degraded, not dropped or errored.
647649
expect(ctx.priorIssueProseTail.length).toBeGreaterThan(0);
648650
expect(ctx.priorIssueProseTail.length).toBeLessThan(2_000);
651+
// Regression guard: the trim must keep the CLOSING of the prior issue (the
652+
// seam the prose template tells the model to open from), not its opening.
653+
expect(ctx.priorIssueProseTail).toContain('PRIOR_CLOSING');
654+
expect(ctx.priorIssueProseTail).not.toContain('PRIOR_OPENING');
649655
});
650656

651657
it('prompt context carries derived lengthTargets for the custom profile', async () => {

0 commit comments

Comments
 (0)