File size: 8,181 Bytes
4d393e8
 
 
 
 
20d28dd
 
 
 
 
 
 
 
4d393e8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7e25da
 
4d393e8
 
 
 
 
 
e7e25da
4d393e8
 
 
 
 
 
 
e7e25da
 
4d393e8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
"""
Terminal output formatting utilities for DiffWeave CLI using Rich.
"""
from __future__ import annotations

import sys
if hasattr(sys.stdout, 'reconfigure'):
    try:
        sys.stdout.reconfigure(encoding='utf-8', errors='replace')
        sys.stderr.reconfigure(encoding='utf-8', errors='replace')
    except Exception:
        pass

import json
from typing import Any
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.tree import Tree
from rich.text import Text

console = Console()
err_console = Console(stderr=True)


def print_json(data: Any):
    """Output machine-readable JSON."""
    console.print(json.dumps(data, indent=2, default=str))


def print_success(message: str):
    console.print(f"[bold green][OK][/bold green] {message}")


def print_info(message: str):
    console.print(f"[bold cyan][i][/bold cyan] {message}")


def print_warning(message: str):
    console.print(f"[bold yellow][!][/bold yellow] {message}")


def print_error(message: str, code: str = None):
    prefix = f"[bold red][FAIL] Error ({code}):[/bold red]" if code else "[bold red][FAIL] Error:[/bold red]"
    err_console.print(f"{prefix} {message}")


def render_status(
    workspace_name: str,
    workspace_id: str,
    docs: list[dict],
    stats: dict,
    pending_proposals: list[dict],
):
    """Render a comprehensive Git-like status display."""
    console.print(
        Panel(
            f"[bold white]Workspace:[/bold white] [bold cyan]{workspace_name}[/bold cyan] ([dim]{workspace_id}[/dim])\n"
            f"[bold white]Engine:[/bold white] DocWeave MCP Server [green]* Connected (29 Tools)[/green]",
            title="[bold blue]DiffWeave Status[/bold blue]",
            border_style="blue",
        )
    )

    # 1. Summary stats
    stats_table = Table(box=None, padding=(0, 2))
    stats_table.add_column("Tracked Docs", style="cyan")
    stats_table.add_column("Running Workflows", style="yellow")
    stats_table.add_column("Awaiting Review", style="magenta")
    stats_table.add_column("Knowledge Register Items", style="green")

    stats_table.add_row(
        str(stats.get("total_documents", len(docs))),
        str(stats.get("workflows_running", 0)),
        str(stats.get("pending_proposals", len(pending_proposals))),
        str(stats.get("knowledge_items", 0)),
    )
    console.print(stats_table)
    console.print()

    # 2. Document Inventory Table
    if docs:
        doc_table = Table(title="Document Knowledge Staging", title_justify="left", header_style="bold cyan")
        doc_table.add_column("Filename", style="white")
        doc_table.add_column("Status", style="bold")
        doc_table.add_column("Workflow Status", style="dim")
        doc_table.add_column("Document ID", style="dim")

        for d in docs:
            w_status = d.get("workflow_status") or d.get("status") or "IDLE"
            if w_status == "COMPLETED":
                status_styled = "[green][OK] COMMITTED[/green]"
            elif w_status == "WAITING_FOR_REVIEW":
                status_styled = "[bold yellow]* WAITING FOR REVIEW[/bold yellow]"
            elif w_status == "RUNNING":
                status_styled = "[cyan]PROCESSING[/cyan]"
            elif w_status == "FAILED":
                status_styled = "[red][FAIL] FAILED[/red]"
            else:
                status_styled = f"[dim]{w_status}[/dim]"

            doc_table.add_row(
                d.get("filename", "unknown"),
                status_styled,
                w_status,
                d.get("document_id", "")[:12] + "...",
            )
        console.print(doc_table)
    else:
        console.print("[dim]No documents uploaded yet. Use 'dw add <file>' to ingest documents.[/dim]")

    console.print()

    # 3. Pending Knowledge PRs
    proposals_list = pending_proposals if isinstance(pending_proposals, list) else (pending_proposals.get("proposals", []) if isinstance(pending_proposals, dict) else [])
    if proposals_list:
        pr_table = Table(title="Pending Knowledge PRs (Requires Human Review)", title_justify="left", header_style="bold magenta")
        pr_table.add_column("PR ID", style="bold yellow")
        pr_table.add_column("Type", style="cyan")
        pr_table.add_column("Summary", style="white")
        pr_table.add_column("Status", style="yellow")

        for p in proposals_list[:10]:
            pr_table.add_row(
                p.get("proposal_id", "")[:8],
                p.get("proposal_type", "CLAIM"),
                p.get("summary", ""),
                p.get("status", "PENDING"),
            )
        console.print(pr_table)
        if len(proposals_list) > 10:
            console.print(f"[dim]...and {len(proposals_list) - 10} more pending reviews. Run 'dw proposals' for full list.[/dim]")
    else:
        console.print("[dim green][OK] All knowledge proposals reviewed. Knowledge register is up to date.[/dim green]")


