Просмотр исходного кода

fix: preserve agent-written spec on confirm-complete, don't clobber Q&A

Root cause: after confirm-complete, the agent writes a polished final
spec directly to the file.  Then syncInterview calls
rewriteInterviewDocument which rebuilds the file from scratch using
extractHistorySection — but that used a case-sensitive marker
('## Q&A history') while the agent wrote '## Q&A History' (capital H).
Result: Q&A history lost, replaced with 'No answers yet.'

Fixes:
- Skip rewriteInterviewDocument when parsed.state is null (agent already
  wrote the final spec — just read it as-is)
- Make extractHistorySection and extractSummarySection case-insensitive
  for the '## Q&A history' marker as a defense-in-depth measure
bobbyunknown 1 месяц назад
Родитель
Сommit
bba60f725c
2 измененных файлов с 21 добавлено и 9 удалено
  1. 10 8
      src/interview/document.ts
  2. 11 1
      src/interview/service.ts

+ 10 - 8
src/interview/document.ts

@@ -104,23 +104,25 @@ export function slugify(value: string): string {
 // ─── Markdown Document Operations ────────────────────────────────────
 
 function extractHistorySection(document: string): string {
-  const marker = '## Q&A history\n\n';
-  const index = document.indexOf(marker);
-  return index >= 0 ? document.slice(index + marker.length).trim() : '';
+  const marker = /## Q&A history/i;
+  const match = document.match(marker);
+  if (!match || match.index === undefined) return '';
+  return document.slice(match.index + match[0].length).trim();
 }
 
 export function extractSummarySection(document: string): string {
   const marker = '## Current spec\n\n';
-  const historyMarker = '\n\n## Q&A history';
   const start = document.indexOf(marker);
   if (start < 0) {
     return '';
   }
   const summaryStart = start + marker.length;
-  const summaryEnd = document.indexOf(historyMarker, summaryStart);
-  return document
-    .slice(summaryStart, summaryEnd >= 0 ? summaryEnd : undefined)
-    .trim();
+  const historyMarker = /\n\n## Q&A history/i;
+  const historyMatch = document.slice(summaryStart).match(historyMarker);
+  const summaryEnd = historyMatch?.index
+    ? summaryStart + historyMatch.index
+    : undefined;
+  return document.slice(summaryStart, summaryEnd).trim();
 }
 
 export function extractTitle(document: string): string {

+ 11 - 1
src/interview/service.ts

@@ -383,7 +383,17 @@ export function createInterviewService(
     // Rename file if assistant provided a title (and file hasn't been renamed yet)
     await maybeRenameWithTitle(interview, state.title);
 
-    const document = await rewriteInterviewDocument(interview, state.summary);
+    // Only rewrite the document when we have structured state from the agent.
+    // When parsed.state is null (e.g. after confirm-complete), the agent has
+    // already written the final polished spec directly to the file — rewriting
+    // would clobber it with a rebuild that loses the agent's formatting and
+    // Q&A history (due to case-sensitive marker mismatch).
+    let document: string;
+    if (parsed.state) {
+      document = await rewriteInterviewDocument(interview, state.summary);
+    } else {
+      document = await readInterviewDocument(interview);
+    }
     const blocks = parseSpecBlocks(document);
 
     const interviewState: InterviewState = {