Skip to content

Implement usethis tool zensical#1756

Open
Copilot wants to merge 14 commits intomainfrom
copilot/implement-usethis-tool-zensical
Open

Implement usethis tool zensical#1756
Copilot wants to merge 14 commits intomainfrom
copilot/implement-usethis-tool-zensical

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 1, 2026

Implements use_zensical() in src/usethis/_core/tool.py following the same manual setup pattern as use_mkdocs(): create docs directory, add doc dependencies, and add configs. Supports both zensical.toml and mkdocs.yml via the config spec — when an existing mkdocs.yml has content, it is respected and no empty zensical.toml is created.

Changes Made

  • Added use_zensical() function with add/remove/how modes, matching the use_mkdocs() pattern
  • Added ZensicalTool dispatch in use_tool() in src/_test.py
  • Added ZensicalTool and use_zensical imports in src/_test.py
  • Added ZensicalToolSpec to ALL_TOOL_SPECS in src/usethis/_tool/impl/spec/all_.py
  • Removed dead OTHER_TOOLS list and unused imports from src/usethis/_tool/impl/base/pyproject_toml.py (fixes vulture hook failure)
  • Removed stale OTHER_TOOLS documentation section from CONTRIBUTING.md
  • Updated tests with hardcoded tool lists and CLI output expectations to include Zensical (test_list.py, test_doc.py, test_init.py)
  • Fixed dogfooding bug where zensical serve failed with "Missing required setting: site_name" when an existing mkdocs.yml was present — removed unconditional zensical.toml .touch() so that add_configs() handles config file creation via first_content resolution logic, respecting existing mkdocs.yml content

Testing

  • All architecture contracts pass (import-linter: 12/12)
  • All affected tests pass (usage table, init output, doc dependencies, spec sync, mkdocs.yml fallback)
  • Static checks pass (prek -a: all hooks passed, basedpyright: 0 errors, vulture: 0 errors)
  • Lesson filed as agent: trace all registry consumers when adding a new tool #1850: trace all registry consumers when adding a new tool

Copilot AI and others added 2 commits April 1, 2026 23:23
Add ZensicalTool with spec, base, config file manager, core function,
CLI command, and tests. Zensical is a modern static site generator for
project documentation that supports both zensical.toml and mkdocs.yml.

Update docs, README, and AGENTS.md accordingly.

Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/5fdaf79d-ef1c-4ee2-a1f2-fbe78f33fd69

Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/5fdaf79d-ef1c-4ee2-a1f2-fbe78f33fd69

Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement usethis tool zensical Implement usethis tool zensical Apr 1, 2026
Copilot AI requested a review from nathanjmcdougall April 1, 2026 23:28
@nathanjmcdougall nathanjmcdougall marked this pull request as ready for review April 2, 2026 00:05
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 2, 2026

Merging this PR will degrade performance by 5.58%

❌ 2 regressed benchmarks
✅ 1 untouched benchmark

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation test_help_flag 52.9 ms 56 ms -5.58%
Simulation test_several_tools_add_and_remove_no_backend 467.7 ms 484.2 ms -3.4%

Comparing copilot/implement-usethis-tool-zensical (099af08) with main (5f17d9a)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (466b9b7) during the generation of this report, so 5f17d9a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

When no config file exists, attempt to run `zensical new .` via the
backend subprocess to scaffold the documentation project. Falls back
to manual creation when the subprocess fails or backend is none.

Move imports to top-level to satisfy ruff PLC0415.

Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/491d9194-fd87-4743-87de-bb6a11c9a30a

Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
Comment thread src/usethis/_core/tool.py Outdated
Per review feedback, simplified use_zensical() to use the same
manual approach as use_mkdocs(): touch config file, add_docs_dir,
add_doc_deps, add_configs. Removed _try_zensical_new() helper and
unused imports (call_backend_subprocess, BackendSubprocessFailedError,
ZensicalTOMLManager, get_project_name).

Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/082a1b83-1f47-492c-8516-b1704d325727

Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
@nathanjmcdougall
Copy link
Copy Markdown
Collaborator

@copilot please register Zensical in the use_tool function (now in src/_test.py)

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator

nathanjmcdougall commented Apr 7, 2026

