Commit Graph

91 Commits

Author SHA1 Message Date
e90faa9ba4 feat(settings): add Blocks tab — 12-block decision schema reference
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m35s
Read-only display of BLOCK_CONFIG from block_writer.py with CREAC role
and JWM functional-purpose annotations per block (sourced from
docs/block-schema.md).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 07:58:04 +00:00
d1e12619d4 refactor(settings): pivot to Coolify env API as source of truth
Investigation showed legal-ai container has no INFISICAL_TOKEN and there
is no /legal-ai folder in Infisical — all env vars are stored in Coolify
and injected into os.environ at container start.

- Replace _read_infisical_values with _read_coolify_envs
- New: _coolify_authoritative_value picks among Coolify duplicates
- PATCH writes via Coolify API (upsert by key)
- Drift = Coolify-stored vs container-runtime (common: Coolify edited
  without redeploy)
- Response field renamed: infisical_value → coolify_value
- New 'has_duplicates' flag per row when Coolify has multiple entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 07:50:02 +00:00
320b9d3529 fix(settings): guard paperclip mcp.json type + sort registrations 2026-05-04 06:40:16 +00:00
394b971856 feat(settings): add MCP registrations endpoint + Coolify volume runbook
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 06:38:47 +00:00
1da3587334 fix(settings): log tool source resolution failures (no silent swallow) 2026-05-04 06:37:09 +00:00
272e49b6b0 feat(settings): add MCP tools introspection endpoint 2026-05-04 06:34:19 +00:00
69bdf7b30a fix(settings): harden PATCH/redeploy per code review
- Add infisicalsdk dependency
- Narrow update→create fallback to NotFound errors only (no silent swallow)
- Truncate Coolify error response text to 200 chars
- Add 60s cooldown to redeploy endpoint
- Move httpx to top-level import
2026-05-04 06:33:01 +00:00
2fe73fcce1 feat(settings): add PATCH env + Coolify redeploy endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 06:26:00 +00:00
c30c987ec2 fix(settings): suppress false drift when Infisical unreachable
- Add infisical_available flag to _build_env_var_row
- Stabilize error code (no exception text in API response)
- Document raw-comparison safety inline
2026-05-04 06:24:26 +00:00
562eae010a feat(settings): add GET /api/settings/mcp/env endpoint
Adds four helper functions (_infisical_client, _infisical_ctx,
_read_infisical_values, _build_env_var_row) and the /api/settings/mcp/env
endpoint that compares Infisical vs container env vars and reports drift.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 06:19:04 +00:00
a3ca32355a fix(settings): tighten coerce/normalize per code review
- reject non-integer floats in int coerce path
- document masking responsibility on to_public_dict
- use tuple for enum_values (immutable)
- treat empty string as None in normalize_for_compare
2026-05-04 06:17:22 +00:00
55a0eca070 feat(settings): add MCP env catalog with type validation
Static whitelist of 18 env vars (multimodal, rerank, halacha, general,
credentials, connection) with per-key type coercion, secret masking, and
drift-comparison helpers for the upcoming settings UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 06:11:32 +00:00
81ccf3a888 feat(retrieval): track page_number on text chunks for multimodal hybrid boost
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 6m33s
The legacy chunker did not track which PDF page each chunk came from.
Stored chunks had page_number=NULL, which blocked the multimodal
hybrid retriever's text+image boost — it joins (chunk, image) on
(document_id, page_number) and the join could never fire.

This change:

- extractor.extract_text now returns (text, page_count, page_offsets);
  page_offsets[i] is the start char offset of page (i+1) in the joined
  text. None for non-PDFs.
- chunker.chunk_document accepts an optional page_offsets and tags
  each chunk with the page that contains its first character (uses
  the existing chunker logic; pages assigned post-hoc by content
  search to keep the diff minimal).
- processor.process_document and precedent_library.ingest_precedent
  forward page_offsets through the chunker. New uploads now carry
  accurate page_number on every chunk.
- Other extract_text callers (tools/documents, tools/workflow,
  web/app.py) updated to unpack the third element (ignored).
- scripts/backfill_chunk_pages.py: per-case retrofit. Re-extracts each
  PDF (re-OCRs via Google Vision if needed, ~$0.0015/page), computes
  page_offsets, and updates page_number on every chunk by content
  search. Idempotent; --force re-runs on already-tagged docs.

