Skip to content

Commit ba17902

Browse files
committed
feat: add linting script to pyproject.toml, enhance error handling in CLI, and improve research plan generation
1 parent 593819c commit ba17902

28 files changed

+103
-92
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ compendium = "compendiumscribe.cli:cli"
5656

5757
[tool.pdm.scripts]
5858
test = "pytest"
59+
lint = "flake8 src tests"

src/compendiumscribe/cli.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import json
2-
import re
32
from datetime import datetime, timezone
43
from pathlib import Path
5-
from typing import Any, TYPE_CHECKING
64

75
import click
86

@@ -24,7 +22,6 @@
2422
)
2523

2624

27-
2825
@click.group()
2926
def cli() -> None:
3027
"""Compendium Scribe: AI Research & Rendering Tool."""
@@ -124,17 +121,22 @@ def handle_progress(update: ResearchProgress) -> None:
124121
click.echo("\nHard shutdown requested.", err=True)
125122
raise SystemExit(1)
126123
except ResearchTimeoutError as exc:
124+
compendium_title = getattr(exc, "compendium_title", None)
127125
timeout_data = {
128126
"research_id": exc.research_id,
129127
"topic": topic,
128+
"title": compendium_title or topic,
130129
"no_background": no_background,
131130
"formats": list(formats),
132131
"max_tool_calls": max_tool_calls,
133132
"timestamp": datetime.now(timezone.utc).isoformat(),
134133
}
135134
Path("timed_out_research.json").write_text(json.dumps(timeout_data, indent=2))
136135
click.echo(f"\n[!] Deep research timed out (ID: {exc.research_id}).", err=True)
137-
click.echo(f"Stored recovery information in timed_out_research.json", err=True)
136+
click.echo(
137+
"Stored recovery information in timed_out_research.json",
138+
err=True,
139+
)
138140
raise SystemExit(1) from exc
139141
except MissingAPIKeyError as exc:
140142
click.echo(f"Configuration error: {exc}", err=True)
@@ -154,7 +156,8 @@ def handle_progress(update: ResearchProgress) -> None:
154156
# If output_path has a suffix, we use it as the stem to avoid out.md.md
155157
base_path = output_path.parent / output_path.stem
156158
else:
157-
slug = slugify(topic)
159+
name_for_slug = compendium.topic or topic
160+
slug = slugify(name_for_slug)
158161
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
159162
base_path = Path(f"{slug}_{timestamp}")
160163

@@ -226,14 +229,15 @@ def recover(input_file: Path):
226229
data = json.loads(input_file.read_text(encoding="utf-8"))
227230
research_id = data["research_id"]
228231
topic = data["topic"]
232+
title = data.get("title") or topic
229233
formats = tuple(data["formats"])
230234
max_tool_calls = data.get("max_tool_calls")
231235
no_background = data.get("no_background", False)
232236
except (json.JSONDecodeError, KeyError) as exc:
233237
click.echo(f"Error: Failed to parse recovery file: {exc}", err=True)
234238
raise SystemExit(1)
235239

236-
click.echo(f"Checking status for research ID: {research_id} ('{topic}')...")
240+
click.echo(f"Checking status for research ID: {research_id} ('{title}')...")
237241

