Add checkpoint review and assumptions field

Separate assumptions from decisions as a first-class checkpoint
field. Add review endpoint that surfaces reasoning consistency
issues: contradictions, hidden assumptions, resolved questions,
and unused entities. Extend draft extraction, prompt context,
and compare diff to include assumptions.
This commit is contained in:
Himanshu Dongre 2026-04-04 21:07:41 +05:30
parent 97b7845cb5
commit 2f155e8dcc
9 changed files with 326 additions and 25 deletions

View file

@ -0,0 +1,30 @@
"""add_assumptions_to_commits
Revision ID: b3c4d5e6f7a8
Revises: a1b2c3d4e5f6
Create Date: 2026-04-04 20:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import JSONB
# revision identifiers, used by Alembic.
revision: str = 'b3c4d5e6f7a8'
down_revision: Union[str, None] = 'a1b2c3d4e5f6'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column(
'commits',
sa.Column('assumptions', JSONB(), nullable=False, server_default='[]'),
)
def downgrade() -> None:
op.drop_column('commits', 'assumptions')

View file

@ -144,6 +144,11 @@ def build_prompt_from_checkpoints(checkpoints: list[CommitModel], recent_message
for d in ckpt.decisions:
lines.append(f"- {d}")
lines.append("")
if ckpt.assumptions:
lines.append(f"{label} Key Assumptions:")
for a in ckpt.assumptions:
lines.append(f"- {a}")
lines.append("")
if ckpt.tasks:
lines.append(f"{label} Open Tasks:")
for t in ckpt.tasks:
@ -251,6 +256,7 @@ class ManualCommitRequest(BaseModel):
summary: str = ""
objective: str = ""
decisions: list[str] = Field(default_factory=list)
assumptions: list[str] = Field(default_factory=list)
tasks: list[str] = Field(default_factory=list)
open_questions: list[str] = Field(default_factory=list)
entities: list[str] = Field(default_factory=list)
@ -266,6 +272,7 @@ class CommitResponse(BaseModel):
summary: str
objective: str
decisions: list
assumptions: list
tasks: list
open_questions: list
entities: list
@ -622,6 +629,7 @@ def manual_commit(payload: ManualCommitRequest, db: Session = Depends(get_db)):
summary=payload.summary,
objective=payload.objective,
decisions=payload.decisions,
assumptions=payload.assumptions,
tasks=payload.tasks,
open_questions=payload.open_questions,
entities=payload.entities,

View file

@ -1,4 +1,4 @@
"""Checkpoint routes for auto drafting."""
"""Checkpoint routes for auto drafting and review."""
import json
import logging
@ -10,8 +10,8 @@ from sqlalchemy import select
from sqlalchemy.orm import Session
from app.db.database import get_db
from app.db.models import ChatSession, TurnEvent
from app.schemas import CheckpointDraftRequest, CheckpointDraftResponse
from app.db.models import ChatSession, CommitModel, TurnEvent
from app.schemas import CheckpointDraftRequest, CheckpointDraftResponse, CheckpointReviewResponse, ReviewIssue
from app.providers.registry import get_adapter
from app.config_loader import get_config
@ -107,6 +107,7 @@ Return a STRICT JSON object with exactly this schema — no extra keys, no markd
"objective": "The main goal the user is working toward in this conversation (1 sentence, or empty string if unclear)",
"summary": "Concise narrative of what was discussed and figured out (2-4 sentences)",
"decisions": ["An explicit decision made in the conversation", "Another explicit decision"],
"assumptions": ["Something taken for granted but not explicitly decided"],
"tasks": ["A concrete action item from the conversation", "Another action item"],
"open_questions": ["An unresolved question from the conversation"],
"entities": ["Key concept, tool, place, or system mentioned"]
@ -114,6 +115,7 @@ Return a STRICT JSON object with exactly this schema — no extra keys, no markd
Rules:
- decisions: only include choices explicitly made in the conversation, not hypothetical ones
- assumptions: things the conversation takes for granted that were NOT explicitly debated or decided (e.g., implicit constraints, assumed technology choices, timeline expectations treated as given)
- tasks: only include things the user said they will do or need to do
- entities: proper nouns and key technical/domain terms only
- All arrays may be empty if nothing relevant was discussed
@ -138,14 +140,17 @@ Rules:
raw_response = adapter.send(messages, model=bg_model, response_format={"type": "json_object"})
data = json.loads(raw_response)
_dedup = lambda items: list(dict.fromkeys([str(x).strip() for x in items if x]))
return CheckpointDraftResponse(
title=str(data.get("title", "")).strip(),
objective=str(data.get("objective", "")).strip(),
summary=str(data.get("summary", "")).strip(),
decisions=list(dict.fromkeys([str(x).strip() for x in data.get("decisions", []) if x])),
tasks=list(dict.fromkeys([str(x).strip() for x in data.get("tasks", []) if x])),
open_questions=list(dict.fromkeys([str(x).strip() for x in data.get("open_questions", []) if x])),
entities=list(dict.fromkeys([str(x).strip() for x in data.get("entities", []) if x])),
decisions=_dedup(data.get("decisions", [])),
assumptions=_dedup(data.get("assumptions", [])),
tasks=_dedup(data.get("tasks", [])),
open_questions=_dedup(data.get("open_questions", [])),
entities=_dedup(data.get("entities", [])),
)
except json.JSONDecodeError:
@ -154,3 +159,114 @@ Rules:
except Exception as e:
logger.error(f"LLM extraction error: {e}")
raise HTTPException(status_code=502, detail=f"Drafting failed: {e}")
# ── Review endpoint ──────────────────────────────────────────────────────────
def _format_bullet_list(items: list, fallback: str = "None listed") -> str:
if not items:
return fallback
return "\n".join(f"- {item}" for item in items)
@router.post("/{checkpoint_id}/review", response_model=CheckpointReviewResponse)
def review_checkpoint(checkpoint_id: uuid.UUID, db: Session = Depends(get_db)):
"""
Review a checkpoint for reasoning consistency.
Sends the checkpoint's structured fields to the background intelligence
provider and returns a small number of high-signal issues.
"""
commit = db.get(CommitModel, checkpoint_id)
if not commit:
raise HTTPException(status_code=404, detail="Checkpoint not found")
prompt = f"""You are a reasoning consistency reviewer.
Review this structured checkpoint that captures the state of a reasoning process.
CHECKPOINT:
Title: {commit.message}
Objective: {commit.objective or "(not set)"}
Summary: {commit.summary or "(not set)"}
Assumptions:
{_format_bullet_list(commit.assumptions or [])}
Decisions:
{_format_bullet_list(commit.decisions or [])}
Tasks:
{_format_bullet_list(commit.tasks or [])}
Open Questions:
{_format_bullet_list(commit.open_questions or [])}
Entities:
{", ".join(commit.entities or []) or "None listed"}
Identify ONLY these issue types:
1. CONTRADICTION: Two decisions, or a decision and an assumption, that appear to conflict with each other.
2. HIDDEN_ASSUMPTION: Something the summary or decisions clearly rely on that is not listed as an assumption or decision. Only flag when the implicit reliance is obvious.
3. RESOLVED_QUESTION: An open question that appears already answered by a decision or the summary.
4. UNUSED_ENTITY: An entity that is not referenced in the summary, decisions, tasks, or objective.
Rules:
- Be conservative. Only flag issues you are confident about.
- Prefer precision over recall it is better to miss an issue than to flag a false one.
- Reference specific text from the checkpoint in each description.
- Return at most 5 issues.
- Suggestions should be brief and actionable.
- If no issues are found, return empty arrays.
Return STRICT JSON no markdown, no explanation:
{{
"issues": [
{{
"type": "contradiction | hidden_assumption | resolved_question | unused_entity",
"description": "Brief description referencing specific checkpoint text"
}}
],
"suggestions": ["Brief actionable suggestion"]
}}"""
try:
cfg = get_config()
bg_provider = cfg.background.provider
bg_model = cfg.background.model
adapter = get_adapter(bg_provider, allow_mock=False)
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Background provider not configured. Error: {e}"
)
try:
raw_response = adapter.send(
[{"role": "user", "content": prompt}],
model=bg_model,
response_format={"type": "json_object"},
)
data = json.loads(raw_response)
issues = []
for item in data.get("issues", []):
issue_type = str(item.get("type", "")).strip()
desc = str(item.get("description", "")).strip()
if issue_type and desc:
issues.append(ReviewIssue(type=issue_type, description=desc))
suggestions = [str(s).strip() for s in data.get("suggestions", []) if s]
return CheckpointReviewResponse(
checkpoint_id=checkpoint_id,
issues=issues[:5],
suggestions=suggestions[:5],
)
except json.JSONDecodeError:
logger.error(f"Review returned invalid JSON: {raw_response}")
raise HTTPException(status_code=502, detail="Failed to parse review response.")
except Exception as e:
logger.error(f"Review error: {e}")
raise HTTPException(status_code=502, detail=f"Review failed: {e}")

View file

@ -108,6 +108,7 @@ class CheckpointDetail(BaseModel):
summary: str
objective: str
decisions: list
assumptions: list
tasks: list
open_questions: list
@ -120,6 +121,9 @@ class CheckpointDiff(BaseModel):
decisions_only_a: list[str]
decisions_only_b: list[str]
decisions_shared: list[str]
assumptions_only_a: list[str] = Field(default_factory=list)
assumptions_only_b: list[str] = Field(default_factory=list)
assumptions_shared: list[str] = Field(default_factory=list)
tasks_only_a: list[str]
tasks_only_b: list[str]
tasks_shared: list[str]
@ -145,6 +149,7 @@ class ReachableCheckpoint(BaseModel):
summary: str
objective: str
decisions: list
assumptions: list
tasks: list
open_questions: list
entities: list
@ -301,6 +306,9 @@ def compare_checkpoints(a_id: uuid.UUID, b_id: uuid.UUID, db: Session = Depends(
dec_only_a, dec_only_b, dec_shared = _diff_lists(
commit_a.decisions or [], commit_b.decisions or []
)
assump_only_a, assump_only_b, assump_shared = _diff_lists(
commit_a.assumptions or [], commit_b.assumptions or []
)
task_only_a, task_only_b, task_shared = _diff_lists(
commit_a.tasks or [], commit_b.tasks or []
)
@ -314,6 +322,7 @@ def compare_checkpoints(a_id: uuid.UUID, b_id: uuid.UUID, db: Session = Depends(
summary=c.summary or "",
objective=c.objective or "",
decisions=[_extract_text(d) for d in (c.decisions or [])],
assumptions=[_extract_text(a) for a in (c.assumptions or [])],
tasks=[_extract_text(t) for t in (c.tasks or [])],
open_questions=[_extract_text(q) for q in (c.open_questions or [])],
)
@ -329,6 +338,9 @@ def compare_checkpoints(a_id: uuid.UUID, b_id: uuid.UUID, db: Session = Depends(
decisions_only_a=dec_only_a,
decisions_only_b=dec_only_b,
decisions_shared=dec_shared,
assumptions_only_a=assump_only_a,
assumptions_only_b=assump_only_b,
assumptions_shared=assump_shared,
tasks_only_a=task_only_a,
tasks_only_b=task_only_b,
tasks_shared=task_shared,

View file

@ -169,6 +169,7 @@ class CommitModel(Base):
summary: Mapped[str] = mapped_column(Text, default="")
objective: Mapped[str] = mapped_column(Text, default="")
decisions: Mapped[dict] = mapped_column(JSONB, default=list)
assumptions: Mapped[dict] = mapped_column(JSONB, default=list)
tasks: Mapped[dict] = mapped_column(JSONB, default=list)
open_questions: Mapped[dict] = mapped_column(JSONB, default=list)
entities: Mapped[dict] = mapped_column(JSONB, default=list)

View file

@ -110,6 +110,18 @@ class CheckpointDraftResponse(BaseModel):
objective: str = ""
summary: str = ""
decisions: list[str] = Field(default_factory=list)
assumptions: list[str] = Field(default_factory=list)
tasks: list[str] = Field(default_factory=list)
open_questions: list[str] = Field(default_factory=list)
entities: list[str] = Field(default_factory=list)
class ReviewIssue(BaseModel):
type: str # contradiction, hidden_assumption, resolved_question, unused_entity
description: str
class CheckpointReviewResponse(BaseModel):
checkpoint_id: uuid.UUID
issues: list[ReviewIssue] = Field(default_factory=list)
suggestions: list[str] = Field(default_factory=list)

View file

@ -250,6 +250,7 @@ export async function createChatCommit(payload: {
summary?: string;
objective?: string;
decisions?: string[];
assumptions?: string[];
tasks?: string[];
open_questions?: string[];
entities?: string[];
@ -322,6 +323,7 @@ export async function draftCheckpoint(payload: {
objective: string;
summary: string;
decisions: string[];
assumptions: string[];
tasks: string[];
open_questions: string[];
entities: string[];
@ -331,6 +333,7 @@ export async function draftCheckpoint(payload: {
objective: string;
summary: string;
decisions: string[];
assumptions: string[];
tasks: string[];
open_questions: string[];
entities: string[];
@ -354,3 +357,9 @@ export async function getSessionCheckpoints(sessionId: string): Promise<import('
return requestV5<import('../types').Commit[]>(`/lineage/sessions/${sessionId}/checkpoints`);
}
export async function reviewCheckpoint(checkpointId: string): Promise<import('../types').CheckpointReviewResponse> {
return requestV5<import('../types').CheckpointReviewResponse>(`/checkpoint/${checkpointId}/review`, {
method: 'POST',
});
}

View file

@ -20,8 +20,9 @@ import {
forkSession,
compareCheckpoints,
getSessionCheckpoints,
reviewCheckpoint,
} from '../api/client';
import type { ChatSession, Commit, CompareResponse, HeadState, TurnEvent, Repo, ProviderStatus } from '../types';
import type { ChatSession, Commit, CompareResponse, CheckpointReviewResponse, HeadState, TurnEvent, Repo, ProviderStatus } from '../types';
import {
Check,
Copy,
@ -239,6 +240,7 @@ function CommitModal({ repoId, sessionId, onClose, onCommitted, providerStatus,
const [obj, setObj] = useState('');
const [tasks, setTasks] = useState('');
const [decisions, setDecs] = useState('');
const [assumptions, setAssumptions] = useState('');
const [openQuestions, setOpenQuestions] = useState('');
const [entities, setEntities] = useState('');
const [loading, setLoading] = useState(false);
@ -268,6 +270,7 @@ function CommitModal({ repoId, sessionId, onClose, onCommitted, providerStatus,
setSummary(draft.summary || '');
setTasks((draft.tasks ?? []).join('\n'));
setDecs((draft.decisions ?? []).join('\n'));
setAssumptions((draft.assumptions ?? []).join('\n'));
setOpenQuestions((draft.open_questions ?? []).join('\n'));
setEntities((draft.entities ?? []).join('\n'));
} catch (e: any) {
@ -290,6 +293,7 @@ function CommitModal({ repoId, sessionId, onClose, onCommitted, providerStatus,
objective: obj.trim(),
tasks: parseLines(tasks),
decisions: parseLines(decisions),
assumptions: parseLines(assumptions),
open_questions: parseLines(openQuestions),
entities: parseLines(entities),
});
@ -368,18 +372,6 @@ function CommitModal({ repoId, sessionId, onClose, onCommitted, providerStatus,
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs uppercase tracking-wider text-gray-500 block mb-1.5">
Tasks (one per line)
</label>
<textarea
className="w-full bg-zinc-900/50 border border-gray-800 rounded-lg px-3 py-2 text-sm text-white outline-none focus:border-gray-500 transition-colors resize-none font-mono"
rows={3}
placeholder={"Set up database\nWrite tests"}
value={tasks}
onChange={e => setTasks(e.target.value)}
/>
</div>
<div>
<label className="text-xs uppercase tracking-wider text-gray-500 block mb-1.5">
Decisions (one per line)
@ -392,6 +384,30 @@ function CommitModal({ repoId, sessionId, onClose, onCommitted, providerStatus,
onChange={e => setDecs(e.target.value)}
/>
</div>
<div>
<label className="text-xs uppercase tracking-wider text-gray-500 block mb-1.5">
Assumptions (one per line)
</label>
<textarea
className="w-full bg-zinc-900/50 border border-gray-800 rounded-lg px-3 py-2 text-sm text-white outline-none focus:border-gray-500 transition-colors resize-none font-mono"
rows={3}
placeholder={"Cloud-hosted deployment\nSingle-tenant for now"}
value={assumptions}
onChange={e => setAssumptions(e.target.value)}
/>
</div>
<div>
<label className="text-xs uppercase tracking-wider text-gray-500 block mb-1.5">
Tasks (one per line)
</label>
<textarea
className="w-full bg-zinc-900/50 border border-gray-800 rounded-lg px-3 py-2 text-sm text-white outline-none focus:border-gray-500 transition-colors resize-none font-mono"
rows={3}
placeholder={"Set up database\nWrite tests"}
value={tasks}
onChange={e => setTasks(e.target.value)}
/>
</div>
<div>
<label className="text-xs uppercase tracking-wider text-gray-500 block mb-1.5">
Open Questions (one per line)
@ -404,14 +420,14 @@ function CommitModal({ repoId, sessionId, onClose, onCommitted, providerStatus,
onChange={e => setOpenQuestions(e.target.value)}
/>
</div>
<div>
<div className="col-span-2">
<label className="text-xs uppercase tracking-wider text-gray-500 block mb-1.5">
Entities (one per line)
</label>
<textarea
className="w-full bg-zinc-900/50 border border-gray-800 rounded-lg px-3 py-2 text-sm text-white outline-none focus:border-gray-500 transition-colors resize-none font-mono"
rows={3}
placeholder={"Redis\nReact\nPostgreSQL"}
rows={2}
placeholder={"Redis, React, PostgreSQL"}
value={entities}
onChange={e => setEntities(e.target.value)}
/>
@ -436,7 +452,35 @@ function CommitModal({ repoId, sessionId, onClose, onCommitted, providerStatus,
// ── CheckpointDetailPanel ─────────────────────────────────────────────────────
function CheckpointDetailPanel({ commit, onClose }: { commit: Commit; onClose: () => void }) {
function CheckpointDetailPanel({ commit, onClose, providerStatus }: { commit: Commit; onClose: () => void; providerStatus: Record<string, ProviderStatus> | null }) {
const [reviewResult, setReviewResult] = useState<CheckpointReviewResponse | null>(null);
const [reviewing, setReviewing] = useState(false);
const [reviewErr, setReviewErr] = useState<string | null>(null);
const bgConfig = providerStatus?.['background_intelligence'];
const bgProviderId = bgConfig?.provider;
const isBgValid = bgProviderId ? providerStatus?.[bgProviderId]?.has_key : false;
const handleReview = async () => {
try {
setReviewing(true);
setReviewErr(null);
const result = await reviewCheckpoint(commit.id);
setReviewResult(result);
} catch (e: any) {
setReviewErr(e.message || 'Review failed');
} finally {
setReviewing(false);
}
};
const issueLabel: Record<string, { text: string; classes: string }> = {
contradiction: { text: 'Possible contradiction', classes: 'text-red-400 bg-red-500/10 border-red-500/20' },
hidden_assumption: { text: 'Hidden assumption', classes: 'text-amber-400 bg-amber-500/10 border-amber-500/20' },
resolved_question: { text: 'Possibly resolved', classes: 'text-blue-400 bg-blue-500/10 border-blue-500/20' },
unused_entity: { text: 'Possibly unused entity', classes: 'text-gray-400 bg-zinc-800 border-gray-700' },
};
const fmt = (d: string) => new Date(d).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
const rows = (items: string[], label: string) =>
items.length > 0 ? (
@ -475,10 +519,56 @@ function CheckpointDetailPanel({ commit, onClose }: { commit: Commit; onClose: (
<p className="text-xs text-gray-400 leading-relaxed">{commit.summary}</p>
</div>
)}
{rows(commit.tasks ?? [], 'Tasks')}
{rows(commit.decisions ?? [], 'Decisions')}
{rows(commit.assumptions ?? [], 'Assumptions')}
{rows(commit.tasks ?? [], 'Tasks')}
{rows(commit.open_questions ?? [], 'Open Questions')}
{rows(commit.entities ?? [], 'Entities')}
{/* Review checkpoint */}
<div className="border-t border-gray-800 pt-3">
<button
onClick={handleReview}
disabled={reviewing || !isBgValid}
title={isBgValid ? 'Review this checkpoint for reasoning consistency' : 'Background provider not configured'}
className="text-[11px] px-3 py-1.5 rounded-md border border-gray-700 text-gray-400 hover:text-white hover:border-gray-500 transition-colors disabled:opacity-40 flex items-center gap-1.5"
>
{reviewing ? <Loader2 className="w-3 h-3 animate-spin" /> : <Zap className="w-3 h-3" />}
{reviewing ? 'Reviewing…' : reviewResult ? 'Re-review checkpoint' : 'Review checkpoint'}
</button>
{reviewErr && <p className="text-[10px] text-red-400 mt-2">{reviewErr}</p>}
{reviewResult && (
<div className="mt-3 space-y-2">
{reviewResult.issues.length === 0 ? (
<p className="text-[11px] text-green-400/70">No issues found reasoning looks consistent.</p>
) : (
<div className="space-y-2">
{reviewResult.issues.map((issue, i) => {
const label = issueLabel[issue.type] || { text: issue.type, classes: 'text-gray-400 bg-zinc-800 border-gray-700' };
return (
<div key={i} className="space-y-1">
<span className={`text-[9px] uppercase tracking-wider px-1.5 py-0.5 rounded border ${label.classes}`}>
{label.text}
</span>
<p className="text-[11px] text-gray-300 leading-relaxed">{issue.description}</p>
</div>
);
})}
</div>
)}
{reviewResult.suggestions.length > 0 && (
<div className="mt-2">
<p className="text-[9px] uppercase tracking-wider text-gray-600 mb-1">Suggestions</p>
{reviewResult.suggestions.map((s, i) => (
<p key={i} className="text-[10px] text-gray-500 leading-relaxed"> {s}</p>
))}
</div>
)}
</div>
)}
</div>
</div>
);
}
@ -592,6 +682,7 @@ function MemorySpacePanel({
forkSourceCommit,
mountedCheckpointId,
refreshKey,
providerStatus,
onMount,
onFork,
onClose,
@ -603,6 +694,7 @@ function MemorySpacePanel({
mountedCheckpointId: string | null;
/** Increment to force the checkpoint list to reload (e.g. after creating a new checkpoint). */
refreshKey: number;
providerStatus: Record<string, ProviderStatus> | null;
onMount: (info: { id: string; message: string } | null) => void;
onFork: (checkpoint: Commit) => void;
onClose: () => void;
@ -823,6 +915,7 @@ function MemorySpacePanel({
<CheckpointDetailPanel
commit={expandedCommit}
onClose={() => { setExpandedId(null); setExpandedCommit(null); }}
providerStatus={providerStatus}
/>
)
)}
@ -847,6 +940,7 @@ function MemorySpacePanel({
{[
{ label: 'Decisions', onlyA: compareResult.diff.decisions_only_a, onlyB: compareResult.diff.decisions_only_b, shared: compareResult.diff.decisions_shared },
{ label: 'Tasks', onlyA: compareResult.diff.tasks_only_a, onlyB: compareResult.diff.tasks_only_b, shared: compareResult.diff.tasks_shared },
{ label: 'Assumptions', onlyA: compareResult.diff.assumptions_only_a ?? [], onlyB: compareResult.diff.assumptions_only_b ?? [], shared: compareResult.diff.assumptions_shared ?? [] },
].map(({ label, onlyA, onlyB, shared }) => (onlyA.length + onlyB.length + shared.length) > 0 && (
<div key={label}>
<p className="text-[9px] uppercase tracking-wider text-gray-600 mb-1">{label}</p>
@ -1738,6 +1832,7 @@ export function ChatWorkspacePage() {
forkSourceCommit={forkSourceCommit}
mountedCheckpointId={mountedCheckpointId}
refreshKey={checkpointRefreshKey}
providerStatus={providerStatus}
onMount={info => {
if (info !== null) {
setMountedCheckpointId(info.id);

View file

@ -103,6 +103,7 @@ export interface Commit {
summary: string;
objective: string;
decisions: string[];
assumptions: string[];
tasks: string[];
open_questions: string[];
entities: string[];
@ -215,6 +216,7 @@ export interface CheckpointDetail {
summary: string;
objective: string;
decisions: string[];
assumptions: string[];
tasks: string[];
open_questions: string[];
}
@ -227,6 +229,9 @@ export interface CheckpointDiff {
decisions_only_a: string[];
decisions_only_b: string[];
decisions_shared: string[];
assumptions_only_a: string[];
assumptions_only_b: string[];
assumptions_shared: string[];
tasks_only_a: string[];
tasks_only_b: string[];
tasks_shared: string[];
@ -237,3 +242,16 @@ export interface CompareResponse {
checkpoint_b: CheckpointDetail;
diff: CheckpointDiff;
}
// ── Checkpoint Review Types ──────────────────────────────────────────────────
export interface ReviewIssue {
type: 'contradiction' | 'hidden_assumption' | 'resolved_question' | 'unused_entity';
description: string;
}
export interface CheckpointReviewResponse {
checkpoint_id: string;
issues: ReviewIssue[];
suggestions: string[];
}