Forward-only would leave the 419 image embeddings backfilled on
cases 8174-24 + 8137-24 unable to boost their corresponding text
chunks. The retrofit script closes that gap (cost ~$0.60).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:49:41 +00:00
f722fa45bd feat(search): add header global search (Phase A) — cases + precedents + docs
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 41s
Adds an always-visible debounced search input in the AppShell header
that fans out to three independent sources in parallel and renders
per-source result groups with their own loading/empty/error states:

- /api/search/cases (NEW): SQL ILIKE on case_number, address, parties,
  title, subject. Returns small projections, no embeddings needed.
- /api/precedent-library/search (existing): semantic over case-law
  halachot + passages.
- /api/search (existing): semantic over case documents + past decisions.

Cmd/Ctrl+K focuses the input; Esc and click-outside close the panel.
This is Phase A of the header redesign — the bar layout itself is
unchanged; row grouping + dynamic context follow in Phase B.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 18:05:51 +00:00
923903217c feat(precedents): auto-trigger Claude extraction via Paperclip wakeup
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 7s
When a precedent is uploaded to the library, the FastAPI container now
fires a Paperclip wakeup so Claude (running locally as the CEO agent)
picks up the new row and runs `precedent_process_pending` for both
metadata and halacha extraction. The user no longer has to remember to
trigger it manually.

Mechanics:
- New `wake_for_precedent_extraction()` in paperclip_client.py creates
  (or reuses) a per-company "ספריית פסיקה — תור חילוץ" project, opens
  a fresh issue assigned to the company CEO with the case_law_id +
  citation in the description, and pings the Board API wakeup endpoint
  with `triggerDetail=precedent_library_upload`.
- ingest_precedent's _run() in app.py captures the returned case_law_id
  and best-effort calls the wake function (failures are logged, not
  surfaced — the upload itself stays clean).
- legal-ceo.md adds the precedent_process_pending tool family and a
  new "חילוץ פסיקה אוטומטי" section that tells the CEO to short-circuit
  past the heartbeat scan when woken with this trigger.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:49:25 +00:00
4a9a6b7970 feat(precedents): UI button queues extraction for local MCP worker
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
The chair wanted a one-click "extract metadata" button on the edit sheet.
The constraint stays the same — claude_session needs the local CLI which
the container doesn't have, so the button can't run the extractor itself.
Compromise: button stamps a queue marker; the local MCP server drains the
queue on demand.

DB (V8): two nullable timestamps on case_law,
metadata_extraction_requested_at and halacha_extraction_requested_at,
with partial indexes for cheap "find pending" scans.

API:
  POST /api/precedent-library/{id}/request-metadata   → stamp the row
  POST /api/precedent-library/{id}/request-halachot   → same for halacha
  GET  /api/precedent-library/queue/pending?kind=...  → read-only view

UI: Sparkles button in the edit sheet header. Click → toast tells the
chair what to run from Claude Code. The button never triggers the
extractor directly from the container.

MCP tool: precedent_process_pending(kind, limit) — runs from Claude Code
with the local CLI, picks up everything stamped, calls the extractor for
each, clears the timestamp on success. Failures keep the timestamp so the
next invocation retries them.

Architectural rule (claude_session local-only) is preserved end-to-end
and called out in the new endpoint comment + tool docstring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:32:25 +00:00
2cfdf35191 refactor(precedents): keep all LLM calls on the local-MCP path
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
Architectural correction: every claude_session caller in this project
runs through the local MCP server (~/.claude.json points at
/home/chaim/legal-ai/mcp-server/.venv/bin/python). The Coolify container
has no `claude` CLI and no claude.ai session, so any LLM call originating
from web/ FastAPI fails with "Claude CLI not found" — which is exactly
what we hit on 403-17.

The earlier Anthropic SDK fallback would have made it work, but at
direct API cost. The chair's preference is to stay on the claude.ai
session for everything. So:

- claude_session.py: removed the SDK fallback, restored CLI-only.
  The error message now points the next person at the architectural
  rule in the module docstring instead of papering over it.
- precedent_library.py:ingest_precedent (called from FastAPI on upload)
  now does only the non-LLM half: extract → chunk → embed → store.
  Sets halacha_extraction_status='pending' for the chair to act on.
- reextract_halachot / reextract_metadata kept, but lazy-import their
  extractors so the FastAPI path can't accidentally pull them in. They
  are reachable only via the MCP tools precedent_extract_halachot /
  precedent_extract_metadata, which run locally with CLI.
- Removed POST /api/precedent-library/{id}/extract-halachot and
  /extract-metadata — they were dead ends from the container.
