flexdoc 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. flexdoc-0.1.0/.agents/skills/simple-modern-uv/SKILL.md +150 -0
  2. flexdoc-0.1.0/.agents/skills/simple-modern-uv/references/adopt-existing.md +135 -0
  3. flexdoc-0.1.0/.agents/skills/simple-modern-uv/references/customize.md +75 -0
  4. flexdoc-0.1.0/.agents/skills/simple-modern-uv/references/faq.md +89 -0
  5. flexdoc-0.1.0/.agents/skills/tbd/SKILL.md +293 -0
  6. flexdoc-0.1.0/.claude/.gitignore +2 -0
  7. flexdoc-0.1.0/.claude/hooks/tbd-closing-reminder.sh +15 -0
  8. flexdoc-0.1.0/.claude/scripts/ensure-gh-cli.sh +123 -0
  9. flexdoc-0.1.0/.claude/scripts/tbd-session.sh +27 -0
  10. flexdoc-0.1.0/.claude/settings.json +47 -0
  11. flexdoc-0.1.0/.claude/skills/tbd/SKILL.md +293 -0
  12. flexdoc-0.1.0/.codex/ensure-gh-cli.sh +123 -0
  13. flexdoc-0.1.0/.codex/hooks.json +47 -0
  14. flexdoc-0.1.0/.codex/tbd-closing-reminder.sh +15 -0
  15. flexdoc-0.1.0/.codex/tbd-session.sh +27 -0
  16. flexdoc-0.1.0/.copier-answers.yml +11 -0
  17. flexdoc-0.1.0/.github/workflows/ci.yml +132 -0
  18. flexdoc-0.1.0/.github/workflows/publish.yml +48 -0
  19. flexdoc-0.1.0/.gitignore +184 -0
  20. flexdoc-0.1.0/.tbd/.gitattributes +2 -0
  21. flexdoc-0.1.0/.tbd/.gitignore +21 -0
  22. flexdoc-0.1.0/.tbd/config.yml +94 -0
  23. flexdoc-0.1.0/AGENTS.md +385 -0
  24. flexdoc-0.1.0/CHANGELOG.md +66 -0
  25. flexdoc-0.1.0/CLAUDE.md +4 -0
  26. flexdoc-0.1.0/LICENSE +21 -0
  27. flexdoc-0.1.0/Makefile +37 -0
  28. flexdoc-0.1.0/PKG-INFO +101 -0
  29. flexdoc-0.1.0/README.md +69 -0
  30. flexdoc-0.1.0/SUPPLY-CHAIN-SECURITY.md +146 -0
  31. flexdoc-0.1.0/TODO.md +38 -0
  32. flexdoc-0.1.0/devtools/lint.py +73 -0
  33. flexdoc-0.1.0/docs/development.md +123 -0
  34. flexdoc-0.1.0/docs/flexdoc-spec.md +1003 -0
  35. flexdoc-0.1.0/docs/installation.md +31 -0
  36. flexdoc-0.1.0/docs/project/research/research-2026-05-29-document-model.md +1067 -0
  37. flexdoc-0.1.0/docs/project/research/research-2026-05-30-multilayer-parsing.md +658 -0
  38. flexdoc-0.1.0/docs/project/research/research-2026-05-30-span-references.md +169 -0
  39. flexdoc-0.1.0/docs/project/review/senior-engineering-review-flexdoc-standalone-2026-06.md +263 -0
  40. flexdoc-0.1.0/docs/project/specs/active/plan-2026-05-29-unified-document-model.md +1062 -0
  41. flexdoc-0.1.0/docs/project/specs/active/plan-2026-05-31-doc-model-refinements.md +285 -0
  42. flexdoc-0.1.0/docs/project/specs/active/plan-2026-05-31-golden-doc-testing.md +191 -0
  43. flexdoc-0.1.0/docs/project/specs/active/plan-2026-06-11-flexdoc-extraction.md +629 -0
  44. flexdoc-0.1.0/docs/project/specs/active/plan-2026-06-11-structural-metadata.md +408 -0
  45. flexdoc-0.1.0/docs/publishing.md +228 -0
  46. flexdoc-0.1.0/docs/usage.md +136 -0
  47. flexdoc-0.1.0/examples/backfill_timestamps.py +141 -0
  48. flexdoc-0.1.0/examples/doc_structure.py +106 -0
  49. flexdoc-0.1.0/examples/normalized_form.py +111 -0
  50. flexdoc-0.1.0/pyproject.toml +207 -0
  51. flexdoc-0.1.0/skills-lock.json +17 -0
  52. flexdoc-0.1.0/src/flexdoc/__init__.py +54 -0
  53. flexdoc-0.1.0/src/flexdoc/docs/__init__.py +155 -0
  54. flexdoc-0.1.0/src/flexdoc/docs/base_blocks.py +170 -0
  55. flexdoc-0.1.0/src/flexdoc/docs/block_info.py +167 -0
  56. flexdoc-0.1.0/src/flexdoc/docs/block_tree.py +142 -0
  57. flexdoc-0.1.0/src/flexdoc/docs/block_types.py +104 -0
  58. flexdoc-0.1.0/src/flexdoc/docs/collect.py +161 -0
  59. flexdoc-0.1.0/src/flexdoc/docs/debug.py +177 -0
  60. flexdoc-0.1.0/src/flexdoc/docs/doc_graph.py +288 -0
  61. flexdoc-0.1.0/src/flexdoc/docs/doc_graph_schema.json +219 -0
  62. flexdoc-0.1.0/src/flexdoc/docs/flex_doc.py +782 -0
  63. flexdoc-0.1.0/src/flexdoc/docs/frontmatter.py +77 -0
  64. flexdoc-0.1.0/src/flexdoc/docs/interval_index.py +73 -0
  65. flexdoc-0.1.0/src/flexdoc/docs/links.py +109 -0
  66. flexdoc-0.1.0/src/flexdoc/docs/node.py +165 -0
  67. flexdoc-0.1.0/src/flexdoc/docs/node_table.py +428 -0
  68. flexdoc-0.1.0/src/flexdoc/docs/paragraphs.py +395 -0
  69. flexdoc-0.1.0/src/flexdoc/docs/render.py +100 -0
  70. flexdoc-0.1.0/src/flexdoc/docs/search_tokens.py +83 -0
  71. flexdoc-0.1.0/src/flexdoc/docs/sections.py +130 -0
  72. flexdoc-0.1.0/src/flexdoc/docs/sizes.py +47 -0
  73. flexdoc-0.1.0/src/flexdoc/docs/span_ref.py +192 -0
  74. flexdoc-0.1.0/src/flexdoc/docs/token_diffs.py +367 -0
  75. flexdoc-0.1.0/src/flexdoc/docs/token_mapping.py +91 -0
  76. flexdoc-0.1.0/src/flexdoc/docs/wordtoks.py +284 -0
  77. flexdoc-0.1.0/src/flexdoc/html/__init__.py +74 -0
  78. flexdoc-0.1.0/src/flexdoc/html/extractor.py +30 -0
  79. flexdoc-0.1.0/src/flexdoc/html/html_in_md.py +510 -0
  80. flexdoc-0.1.0/src/flexdoc/html/html_plaintext.py +25 -0
  81. flexdoc-0.1.0/src/flexdoc/html/html_tags.py +423 -0
  82. flexdoc-0.1.0/src/flexdoc/html/timestamps.py +58 -0
  83. flexdoc-0.1.0/src/flexdoc/py.typed +0 -0
  84. flexdoc-0.1.0/src/flexdoc/util/__init__.py +11 -0
  85. flexdoc-0.1.0/src/flexdoc/util/read_time.py +59 -0
  86. flexdoc-0.1.0/src/flexdoc/util/token_estimate.py +34 -0
  87. flexdoc-0.1.0/tests/__init__.py +0 -0
  88. flexdoc-0.1.0/tests/docs/__init__.py +0 -0
  89. flexdoc-0.1.0/tests/docs/test_base_blocks.py +469 -0
  90. flexdoc-0.1.0/tests/docs/test_block_info.py +87 -0
  91. flexdoc-0.1.0/tests/docs/test_block_types.py +334 -0
  92. flexdoc-0.1.0/tests/docs/test_blocks.py +326 -0
  93. flexdoc-0.1.0/tests/docs/test_caching_threadsafe.py +123 -0
  94. flexdoc-0.1.0/tests/docs/test_collect.py +346 -0
  95. flexdoc-0.1.0/tests/docs/test_doc_graph.py +329 -0
  96. flexdoc-0.1.0/tests/docs/test_flex_doc.py +455 -0
  97. flexdoc-0.1.0/tests/docs/test_footnote_ref.py +37 -0
  98. flexdoc-0.1.0/tests/docs/test_frontmatter.py +122 -0
  99. flexdoc-0.1.0/tests/docs/test_interval_index.py +111 -0
  100. flexdoc-0.1.0/tests/docs/test_links.py +153 -0
  101. flexdoc-0.1.0/tests/docs/test_node.py +60 -0
  102. flexdoc-0.1.0/tests/docs/test_node_table.py +377 -0
  103. flexdoc-0.1.0/tests/docs/test_offsets.py +107 -0
  104. flexdoc-0.1.0/tests/docs/test_sections.py +159 -0
  105. flexdoc-0.1.0/tests/docs/test_span_ref.py +245 -0
  106. flexdoc-0.1.0/tests/docs/test_spans.py +116 -0
  107. flexdoc-0.1.0/tests/docs/test_subdoc_and_empty.py +31 -0
  108. flexdoc-0.1.0/tests/docs/test_token_diffs.py +128 -0
  109. flexdoc-0.1.0/tests/docs/test_token_mapping.py +119 -0
  110. flexdoc-0.1.0/tests/docs/test_token_validation.py +40 -0
  111. flexdoc-0.1.0/tests/docs/test_wordtoks.py +98 -0
  112. flexdoc-0.1.0/tests/golden/README.md +57 -0
  113. flexdoc-0.1.0/tests/golden/documents/footnotes_refs.md +15 -0
  114. flexdoc-0.1.0/tests/golden/documents/inline_html.md +16 -0
  115. flexdoc-0.1.0/tests/golden/documents/kitchen_sink.md +29 -0
  116. flexdoc-0.1.0/tests/golden/documents/malformed.md +20 -0
  117. flexdoc-0.1.0/tests/golden/documents/nested_list.md +17 -0
  118. flexdoc-0.1.0/tests/golden/documents/tight_vs_loose.md +18 -0
  119. flexdoc-0.1.0/tests/golden/documents/unicode.md +11 -0
  120. flexdoc-0.1.0/tests/golden/expected/footnotes_refs/docgraph.yaml +63 -0
  121. flexdoc-0.1.0/tests/golden/expected/footnotes_refs/reassembled.md +9 -0
  122. flexdoc-0.1.0/tests/golden/expected/footnotes_refs/report.yaml +216 -0
  123. flexdoc-0.1.0/tests/golden/expected/inline_html/docgraph.yaml +63 -0
  124. flexdoc-0.1.0/tests/golden/expected/inline_html/reassembled.md +11 -0
  125. flexdoc-0.1.0/tests/golden/expected/inline_html/report.yaml +173 -0
  126. flexdoc-0.1.0/tests/golden/expected/kitchen_sink/docgraph.yaml +264 -0
  127. flexdoc-0.1.0/tests/golden/expected/kitchen_sink/reassembled.md +17 -0
  128. flexdoc-0.1.0/tests/golden/expected/kitchen_sink/report.yaml +383 -0
  129. flexdoc-0.1.0/tests/golden/expected/malformed/docgraph.yaml +45 -0
  130. flexdoc-0.1.0/tests/golden/expected/malformed/reassembled.md +11 -0
  131. flexdoc-0.1.0/tests/golden/expected/malformed/report.yaml +127 -0
  132. flexdoc-0.1.0/tests/golden/expected/nested_list/docgraph.yaml +199 -0
  133. flexdoc-0.1.0/tests/golden/expected/nested_list/reassembled.md +7 -0
  134. flexdoc-0.1.0/tests/golden/expected/nested_list/report.yaml +195 -0
  135. flexdoc-0.1.0/tests/golden/expected/tight_vs_loose/docgraph.yaml +187 -0
  136. flexdoc-0.1.0/tests/golden/expected/tight_vs_loose/reassembled.md +11 -0
  137. flexdoc-0.1.0/tests/golden/expected/tight_vs_loose/report.yaml +220 -0
  138. flexdoc-0.1.0/tests/golden/expected/unicode/docgraph.yaml +86 -0
  139. flexdoc-0.1.0/tests/golden/expected/unicode/reassembled.md +5 -0
  140. flexdoc-0.1.0/tests/golden/expected/unicode/report.yaml +132 -0
  141. flexdoc-0.1.0/tests/golden/test_golden_docs.py +156 -0
  142. flexdoc-0.1.0/tests/html/__init__.py +0 -0
  143. flexdoc-0.1.0/tests/html/test_html_tag_hardening.py +24 -0
  144. flexdoc-0.1.0/tests/html/test_html_tags.py +843 -0
  145. flexdoc-0.1.0/tests/html/test_html_validation_and_classes.py +19 -0
  146. flexdoc-0.1.0/tests/html/test_timestamps.py +76 -0
  147. flexdoc-0.1.0/tests/test_examples.py +24 -0
  148. flexdoc-0.1.0/tests/test_root_api.py +53 -0
  149. flexdoc-0.1.0/tests/test_supply_chain.py +41 -0
  150. flexdoc-0.1.0/uv.lock +1149 -0
