Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,15 @@ permanently break composition. `session_links` is also the topology-edge table
(the docs' older `topology_edges` name): it persists every parent reference a
parser asserts, even when the parent isn't ingested yet, keyed
`(src_session_id, dst_origin, dst_native_id, link_type)`, resolved on each save
by `resolve_session_links_for_session`. `TopologyEdgeStatus` =
unresolved/resolved/repaired/**quarantined** (cycle-break).
by `_resolve_session_graph`/`_resolve_outbound_session_links`
(`storage/sqlite/archive_tiers/write.py`) — the sole production
implementation, invoked unconditionally from `write_parsed_session_to_archive`
(the single choke point both live incremental ingest and full raw
replay/reindex go through). `storage/sqlite/queries/session_links.py`'s
similarly-named async `resolve_session_links_for_session` has no production
caller; it exists only as test infrastructure (`polylogue-enium`).
`TopologyEdgeStatus` = unresolved/resolved/repaired/**quarantined**
(cycle-break).

### The five tiers (durability is the axis)

Expand Down
18 changes: 0 additions & 18 deletions polylogue/archive/provider/semantics.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,24 +157,6 @@ def extract_content_blocks(content: Sequence[object] | None) -> list[ContentBloc
return blocks


def extract_display_text_from_content_blocks(content: Sequence[object] | None) -> str:
"""Rebuild human-readable text from stored structured content blocks."""
if not content:
return ""

parts: list[str] = []
for raw_block in content:
block = _content_block_record(raw_block)
if block is None:
continue
if block.get("type") not in {"text", "code", "tool_result", "thinking"}:
continue
text = _string_field(block, "text")
if text:
parts.append(text)
return "\n".join(parts)


def extract_claude_code_text(content: Sequence[object] | None) -> str:
"""Extract text from Claude Code content blocks, excluding non-text blocks."""
if not content:
Expand Down
36 changes: 0 additions & 36 deletions polylogue/archive/semantic/pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,42 +530,6 @@ def resolve_model_identity(
)


def _usage_payload(value: object) -> CostUsagePayload:
usage = _record(value)
input_tokens = _coerce_int(
usage.get("input_tokens")
or usage.get("prompt_tokens")
or usage.get("inputTokenCount")
or usage.get("promptTokenCount")
)
output_tokens = _coerce_int(
usage.get("output_tokens")
or usage.get("completion_tokens")
or usage.get("outputTokenCount")
or usage.get("candidatesTokenCount")
)
cache_read_tokens = _coerce_int(
usage.get("cache_read_tokens")
or usage.get("cache_read_input_tokens")
or usage.get("cached_tokens")
or usage.get("cachedContentTokenCount")
)
cache_write_tokens = _coerce_int(
usage.get("cache_write_tokens")
or usage.get("cache_creation_input_tokens")
or usage.get("cache_creation_tokens")
)
explicit_total = _coerce_int(usage.get("total_tokens") or usage.get("totalTokenCount"))
total = explicit_total or input_tokens + output_tokens + cache_read_tokens + cache_write_tokens
return CostUsagePayload(
input_tokens=input_tokens,
output_tokens=output_tokens,
cache_read_tokens=cache_read_tokens,
cache_write_tokens=cache_write_tokens,
total_tokens=total,
)


def _token_usage_payload(tokens: TokenUsage | None) -> CostUsagePayload:
if tokens is None:
return CostUsagePayload()
Expand Down
20 changes: 0 additions & 20 deletions polylogue/cli/archive_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
from polylogue.archive.query.spec import (
QuerySpecError,
SessionQuerySpec,
normalize_action_sequence,
normalize_action_terms,
parse_query_date,
resolve_default_root_filter,
session_count_unit_label,
Expand Down Expand Up @@ -1832,10 +1830,6 @@ def _optional_str(value: object) -> str | None:
return text or None


def _tags(value: object) -> tuple[str, ...]:
return _csv_tokens(value)


def _csv_tokens(value: object) -> tuple[str, ...]:
if value is None:
return ()
Expand Down Expand Up @@ -1876,20 +1870,6 @@ def _tool_tokens(value: object) -> tuple[str, ...]:
return tuple(token.lower() for token in _csv_tokens(value))


def _action_tokens(field: str, value: object) -> tuple[str, ...]:
try:
return normalize_action_terms(field, value)
except QuerySpecError as exc:
raise click.UsageError(f"invalid {exc.field}: {exc.value}") from exc


def _action_sequence_tokens(value: object) -> tuple[str, ...]:
try:
return normalize_action_sequence("action_sequence", value)
except QuerySpecError as exc:
raise click.UsageError(f"invalid {exc.field}: {exc.value}") from exc


def _message_type(value: object) -> str | None:
if not value:
return None
Expand Down
12 changes: 0 additions & 12 deletions polylogue/cli/click_option_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,6 @@ def get_metavar(self, param: click.Parameter, ctx: click.Context | None = None)
return "TEXT"


def _load_message_types() -> list[str]:
from polylogue.archive.message.types import MessageType

return [m.value for m in MessageType]


def _load_material_origins() -> list[str]:
from polylogue.core.enums import MaterialOrigin

return [m.value for m in MaterialOrigin]


def _load_retrieval_lanes() -> list[str]:
from polylogue.archive.query.spec import QUERY_RETRIEVAL_LANES

Expand Down
33 changes: 0 additions & 33 deletions polylogue/cli/commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,11 @@

import click

from polylogue.cli.shared.check_models import VacuumResult
from polylogue.cli.shared.check_options import apply_check_command_options
from polylogue.cli.shared.check_rendering_json import emit_json_output
from polylogue.cli.shared.check_rendering_plain import render_plain_output
from polylogue.cli.shared.check_support import (
format_count_mapping as _format_count_mapping_impl,
)
from polylogue.cli.shared.check_support import (
make_schema_progress_callback as _make_schema_progress_callback_impl,
)
from polylogue.cli.shared.check_support import (
parse_schema_samples as _parse_schema_samples_impl,
)
from polylogue.cli.shared.check_support import run_vacuum as _run_vacuum_impl
from polylogue.cli.shared.check_support import vacuum_database as _vacuum_database_impl
from polylogue.cli.shared.check_workflow import CheckCommandOptions, run_check_workflow, validate_check_options
from polylogue.cli.shared.types import AppEnv
from polylogue.core.protocols import ProgressCallback


def _format_count_mapping(counts: dict[str, int]) -> str:
return _format_count_mapping_impl(counts)


@click.command("doctor")
Expand Down Expand Up @@ -97,20 +80,4 @@ def check_command(
render_plain_output(env, result, options)


def _make_schema_progress_callback() -> ProgressCallback:
return _make_schema_progress_callback_impl()


def _run_vacuum(env: AppEnv) -> None:
_run_vacuum_impl(env)


def _vacuum_database(env: AppEnv) -> VacuumResult:
return _vacuum_database_impl(env)


def _parse_schema_samples(raw: str) -> int | None:
return _parse_schema_samples_impl(raw)


__all__ = ["check_command"]
11 changes: 0 additions & 11 deletions polylogue/cli/shared/check_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,6 @@ def format_count_mapping(counts: dict[str, int]) -> str:
return ", ".join(f"{key}={value:,}" for key, value in sorted(counts.items()))


def format_semantic_metric_summary(metric_summary: dict[str, dict[str, int]]) -> str:
return ", ".join(
(
f"{metric}(preserved={counts.get('preserved', 0):,}, "
f"declared_loss={counts.get('declared_loss', 0):,}, "
f"critical_loss={counts.get('critical_loss', 0):,})"
)
for metric, counts in sorted(metric_summary.items())
)


def parse_schema_samples(raw: str) -> int | None:
value = raw.strip().lower()
if value == "all":
Expand Down
6 changes: 0 additions & 6 deletions polylogue/cli/shared/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@
from polylogue.ui import UI


def _lazy_ui() -> UI:
from polylogue.ui import UI as _UI

return _UI(plain=True)


def _lazy_services(runtime: ResolvedRuntimeConfig | None) -> RuntimeServices:
from polylogue.services import build_runtime_services

Expand Down
107 changes: 2 additions & 105 deletions polylogue/context/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,9 @@
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass
from pathlib import Path
from types import SimpleNamespace
from typing import TYPE_CHECKING, Any, TypedDict
from typing import TYPE_CHECKING, Any

from polylogue.core.timestamps import parse_archive_datetime
from polylogue.mcp.archive_support import archive_index_active_paths, archive_query_filters
from polylogue.storage.sqlite.archive_tiers.archive import (
ArchiveSessionSearchHit,
ArchiveSessionSummary,
ArchiveStore,
)
from polylogue.mcp.archive_support import archive_index_active_paths
Comment on lines +13 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep the query result boundary strongly typed.

This change introduces Any, which makes the retained select_context_image_sessions query result untyped. Use the existing session type or define a small protocol for the fields consumed by this module instead of widening the callback to Sequence[Any].

As per coding guidelines, **/*.py must use strict typing with mypy --strict; do not bypass type and identifier checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@polylogue/context/selection.py` around lines 13 - 15, Remove the Any-based
typing from select_context_image_sessions and keep its query result strongly
typed using the existing session type, or define a narrow Protocol containing
the fields consumed by this module. Update the callback/result annotation and
related imports so mypy --strict validates the boundary without broad
Sequence[Any] or other type-checking bypasses.

Source: Coding guidelines


if TYPE_CHECKING:
from polylogue.archive.query.spec import SessionQuerySpec
Expand All @@ -43,17 +36,6 @@ class ContextImageSelection:
query_total: int = 0


class ArchiveContextImageFilters(TypedDict):
origins: tuple[str, ...]
excluded_origins: tuple[str, ...]
tags: tuple[str, ...]
excluded_tags: tuple[str, ...]
repo_names: tuple[str, ...]
cwd_prefix: str | None
since_ms: int | None
until_ms: int | None


@dataclass(frozen=True, slots=True)
class _ContextImageQueryAttempt:
query: str | None
Expand Down Expand Up @@ -195,88 +177,3 @@ def archive_context_image_active(
archive_root=archive_root,
db_anchor_path=db_anchor_path,
)


def query_archive_context_image(
archive: ArchiveStore,
spec: SessionQuerySpec,
*,
default_limit: int,
) -> list[SimpleNamespace]:
"""Project archive sessions into the context-image summary surface."""
query = " ".join(spec.query_terms).strip()
kwargs = archive_context_image_filters(spec)
if query:
rows: list[ArchiveSessionSummary | ArchiveSessionSearchHit] = list(
archive.search_summaries(
query,
limit=spec.limit or default_limit,
offset=spec.offset,
sort="date",
reverse=spec.reverse,
**kwargs,
)
)
else:
rows = list(
archive.list_summaries(
limit=spec.limit or default_limit,
offset=spec.offset,
sort="date",
reverse=spec.reverse,
**kwargs,
)
)

summaries: list[ArchiveSessionSummary] = []
for row in dedupe_archive_context_image_rows(rows):
if isinstance(row, ArchiveSessionSearchHit):
try:
summaries.append(archive.read_summary(row.session_id))
except KeyError:
continue
else:
summaries.append(row)
return [archive_context_image_summary(row) for row in summaries]


def archive_context_image_filters(spec: SessionQuerySpec) -> ArchiveContextImageFilters:
filters = archive_query_filters(spec)
return {
"origins": filters["origins"],
"excluded_origins": filters["excluded_origins"],
"tags": filters["tags"],
"excluded_tags": filters["excluded_tags"],
"repo_names": filters["repo_names"],
"cwd_prefix": filters["cwd_prefix"],
"since_ms": filters["since_ms"],
"until_ms": filters["until_ms"],
}


def archive_context_image_summary(row: ArchiveSessionSummary) -> SimpleNamespace:
return SimpleNamespace(
id=row.session_id,
origin=row.origin,
title=row.title,
display_title=row.title,
created_at=parse_archive_datetime(row.created_at),
updated_at=parse_archive_datetime(row.updated_at),
message_count=row.message_count,
messages=(),
tool_use_count=0,
)


def dedupe_archive_context_image_rows(
rows: list[ArchiveSessionSummary | ArchiveSessionSearchHit],
) -> list[ArchiveSessionSummary | ArchiveSessionSearchHit]:
deduped: list[ArchiveSessionSummary | ArchiveSessionSearchHit] = []
seen: set[str] = set()
for row in rows:
session_id = row.session_id
if session_id in seen:
continue
seen.add(session_id)
deduped.append(row)
return deduped
12 changes: 0 additions & 12 deletions polylogue/core/dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,3 @@ def parse_date(date_str: str) -> datetime | None:
# Ensure result is always UTC-aware
result = result.replace(tzinfo=timezone.utc)
return result


def format_date_iso(dt: datetime) -> str:
"""Format datetime as ISO string compatible with storage layer.

Args:
dt: datetime to format

Returns:
ISO 8601 formatted string (YYYY-MM-DD HH:MM:SS)
"""
return dt.strftime("%Y-%m-%d %H:%M:%S")
Loading