- Dropped the `anthropic` Python dep that the SDK fallback required.
- UI: removed the "refresh halachot" and "sparkles metadata" buttons
  that called those endpoints. Edit sheet now points the chair at the
  MCP tool names instead.

Halacha and metadata extraction for an uploaded precedent now happen
when the chair (via Claude Code) runs:
  mcp__legal-ai__precedent_extract_metadata <case_law_id>
  mcp__legal-ai__precedent_extract_halachot <case_law_id>

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 11:06:08 +00:00
73a79ea7e8 feat(precedents): metadata auto-fill, edit sheet, persuasive extraction
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
Three improvements to the precedent library based on usage feedback:

1. Auto-fill metadata at upload time. New service
   precedent_metadata_extractor reads the ruling's full_text and
   suggests case_name (short), summary, headnote, key_quote,
   subject_tags, appeal_subtype. The merge policy fills only empty
   fields, preserving everything the chair typed in the upload form.
   Wired into the ingest pipeline; also exposed as a re-run endpoint
   POST /api/precedent-library/{id}/extract-metadata for existing
   records.

2. Edit sheet in the UI. Pencil icon on each library row opens a
   pre-populated form covering every field. A Sparkles button on the
   sheet runs the metadata extractor on demand and refreshes the
   form. The case_number is read-only because halachot are FK'd to
   it; renaming requires delete + re-upload.

3. Halacha extractor branches on is_binding. Sources marked binding
   (Supreme/Administrative) keep the strict halacha prompt. Non-binding
   sources (other appeals committees, district courts on planning
   matters) get a different prompt that extracts applications,
   interpretive principles, and persuasive conclusions — labeled with
   new rule_types 'application' and 'persuasive'. The fallback also
   widens chunk selection: if the chunker labeled nothing as
   legal_analysis/ruling/conclusion, we now run on all chunks rather
   than returning zero halachot for a usable ruling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:19:35 +00:00
7ee90dce31 feat: external precedent library with auto halacha extraction
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
Adds a third corpus of legal authority distinct from style_corpus
(Daphna's prior decisions for voice) and case_precedents (chair-attached
quotes per case). The new corpus holds chair-uploaded court rulings and
other appeals committee decisions, with binding rules (הלכות) extracted
automatically and queued for chair approval.

Pipeline (web/app.py + services/precedent_library.py):
file → extract → chunk → Voyage embed → halacha_extractor → store +
publish progress over the existing Redis SSE channel.

Schema V7 (services/db.py): extends case_law with source_kind +
extraction status fields under a CHECK constraint pinning practice_area
to the three appeals committee domains (rishuy_uvniya, betterment_levy,
compensation_197). New precedent_chunks (vector(1024)) and halachot
tables (vector(1024) over rule_statement, IVFFlat indexes, gin on
practice_areas/subject_tags). Halachot start as pending_review; only
approved/published rows are visible to search_precedent_library.

Agents: legal-writer, legal-researcher, legal-analyst, legal-ceo,
legal-qa get search_precedent_library. legal-writer prompt explains
the three-corpus distinction and CREAC use; legal-qa now verifies that
every cited halacha resolves to an approved row in the corpus.

UI: /precedents page with four tabs — library / semantic search /
pending review (J/K nav, A/R/E shortcuts, badge count) / stats.
Reuses the existing upload-sheet progress + SSE pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 08:38:18 +00:00
e849285806 home: split cases table by appeal type + add appeal-type chart
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 32s
Backend (cases listing)
- /api/cases: also return updated_at, created_at, practice_area,
  appeal_subtype, subject. The detail-mode response was previously
  dropping these even though db.list_cases reads them, leaving the
  UI's "תחום" and "עודכן" columns blank.

Frontend
- Split the home table into two: רישוי (1xxx) and היטל השבחה ופיצויים
  (8xxx + 9xxx), bucketing on appeal_subtype with a case-number-prefix
  fallback. The "תחום" column is now redundant and removed.
- New AppealTypeBars chart in the right rail next to the existing
  status donut.
- Donut: switch to a vertical layout (donut on top, legend below in a
  3-col grid) so labels like "חדש / בעיבוד" no longer wrap inside the
  320px sidebar; counts now align in a tabular column.
- CasesTable accepts emptyText/searchPlaceholder so each split table
  has its own copy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:44:41 +00:00