238242
config = ResearchConfig(
239243
background=not no_background,
@@ -243,13 +247,13 @@ def recover(input_file: Path):
243247
try:
244248
compendium = recover_compendium(
245249
research_id=research_id,
246-
topic=topic,
250+
topic=title,
247251
config=config,
248252
)
249253

250254
click.echo("Research completed! Writing outputs.")
251255

252-
slug = slugify(topic)
256+
slug = slugify(title)
253257
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
254258
base_path = Path(f"{slug}_{timestamp}")
255259

src/compendiumscribe/compendium/entities.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import xml.etree.ElementTree as ET
55

66

7-
8-
97
@dataclass
108
class Citation:
119
"""Represents a single cited source returned by deep research."""

src/compendiumscribe/compendium/html_site_renderer.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
if TYPE_CHECKING: # pragma: no cover - hints only
1111
from .compendium import Compendium
12-
from .entities import Citation, Section
12+
from .entities import Section
1313

1414

1515
def _html_head(title: str, depth: int = 0) -> list[str]:
@@ -297,9 +297,6 @@ def _render_open_questions_page(compendium: "Compendium") -> str:
297297
return "\n".join(parts) + "\n"
298298

299299

300-
301-
302-
303300
def render_html_site(compendium: "Compendium") -> dict[str, str]:
304301
"""Render the compendium as a navigable multi-file HTML site.
305302

src/compendiumscribe/compendium/pdf.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ def render_pdf(compendium: Compendium) -> bytes:
2525
pdf = CompendiumPDF()
2626
pdf.alias_nb_pages()
2727
pdf.add_page()
28-
28+
2929
# Title
3030
pdf.set_font("helvetica", "B", 24)
3131
pdf.cell(0, 20, compendium.topic, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align="C")
32-
32+
3333
pdf.set_font("helvetica", "I", 10)
3434
generated_at = compendium.generated_at.strftime("%Y-%m-%d %H:%M:%S")
3535
pdf.cell(0, 10, f"Generated at: {generated_at} UTC", new_x=XPos.LMARGIN, new_y=YPos.NEXT, align="C")
@@ -58,18 +58,18 @@ def render_pdf(compendium: Compendium) -> bytes:
5858
pdf.set_font("helvetica", "B", 16)
5959
pdf.cell(0, 10, "Sections", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
6060
pdf.ln(2)
61-
61+
6262
for section in compendium.sections:
6363
# Check for page break if near bottom
6464
if pdf.get_y() > 250:
6565
pdf.add_page()
66-
66+
6767
pdf.set_font("helvetica", "B", 14)
6868
title = section.title
6969
if section.identifier:
7070
title = f"{section.identifier}: {title}"
7171
pdf.cell(0, 10, title, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
72-
72+
7373
pdf.set_font("helvetica", "", 11)
7474
pdf.multi_cell(0, 6, section.summary)
7575
pdf.ln(2)
@@ -79,7 +79,7 @@ def render_pdf(compendium: Compendium) -> bytes:
7979
pdf.cell(0, 6, "Key Terms: ", ln=False)
8080
pdf.set_font("helvetica", "", 10)
8181
pdf.multi_cell(0, 6, ", ".join(section.key_terms))
82-
82+
8383
if section.insights:
8484
pdf.ln(2)
8585
for insight in section.insights:

src/compendiumscribe/compendium/text_utils.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from __future__ import annotations
22

3-
from typing import Iterator
43
import mistune
54

65

76
def slugify(text: str, max_length: int | None = 100) -> str:
87
"""Convert text to a URL-friendly slug.
9-
8+
109
Args:
1110
text: The text to convert to a slug.
1211
max_length: Maximum length of the resulting slug. If None, no truncation.
@@ -16,14 +15,12 @@ def slugify(text: str, max_length: int | None = 100) -> str:
1615

1716
slug = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
1817
slug = slug or "page"
19-
18+
2019
if max_length is not None and len(slug) > max_length:
2120
# Truncate and remove any trailing hyphen from the cut
2221
slug = slug[:max_length].rstrip("-")
23-
24-
return slug
25-
2622

23+
return slug
2724

2825

2926
def format_html_text(text: str | None) -> str:
@@ -33,18 +30,18 @@ def format_html_text(text: str | None) -> str:
3330
return ""
3431

3532
# Create a markdown parser with escaping enabled in the renderer
36-
# This prevents raw HTML tags from being passed through while
33+
# This prevents raw HTML tags from being passed through while
3734
# ensuring that markdown-generated HTML (like <code>) is NOT double-escaped.
3835
renderer = mistune.HTMLRenderer(escape=True)
3936
markdown = mistune.create_markdown(renderer=renderer)
40-
37+
4138
result = markdown(text).strip()
42-
43-
# If mistune wrapped it in <p>...</p> and it's a single paragraph,
39+
40+
# If mistune wrapped it in <p>...</p> and it's a single paragraph,
4441
# we might want to strip it for inline use.
4542
if result.startswith("<p>") and result.endswith("</p>") and result.count("<p>") == 1:
4643
result = result[3:-4]
47-
44+
4845
return result
4946

5047

src/compendiumscribe/compendium/xml_parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def _parse_root(root: ET.Element) -> Compendium:
8585
citations=citations,
8686
open_questions=open_questions,
8787
)
88-
88+
8989
if generated_at:
9090
compendium.generated_at = generated_at
9191

src/compendiumscribe/create_llm_clients.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def create_openai_client(*, timeout: int | None = None) -> OpenAI:
2525
actual_timeout = timeout if timeout is not None else DEFAULT_TIMEOUT_SECONDS
2626

2727
client = OpenAI(api_key=api_key, timeout=actual_timeout)
28-
28+
2929
# Quick sanity check for the required API capability
3030
if not hasattr(client, "responses"):
3131
raise RuntimeError(

src/compendiumscribe/prompts/topic_blueprint.prompt.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
You are a research planning assistant preparing inputs for a deep research model. Given the topic below, output a concise JSON object that captures:
66

7+
- `title`: a clear, engaging title for the research compendium.
78
- `primary_objective`: a single sentence describing the overarching research goal.
89
- `audience`: the intended audience in one sentence.
910
- `key_sections`: 3-5 objects each with `title` and `focus` outlining recommended sections of the final compendium.

src/compendiumscribe/research/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,3 @@
6060
"coerce_optional_string",
6161
"get_field",
6262
]
63-

0 commit comments

Comments
 (0)