Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2f7a8a6
Teach Roadmap to inventory existing formalization
Deicyde Oct 2, 2026
c39638e
Replace unbounded Mermaid DAGs with a scalable explorer
Deicyde Oct 4, 2026
0605b02
Prune navigation for large blueprint sites
Deicyde Oct 4, 2026
a432e3e
Reconcile explorer tests with upstream link caching
Deicyde Oct 4, 2026
5221bc3
Teach Roadmap to inventory existing formalization
Deicyde Oct 2, 2026
a8c0e57
Replace unbounded Mermaid DAGs with a scalable explorer
Deicyde Oct 4, 2026
2ab3616
Prune navigation for large blueprint sites
Deicyde Oct 4, 2026
5186179
Reconcile explorer tests with upstream link caching
Deicyde Oct 4, 2026
87e7604
Bound generated search indexes to page titles
Deicyde Oct 5, 2026
ed2234f
Reconcile explorer with shared graph indexes
Deicyde Oct 5, 2026
2a8f318
Merge current main and resolve roadmap-scale review blockers
Deicyde Oct 5, 2026
78212f1
Merge trusted source snapshots into roadmap scale work
Deicyde Oct 5, 2026
9e8e5a8
Require real compiled names for formalized catalogs
Deicyde Oct 5, 2026
e2f7eae
Merge commit 'refs/codex-review/pr90-live-ed2234f' into codex/pr90-fi…
Deicyde Oct 5, 2026
95311cb
Merge current main into roadmap scale work
Deicyde Oct 5, 2026
3374523
Teach Roadmap to inventory existing formalization
Deicyde Oct 2, 2026
6b24515
Replace unbounded Mermaid DAGs with a scalable explorer
Deicyde Oct 4, 2026
726615a
Prune navigation for large blueprint sites
Deicyde Oct 4, 2026
5a91d13
Reconcile explorer tests with upstream link caching
Deicyde Oct 4, 2026
bd818ad
Bound generated search indexes to page titles
Deicyde Oct 5, 2026
78ee256
Reconcile explorer with shared graph indexes
Deicyde Oct 5, 2026
346fd65
Separate source inventories from mathematical exposition
Deicyde Oct 5, 2026
de7f42e
Reconcile concurrent roadmap scale restack
Deicyde Oct 5, 2026
e66a8c6
Merge commit 'refs/codex-review/pr90-live-346fd65' into codex/pr90-fi…
Deicyde Oct 5, 2026
c01f4ee
Reconcile inventory semantics with reviewed safeguards
Deicyde Oct 5, 2026
ea8537f
Remove duplicate graph link helper
Deicyde Oct 5, 2026
7332aad
Preserve catalog pickle compatibility on Python 3.10
Deicyde Oct 5, 2026
0af7b12
Unify dependency maps in a scalable explorer
Deicyde Oct 5, 2026
8236028
Fix WebKit scroll assertion
Deicyde Oct 5, 2026
bc2250a
Harden explorer accessibility and deep-graph fitting
Deicyde Oct 5, 2026
30da316
Describe the unified dependency explorer
Deicyde Oct 5, 2026
f6507d5
Keep explorer workflow guidance aligned
Deicyde Oct 5, 2026
db6208c
Scroll mobile pagination before hit testing
Deicyde Oct 5, 2026
69bdb1f
Make dependency explorer full-page and hierarchical
Deicyde Oct 5, 2026
09ee4ac
Merge remote-tracking branch 'origin/main' into fix/roadmap-existing-…
Deicyde Oct 5, 2026
228b6ac
Preserve explorer catalog links through MkDocs
Deicyde Oct 5, 2026
f7e0168
Turn explorer into a mathematical knowledge atlas
Deicyde Oct 5, 2026
afd7db8
Merge remote-tracking branch 'origin/main' into fix/roadmap-existing-…
Deicyde Oct 5, 2026
368a37a
Load authored atlas taxonomies outside file layout
Deicyde Oct 6, 2026
9f86e2a
Fit the complete knowledge atlas at readable scale
Deicyde Oct 6, 2026
2ee8c66
Merge remote-tracking branch 'origin/main' into fix/roadmap-existing-…
Deicyde Oct 6, 2026
216ad27
Merge remote-tracking branch 'origin/main' into fix/roadmap-existing-…
Deicyde Oct 6, 2026
dd85097
Keep conditional status visible in bounded vault maps
Deicyde Oct 6, 2026
14053ac
Rewrite catalog links with bracketed math labels
Deicyde Oct 6, 2026
e27ce0f
Merge remote-tracking branch 'origin/main' into fix/roadmap-existing-…
Deicyde Oct 6, 2026
23b3523
Resolve catalog Lean targets before audit policy
Deicyde Oct 6, 2026
53f4633
Keep atlas guidance in an on-demand reference
Deicyde Oct 6, 2026
f3a4e0c
Version inventory runtime and coverage schemas
Deicyde Oct 6, 2026
2e2633e
Polish runtime v4 compatibility coverage
Deicyde Oct 6, 2026
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
20 changes: 19 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ jobs:
- run: uv run pytest -q
- run: make check-example