f7249b7807 admin/skills: fail loud on DB error + read skills dir from env
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m8s
- Raise HTTPException(503) when Paperclip DB is unreachable instead of
  silently falling through to disk-only mode and returning [].
- Honor PAPERCLIP_SKILLS_DIR env var (falls back to ~/.paperclip/...).
  In the Coolify container the host's skills dir is bind-mounted at
  /paperclip-skills; without this, Path.home() resolved to /root/ and
  the disk inventory was always empty.

Both bugs together silently turned a Paperclip DB outage into "no skills
installed" on the /skills page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:24:39 +00:00
5deb38f5cf paperclip: assign CEO on issue creation so wakeup gate accepts run
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 9s
Paperclip heartbeat staleness gate (heartbeat.js evaluateQueuedRunStaleness)
cancels queued runs when issue.assigneeAgentId !== run.agentId, with error
"issue assignee changed before the queued run could start". Older Paperclip
versions auto-assigned on wakeup; the current version does not, so issues
created with NULL assignee silently never run.

Set assignee_agent_id to the company's CEO at INSERT time. Affects both the
project setup issue and the "התחל תהליך ניסוח" workflow issue.
2026-05-01 15:32:22 +00:00
f256eddbb1 git_sync: full case-dir backup to Gitea (sweep + explicit commits)
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m25s
The case repo is the user's backup, so anything in the dir must end up
on Gitea. Two layers:

1. Periodic sweep (every 30s) — git_sync.sweep_loop runs as a FastAPI
   background task. It scans every case dir, runs git status --porcelain
   on each, and commit_and_push's any dirty changes with an auto-built
   Hebrew message ("אוטו: טיוטות (2) · מסמכים"). Catches files written
   outside the API path: agent research artefacts, manual edits, etc.

2. Explicit commits at known write paths — DOCX export, interim draft,
   apply_user_edit, revise_draft, mark-final, analysis DOCX export.
   These give immediate feedback with descriptive messages instead of
   waiting up to 30s for the sweep.

safe.directory injection added to _git_env so sweep + explicit commits
work even when the running uid differs from the case-dir owner (host
runs vs. uniform-root container).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 18:27:36 +00:00
fa70944ed4 case-create: surface Gitea repo result + UI retry button
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m29s
The auto-creation in case_create had two failure modes that combined to
make repos silently missing: a stale GITEA_TOKEN returning 401, and the
outer try/except in case_create that swallowed every exception with a
bare pass. Result: cases like 8174-24 ended up with a local git repo and
Paperclip project but no Gitea repo, with no signal anywhere.

_setup_gitea_remote now returns {ok, url, error} and never raises; the
result is attached to the case JSON and the FastAPI endpoint logs a
warning when ok=false. The UI gets a "צור ריפו ב-Gitea" button on the
case header that appears only when the repo or remote is missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 18:12:05 +00:00
1fbcdd0d16 paperclip: auto-attach default workspace on project creation
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 8s
Without a primary workspace on a project, the "סביבות עבודה" tab in
Paperclip stays hidden (gate: enableIsolatedWorkspaces && S0t list
non-empty), and agents wake with cwd=`/home/chaim` instead of the
legal-ai source tree. New helper inserts a primary workspace pointing at
LEGAL_AI_WORKSPACE_CWD (default /home/chaim/legal-ai) on both new and
legacy/existing-project paths. Idempotent — skips if any workspace row
already exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 15:17:04 +00:00
9bdfb05350 Upload progress: Redis-backed store + flushed SSE + client fallback
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m24s
The previous in-memory _progress dict + polling SSE handler had a 30s silent
tail after completion. HTTP/2 framing in the proxy chain (Traefik) buffered
the small chunks until the stream closed, so when a transient blip caused
EventSource to reconnect, the server returned 404 and the UI stuck on the
"מתחיל…" placeholder forever. Reproduced live: 445 bytes withheld 31s.

Changes:
  • web/progress_store.py — ProgressStore wraps Redis with TTL (5m), atomic
    GETDEL, dict-like API. Best-effort: Redis errors are logged and swallowed
    so observability outages don't break uploads.
  • web/app.py — _progress is now Redis-backed; every set/get/active/pop is
    awaited. SSE handler emits a heartbeat each tick (forces HTTP/2 flush),
    drops the 30s post-completion sleep, and returns a terminal
    {"status":"unknown"} payload instead of 404 when the task is gone — so
    EventSource closes cleanly instead of reconnect-looping. New _SSE_HEADERS
    set X-Accel-Buffering: no.
  • web-ui useProgress(taskId, caseNumber) — 10s fallback that invalidates
    the case detail if no SSE message arrived; treats "unknown" as terminal
    and triggers a refetch from the source of truth.
  • upload-sheet wires caseNumber through and renders "unknown" as completed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:53:23 +00:00
