File size: 2,052 Bytes
391c43e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import type { InterviewItem } from './types';

export interface ItemCheckResult {
  itemId: string;
  passed: boolean;
  reason?: string;
}

export interface ItemSummary {
  id: string;
  elicit: string;
  passed: boolean;
  reason?: string;
}

/** Opening of the incomplete-feedback message — also used to skip rendering it twice in the UI. */
export const INCOMPLETE_PREFIX = "Not done yet —";

/**
 * Rolls per-assertion results up to one verdict per required item. A required
 * item passes only if it has at least one result and all of its results passed
 * (no results = could not verify = fail closed). Optional items are excluded.
 */
export function summarizeCompletion(
  items: InterviewItem[],
  results: ItemCheckResult[]
): ItemSummary[] {
  const byItem = new Map<string, ItemCheckResult[]>();
  for (const r of results) {
    const arr = byItem.get(r.itemId) ?? [];
    arr.push(r);
    byItem.set(r.itemId, arr);
  }

  return items
    .filter(i => i.required !== false)
    .map(item => {
      const rs = byItem.get(item.id) ?? [];
      const passed = rs.length > 0 && rs.every(r => r.passed);
      const reason = rs.filter(r => !r.passed).map(r => r.reason).filter(Boolean).join('; ') || undefined;
      return { id: item.id, elicit: item.elicit, passed, reason };
    });
}

/**
 * Decides whether an interview is complete. Returns null when every required
 * item is satisfied, otherwise a feedback string naming the unmet items —
 * which the completion gate hands back to the agent so it keeps working.
 */
export function buildCompletionFeedback(
  items: InterviewItem[],
  results: ItemCheckResult[]
): string | null {
  const unmet = summarizeCompletion(items, results).filter(s => !s.passed);
  if (unmet.length === 0) return null;

  const lines = unmet.map(s => `- ${s.elicit}${s.reason ? ` (${s.reason})` : ''}`);
  return `${INCOMPLETE_PREFIX} these items aren't fully captured in the artifact:\n${lines.join('\n')}\n\nGather what's missing, record it into the artifact, then run status --complete again.`;
}