browser:
name: WebKit dependency explorer regressions
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.1"
python-version: "3.13"
enable-cache: true
- name: Install locked browser test environment
run: uv sync --frozen --extra dev --extra browser
- name: Install WebKit and Linux system dependencies
run: uv run --frozen --extra browser playwright install --with-deps webkit
- name: Run dependency explorer browser regressions
run: uv run --frozen --extra dev --extra browser pytest -q tests/test_dag_viewer_browser.py

real-lean:
name: real Lean (fixture toolchain)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -83,7 +101,7 @@ jobs:
AUTOFORM_REQUIRE_REAL_LEAN_TESTS: "1"

windows-hardening:
name: Windows inspection, subprocess, and publication hardening
name: Windows capability, inspection, subprocess, and publication hardening
runs-on: windows-latest
timeout-minutes: 15
steps:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ Autoform keeps the roadmap and dependency graph as Markdown under
format and command contracts, or browse the
[Cabannes thesis example](skills/setup/assets/cabannes-thesis-project/README.md).

The browser publication presents large roadmaps as a full-page mathematics
atlas: authored areas and bounded hierarchy preserve readable context, while
dependency edges remain distinct from containment and repository provenance.

## Development

```bash
Expand Down
172 changes: 136 additions & 36 deletions autoform_cli/README.md

Large diffs are not rendered by default.

29 changes: 27 additions & 2 deletions autoform_cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,17 +380,41 @@ def _check(args: argparse.Namespace) -> int:

linker = None
if args.lean_root is not None:
lean_names = tuple(
dict.fromkeys(
name
for node in graph.nodes.values()
for name in declaration_names(node.lean or "")
)
)
try:
linker = build_linker(args.lean_root)
linker = build_linker(args.lean_root, names=lean_names)
except OSError as error:
print(f"error: {index_failure_message(error)}")
return 1

statuses = status.derive(graph)
summary = " · ".join(f"{count} {state.label}" for state, count in status.summarize(statuses))
containers = frozenset(
node.parent for node in graph.nodes.values() if node.parent is not None
)
target_statuses = {
node_id: statuses[node_id]
for node_id, node in graph.nodes.items()
if node_id not in containers and node.formalizable
}
inventories = sum(
node_id not in containers and node.catalog == "module"
for node_id, node in graph.nodes.items()
)
summary = " · ".join(
f"{count} {state.label}" for state, count in status.summarize(target_statuses)
)
print(f"OK: {len(graph.nodes)} articles, {graph.edge_count} dependencies")
if summary:
print(f" {summary}")
if inventories:
label = "module inventory" if inventories == 1 else "module inventories"
print(f" {inventories} {label}")

if linker is None:
return 0
Expand Down Expand Up @@ -423,6 +447,7 @@ def _audit(args: argparse.Namespace) -> int:
" coverage: "
f"{counts['MAPPED']} mapped · "
f"{counts['DECOMPOSED']} decomposed · "
f"{counts['INVENTORIED']} inventoried · "
f"{counts['DEFERRED']} deferred · "
f"{counts['OUT']} out"
)
Expand Down
94 changes: 81 additions & 13 deletions autoform_cli/_tree_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ def _materialize_regular_files(
directories: tuple[tuple[str, tuple[str, ...]], ...],
files: tuple[tuple[str, tuple[str, ...], bytes], ...],
placeholders: tuple[tuple[str, tuple[str, ...]], ...],
*,
verify_bytes: bool,
) -> None:
"""Create a snapshot using only retained directory descriptors."""

Expand Down Expand Up @@ -255,16 +257,27 @@ def _materialize_regular_files(
)
)

_verify_materialized_snapshot(
descriptors[()],
root_identity,
directories,
files,
placeholders,
directory_identities,
directory_permissions,
created_files,
)
_tree_snapshot_checkpoint("before-materialization-final-verification", "")
if verify_bytes:
_verify_materialized_snapshot(
descriptors[()],
root_identity,
directories,
files,
placeholders,
directory_identities,
directory_permissions,
created_files,
)
else:
_verify_materialized_metadata(
descriptors,
directories,
files,
placeholders,
directory_identities,
created_files,
)
parent.verify()
_verify_named_directory(parent.descriptor, name, root_identity)
succeeded = True
Expand Down Expand Up @@ -453,6 +466,55 @@ def _verify_materialized_snapshot(
raise TreeSnapshotError("materialized file metadata changed before commit")


def _verify_materialized_metadata(
descriptors: dict[tuple[str, ...], int],
directories: tuple[tuple[str, tuple[str, ...]], ...],
files: tuple[tuple[str, tuple[str, ...], bytes], ...],
placeholders: tuple[tuple[str, tuple[str, ...]], ...],
directory_identities: dict[tuple[str, ...], tuple[int, int, int]],
created_files: list[tuple[tuple[str, ...], tuple[int, ...]]],
) -> None:
"""Verify exact names, kinds and metadata without rereading captured bytes."""

expected_names: dict[tuple[str, ...], set[str]] = {
parts: set() for _relative, parts in directories
}
for _relative, parts in directories:
if parts:
expected_names.setdefault(parts[:-1], set()).add(parts[-1])
for _relative, parts, _data in files:
expected_names.setdefault(parts[:-1], set()).add(parts[-1])
for _relative, parts in placeholders:
expected_names.setdefault(parts[:-1], set()).add(parts[-1])

for parts, names in expected_names.items():
descriptor = descriptors.get(parts)
expected_identity = directory_identities.get(parts)
if descriptor is None or expected_identity is None:
raise TreeSnapshotError("materialized tree has a missing directory")
if _directory_entry_identity(os.fstat(descriptor)) != expected_identity:
raise TreeSnapshotError("materialized directory metadata changed before commit")
if tuple(sorted(os.listdir(descriptor))) != tuple(sorted(names)):
raise TreeSnapshotError("materialized tree changed before commit")
if parts:
parent_descriptor = descriptors.get(parts[:-1])
if parent_descriptor is None:
raise TreeSnapshotError("materialized tree has a missing directory")
_verify_named_directory(parent_descriptor, parts[-1], expected_identity)

for parts, expected_signature in created_files:
parent_descriptor = descriptors.get(parts[:-1])
if parent_descriptor is None:
raise TreeSnapshotError("materialized file has a missing parent")
observed = os.stat(
parts[-1],
dir_fd=parent_descriptor,
follow_symlinks=False,
)
if _stat_signature(observed) != expected_signature:
raise TreeSnapshotError("materialized file metadata changed before commit")


def _cleanup_materialization(
parent: RetainedDirectory | None,
root_name: str,
Expand Down Expand Up @@ -630,16 +692,21 @@ def generation_revision(self) -> str:
_update_digest(digest, b"identity", relative, encoded)
return digest.hexdigest()

def materialize(self, destination: Path) -> None:
def materialize(self, destination: Path, *, verify_bytes: bool = True) -> None:
"""Write captured regular files below a fresh private directory."""

issues = self.unsupported_entries()
if issues:
relative, reason = issues[0]
raise TreeSnapshotError(f"{relative}: {reason}")
self.materialize_regular_files(destination)
self.materialize_regular_files(destination, verify_bytes=verify_bytes)

def materialize_regular_files(self, destination: Path) -> None:
def materialize_regular_files(
self,
destination: Path,
*,
verify_bytes: bool = True,
) -> None:
"""Materialize safe content after a caller has recorded invalid entries."""

directory_parts = tuple(
Expand All @@ -662,6 +729,7 @@ def materialize_regular_files(self, destination: Path) -> None:
for (relative, data), (_validated, parts) in zip(self.files, file_parts)
),
placeholder_parts,
verify_bytes=verify_bytes,
)

def unsupported_entries(self) -> tuple[tuple[str, str], ...]:
Expand Down
Loading
Loading