5e4c03d0cd Case sync: refresh remote URL with current token before each push
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
Cases failed to push silently after the Gitea token in Infisical was
rotated: the embedded credential in each case repo's origin URL was
the old token, the rotation never propagated, and capture_output=True
hid the auth failure as a logger.warning. Three cases (1033-25,
1130-25, 1194-25) accumulated unpushed commits over weeks before
this was noticed.

Fixes the root cause in two places: web/gitea_client.py for uploads
through the FastAPI endpoint, and mcp-server/services/git_sync.py
for case_update / document_upload through MCP tools (which previously
committed but never pushed at all).

The new commit_and_push helper:
- re-injects the current GITEA_ACCESS_TOKEN into the existing origin
  URL on every call, so pushes survive token rotation
- logs push failures at WARNING with the actual stderr (the previous
  code suppressed errors entirely)
- continues to push even when the commit was a no-op, in case earlier
  commits are still unpushed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:14:57 +00:00
6a47320b9c get_case_issues: also match issues by [ערר X] title prefix
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 8s
The original implementation only returned issues with a plugin_state
linkage (legal-case-number key), which was set just on the initial
setup issue. Sub-agents that created follow-up issues during the case
workflow tagged them in the title ("[ערר 1130-25] כתיבת החלטה" etc.)
but didn't write a plugin_state row, so 23 of 24 historical issues
for case 1130-25 were invisible to the agent activity feed.

Widened the lookup to UNION two paths:
  (a) plugin_state.scope_id matches via the legal-case-number key
  (b) issues.title LIKE '%[ערר {case_number}]%' OR '%ערר {case_number}%'

Used DISTINCT ON (i.id) + post-sort by created_at to dedupe and keep
chronological order. The widget on https://legal-ai.../cases/1130-25
will now show the full history (was 1 issue → now 16).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:53:20 +00:00
7d86ed4a62 Archive: also cancel open Paperclip issues to clear agent widget
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 32s
When a case is archived, the legal-ai UI's AgentStatusWidget kept showing
"agents started working, waiting for first report" because related
Paperclip issues remained in 'todo' / 'in_progress' status. Concrete
example: case 1130-25 had two open issues (CMP-15 ניתוח תכנוני, CMP-21
כתיבת החלטה) that lingered after the case was finalized; 1194-25 had
two more (CMP-37, CMP-44).

Extended pc_archive_project to also UPDATE issues SET status='cancelled',
cancelled_at=now() WHERE project_id matches AND status IN
('backlog','todo','in_progress','blocked','in_review'). Returns the list
of cancelled issues so the toast can announce the count.

Updated cases.ts ArchiveResult.paperclip.issues_cancelled type and the
toast message in case-archive-action to surface "(N משימות פתוחות בוטלו)"
when relevant.

Restore is intentionally unchanged — we don't auto-recreate cancelled
issues; if work needs to resume, a fresh issue should be created.

Stale issues for 1130-25 / 1194-25 cancelled directly in DB as a one-off
cleanup (CMP-15, CMP-21, CMP-37, CMP-44).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:14:12 +00:00
2b7f291928 Case archive/restore with Paperclip sync
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
Adds a comprehensive archive flow for closed cases — separate /archive
screen in the UI, archive/restore actions on the case detail page, and
automatic two-way sync with Paperclip.

Backend (web/app.py + mcp-server/services/db.py):
- New SCHEMA_V6 migration: cases.archived_at TIMESTAMPTZ + partial index
- list_cases gains include_archived/archived_only flags; default excludes
  archived rows so the main /api/cases list hides closed cases
- archive_case / restore_case helpers in db.py
- POST /api/cases/{n}/archive sets archived_at and calls
  pc_archive_project (sets Paperclip projects.archived_at via direct DB)
- POST /api/cases/{n}/restore clears archived_at and calls
  pc_restore_project (clears Paperclip archived_at)
- archive_project / restore_project in paperclip_client.py — name-based
  match consistent with create_project's lookup

Frontend (web-ui):
- cases.ts: scope param ("active"|"archived"|"all") on useCases;
  useArchiveCase / useRestoreCase mutations
