Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/session/compaction-handoff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,26 @@ describe("extractHandoffArtifact", () => {
expect(rendered).toContain(token);
expect(rendered).toContain(`Output: ${token}`);
});

test("harvests a later standing token that is a substring of an in-window token", () => {
const inWindow = "FILES_DONE=src/a.ts.bak";
const later = "FILES_DONE=src/a.ts";
const prefix = `${"W".repeat(500 - inWindow.length - 1)} `;
const goal = `${prefix}${inWindow} ${later}`;
const window = goal.replace(/\s+/g, " ").trim().slice(0, 500);
expect(window.endsWith(inWindow)).toBe(true);
expect(window.includes(later)).toBe(true);
expect(window.includes(`${later} `)).toBe(false);

const { artifact, spine } = extractHandoffArtifact(
[userTurn(goal)],
"narrative",
);
expect(artifact.goal).toBe(goal.replace(/\s+/g, " ").trim());
expect(artifact.goal.endsWith(` ${later}`)).toBe(true);
expect(spine.goal).toContain(inWindow);
expect(spine.goal.endsWith(` ${later}`)).toBe(true);
});
});

describe("recoverEvidenceMarkers", () => {
Expand Down Expand Up @@ -698,6 +718,37 @@ describe("iterative folding", () => {
expect(again.spineText).toContain(token);
});

test("without-file re-fold keeps an operator Goal that ends with ellipsis", () => {
const goal = "Fix the tests...";
const first = buildHandoffFold([userTurn(goal)], "narrative");
expect(first.spineText).toContain(`Goal: ${goal}`);
expect(first.artifact.goal).toBe(goal);

const withoutFile = buildHandoffFold(
[spineTurn(first.spineText), userTurn("Continue.")],
"narrative",
);
expect(withoutFile.artifact.goal).toBe(goal);
expect(withoutFile.spineText).toContain(`Goal: ${goal}`);
});

test("without-file re-fold keeps FILES_DONE=src/foo... as one token", () => {
const token = "FILES_DONE=src/foo...";
const first = buildHandoffFold([userTurn(token)], "narrative");
expect(first.spineText).toContain(`Goal: ${token}`);
expect(first.spineText).toContain(`Output: ${token}`);
expect(first.artifact.goal).toBe(token);

const withoutFile = buildHandoffFold(
[spineTurn(first.spineText), userTurn("Continue.")],
"narrative",
);
expect(withoutFile.artifact.goal).toBe(token);
expect(withoutFile.spineText).toContain(`Goal: ${token}`);
expect(withoutFile.spineText).toContain(`Output: ${token}`);
expect(withoutFile.artifact.goal).not.toContain(`${token} ${token}`);
});

test("a truncated spine fragment collapses into the full prior-file constraint", () => {
const full = `Must never ship without ${"x".repeat(80)}`;
const first = buildHandoffFold([userTurn(full)], "narrative");
Expand Down
41 changes: 34 additions & 7 deletions src/session/compaction-handoff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,39 @@ function capGoal(text: string, maxChars: number): string {
if (start < end && tokenEnd > end) end = tokenEnd;
}
let capped = line.slice(0, end);
const present = new Set(outputFormatTokens(capped));
for (const token of tokens) {
if (!capped.includes(token)) capped = `${capped} ${token}`;
if (present.has(token)) continue;
capped = `${capped} ${token}`;
present.add(token);
}
return capped;
}

function hasStandingOutputToken(goal: string, token: string): boolean {
return outputFormatTokens(goal).includes(token);
}

function trailingEllipsisIsStandingToken(goal: string): boolean {
if (!goal.endsWith(SPINE_CUT_SENTINEL)) return false;
return outputFormatTokens(goal).some(
(token) => token.endsWith(SPINE_CUT_SENTINEL) && goal.endsWith(token),
);
}

function mergeStandingOutputToken(
goal: string | undefined,
token: string,
): string {
if (goal === undefined || goal.length === 0) return token;
if (hasStandingOutputToken(goal, token)) return goal;
const prefix =
goal.endsWith(SPINE_CUT_SENTINEL) && !trailingEllipsisIsStandingToken(goal)
? goal.slice(0, -SPINE_CUT_SENTINEL.length)
: goal;
return `${prefix} ${token}`;
}

function cutSpineGoal(text: string): string {
const line = text.replace(/\s+/g, " ").trim();
if (line.length <= SPINE_GOAL_CHARS) return line;
Expand Down Expand Up @@ -271,17 +298,17 @@ function parseSpineText(text: string): CarriedFacts {
for (const line of text.split("\n")) {
const trimmed = line.trim();
if (trimmed.startsWith("Goal: ")) {
let goal = trimmed.slice("Goal: ".length).trim();
if (goal.endsWith(SPINE_CUT_SENTINEL))
goal = goal.slice(0, -SPINE_CUT_SENTINEL.length);
// Keep operator-authored trailing "..." (Fix the tests...,
// FILES_DONE=src/foo...). The spine cut sentinel is stripped only when
// mergeStandingOutputToken restores an Output token onto a truncated
// Goal prefix.
const goal = trimmed.slice("Goal: ".length).trim();
if (goal.length > 0) carried.goal = goal;
} else if (trimmed.startsWith(HANDOFF_OUTPUT_LINE_PREFIX)) {
for (const token of outputFormatTokens(
trimmed.slice(HANDOFF_OUTPUT_LINE_PREFIX.length),
)) {
if (carried.goal === undefined) carried.goal = token;
else if (!carried.goal.includes(token))
carried.goal = `${carried.goal} ${token}`;
carried.goal = mergeStandingOutputToken(carried.goal, token);
}
} else if (trimmed.startsWith("Constraints: ")) {
for (const constraint of trimmed
Expand Down
Loading