@copilot Some of the tests are failing. Please check the logs, fix the issue, and make a lesson.

Copy link
Copy Markdown
Collaborator

@nathanjmcdougall nathanjmcdougall left a comment

Choose a reason for hiding this comment

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

…tests for zensical

Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/97dde0ae-f496-4478-a145-0ee24759049b

Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 7, 2026

@copilot Some of the tests are failing. Please check the logs, fix the issue, and make a lesson.

Fixed in 2d8bca6. The failures were caused by three missing updates when adding ZensicalTool:

  1. ZensicalToolSpec was not added to ALL_TOOL_SPECS — caused test_in_sync_with_all_tools to fail
  2. Usage table tests in test_list.py didn't include the Zensical row — 3 tests failed
  3. CLI output assertions in test_init.py and test_doc.py still referenced mkdocs instead of zensical — 4 tests failed

Lesson filed as #1850: when adding a new tool, grep for the most recently added sibling entry to find all registration points and hardcoded test expectations that need updating.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator

@copilot dogfooding failed:

 uvx --from . usethis tool zensical                                                            
      Built usethis @ file:///C:/Users/namc/Repositories/usethis-python
Installed 29 packages in 1.09s
✔ Adding dependency 'zensical' to the 'doc' group in 'pyproject.toml'.                                                                                
☐ Run 'uv run zensical build' to build the documentation.
☐ Run 'uv run zensical serve' to serve the documentation locally.
(usethis) PS C:\Users\namc\Repositories\usethis-python> uv run zensical serve
[error] Failed to load configuration: ConfigurationError: Missing required setting: site_name

…spec

When an existing mkdocs.yml has content, the first_content resolution
picks it over the empty zensical.toml, so config is never written to
zensical.toml. This causes `zensical serve` to fail with "Missing
required setting: site_name".

Fix: Zensical only manages zensical.toml. Each tool manages its own
config files independently.

Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/a9aad48e-e19c-485f-8ad9-7894a746e1b1

Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 7, 2026

@copilot dogfooding failed:

...

Fixed in 04d93c7. The root cause: ZensicalToolSpec.config_spec() included MkDocsYMLManager, so first_content resolution picked the existing mkdocs.yml as the active file manager. Since config already existed there, add_configs() was a no-op and zensical.toml stayed empty.

Fix: removed MkDocsYMLManager from the config spec. Zensical now only manages zensical.toml. Lesson filed as #1867.

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator

@copilot that's a mistake - zensical definitely can be configured via mkdocs.yml as a compatibility concern. You should revert and fix the issue that was creating an empty zensical.toml file. When dogfooding, I'd expect that the existing mkdocs.yml should be respected.

Reverted the removal of MkDocsYMLManager from ZensicalToolSpec — zensical
can be configured via mkdocs.yml as a compatibility concern.

Fixed the actual dogfooding issue: use_zensical() was unconditionally
touching zensical.toml before add_configs(), creating an empty file even
when mkdocs.yml already had content. Removed the .touch() call; add_configs()
already creates the config file when needed via the resolution logic.

Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/ad1b4176-93a9-4856-9455-56aaeef87761

Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 7, 2026

@copilot that's a mistake - zensical definitely can be configured via mkdocs.yml as a compatibility concern. You should revert and fix the issue that was creating an empty zensical.toml file. When dog...

Fixed in 75aa9f2. Reverted the MkDocsYMLManager removal — zensical's config spec again includes both ZensicalTOMLManager and MkDocsYMLManager.

The actual fix: removed the unconditional (usethis_config.cpd() / "zensical.toml").touch() from use_zensical(). The add_configs() method already creates the config file via the first_content resolution logic (lines 329-335 in base.py), so when mkdocs.yml already has content, it's respected and no empty zensical.toml is created.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds first-class support for the Zensical documentation tool across usethis, including a new use_zensical() core API, CLI command wiring, tool/spec registration, and associated docs/tests updates. It also updates the default documentation toolset to prefer Zensical.

Changes:

  • Implemented use_zensical() and the ZensicalTool/ZensicalToolSpec pair, plus CLI command usethis tool zensical.
  • Registered Zensical in tool/spec registries and updated import-linter layering accordingly.
  • Updated hardcoded CLI outputs, usage tables, and documentation to reflect Zensical as the recommended doc framework.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/usethis/_ui/interface/test_tool.py Adds CLI integration tests for usethis tool zensical add/how/remove.