- /archive page (new): table of archived cases with restore button +
  search, sort, empty state matching the editorial aesthetic of /
- case-archive-action.tsx: button on case detail header. Active case →
  confirm dialog → archive. Archived case → restore (no confirm).
  Toast announces both legal-ai and Paperclip outcomes (synced, not
  found in pc, error)
- case-header shows "בארכיון" badge when archived_at is set
- Nav: ארכיון link added to AppShell after בית

Tested end-to-end against the live DB:
- 1130-25 archive → list_cases(include_archived=False) excludes it,
  list_cases(archived_only=True) includes it, restore reverses
- pc archive/restore on 1194-25 verified via direct DB lookup
- TypeScript compiles clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 18:54:52 +00:00
36ca713dfa Retrofit: tighten yod-bet pattern, add cover-block fallback
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 6s
The "על כן" pattern for block-yod-bet was too greedy and matched mid-discussion
transitional sentences (e.g. "על כן, במקום בו..."), which caused forward-scan
to skip block-yod-alef ("סוף דבר") via the pointer advance.

Tightened to require an operative subject (אנו / הערר / הוועדה / ועדת הערר)
so terminal "על כן, אנו מחליטים" still matches but mid-block transitions don't.

Added structural_fallback for cover blocks (alef/bet/gimel/dalet) — these are
template metadata not present in user-edited DOCX bodies. Inject zero-content
anchors so apply_user_edit can still target them later. The frontend toast
distinguishes real content gaps from fallback anchors.

Also expanded heading patterns based on training corpus inspection:
- block-vav: על המקרקעין חלות / במצב התכנוני / התכניות החלות
- block-zayin: טענות העוררת
- block-chet: עיקר תגובת המשיב
- block-tet: הדיון בוועדת הערר

For case 1130-25, this raises detection from 6/12 to 11/12 blocks — only
block-yod-bet remains missing (Daphna's edit ends at "סוף דבר" + numbered
ruling, no terminal "ההחלטה" or "על כן אנו מחליטים" paragraph).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 06:57:41 +00:00
eac7784b87 Trigger appraiser-facts extraction from the UI
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 36s
Extraction is expensive (multi-minute LLM calls) and runs across every
appraisal in the case at once, so we don't kick it off silently on every
tag save. The chair tags the appraisals, then runs extraction once when
they're ready.

- New POST /api/cases/{n}/extract-appraiser-facts endpoint returns the
  extractor's summary as-is: status=completed with fact counts and
  conflicts, or status=sides_missing with the list of still-untagged
  appraisal docs.
- DocumentTypeEditor now has a two-phase popover. After a successful
  save on an appraisal doc, the body switches to a confirmation view
  with a "חלץ עובדות שמאיות עכשיו" button. The result (completed /
  sides_missing / no_appraisals / error) renders in the same popover
  so the chair sees exactly which appraisals still need tagging
  without closing and reopening anything.
- useExtractAppraiserFacts React-Query mutation invalidates the case
  detail on success so downstream views (conflict rendering in
  block-tet context) pick up the new facts.
2026-04-19 09:42:49 +00:00
c536ed0e63 Edit document doc_type and appraiser side from the case UI
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
Until now changing a document's doc_type required a manual SQL update.
Adds an inline editor on the document badge so the chair can retag
without leaving the case page, and threads an appraiser_side tag
(committee / appellant / deciding) through the appraisal pipeline so
betterment-levy cases — which usually have 2-3 appraisers — render
conflicts with the deciding appraiser's view marked as governing.

Backend
- New appraiser_facts.appraiser_side column (V5.1) populated from
  documents.metadata.appraiser_side at extraction time.
- extract_appraiser_facts now returns status='sides_missing' with the
  list of untagged appraisals instead of running with empty side
  labels — chair must tag every appraisal first via the UI.
- Conflict detection orders entries committee → appellant → deciding so
  the deciding appraiser appears last; block-tet's prompt instructs the
  writer to phrase the deciding appraiser's view as the governing
  factual finding ("ואולם, השמאי המכריע קבע...").
- New PATCH /api/cases/{n}/documents/{doc_id} (Pydantic model with
  whitelist validation) and matching document_update MCP tool. Both
  merge appraiser_side into metadata JSONB instead of touching the
  schema.

UI
- New shared doc-types module exports the canonical 11 doc_type
  options plus the 3 appraiser-side options; both upload-sheet and
  the document badge now read from it instead of duplicating Hebrew
  labels.