def render_diff(diff_data: dict):
    """Render a visual, high-contrast semantic knowledge diff."""
    summary = diff_data.get("summary", {})
    additions = diff_data.get("additions", [])
    changes = diff_data.get("changes", [])
    conflicts = diff_data.get("conflicts", [])
    removals = diff_data.get("removals", [])

    console.print(
        f"[bold]Knowledge Semantic Diff[/bold] "
        f"([green]+{summary.get('additions_count', 0)} additions[/green], "
        f"[yellow]~{summary.get('changes_count', 0)} changes[/yellow], "
        f"[red]!{summary.get('conflicts_count', 0)} conflicts[/red], "
        f"[red]-{summary.get('removals_count', 0)} removals[/red])\n"
    )

    if not additions and not changes and not conflicts and not removals:
        console.print("[dim]No pending knowledge differences detected against committed truth.[/dim]")
        return

    # 1. Conflicts
    for idx, c in enumerate(conflicts, 1):
        existing = c.get("existing", {})
        proposed = c.get("proposed", {})
        evidence_list = proposed.get("evidence", [])
        evidence_quote = evidence_list[0].get("quote", "N/A") if evidence_list else "None cited"

        body = (
            f"[bold red]COLLISION / CONTRADICTION DETECTED[/bold red]\n"
            f"[bold white]Target Claim / Metric:[/bold white] {c.get('title')}\n\n"
            f"[red]◀ COMMITTED BASELINE:[/red]\n"
            f"   Value: \"{existing.get('value')}\"\n"
            f"   Confidence: {existing.get('confidence', 1.0)}\n\n"
            f"[green]▶ PROPOSED UPDATE (From new document):[/green]\n"
            f"   Value: \"{proposed.get('value')}\"\n"
            f"   Confidence: {proposed.get('confidence', 1.0)}\n"
            f"   Evidence Quote: [italic cyan]\"{evidence_quote}\"[/italic cyan]\n\n"
            f"[dim]Proposal ID: {c.get('proposal_id')}[/dim]"
        )
        console.print(Panel(body, title=f"[bold red]! CONFLICT #{idx}[/bold red]", border_style="red"))

    # 2. Additions
    for idx, a in enumerate(additions, 1):
        evidence_list = a.get("evidence", [])
        evidence_quote = evidence_list[0].get("quote", "N/A") if evidence_list else "None cited"
        body = (
            f"[bold green]+ NEW {a.get('type', 'CLAIM')}:[/bold green] [bold white]{a.get('title')}[/bold white]\n"
            f"Value: \"{a.get('proposed_value')}\"\n"
            f"Confidence: {a.get('confidence', 1.0)}\n"
            f"Evidence: [italic cyan]\"{evidence_quote}\"[/italic cyan]\n"
            f"[dim]Proposal ID: {a.get('proposal_id')}[/dim]"
        )
        console.print(Panel(body, title=f"[bold green]+ ADDITION #{idx}[/bold green]", border_style="green"))

    # 3. Changes
    for idx, ch in enumerate(changes, 1):
        existing = ch.get("existing", {})
        proposed = ch.get("proposed", {})
        body = (
            f"[bold yellow]~ MODIFIED CLAIM:[/bold yellow] [bold white]{ch.get('title')}[/bold white]\n"
            f"Existing: \"{existing.get('value')}\"\n"
            f"Proposed: \"{proposed.get('value')}\"\n"
            f"[dim]Proposal ID: {ch.get('proposal_id')}[/dim]"
        )
        console.print(Panel(body, title=f"[bold yellow]~ UPDATE #{idx}[/bold yellow]", border_style="yellow"))