tests/usethis/_ui/interface/test_init.py Updates init output expectations from MkDocs to Zensical.
tests/usethis/_ui/interface/test_doc.py Updates doc toolset dependency expectation to zensical.
tests/usethis/_tool/impl/base/test_zensical.py Adds unit tests for Zensical tool “how to use” output across backends.
tests/usethis/_core/test_list.py Updates usage table expectations to include Zensical.
tests/usethis/_core/test_core_tool.py Adds core tests for use_zensical() including mkdocs.yml fallback behavior.
src/usethis/_ui/interface/tool.py Adds the zensical Typer command and wires it into _run_tool.
src/usethis/_toolset/doc.py Switches recommended doc framework from MkDocs to Zensical.
src/usethis/_tool/impl/spec/zensical.py Introduces Zensical tool spec with config resolution across zensical.toml and mkdocs.yml.
src/usethis/tool/impl/spec/all.py Registers ZensicalToolSpec in ALL_TOOL_SPECS.
src/usethis/_tool/impl/base/zensical.py Introduces Zensical tool implementation and backend-aware “how” messages.
src/usethis/tool/all.py Registers ZensicalTool in ALL_TOOLS.
src/usethis/_core/tool.py Adds use_zensical() implementation.
src/usethis/_config_file.py Adds ZensicalTOMLManager and includes it in files_manager().
src/_test.py Adds ZensicalTool dispatch in use_tool() and imports use_zensical.
ruff.toml Updates the banned-api guidance message for typer.testing.CliRunner.
README.md Updates docs tool references to Zensical and adds CLI reference entry.
docs/module-tree.txt Updates module tree to include new zensical implementation/spec modules.
docs/functions.txt Adds use_zensical() and zensical() to the exported function list.
docs/cli/overview.md Updates CLI overview to reference Zensical.
CONTRIBUTING.md Removes stale OTHER_TOOLS section and updates test-writing guidance text.
AGENTS.md Updates module tree and function reference with Zensical entries.
.importlinter Adds zensical to the architecture layer contract lists.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CONTRIBUTING.md Outdated
Comment thread src/usethis/_core/tool.py
Comment on lines +594 to +595
# N.B. no need to remove configs because they all lie in managed files.

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

use_zensical(remove=True) assumes configs only live in managed files, but ZensicalToolSpec.config_spec() can select mkdocs.yml as the active config file. In that case, removing Zensical currently leaves behind managed config entries in mkdocs.yml and can cause the tool to still be detected as used. Consider adjusting the removal path (e.g., call tool.remove_configs() with care) and/or changing which config items are considered managed so removal and usage detection behave consistently.

Suggested change
# N.B. no need to remove configs because they all lie in managed files.
# Remove configs explicitly because they may live in active project config
# files such as mkdocs.yml, not only in managed files.
tool.remove_configs()

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +66
def config_spec(self) -> ConfigSpec:
"""Get the configuration specification for this tool."""
return ConfigSpec.from_flat(
file_managers=[
ZensicalTOMLManager(),
MkDocsYMLManager(),
],
resolution="first_content",
config_items=[
ConfigItem(
description="Site Name",
root={
Path("zensical.toml"): ConfigEntry(
keys=["project", "site_name"],
get_value=lambda: get_project_name(),
),
Path("mkdocs.yml"): ConfigEntry(
keys=["site_name"],
get_value=lambda: get_project_name(),
),
},
),
ConfigItem(
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

ZensicalToolSpec.config_spec() marks site_name/nav config items as managed (default), including the mkdocs.yml entries. This has two concrete side effects: (1) is_likely_used() can report Zensical as used solely due to pre-existing mkdocs.yml keys, and (2) if remove_configs() is ever invoked it would delete mkdocs.yml keys even when they weren't added by usethis. To avoid that, consider splitting these into separate ConfigItems (zensical.toml managed=True, mkdocs.yml managed=False) or otherwise ensuring mkdocs.yml entries are not treated as managed-by-Zensical.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement usethis tool zensical

3 participants