- New DocumentTypeEditor renders a Popover off the doc-type Badge
  with two Selects. The save button stays disabled while doc_type is
  appraisal but no side has been picked, mirroring the backend
  enforcement so the user finds out before triggering extraction.
- usePatchDocument React-Query mutation invalidates the case detail
  on success so the badge updates without a manual refresh.
2026-04-19 06:26:51 +00:00
726498126d Add Track Changes architecture for draft revisions (CMP + CMPA)
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m29s
Fixes critical bug in 1033-25: user-uploaded עריכה-*.docx files were
orphaned on disk while exports kept rebuilding from stale DB blocks.

New architecture:
- User-uploaded DOCX becomes the source of truth (cases.active_draft_path)
- System edits via XML surgery with real Word <w:ins>/<w:del> revisions
- User can Accept/Reject each change from within Word

Components:
- docx_reviser.py: XML surgery for Track Changes (15 tests)
- docx_retrofit.py: retroactive bookmark injection with Hebrew marker
  detection + heading heuristic (9 tests)
- docx_exporter.py: emits bookmarks around each of the 12 blocks
- 3 new MCP tools: apply_user_edit, list_bookmarks, revise_draft
- 4 new/updated endpoints: upload (auto-registers active draft),
  /exports/revise, /exports/bookmarks, /exports/{filename}/retrofit,
  /active-draft
- DB migration: cases.active_draft_path column
- UI: correct banner using real v-numbers, "מקור האמת" badge,
  detailed upload toast with bookmarks_added/missing_blocks
- agents: legal-exporter (3 export modes), legal-ceo (stage G for
  revision handling), legal-writer (revision mode)

Multi-tenancy:
- Works for both CMP (1xxx cases) and CMPA (8xxx/9xxx cases)
- New revise-draft skill added to both companies
- deploy-track-changes.sh syncs skills CMP ↔ CMPA
- retrofit_case.py: one-off retrofit of existing files

Tests: 34 passing (15 reviser + 9 retrofit + 4 exporter bookmarks + 6 e2e)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:49:30 +00:00
d7a79cf5ec Show per-case agent status instead of global — fix Hebrew translation of "running"
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m41s
Agent status widget now checks heartbeat_runs + wakeup_requests to determine
if an agent is running on *this* case. Agents running on other cases show as idle.
Added "running" to STATUS_DOT/STATUS_LABEL maps so it displays in Hebrew.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:47:42 +00:00
3288624349 Add methodology settings page with golden ratios, discussion rules, and checklists
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m29s
New /methodology page with 3 tabs for viewing and editing decision
writing methodology. Uses DB override pattern: hardcoded Python
constants serve as defaults, edits saved to appeal_type_rules table,
delete restores default.

Backend: 3 generic endpoints (GET/PUT/DELETE /api/methodology/{category}/{key})
with validation per category type.

Frontend: methodology.ts hooks, GoldenRatiosPanel (number inputs per
outcome/section), DiscussionRulesPanel (accordion with textarea per
rule), ContentChecklistsPanel (markdown editor with preview toggle).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:30:39 +00:00
c9a8cca35f Link agents to CMPA company, route CEO wakeup per-company
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 8s
Created 7 agents in CMPA (betterment levy) company, mirroring
the CMP agents with same config and hierarchy. CEO_AGENTS dict
maps company_id to the correct CEO for wakeup routing.

wake_ceo_agent and post_comment now resolve the correct CEO
based on company_id. create_workflow_issue returns company_id.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:09:37 +00:00
1e4c5c1518 Add Paperclip agent activity mirror to case detail page
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m16s
New "Agents" tab in case detail shows all Paperclip agent comments,
issue status, and agent status for each case — eliminating the need
to switch between Legal-AI and Paperclip UIs.

Backend: 4 new DB query functions in paperclip_client.py (issues,
comments, agents, post_comment) + 2 new API endpoints (GET/POST
/api/cases/{case_number}/agents). Comment posting uses Board API
with DB+wakeup fallback to ensure CEO routing.

Frontend: agents.ts hooks (10s polling), AgentActivityFeed component
(markdown timeline + comment input), AgentStatusWidget (sidebar),
4th tab in case detail page.