@@ -0,0 +1,150 @@
1
+ ---
2
+ name: simple-modern-uv
3
+ description: >-
4
+ Set up, modernize, or update Python projects using the simple-modern-uv template:
5
+ uv, ruff, BasedPyright, pytest, GitHub Actions CI, and tag-driven PyPI publishing.
6
+ Use when starting a new Python project or package; when modernizing, upgrading, or
7
+ migrating an existing Python package to uv (from Poetry, setuptools, pip,
8
+ requirements.txt, or PDM); or when updating a project already built from
9
+ simple-modern-uv to the latest template.
10
+ license: MIT
11
+ compatibility: Requires git and uv (https://docs.astral.sh/uv/); network access to GitHub
12
+ metadata:
13
+ author: jlevy (github.com/jlevy)
14
+ source: https://github.com/jlevy/simple-modern-uv
15
+ allowed-tools: Bash(uvx:*), Bash(uv:*), Bash(git:*), Bash(make:*), Bash(gh:*), Read, Write, Edit
16
+ ---
17
+ # simple-modern-uv: Modern Python Project Setup
18
+
19
+ [simple-modern-uv](https://github.com/jlevy/simple-modern-uv) is a minimal, modern
20
+ Python project template: **uv** for everything package-related, **ruff** for
21
+ lint/format, **BasedPyright** for type checking, **pytest**, GitHub Actions CI, and
22
+ tag-driven publishing to PyPI with dynamic versioning (the git tag is the version).
23
+ It is a [Copier](https://copier.readthedocs.io) template, so projects can pull future
24
+ template improvements at any time with `copier update`.
25
+
26
+ There are three workflows.
27
+ Pick by situation:
28
+
29
+ 1. **New project**: create a project from scratch.
30
+ 2. **Upgrade existing project**: convert any existing Python package to this template’s
31
+ structure and tooling.
32
+ See [references/adopt-existing.md](references/adopt-existing.md).
33
+ 3. **Update templated project**: the project has a `.copier-answers.yml` from this
34
+ template; pull the latest template changes.
35
+
36
+ For all workflows: when something fails, check [references/faq.md](references/faq.md)
37
+ before improvising. For post-setup customization (license change, private/unpublished
38
+ package, entry points), see [references/customize.md](references/customize.md).
39
+
40
+ If this file was fetched from a URL rather than installed as a folder, fetch the
41
+ reference files from the **same ref**: replace `SKILL.md` in the URL you used with
42
+ `references/<name>.md` (don’t mix a pinned `SKILL.md` with references from `main`).
43
+
44
+ ## The Interview: What to Ask the User (and What Not to Ask)
45
+
46
+ Every workflow uses the same answer set (the template’s questions in
47
+ [copier.yml](https://github.com/jlevy/simple-modern-uv/blob/main/copier.yml)). **Infer
48
+ everything you can, then ask the user to confirm once, in a single batched message.**
49
+ Never ask one question at a time, and never ask about things listed under “conventions”
50
+ below.
51
+
52
+ **Essentials** (confirm these; they are the user’s decisions):
53
+
54
+ | Answer key | Meaning | Notes |
55
+ | --- | --- | --- |
56
+ | `package_name` | PyPI package and GitHub repo name | Normalize to **kebab-case** (`acme-widgets`); show the user the name you derived |
57
+ | `package_module` | Python module name | Derived automatically as **snake_case** (`acme_widgets`); show it, don’t ask |
58
+ | `package_description` | One-line description | |
59
+ | `package_license` | `MIT` (default), `Apache-2.0`, `BSD-3-Clause`, `AGPL-3.0-or-later`, `Proprietary`, or `None` (decide later) | Always surface, naming the default |
60
+ | `publish_to_pypi` | `true` (default) or `false` | Always surface; `false` for private packages and apps |
61
+
62
+ For `package_author_name`, `package_author_email`, and `package_github_org`, **infer**
63
+ values from `git config user.name` and `user.email` and the repo’s remote or
64
+ `gh auth status`, and include them in the confirmation summary rather than asking.
65
+
66
+ **Conventions** (apply silently; mention in your final summary; deviate only if the user
67
+ explicitly asks): `src/` layout; first version is the git tag `v0.1.0` for a new project
68
+ (for upgrades of published packages, the next version above what’s on PyPI); Python
69
+ 3.11+; line length 100; the template’s tool choices.
70
+ These are never questions.
71
+
72
+ ## Workflow 1: New Project
73
+
74
+ 1. Run the interview (above).
75
+ Then render, replacing the answer values:
76
+
77
+ ```bash
78
+ uvx copier@9.15.1 copy --defaults \
79
+ --data package_name=acme-widgets \
80
+ --data "package_description=One-line description" \
81
+ --data "package_author_name=Jane Doe" \
82
+ --data package_author_email=jane@example.com \
83
+ --data package_github_org=acme \
84
+ --data package_license=MIT \
85
+ --data publish_to_pypi=true \
86
+ gh:jlevy/simple-modern-uv acme-widgets
87
+ ```
88
+
89
+ (Omit `--data` keys that should take defaults.
90
+ `package_module` derives automatically from `package_name`.)
91
+
92
+ 2. Initialize git, commit, then install.
93
+ Sync only after the first commit: dynamic versioning reads git, and syncing first
94
+ leaves the editable install’s version at `0.0.0`.
95
+
96
+ ```bash
97
+ cd acme-widgets
98
+ git init --initial-branch=main
99
+ git add . && git commit -m "Initial commit from simple-modern-uv."
100
+ uv sync --all-extras
101
+ git add uv.lock && git commit -m "Add uv.lock."
102
+ ```
103
+
104
+ 3. **Verify**: `make lint` and `make test` must pass.
105
+ Fix anything that doesn’t before declaring success.
106
+
107
+ 4. Offer next steps (don’t just stop): create the GitHub repo and push (an empty repo
108
+ with no README, license, or .gitignore; the template provides them); fill in
109
+ `README.md`; if publishing, `docs/publishing.md` covers the one-time PyPI Trusted
110
+ Publisher setup; tag `v0.1.0` when ready to release.
111
+
112
+ ## Workflow 2: Upgrade an Existing Project
113
+
114
+ Follow [references/adopt-existing.md](references/adopt-existing.md).
115
+ In brief: run the interview with answers **inferred from the repo itself**
116
+ (pyproject/setup.py metadata, LICENSE file, git remote, PyPI presence), render the
117
+ template into a sibling temp directory with those answers, merge the template’s
118
+ structure into the project (preserving its dependencies, metadata, and code), translate
119
+ old tool configs to uv equivalents, write `.copier-answers.yml` so future updates work,
120
+ and verify with `uv sync`, lint, and tests.
121
+ Land everything on a branch as one reviewable change.
122
+
123
+ ## Workflow 3: Update a Templated Project
124
+
125
+ Requires a clean working tree and the project’s `.copier-answers.yml`.
126
+
127
+ 1. If the template has questions the project has never answered (keys present in the
128
+ template’s `copier.yml` but missing from the project’s `.copier-answers.yml`), first
129
+ check the project’s actual state and pass matching `--data` so defaults can’t revert
130
+ hand customizations (see “Reconciling New Questions on Update” in
131
+ [references/customize.md](references/customize.md)).
132
+
133
+ 2. ```bash
134
+ uvx copier@9.15.1 update --defaults --skip-answered
135
+ ```
136
+
137
+ 3. Resolve any `*.rej` files or inline conflict markers, re-run `make lint` and
138
+ `make test`, then summarize what the template update changed (check the
139
+ [release notes](https://github.com/jlevy/simple-modern-uv/releases)).
140
+
141
+ ## Verification Gate (All Workflows)
142
+
143
+ Before reporting success: `uv sync --all-extras`, `make lint`, and `make test` all pass,
144
+ and for publishable packages `uv build` produces a wheel with a sensible version
145
+ (requires at least one git commit; see the FAQ if the version looks wrong).
146
+ Report what you ran and the results.
147
+
148
+ <!-- This document follows common-doc-guidelines.md.
149
+ See github.com/jlevy/practical-prose and review guidelines before editing.
150
+ -->
@@ -0,0 +1,135 @@
1
+ # Upgrading an Existing Project to simple-modern-uv
2
+
3
+ Convert an existing Python package (setuptools/pip, Poetry, PDM, hatch, or plain
4
+ scripts) to the simple-modern-uv structure.
5
+ The approach: render the template next to the project, then merge it in deliberately;
6
+ never blindly overwrite.
7
+ Work on a branch and land the whole migration as one reviewable change.
8
+
9
+ Out of scope (stop and tell the user): C extensions or custom build steps, conda
10
+ environments, monorepos/workspaces with multiple packages.
11
+ These need decisions a checklist shouldn’t make.
12
+
13
+ ## Step 1: Infer the Interview Answers from the Repo
14
+
15
+ | Answer | Where to look |
16
+ | --- | --- |
17
+ | `package_name` | `[project] name`, `setup.py name=`, `[tool.poetry] name`, or the repo name |
18
+ | `package_description` | `[project] description` or `setup.py description=` |
19
+ | `package_author_name` / `email` | `[project] authors`, `[tool.poetry] authors`, or git log |
20
+ | `package_github_org` | `git remote get-url origin` |
21
+ | `package_license` | `LICENSE` file contents and `license` field/classifiers; map to MIT, Apache-2.0, BSD-3-Clause, or AGPL-3.0-or-later; no license found maps to None; else Proprietary |
22
+ | `publish_to_pypi` | `true` if the package is on PyPI (`uv pip index` or check pypi.org) or clearly intended for it; `false` for apps/private code (a `Private :: Do Not Upload` classifier is a definitive no) |
23
+
24
+ Confirm the essentials with the user in one batched message (per the interview contract
25
+ in SKILL.md), flagging anything that will change.
26
+ One decision that must be surfaced if it applies: if the project currently supports
27
+ Python older than 3.11 **and** is published, adopting the template raises
28
+ `requires-python` to `>=3.11`. Dropping versions for existing users is the user’s call,
29
+ not yours.
30
+
31
+ ## Step 2: Render the Template Beside the Project
32
+
33
+ ```bash
34
+ cd ..
35
+ uvx copier@9.15.1 copy --defaults \
36
+ --data package_name=<name> ... \
37
+ gh:jlevy/simple-modern-uv <project>-template-render
38
+ ```
39
+
40
+ Use the render as the source of truth for structure; you will copy from it into the real
41
+ project.
42
+
43
+ ## Step 3: Merge Structure into the Project
44
+
45
+ Copy from the render, adapting as you go:
46
+
47
+ - **`pyproject.toml`**: start from the rendered one and port the project’s reality into
48
+ it: `dependencies`, extras, entry points, tool configs that should survive (translate
49
+ as below). Keep the rendered `[build-system]` (hatchling with uv-dynamic-versioning),
50
+ `[tool.uv]`, `[tool.ruff]`, `[tool.basedpyright]`, `[tool.pytest.ini_options]`
51
+ sections.
52
+ - **Code layout**: move code to `src/<module>/` (the convention; if the user insists on
53
+ a flat layout, adjust `packages` and `testpaths` instead).
54
+ Ensure `py.typed` exists in the package.
55
+ Tests go in `tests/`.
56
+ - **Copy verbatim**: `devtools/lint.py`, `Makefile`, `.gitignore` (merge with existing
57
+ entries), `.github/workflows/ci.yml` (and `publish.yml` if publishing),
58
+ `docs/installation.md`, `docs/development.md`.
59
+ - **Agent instruction files**: copy `AGENTS.md` and `CLAUDE.md` from the render if the
60
+ project has neither.
61
+ If the project already has an `AGENTS.md`, keep its content and fold in the template’s
62
+ build/test commands.
63
+ If it already has a `CLAUDE.md`, don’t overwrite it; add the `@AGENTS.md` import line
64
+ at the top so Claude Code picks up the shared conventions.
65
+ - **`.copier-answers.yml`**: copy from the render.
66
+ This is what makes future `copier update` work; without it the project is orphaned
67
+ from the template.
68
+ - **Delete superseded files** (after their content is ported): `setup.py`, `setup.cfg`,
69
+ `requirements*.txt`, `poetry.lock`, `pdm.lock`, `Pipfile*`, old
70
+ `tox.ini`/`.flake8`/`mypy.ini`/`.pylintrc`, old CI workflows that the template’s CI
71
+ replaces.
72
+
73
+ ## Per-Source Translations
74
+
75
+ **Poetry**:
76
+
77
+ - Caret/tilde specs → PEP 621 ranges: `^1.2.3` → `>=1.2.3,<2.0.0`; `~1.2.3` →
78
+ `>=1.2.3,<1.3.0`. Prefer simple `>=` floors where the project doesn’t actually need
79
+ upper bounds.
80
+ - `[tool.poetry.dependencies]` → `[project] dependencies` (drop the `python` entry;
81
+ that’s `requires-python`).
82
+ - `[tool.poetry.group.dev.dependencies]` (or `dev-dependencies`) →
83
+ `[dependency-groups] dev`.
84
+ - `[tool.poetry.scripts]` → `[project.scripts]`.
85
+ - `[tool.poetry] version` → delete; the version now comes from git tags
86
+ (`dynamic = ["version"]`).
87
+ - `packages = [{include = "pkg", from = "src"}]` → hatch’s
88
+ `[tool.hatch.build.targets.wheel] packages = ["src/pkg"]` (already in the rendered
89
+ file).
90
+
91
+ **setuptools/pip**:
92
+
93
+ - `setup.py`/`setup.cfg` metadata → `[project]` table.
94
+ `install_requires` → `dependencies`; `extras_require` →
95
+ `[project.optional-dependencies]`; `entry_points["console_scripts"]` →
96
+ `[project.scripts]`.
97
+ - `requirements.txt` → runtime deps to `dependencies`; `requirements-dev.txt` →
98
+ `[dependency-groups] dev`. Drop exact `==` pins unless genuinely required; `uv.lock`
99
+ provides reproducibility.
100
+
101
+ **mypy → BasedPyright**: drop `mypy.ini`/`[tool.mypy]`; the rendered
102
+ `[tool.basedpyright]` block is the starting point.
103
+ Existing `# type: ignore` comments still work.
104
+ If legacy code produces a wall of errors, see the FAQ: relax first, ratchet later.
105
+
106
+ **Other linters (flake8, isort, black, pylint)**: delete their configs; ruff covers them
107
+ (`I` rules replace isort, the formatter replaces black).
108
+
109
+ ## Step 4: Lock, Verify, Iterate
110
+
111
+ ```bash
112
+ uv sync --all-extras # creates uv.lock; commit it
113
+ make lint # codespell + ruff + basedpyright
114
+ make test
115
+ uv build # only if publishing
116
+ ```
117
+
118
+ Fix failures using [faq.md](faq.md).
119
+ Lint/type errors in legacy code are normal on first run: auto-fix what `make lint`
120
+ fixes, relax basedpyright rules where the noise is unhelpful (leave a comment), and
121
+ don’t rewrite working code just to satisfy a rule.
122
+
123
+ ## Step 5: Versioning and Finish
124
+
125
+ - Dynamic versioning needs a tag: if the package is on PyPI at version X.Y.Z, tag the
126
+ merge commit `vX.Y.(Z+1)` (or the next minor) **after** merging; an unpublished
127
+ project starts at `v0.1.0`. Until a tag exists, builds get a dev version: fine for CI,
128
+ wrong for release.
129
+ - Commit everything (including `uv.lock` and `.copier-answers.yml`) on the branch and
130
+ summarize: what moved, what was translated, what was deleted, anything the user should
131
+ review by hand (license, classifiers, CI triggers).
132
+
133
+ <!-- This document follows common-doc-guidelines.md.
134
+ See github.com/jlevy/practical-prose and review guidelines before editing.
135
+ -->
@@ -0,0 +1,75 @@
1
+ # Customizing a simple-modern-uv Project
2
+
3
+ Common customizations after (or during) setup.
4
+ Where a customization maps to a template question, prefer answering the question over
5
+ hand-editing: hand edits to template-managed files can be reverted by a later
6
+ `copier update` (see “Reconciling New Questions on Update” below).
7
+
8
+ ## Changing the License
9
+
10
+ Preferred: set the `package_license` answer (`MIT`, `Apache-2.0`, `BSD-3-Clause`,
11
+ `AGPL-3.0-or-later`, `Proprietary`, or `None` to decide later) when rendering, or
12
+ re-answer it later with:
13
+
14
+ ```bash
15
+ uvx copier@9.15.1 update --data package_license=Apache-2.0
16
+ ```
17
+
18
+ This updates the `LICENSE` file and the `license` field in `pyproject.toml` together,
19
+ and records the answer in `.copier-answers.yml`. `None` renders no `LICENSE` file and no
20
+ `license` field; re-answer the question when the project picks one.
21
+ For a license outside the template’s choices: pick `Proprietary` (so the template stops
22
+ managing it), then replace `LICENSE` and set `license` in `pyproject.toml` to the
23
+ correct SPDX identifier yourself.
24
+
25
+ ## Private or Unpublished Packages
26
+
27
+ Set `publish_to_pypi=false` (at render time, or via
28
+ `copier update --data publish_to_pypi=false`). This removes
29
+ `.github/workflows/publish.yml` and `docs/publishing.md`, and adds the
30
+ `Private :: Do Not Upload` classifier so PyPI rejects an accidental upload.
31
+ Everything else (CI, lint, tests, versioning) keeps working.
32
+ To start publishing later, flip the answer back the same way, then do the one-time
33
+ Trusted Publisher setup in `docs/publishing.md`.
34
+
35
+ ## Reconciling New Questions on Update
36
+
37
+ When the template adds questions over time, a project that predates them has no recorded
38
+ answer, and `copier update --defaults` fills in the default, which can contradict hand
39
+ edits made before the question existed.
40
+ Before updating an older project, check its actual state and pass explicit `--data`:
41
+
42
+ - Hand-replaced license?
43
+ Pass the matching `package_license` (or `Proprietary` for anything custom).
44
+ - Deleted `publish.yml` by hand, or has the `Private :: Do Not Upload` classifier?
45
+ Pass `publish_to_pypi=false`.
46
+
47
+ Rule of thumb: defaults describe a fresh project, not yours; anywhere the project
48
+ visibly deviates from a fresh render, make the answer explicit.
49
+
50
+ ## Apps and CLIs (vs. Libraries)
51
+
52
+ - Entry points live in `[project.scripts]`: `mycli = "my_module.cli:main"`; then
53
+ `uv run mycli` works and installs expose the command.
54
+ - An app that’s not a library usually wants `publish_to_pypi=false`; it still gets the
55
+ full dev workflow.
56
+ - For a long-lived service, consider pinning the Python version with a `.python-version`
57
+ file (uv reads it automatically).
58
+
59
+ ## Other Common Tweaks
60
+
61
+ - **Line length**: `[tool.ruff] line-length` in `pyproject.toml` (default 100; black
62
+ uses 88).
63
+ - **Stricter/looser type checking**: toggle the commented `report*` settings in
64
+ `[tool.basedpyright]`; the template ships a pragmatic middle ground.
65
+ - **More ruff rules**: uncomment entries in `[tool.ruff.lint] select` (e.g. `"D"` for
66
+ docstring rules, `"SIM"` for simplifications).
67
+ - **OS matrix in CI**: edit `os:` in `.github/workflows/ci.yml`
68
+ (`["ubuntu-latest", "macos-latest", "windows-latest"]`).
69
+ - **Spell-check exceptions**: `[tool.codespell] ignore-words-list`.
70
+
71
+ After any customization, re-run `make lint` and `make test`.
72
+
73
+ <!-- This document follows common-doc-guidelines.md.
74
+ See github.com/jlevy/practical-prose and review guidelines before editing.
75
+ -->
@@ -0,0 +1,89 @@
1
+ # FAQ: Common Problems
2
+
3
+ Failure modes seen in real setups and migrations, with fixes.
4
+ Check here before improvising.
5
+
6
+ ## Build Fails: “This does not appear to be a Git project”
7
+
8
+ `uv-dynamic-versioning` reads the version from git.
9
+ The project must be a git repo with at least one commit before `uv build` (or any
10
+ build-backend invocation, including the editable install during `uv sync`) can resolve a
11
+ version. Fix:
12
+ `git init --initial-branch=main && git add . && git commit -m "Initial commit"`.
13
+
14
+ ## Version Is 0.0.0, 0.1.devN, or Otherwise Wrong
15
+
16
+ No git tag yet: dynamic versioning derives the version from the latest `v*` tag.
17
+
18
+ - New project: tag `v0.1.0` when ready to release; dev versions before that are normal
19
+ and harmless for CI.
20
+ - Migrated package already on PyPI at X.Y.Z: the first tag must be **greater** than
21
+ X.Y.Z (e.g. `vX.Y.(Z+1)`), or the publish will be rejected as a duplicate/downgrade.
22
+ - Tag exists but ignored: tags must look like `v1.2.3`; also check CI uses
23
+ `fetch-depth: 0` (the template’s workflows do) so tags are available.
24
+ - `importlib.metadata` still reports an old version after committing or tagging: the
25
+ editable install’s metadata is captured at sync time and uv won’t refresh it on its
26
+ own. Run `uv sync --reinstall-package <module>` (and sync only after the first commit
27
+ on new projects).
28
+
29
+ ## `uv sync` Fails on Python Version
30
+
31
+ The template requires Python 3.11+. `uv python install` downloads a managed interpreter;
32
+ pin one for the project with `uv python pin 3.12` (writes `.python-version`). If uv
33
+ itself errors with “required-version”, upgrade uv: the template requires uv >= 0.9.
34
+
35
+ ## BasedPyright Erupts with Hundreds of Errors on Legacy Code
36
+
37
+ Expected on first run over older code.
38
+ Don’t rewrite the codebase to satisfy it, and don’t turn it off:
39
+
40
+ 1. Start from the template’s `[tool.basedpyright]` block, which already relaxes the
41
+ noisiest rules.
42
+ 2. Temporarily disable the loudest remaining categories (uncomment the provided
43
+ `report*` lines, e.g. `reportUnknownVariableType = false`), leaving a comment to
44
+ ratchet later.
45
+ 3. Existing mypy-style `# type: ignore` comments still work; prefer fixing real findings
46
+ over suppressing them.
47
+
48
+ ## codespell Flags Names or Legacy Prose
49
+
50
+ Add exceptions in `pyproject.toml`: `[tool.codespell] ignore-words-list = "word1,word2"`
51
+ or `skip = "path1,path2"`.
52
+
53
+ ## Conflicts During `copier update`
54
+
55
+ Copier writes `*.rej` files (or inline conflict markers) where the template and local
56
+ edits collide. Resolve each by hand, keeping the project’s intent; delete the `.rej`
57
+ files; re-run `make lint` and `make test`. A dirty working tree also blocks updates;
58
+ commit or stash first.
59
+
60
+ ## `publish.yml` Came Back or License Reverted After an Update
61
+
62
+ The project predates the `publish_to_pypi` and `package_license` questions and the
63
+ update filled them with defaults.
64
+ Re-run the update passing the project’s reality, e.g.
65
+ `uvx copier@9.15.1 update --data publish_to_pypi=false`, and see “Reconciling New
66
+ Questions on Update” in [customize.md](customize.md).
67
+
68
+ ## Publish Workflow Fails with OIDC or Permission Errors
69
+
70
+ The one-time PyPI Trusted Publisher setup hasn’t been done for this repo (or the
71
+ workflow filename doesn’t match what PyPI was told).
72
+ Follow `docs/publishing.md` in the project; no API tokens are needed.
73
+
74
+ ## Lockfile Resolution Seems Stale or Refuses a Brand-New Release
75
+
76
+ `UV_EXCLUDE_NEWER` (set in the template’s CI) enforces a 14-day supply-chain cooling-off
77
+ window, so releases newer than that are deliberately invisible.
78
+ This is a feature; don’t remove it to get a day-old package.
79
+ Locally, leave the variable unset for normal work, or set it to match CI when debugging
80
+ resolution differences.
81
+
82
+ ## Tests Pass Locally but CI Fails on a Python Version
83
+
84
+ The CI matrix runs 3.11–3.14. Most failures are version-specific syntax/stdlib use; run
85
+ the failing version locally with `uv run --python 3.11 pytest`.
86
+
87
+ <!-- This document follows common-doc-guidelines.md.
88
+ See github.com/jlevy/practical-prose and review guidelines before editing.
89
+ -->