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
2 changes: 1 addition & 1 deletion graphify/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class FileType(str, Enum):
_MTIME_SUBSECOND_S = 0.05

CODE_EXTENSIONS = {'.py', '.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.ejs', '.ets', '.go', '.rs', '.java', '.groovy', '.gradle', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.cu', '.cuh', '.metal', '.rb', '.rake', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.luau', '.toc', '.zig', '.ps1', '.psm1', '.psd1', '.ex', '.exs', '.m', '.mm', '.ml', '.mli', '.jl', '.vue', '.svelte', '.astro', '.dart', '.v', '.sv', '.svh', '.sql', '.r', '.f', '.F', '.f90', '.F90', '.f95', '.F95', '.f03', '.F03', '.f08', '.F08', '.pas', '.pp', '.dpr', '.dpk', '.lpr', '.inc', '.dfm', '.lfm', '.lpk', '.sh', '.bash', '.json', '.tf', '.tfvars', '.hcl', '.dm', '.dme', '.dmi', '.dmm', '.dmf', '.sln', '.slnx', '.csproj', '.fsproj', '.vbproj', '.xaml', '.razor', '.cshtml', '.cls', '.trigger', '.lisp', '.cl', '.lsp', '.asd'}
DOC_EXTENSIONS = {'.md', '.mdx', '.qmd', '.skill', '.txt', '.rst', '.html', '.yaml', '.yml'}
DOC_EXTENSIONS = {'.md', '.mdx', '.qmd', '.skill', '.txt', '.rst', '.html', '.yaml', '.yml', '.typ'}
PAPER_EXTENSIONS = {'.pdf'}
IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'}
OFFICE_EXTENSIONS = {'.docx', '.xlsx'}
Expand Down
5 changes: 4 additions & 1 deletion graphify/file_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
# `_file_to_text` is a straight ``read_text`` (so a char range matches the bytes
# the model is shown). Deliberately excludes code (.py, .ts, ...) and binary
# docs (.pdf) — those are never sliced.
_SPLITTABLE_TEXT_SUFFIXES = frozenset({".md", ".mdx", ".markdown", ".txt", ".rst"})
# .typ (Typst) qualifies on both counts (#2826): it is prose source read straight
# through `read_text`, and although its headings are `=`/`==` rather than `#`,
# the blank-line and newline boundaries below still cut it at paragraph edges.
_SPLITTABLE_TEXT_SUFFIXES = frozenset({".md", ".mdx", ".markdown", ".txt", ".rst", ".typ"})

# Boundary preferences, strongest first. A Markdown heading (``\n#``) keeps a
# section with its title; a blank line keeps a paragraph intact; a bare newline
Expand Down
26 changes: 26 additions & 0 deletions tests/test_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,32 @@ def test_classify_skill():
# #1901: .skill agent files (Markdown with YAML frontmatter) were dropped as unclassified.
assert classify_file(Path("10_Orchestrator.skill")) == FileType.DOCUMENT

def test_classify_typst():
# #2826: Typst is a prose authoring format, but .typ was in no extension set,
# so a repo whose report/spec chapters are written in Typst lost all of them.
assert classify_file(Path("main.typ")) == FileType.DOCUMENT

def test_typst_is_watched():
# watch derives _WATCHED_EXTENSIONS from DOC_EXTENSIONS, so a new document
# type must also trigger an incremental rebuild when it is edited (#2826).
from graphify.watch import _WATCHED_EXTENSIONS
assert ".typ" in _WATCHED_EXTENSIONS

def test_detect_surfaces_typst_chapters_as_documents(tmp_path):
"""The reported symptom (#2826): a Typst-authored report reaches the corpus.

Classification alone is not the promise — the file has to come back out of
``detect()`` in the document set, which is what was empty for the reporter.
"""
(tmp_path / "main.typ").write_text(
"= Test Report\n\nProse documentation authored in Typst.\n", encoding="utf-8")
(tmp_path / "chapter.typ").write_text(
"== Measurements\n\nMore prose.\n", encoding="utf-8")

docs = detect(tmp_path)["files"]["document"]

assert sorted(Path(d).name for d in docs) == ["chapter.typ", "main.typ"]

def test_classify_pdf():
assert classify_file(Path("paper.pdf")) == FileType.PAPER

Expand Down
18 changes: 18 additions & 0 deletions tests/test_file_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,24 @@ def test_expand_does_not_slice_code_even_when_oversized(tmp_path):
assert units == [f] # stays whole — code needs whole-symbol context


def test_expand_oversized_typst_is_sliced_with_full_coverage(tmp_path):
"""#2826: Typst chapters are prose, so an oversized one must slice, not truncate.

Typst marks headings with `=` rather than `#`, so the heading separator never
fires — the blank-line boundary carries it, which is the same fallback a .txt
document relies on.
"""
text = ("= Section\n\n" + "word " * 200 + "\n\n") * 30
f = _write(tmp_path / "report.typ", text)
assert is_splittable_text(f)
units = expand_oversized_files([f], max_chars=2000)
slices = [u for u in units if isinstance(u, FileSlice)]
assert len(slices) >= 2
assert "".join(read_slice_text(s) for s in slices) == text
assert all((s.end - s.start) <= 2000 for s in slices)
assert all(s.path == f for s in slices)


def test_expand_unreadable_file_passes_through(tmp_path):
missing = tmp_path / "nope.md"
units = expand_oversized_files([missing], max_chars=10)
Expand Down