Skip to content
Open
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
16 changes: 8 additions & 8 deletions src/socrates120x/audit/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _load_ignore_list(project: Path, section: str) -> set[str]:
if not config_path.is_file():
return set()
try:
data = json.loads(config_path.read_text())
data = json.loads(config_path.read_text(encoding="utf-8"))
except (OSError, ValueError):
return set()
if not isinstance(data, dict):
Expand Down Expand Up @@ -198,7 +198,7 @@ def run(self, project: Path) -> list[Finding]:
path = project / adapter
if not path.is_file():
continue
text = path.read_text(errors="replace")
text = path.read_text(errors="replace", encoding="utf-8")
if "AGENTS.md" not in text:
findings.append(Finding(
check=self.name,
Expand Down Expand Up @@ -229,7 +229,7 @@ def run(self, project: Path) -> list[Finding]:
acc = sprint / "acceptance.md"
if not acc.is_file():
continue
for line_no, line in enumerate(acc.read_text(errors="replace").splitlines(), 1):
for line_no, line in enumerate(acc.read_text(errors="replace", encoding="utf-8").splitlines(), 1):
lower = line.lower()
for weasel in WEASEL_WORDS:
if weasel.lower() in lower:
Expand Down Expand Up @@ -258,7 +258,7 @@ def run(self, project: Path) -> list[Finding]:
state = project / "planning" / "STATE.md"
if not state.is_file():
return [] # Already flagged by RequiredFilesCheck.
text = state.read_text(errors="replace")
text = state.read_text(errors="replace", encoding="utf-8")
m = self._DATE.search(text)
if not m:
return [Finding(
Expand Down Expand Up @@ -294,7 +294,7 @@ def run(self, project: Path) -> list[Finding]:
risks = project / "planning" / "RISKS.md"
if not risks.is_file():
return []
lower = risks.read_text(errors="replace").lower()
lower = risks.read_text(errors="replace", encoding="utf-8").lower()
if not any(phrase.lower() in lower for phrase in ALWAYS_ON_RISK_PHRASES):
return [Finding(
check=self.name,
Expand All @@ -318,7 +318,7 @@ def run(self, project: Path) -> list[Finding]:
domain = project / "planning" / "DOMAIN.md"
if not domain.is_file():
return []
terms = self._extract_terms(domain.read_text(errors="replace"))
terms = self._extract_terms(domain.read_text(errors="replace", encoding="utf-8"))
if not terms:
return []

Expand Down Expand Up @@ -366,15 +366,15 @@ def _concatenate_other_files(self, project: Path) -> str:
for rel in candidates:
path = project / rel
if path.is_file():
parts.append(path.read_text(errors="replace"))
parts.append(path.read_text(errors="replace", encoding="utf-8"))
# Also include sprint files.
sprints = project / "planning" / "sprints"
if sprints.is_dir():
for sprint in sprints.iterdir():
if not sprint.is_dir():
continue
for f in sprint.glob("*.md"):
parts.append(f.read_text(errors="replace"))
parts.append(f.read_text(errors="replace", encoding="utf-8"))
return "\n".join(parts)


Expand Down
8 changes: 4 additions & 4 deletions src/socrates120x/audit/companyos_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def run(self, project: Path) -> list[Finding]:
for pattern in sorted(patterns.glob("*.md")):
if pattern.name == "README.md":
continue
body = pattern.read_text(errors="replace")
body = pattern.read_text(errors="replace", encoding="utf-8")
m = self._SOURCE_LINE.search(body)
if not m:
continue
Expand Down Expand Up @@ -137,7 +137,7 @@ def run(self, project: Path) -> list[Finding]:
if not proposals.is_file() or not builds.is_dir():
return []
build_names = {p.name for p in builds.iterdir() if p.is_dir()}
body = proposals.read_text(errors="replace")
body = proposals.read_text(errors="replace", encoding="utf-8")
findings: list[Finding] = []
seen: set[str] = set()
for m in self._SLUG.finditer(body):
Expand Down Expand Up @@ -167,7 +167,7 @@ def _build_client_reference(build: Path) -> str | None:
if answers.is_file():
import json
try:
data = json.loads(answers.read_text())
data = json.loads(answers.read_text(encoding="utf-8"))
if isinstance(data, dict):
v = data.get("client")
if isinstance(v, str) and v.strip():
Expand All @@ -176,7 +176,7 @@ def _build_client_reference(build: Path) -> str | None:
pass
agents = build / "AGENTS.md"
if agents.is_file():
text = agents.read_text(errors="replace")
text = agents.read_text(errors="replace", encoding="utf-8")
m = re.search(r"Client:\s*\*\*([^*\n]+)\*\*", text)
if m:
return m.group(1).strip()
Expand Down
2 changes: 1 addition & 1 deletion src/socrates120x/companyos.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def scaffold_companyos(target: Path, *, overwrite: bool = False) -> list[Path]:
for rel, body in files.items():
path = target / rel
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(body)
path.write_text(body, encoding="utf-8")
written.append(path)
return written

Expand Down
4 changes: 2 additions & 2 deletions src/socrates120x/decide.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ def record_decision(project: Path, text: str) -> int:
today = _dt.date.today().isoformat()
bullet = f"- **{cleaned} ({today})**"

body = decisions_path.read_text()
body = decisions_path.read_text(encoding="utf-8")
new_body = _insert_decision(body, bullet)
decisions_path.write_text(new_body)
decisions_path.write_text(new_body, encoding="utf-8")
print(f"Appended to {decisions_path}:")
print(f" {bullet}")
return 0
Expand Down
2 changes: 1 addition & 1 deletion src/socrates120x/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def run_extract(

slug = _sanitize_slug(iv.answers.get("pattern_slug", "untitled"))
target = target_dir / f"CANDIDATE-{slug}.md"
target.write_text(render_pattern(iv.answers, project=project))
target.write_text(render_pattern(iv.answers, project=project), encoding="utf-8")
print(f"\nPattern candidate written to: {target}")
print()
print("Next steps:")
Expand Down
4 changes: 2 additions & 2 deletions src/socrates120x/interview.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,10 @@ class Interview:

def load(self) -> None:
if self.answers_path.exists() and self.resume:
self.answers = json.loads(self.answers_path.read_text())
self.answers = json.loads(self.answers_path.read_text(encoding="utf-8"))

def save(self) -> None:
self.answers_path.write_text(json.dumps(self.answers, indent=2) + "\n")
self.answers_path.write_text(json.dumps(self.answers, indent=2) + "\n", encoding="utf-8")

def run(
self,
Expand Down
4 changes: 2 additions & 2 deletions src/socrates120x/journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def create_or_open_entry(project: Path, *, show: bool = False, list_all: bool =
entry = journal_dir / f"{today}.md"
is_new = not entry.exists()
if is_new:
entry.write_text(_template(today))
entry.write_text(_template(today), encoding="utf-8")
print(f"Created {entry}")

cmd = editor_command()
Expand Down Expand Up @@ -93,5 +93,5 @@ def _show_latest(journal_dir: Path) -> int:
if not entries:
print("(no journal entries yet)")
return 0
print(entries[0].read_text())
print(entries[0].read_text(encoding="utf-8"))
return 0
16 changes: 8 additions & 8 deletions src/socrates120x/onboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def _load_answers_json(project: Path) -> dict[str, Any] | None:
if not path.is_file():
return None
try:
data = json.loads(path.read_text())
data = json.loads(path.read_text(encoding="utf-8"))
if isinstance(data, dict):
return data
except (OSError, ValueError):
Expand Down Expand Up @@ -79,12 +79,12 @@ def _synthesize_from_markdown(project: Path) -> str:
today = _dt.date.today().isoformat()
name = project.name

readme = (project / "README.md").read_text(errors="replace") if (project / "README.md").is_file() else ""
agents = (project / "AGENTS.md").read_text(errors="replace") if (project / "AGENTS.md").is_file() else ""
state = (project / "planning" / "STATE.md").read_text(errors="replace") if (project / "planning" / "STATE.md").is_file() else ""
decisions = (project / "planning" / "DECISIONS.md").read_text(errors="replace") if (project / "planning" / "DECISIONS.md").is_file() else ""
risks = (project / "planning" / "RISKS.md").read_text(errors="replace") if (project / "planning" / "RISKS.md").is_file() else ""
questions = (project / "planning" / "QUESTIONS.md").read_text(errors="replace") if (project / "planning" / "QUESTIONS.md").is_file() else ""
readme = (project / "README.md").read_text(errors="replace", encoding="utf-8") if (project / "README.md").is_file() else ""
agents = (project / "AGENTS.md").read_text(errors="replace", encoding="utf-8") if (project / "AGENTS.md").is_file() else ""
state = (project / "planning" / "STATE.md").read_text(errors="replace", encoding="utf-8") if (project / "planning" / "STATE.md").is_file() else ""
decisions = (project / "planning" / "DECISIONS.md").read_text(errors="replace", encoding="utf-8") if (project / "planning" / "DECISIONS.md").is_file() else ""
risks = (project / "planning" / "RISKS.md").read_text(errors="replace", encoding="utf-8") if (project / "planning" / "RISKS.md").is_file() else ""
questions = (project / "planning" / "QUESTIONS.md").read_text(errors="replace", encoding="utf-8") if (project / "planning" / "QUESTIONS.md").is_file() else ""

tagline = _extract_tagline(readme, agents)
client = _extract_field(agents, "Client") or _extract_field(readme, "Client")
Expand Down Expand Up @@ -177,7 +177,7 @@ def write_welcome(project: Path) -> Path:
"""Write WELCOME.md into the project root and return its path."""
body = synthesize_welcome(project)
target = project / "WELCOME.md"
target.write_text(body)
target.write_text(body, encoding="utf-8")
return target


Expand Down
6 changes: 3 additions & 3 deletions src/socrates120x/pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def write_pack(
format=format,
)
target = project / f".socrates-architect-pack.{FORMAT_EXTENSIONS[format]}"
target.write_text(body)
target.write_text(body, encoding="utf-8")
return target


Expand Down Expand Up @@ -233,7 +233,7 @@ def _kit_sections(kit: Path) -> list[_Section]:
path = kit / name
if not path.is_file():
continue
text = path.read_text(errors="replace").strip()
text = path.read_text(errors="replace", encoding="utf-8").strip()
if not text:
continue
out.append(_Section(
Expand All @@ -253,7 +253,7 @@ def _file_section(path: Path, *, rel_display: str, label: str) -> _Section:
path=rel_display,
kind="missing",
)
text = path.read_text(errors="replace").strip()
text = path.read_text(errors="replace", encoding="utf-8").strip()
if not text:
return _Section(
label=f"{label} (`{rel_display}`)",
Expand Down
8 changes: 4 additions & 4 deletions src/socrates120x/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def review_patterns(companyos_root: Path, *, use_cache: bool = True) -> PatternR
candidates += 1
else:
promoted += 1
body = pattern.read_text(errors="replace")
body = pattern.read_text(errors="replace", encoding="utf-8")
source = _extract_source_project(body)
extracted = _extract_extracted_date(body)

Expand Down Expand Up @@ -215,7 +215,7 @@ def _slug_in_project(slug: str, project_dir: Path) -> bool:
needle = slug.lower()
for f in project_dir.rglob("*.md"):
try:
text = f.read_text(errors="replace").lower()
text = f.read_text(errors="replace", encoding="utf-8").lower()
except OSError:
continue
if needle in text:
Expand Down Expand Up @@ -245,7 +245,7 @@ def _load_usage_cache(patterns_dir: Path) -> dict[str, Any] | None:
if not cache_path.is_file():
return None
try:
data = json.loads(cache_path.read_text())
data = json.loads(cache_path.read_text(encoding="utf-8"))
except (OSError, ValueError):
return None
if not isinstance(data, dict):
Expand All @@ -261,7 +261,7 @@ def _save_usage_cache(patterns_dir: Path, payload: dict[str, Any]) -> None:
if not patterns_dir.is_dir():
return
with contextlib.suppress(OSError):
(patterns_dir / USAGE_CACHE_FILENAME).write_text(json.dumps(payload, indent=2))
(patterns_dir / USAGE_CACHE_FILENAME).write_text(json.dumps(payload, indent=2), encoding="utf-8")


def format_pattern_report(report: PatternReport, *, use_color: bool | None = None) -> str:
Expand Down
2 changes: 1 addition & 1 deletion src/socrates120x/prompting.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def _ask_multiline_editor(output_fn: OutputFn, q: Question) -> str:
try:
output_fn(f" (opening {editor[0]} — save & quit to submit)")
subprocess.run([*editor, str(tmp_path)], check=True)
raw = tmp_path.read_text()
raw = tmp_path.read_text(encoding="utf-8")
finally:
with contextlib.suppress(OSError):
tmp_path.unlink()
Expand Down
2 changes: 1 addition & 1 deletion src/socrates120x/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def render_all(target: Path, answers: dict[str, Any]) -> list[Path]:
for rel, body in files.items():
path = target / rel
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(body)
path.write_text(body, encoding="utf-8")
written.append(path)
return written

Expand Down
6 changes: 3 additions & 3 deletions src/socrates120x/ship.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def _extract_check(project: Path) -> ShipFinding:
if not loc.is_dir():
continue
for f in loc.glob("CANDIDATE-*.md"):
text = f.read_text(errors="replace")
text = f.read_text(errors="replace", encoding="utf-8")
if f"`{project.name}`" in text:
found = True
break
Expand Down Expand Up @@ -180,7 +180,7 @@ def _state_check(project: Path) -> ShipFinding:
answers_path = project / ".socrates-answers.json"
if answers_path.is_file():
try:
data = json.loads(answers_path.read_text())
data = json.loads(answers_path.read_text(encoding="utf-8"))
except (OSError, ValueError):
data = None
if isinstance(data, dict) and data.get("state_next"):
Expand All @@ -189,7 +189,7 @@ def _state_check(project: Path) -> ShipFinding:
pass
# Fall back to recency check via the embedded date.
import re
m = re.search(r"Last updated:\s*(\d{4}-\d{2}-\d{2})", state.read_text(errors="replace"))
m = re.search(r"Last updated:\s*(\d{4}-\d{2}-\d{2})", state.read_text(errors="replace", encoding="utf-8"))
if not m:
return ShipFinding(
name="state",
Expand Down
10 changes: 5 additions & 5 deletions src/socrates120x/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def _extract_tagline(project: Path) -> str:
answers_path = project / ".socrates-answers.json"
if answers_path.is_file():
try:
data = json.loads(answers_path.read_text())
data = json.loads(answers_path.read_text(encoding="utf-8"))
if isinstance(data, dict):
t = data.get("tagline")
if isinstance(t, str):
Expand All @@ -143,7 +143,7 @@ def _extract_tagline(project: Path) -> str:
pass
agents = project / "AGENTS.md"
if agents.is_file():
m = re.search(r"\*\*[^*]+\*\*\s+—\s+(.+)", agents.read_text(errors="replace"))
m = re.search(r"\*\*[^*]+\*\*\s+—\s+(.+)", agents.read_text(errors="replace", encoding="utf-8"))
if m:
return m.group(1).strip()
return ""
Expand Down Expand Up @@ -171,7 +171,7 @@ def _state_age_days(project: Path) -> int | None:
state = project / "planning" / "STATE.md"
if not state.is_file():
return None
m = _DATE.search(state.read_text(errors="replace"))
m = _DATE.search(state.read_text(errors="replace", encoding="utf-8"))
if not m:
return None
try:
Expand Down Expand Up @@ -208,9 +208,9 @@ def _has_extract(project: Path) -> bool:
sibling = parent.parent / "patterns"
if sibling.is_dir():
for f in sibling.glob("CANDIDATE-*.md"):
if f"Source project | `{project.name}`" in f.read_text(errors="replace"):
if f"Source project | `{project.name}`" in f.read_text(errors="replace", encoding="utf-8"):
return True
if f"`{project.name}`" in f.read_text(errors="replace"):
if f"`{project.name}`" in f.read_text(errors="replace", encoding="utf-8"):
return True
# 3) Or has an in-progress extract answers file.
return (project / ".socrates-extract-answers.json").is_file()
Expand Down
6 changes: 3 additions & 3 deletions src/socrates120x/timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def _journal_events(project: Path) -> list[TimelineEvent]:
d = _dt.date.fromisoformat(entry.stem)
except ValueError:
continue
first_line = _first_real_line(entry.read_text(errors="replace"))
first_line = _first_real_line(entry.read_text(errors="replace", encoding="utf-8"))
events.append(TimelineEvent(
date=d,
kind=EventKind.JOURNAL,
Expand Down Expand Up @@ -119,7 +119,7 @@ def _sprint_events(project: Path) -> list[TimelineEvent]:
req = sprint / "requirements.md"
detail = ""
if req.is_file():
detail = _extract_goal(req.read_text(errors="replace"))
detail = _extract_goal(req.read_text(errors="replace", encoding="utf-8"))
events.append(TimelineEvent(
date=mtime,
kind=EventKind.SPRINT,
Expand All @@ -137,7 +137,7 @@ def _decision_events(project: Path) -> list[TimelineEvent]:
if not decisions_file.is_file():
return []
events: list[TimelineEvent] = []
for line in decisions_file.read_text(errors="replace").splitlines():
for line in decisions_file.read_text(errors="replace", encoding="utf-8").splitlines():
stripped = line.lstrip()
if not stripped.startswith("- "):
continue
Expand Down
Loading
Loading