Also includes new-company-setup-guide.md documenting the process
for setting up the betterment levy (CMPA) company.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:44:42 +00:00
3b260a094d Remove legacy vanilla frontend, clarify web/ vs web-ui/ in CLAUDE.md
- Delete web/static/index.html and design-system.css (replaced by Next.js)
- Remove GET / HTML route and StaticFiles import from app.py
- CLAUDE.md: document that web/ = FastAPI API, web-ui/ = Next.js frontend

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:41:02 +00:00
43b8106f55 Fix wakeup API source/triggerDetail enum values
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 10s
Paperclip expects source ∈ {timer, assignment, on_demand, automation}
and triggerDetail ∈ {manual, ping, callback, system}.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:08:07 +00:00
6228846223 Add "Start Workflow" button to trigger CEO agent from web UI
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 49s
New endpoint POST /api/cases/{case_number}/start-workflow creates a
Paperclip issue, wakes the CEO agent via wakeup API, and transitions
case status to "processing". Button appears on case page only when
status is "new" or "documents_ready".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:51:23 +00:00
82ba4663ba Fix case repo sync + auto-create Gitea repos + add sync indicator
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m30s
- auto-sync-cases.sh: fix broken directory scan (was looking for
  status subdirs that don't exist), fix env var word-splitting bug,
  add safe.directory handling and error logging
- cases.py: auto-create Gitea repo on case_create, fix
  documents/original → documents/originals naming mismatch
- app.py: add GET /api/cases/{case_number}/git-status endpoint
- web-ui: add SyncIndicator component in case header showing
  sync status (synced/pending/no remote) with last commit time
- pyproject.toml: add httpx dependency
- CLAUDE.md: update Paperclip wakeup API docs
- settings page: switch tag input from Select to free-text with datalist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:28:16 +00:00
1133272e34 Fix Paperclip integration (identifier→issue_prefix) + add settings page
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 34s
- Fix column name mismatch in paperclip_client.py and app.py: Paperclip's
  companies table uses `issue_prefix`, not `identifier`
- Fix _LEGAL_DB_URL to read from POSTGRES_URL env var (used in container)
- Add settings page (/settings) for managing tag → Paperclip company mappings
- Replace "תיק חדש" nav item with "הגדרות" (new case is on home page)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:09:08 +00:00
23f6b5d825 Remove Paperclip Docker references — runs locally via pm2
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m38s
- Deleted from Coolify (was exited:unhealthy since Apr 7)
- Updated CLAUDE.md service table: Paperclip is now pm2/local
- Removed Docker skills path fallback in app.py (always use local)
- Removed old paperclip-bug-report.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:13:26 +00:00
a093944967 Add delete button for draft files in case drafts panel
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 45s
- Add DELETE /api/cases/{case_number}/exports/{filename} endpoint
- Add useDeleteDraft hook in exports API
- Add trash icon + confirmation dialog in drafts panel UI
- Final files (סופי-) cannot be deleted as a safety measure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:05:30 +00:00
e698419faf Fix git not found error crashing document uploads in container
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m13s
Install git in Docker image and wrap all subprocess git calls in
try/except so a missing or failing git binary never kills an upload
that already succeeded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:38:40 +00:00
5028f677f1 Fix English statuses and labels throughout UI to Hebrew
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 7s
- Complete STATUS_LABELS in case view (added outcome_set, direction_approved,
  drafting, qa_review, reviewed)
- Add DOC_STATUS_LABELS for diagnostics page (failed/stuck documents)
- Add completed/failed/pending/error to global STEP_LABELS
- Translate settings page table headers to Hebrew

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 06:32:03 +00:00
2faae002e7 Add settings page for tag-to-company mappings and auto-create Paperclip projects
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m22s
When a case is created, a Paperclip project is now automatically created in
the correct company based on the appeal_subtype tag. Tag-to-company mappings
are managed via a new Settings page that pulls companies from Paperclip DB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 06:24:23 +00:00
140a2e442d Add drafts & feedback tab to case page, remove global feedback page
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 32s
Move draft management (export DOCX, download, upload revised version, mark
final) and chair feedback into a new "טיוטות והערות" tab on the case detail
page. Remove the standalone /feedback page and its nav link since feedback
is now case-scoped.

Also fix /api/admin/skills 500 error when Paperclip DB is unreachable by
adding a connection timeout and graceful fallback to disk-only skills.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 05:55:46 +00:00
b248e1414d Add upload endpoint for updated analysis files
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 34s
PUT /api/cases/{n}/research/analysis/upload accepts a markdown file and
validates: UTF-8 encoding, parseable structure, at least one threshold
or issue section, matching case number. Backs up existing file before
replacing. UI adds "העלה ניתוח מעודכן" button with status feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 19:34:06 +00:00