Changelog ============================= Formal changelog tracking starts with 0.6.0 — for anything earlier, see the `commit history `_ on GitHub. 1.0.0b1 ---------- Added ~~~~~~~~ - ``git`` template activated: a local repository with a configurable branching strategy (``config/git.toml``, four layouts: with or without major-version branches, with or without stages), feature/fix branches, merges across stages, and Conventional Commits. - Automatic breaking-change handling on major-version branches: merging a breaking change redirects it to a new ``v{N+1}-alpha`` line, and a ``commit-msg`` hook blocks direct breaking commits on major-version branches. - Stage-based releases: ``python tools git release`` produces PEP 440 pre-releases on alpha (``a``) and beta (``b``), and a stable release on the base branch. The next version is computed from the Conventional Commits since the latest stable tag. - ``remote_repository`` template: the shared foundation for hosting platforms, with a generic ``create_repo``/``delete_repo`` runner and a provider that picks the default platform (``config/remote_repository.toml``). - ``github`` template activated as the GitHub adapter for ``remote_repository``, using the ``gh`` CLI. Releases are published to GitHub, and pre-release versions are marked as pre-releases. - ``appcraft/scripts/release.py``: dev-only tooling that uses appcraft's own ``git``/``remote_repository``/``github`` templates to release appcraft itself, configured for major-version branches with ``beta``/``main`` stages (no ``alpha`` yet). appcraft's own repo isn't a generated project, so it can't install these templates onto itself the normal way — their runner depends on the generated project's own runner/config system — but their domain/application logic (branching rules, Conventional Commits parsing, PEP 440 version detection, the ``git``/``gh`` subprocess adapters) has no such dependency, so this script reuses it directly (merging the four templates' ``files/`` trees into a throwaway scratch directory via ``TemplateAdder``, the same mechanism ``add_template`` itself uses) instead of duplicating it. Commands: ``install-hook``, ``start``, ``merge``, ``detect-version``, ``release``, ``new-major``, ``changelog``, ``cleanup``. - A "Developing Your Project" guide, covering the runner → presentation → use case → repository → adapter flow, which folder a new piece of code belongs in, configuration, and the interactive/non-interactive rule — with per-template recipes for adding a Flask API route, a Flask UI page, and a SQLAlchemy table moved to each template's own page. - A "What Is Appcraft, and Why Use It" section at the top of Getting Started. - ``infrastructure/utils/logger/provider.py``'s ``get_default_logger()``, following the same ``is_installed()``-based pattern as the database/web-scraping/remote-repository providers: resolves to the ``logs`` template's richer logger when installed, the zero-dependency default otherwise. - ``Logger.named(name)``: a view of the same singleton logger that shows ``name`` in place of ``"appcraft"`` on each line (console and file alike), for tagging log lines by layer or domain (``logger.named("application")``, ``logger.named("user")``, any string) without creating a second logger or handler. Changed ~~~~~~~~ - ``git`` and ``github`` migrated to the framework's current conventions (``Port``/``UseCase``/``Adapter``). Their previous code used ``application/services/`` and ``AdapterInterface``, which didn't exist anywhere in the framework. - The project's version lives only in ``pyproject.toml`` (``[project]`` or ``[tool.poetry]``). ``config/app.toml`` no longer has a ``version`` key, and ``AppManager.version`` reads it from ``pyproject.toml`` (``APPCRAFT_VERSION`` still overrides it). - The documentation is no longer kept in ``docs/latest``; it lives in the version folder of each release, and ``latest`` only redirects to the newest one. - ``BaseMapper`` renamed to ``MapperBase``, matching ``RepositoryBase``'s suffix instead of prefixing "Base" — the two classes play the same role (deriving ``model``/``schema`` or ``model``/``new_model`` from a generic subscript via ``PropertyMeta``) and are now named the same way. - ``FilterInterface`` renamed to ``Filter``, matching how ``ValueObject`` is already named: both are pure, zero-behavior contracts that already have a ``*Base`` sibling with the shared concrete implementation (``FilterBase``, ``ValueObjectBase``), so the plain name goes to the contract instead of the suffix. - ``LoggerInterface`` removed and merged into ``LoggerBase``. Unlike ``Filter``/``ValueObject``, it wasn't a pure contract to begin with — it already inherited ``logging.Logger`` directly — and nothing in the framework subclassed it independently of ``LoggerBase``, so the separate class added a layer without adding a guarantee. - The ``HTMLElementInterface``/``HTMLReadingElementInterface``/``HTMLNavigationElementInterface``/``HTMLInteractiveElementInterface`` chain renamed to ``HTMLElement``/``HTMLReadingElement``/``HTMLNavigationElement``/``HTMLInteractiveElement``. None of the four had a ``*Base`` sibling, and the chain is already distinguished by its own words (``Reading``/``Navigation``/``Interactive``), not by the suffix. - ``HTMLParserInterface`` (``httpx``, ``curl_cffi``) renamed to ``HTMLParser``. The ``selectolax`` backend's own import of its real ``HTMLParser`` class is now aliased to ``LibHTMLParser`` to avoid colliding with it. - ``LoggerBase`` renamed to ``Logger`` (bare, no suffix): once ``LoggerInterface`` was merged into it, nothing else needed telling apart from it. This required two supporting changes first — ``Initializer``'s own empty ``class Logger(LoggerBase): pass`` was removed in favor of calling the new ``get_default_logger()`` provider directly, and the ``logs`` template's real, structlog-backed ``Logger`` was renamed to ``StructlogLogger`` — otherwise either one would have collided with the bare name. - The ``base/`` subfolder holding just one ``*Base`` class — never a documented convention, unlike ``interface/`` — collapsed into the package's own ``__init__.py`` in every place it appeared: ``application/mappers/bases/__init__.py`` → ``application/mappers/__init__.py``, ``domain/filters/base/__init__.py`` → ``domain/filters/__init__.py`` (alongside the concrete filters it already held), ``infrastructure/core/package/manager/base/__init__.py`` → ``infrastructure/core/package/manager/__init__.py``, and ``infrastructure/utils/logger/base/__init__.py`` → ``infrastructure/utils/logger/__init__.py``. Matches how ``application/repositories/__init__.py`` already held ``Repository``, ``RepositoryBase`` and ``GenericRepository`` together, with no subfolder. - ``BaseConfig`` renamed to ``ConfigBase``, matching its own siblings' suffix style (``TomlConfig``, ``JsonConfig``, ``EnvConfig``, ``PyProjectConfig``) instead of prefixing "Base". Unlike ``Filter``/``ValueObject``, it keeps the ``Base``/suffix shape rather than going bare — ``Config`` is already a different, concrete, heavily-used class (the composed config reader), not a synonym for the abstract root. - ``domain/value_objects/base/__init__.py`` collapsed into ``domain/value_objects/__init__.py`` the same way, now holding ``ValueObject``, ``ValueObjectBase``, ``IntValueObject`` and ``StringValueObject`` together. - ``IAppProvider``/``AppProvider`` renamed to ``AppPort``/``AppAdapter`` and moved to ``application/ports/app.py``/``infrastructure/app/adapter.py``. This wasn't just an ``I``-prefix fix: it was the one remaining "Provider" implemented as an ABC-plus-one-Adapter class pair instead of a plain ``get_default_*()`` function, because — unlike the database/web-scraping/remote-repository/logger Providers — nothing ever picks between installed alternatives here; there's exactly one implementation, which is precisely what ``Port``/``Adapter`` already name elsewhere in the framework. The now-empty ``application/providers/`` package was removed. - ``infrastructure/framework/appcraft/core/``, ``app/`` and ``utils/`` moved to ``infrastructure/core/``, ``infrastructure/app/`` and ``infrastructure/utils/`` — directly under ``infrastructure/``, no longer nested under the templating engine's own namespace. None of the three is about appcraft's templating mechanism: ``core`` is cross-cutting runtime machinery (config, the runner system, error handling, package managers) with no single concept of its own, ``app`` is just another concept-specific ``Port``/``Adapter`` pair like ``database``/``git``/``web_scraping`` and belongs at the same depth as them, and ``utils`` is generic helpers (``Printer``, ``ComponentPrinter``, the logger). ``infrastructure/framework/appcraft/templates/`` — the ``is_installed()`` markers — is the one part that's genuinely about the templating mechanism, and stayed put. - ``git``'s and ``remote_repository``'s domain exceptions standardized on the ``Error`` suffix: ``GitException``/``NotAGitRepositoryError``/``NotAReleaseBranchError``/``FirstReleaseNotEntryError`` mixed ``Exception`` on the base class with ``Error`` on every child, same for ``RemoteRepositoryException`` and its three children — both families now read ``GitError``/``RemoteRepositoryError`` at the root too. ``web_scraping``'s own exceptions (``WebScrapingException``, ``NoSuchElementException``, ``BrowserNotInstalledException``, ``NoBrowsersInstalledException``) were internally consistent already, but in the suffix the rest of the framework doesn't use — ``domain/models``/``domain/value_objects`` are ``Error``-suffixed throughout, with no ``Exception``-suffixed root at all — so they were renamed too (``WebScrapingError``, ``NoSuchElementError``, ``BrowserNotInstalledError``, ``NoBrowsersInstalledError``), making ``Error`` the one suffix used everywhere in the framework. - Every ``pyproject.toml`` in the repository — the CLI's own root file and every template's shipped ``files/pyproject.toml`` fragment (``base``, ``flask``, ``flask_ui``, ``sqlalchemy``, ``mongodb``, ``locales``, ``logs``, ``data_analysis``, ``httpx``, ``curl_cffi``, ``playwright``, ``selenium``, ``prompt_toolkit``, ``poetry``, ``pipenv``, ``uv``) — rewritten in pure PEP 621/735 form (``[project]`` with a ``dependencies`` list of PEP 508 requirement strings, ``[dependency-groups]`` for dev deps, PEP 639 string licenses), replacing Poetry's own ``[tool.poetry.*]`` dialect and caret/tilde ranges. A package-manager-specific format now only ever appears as something ``poetry``/``pipenv``/``uv``'s own ``pre_install`` writes at install time — never baked into a template's shipped files. - Dependencies specific to a **delivery surface** — not a business feature — now live under named PEP 735 ``[dependency-groups]`` instead of plain ``[project.dependencies]``: ``web`` (``flask``/``flask_ui``, plus ``sqlalchemy``'s own ``flask-sqlalchemy`` when both are installed, plus the unrelated ``websockets`` stub's dependencies — a websocket server is another network-facing delivery surface, same bucket, even though neither template depends on the other), ``cli`` (``prompt_toolkit``). A ``gui`` group is reserved for a future desktop/mobile template; nothing ships there yet. Business-logic dependencies — ``sqlalchemy``/``mongodb``, ``selenium``/``playwright``/``httpx``/``curl_cffi``, ``locales``, ``logs``, ``data_analysis`` — stay plain main dependencies: a use case that reads the database, scrapes a page, or translates a string needs that dependency regardless of whether the app is driven by a CLI, served over HTTP, or wrapped in a desktop GUI, so there's no delivery target where excluding them wouldn't just break the app. (A first pass made ``database``/``web_scraping``/``i18n``/``observability``/``data`` their own excludable groups too, and gave ``tests`` its own group — both reverted the same day: the former conflated a business feature with a delivery surface, and test tooling is a dev-time concern regardless of delivery target, so it folded into ``dev`` instead.) Every group installs by default (never marked optional) and is excludable with ``poetry install --without `` / ``uv sync --no-group `` — e.g. ``--without web`` for a CLI-only build that keeps every business dependency but drops the web server. ``uv``'s ``pre_install`` now also sets ``[tool.uv].default-groups`` (listing every group, ``dev`` included — confirmed empirically that setting it at all replaces uv's implicit default rather than adding to it) so this works the same as Poetry's always-on-unless-``optional`` groups. ``pipenv``'s ``pre_install`` now flattens every group, ``dev`` included, into ``[packages]`` — Pipfile has no "default but individually excludable" named-group concept (a bare ``pipenv install`` only ever installs ``[packages]``, confirmed empirically), so this is the only way to keep its default install matching Poetry/uv's; the accepted cost is that Pipenv can't exclude a single group, and converting away from it afterwards permanently loses the original group boundaries. - The CLI's own ``requires-python`` raised from ``>=3.9`` to ``>=3.12``, matching what every generated project already requires (``base/files/pyproject.toml``'s own floor) — there's little point supporting a Python for the CLI that nothing it generates can run on anyway. - (Tried, then reverted the same day: mirroring every template family's real packages — Flask, SQLAlchemy, pandas, and so on — as same-named optional groups (``web``, ``database``, ``web_scraping``, ``cli``, ``i18n``, ``observability``, ``data``, ``ml``, ``tests``) on the CLI's own root ``pyproject.toml``, so a contributor editing one template's source could ``poetry install --with `` and get clean pyright resolution for it. These packages aren't dependencies of the CLI itself under any circumstance — they're scaffold content the CLI ships, never something `import appcraft` reaches — so declaring them here, even as opt-in groups, misrepresented what the published package actually needs. Reverted to just ``dev`` (``sphinx``, ``sphinx_rtd_theme``, ``pyright`` — the CLI's genuine dev tooling for building its own docs and type-checking its own source). The tradeoff this reintroduces: pyright reports an unresolved import for a template file whose real dependency isn't installed in this repo's own venv, which is expected now rather than a bug to chase — the correct way to verify a template's own code is the ``appcraft/templates/temp/files`` dev loop, which installs that template's real dependencies into a separate venv of its own.) - The CLI's own root ``pyproject.toml`` gained a full ``[project.urls]`` table (``Homepage``/``Repository``/``Documentation``/``Changelog``/``Issues``, all pointing at the real GitHub repo and docs site — confirmed the ``Changelog`` URL matches a real built page), ``keywords``, broader ``classifiers`` (specific Python versions, ``Environment :: Console``, ``Intended Audience :: Developers``, ``Topic :: Software Development :: Code Generators``), and an explicit PEP 639 ``license-files = ["LICENSE"]`` instead of relying on setuptools' implicit auto-discovery. Verified by actually building a real sdist (``python -m build --sdist``) and inspecting ``PKG-INFO``: every field lands correctly, including ``License-Expression: MIT``/``License-File: LICENSE`` (``Metadata-Version`` 2.4, required for those two). ``[build-system].requires``' ``"toml"`` entry was checked and kept, not removed — ``setup.py`` genuinely imports it at build time to read ``pyproject.toml`` directly for ``package_data``/the legacy ``url=`` kwarg, confirmed by reading the file rather than assumed. - ``base/files/pyproject.toml``'s ``[project.urls]`` had a real bug: ``Repository``/``Documentation`` pointed at ``github.com/duxtec/appcraft``/``duxtec.github.io/appcraft`` — appcraft's *own* repo and docs, copy-pasted in rather than adjusted, so every generated project was born with metadata claiming to be part of appcraft itself. Changed to generic ``example.com`` placeholders, matching ``Homepage``'s existing style. `base` also gained a ``LICENSE`` file (MIT, placeholder copyright holder matching the existing ``authors`` placeholder) and ``license-files = ["LICENSE"]`` — previously ``license = "MIT"`` was declared with no actual license file shipped to back it. Note: ``license-files`` doesn't survive Poetry's own conversion (``poetry`` has no ``[tool.poetry]`` equivalent field) — accepted as-is rather than engineered around, since a Poetry-managed generated project also sets ``package-mode = false`` and is never actually built/published, making the field purely decorative there regardless. ``classifiers``/``keywords``/``[project.urls]`` were also filled out more completely — matching the same shape as the CLI's own root ``pyproject.toml`` (an ``Issues`` link added; specific supported Python versions, ``Environment :: Console``, ``Intended Audience :: Developers`` added to classifiers; ``clean-architecture`` added to keywords, a fact true of every generated project regardless of which templates are installed) — as a fuller worked example for the user to edit, not because appcraft needs these values to be anything specific. ``Development Status :: 4 - Beta`` was also changed to ``3 - Alpha``: a project that was *just* scaffolded, with no code of the user's own written yet, calling itself "Beta" was never accurate. Fixed ~~~~~~~~ - ``docs/scripts/generate_latest_redirect.py`` only ever wrote ``build/latest/index.html``, so a deep link like ``latest/getting_started.html`` or ``latest/templates/index.html`` 404'd even though ``latest/index.html`` itself resolved fine (confirmed against the live deployed site) — the README's own links to anything other than the front page were broken. Now walks the newest version's build output and writes a matching redirect stub for every ``.html`` page found there. - ``RepositoryBase.create()`` was an empty stub that returned ``None`` instead of forwarding to the adapter, unlike every other method on the class. A repository that didn't override ``create()`` itself silently created nothing. - ``ConfigBase._load()`` was dead code — nothing called it — and was removed. - ``JsonConfig.__init__`` was a no-op override of ``ConfigBase.__init__`` with the same signature and default; removed. - ``Config.load_file()`` passed a full file path (e.g. ``"config/database.toml"``) to ``PyProjectConfig.get()``, which expects a bare file name (``"database"``) — harmless today only because every mismatch silently fell through to ``{}``. The check was redundant anyway (``Config.get()`` already tries ``pyproject.toml`` before ``load_file()`` is ever reached), so it was removed rather than fixed in place. - ``locales``, ``logs`` and ``data_analysis`` declared their real dependencies only in a ``Pipfile``, so a default Poetry project never installed them — each now also ships a ``pyproject.toml`` fragment. ``logs``' fragment keeps only ``structlog`` (the one dependency the template's code actually imports) rather than carrying over ``better-exceptions``/``rich``/``logbook``/``flask-logging``/``graypy``, which were declared but never used anywhere in the template. ``data_analysis``'s original exact pins (``pandas==2.2.3``, ``numpy==2.1.1``, ``matplotlib==3.9.2``, ``scikit-learn==1.5.2``, all from 2024) have no prebuilt wheel for current Python and fail to build from source — repinned to current, installable versions instead. ``ci_cd``'s empty ``Pipfile`` wasn't a bug: its runner has no real dependency yet. - ``TemplateAdder.union_dicts`` deduplicated merged lists with ``set(dict1[prop])``, which crashed (``unhashable type``) the moment a merged list held tables instead of plain strings — exposed by ``base``'s own ``pyproject.toml`` switching to PEP 621's ``authors = [{name = ..., email = ...}]`` form (the previous Poetry-style ``authors = ["Name "]`` was a list of hashable strings, so the bug never surfaced). Switched to an equality-based membership check, correct for any list element type. - ``poetry``'s ``pre_install`` used ``pyproject.setdefault("build-system", ...)``, which only ever filled in a missing key — harmless while ``base``'s own ``pyproject.toml`` happened to already declare the matching ``poetry-core`` backend, but once ``base`` switched to a plain ``setuptools`` default (package-manager-agnostic, see above) the ``setdefault`` silently stopped firing, leaving a Poetry-managed project with the wrong build backend. Now overwritten unconditionally. ``uv``'s own ``pre_install`` had the mirror problem one level further: swapping to ``uv`` left whatever backend was already in ``[build-system]`` in place, and ``uv sync`` tries to build/install the generated project itself as a distribution — which fails outright against this framework's multi-directory (``domain``/``application``/``infrastructure``/...) layout. Fixed by having ``uv``'s ``pre_install`` set ``[tool.uv]\npackage = false`` (uv's equivalent of Poetry's ``package-mode = false``) and drop ``[build-system]`` entirely, since it's irrelevant once the project itself is never built. - ``flask-sqlalchemy`` was imported by ``infrastructure/framework/flask/sqlalchemy.py`` but declared in neither ``flask``'s nor ``sqlalchemy``'s ``pyproject.toml`` — only ``sqlalchemy``'s old ``Pipfile`` happened to carry it, so a default Poetry project combining ``flask``+``sqlalchemy`` never actually installed it (silently masked: ``FlaskSQLAlchemy(self.app).init_app()`` catches the resulting error and just logs it). Now declared in ``sqlalchemy``'s manifest, in both formats. ``sqlalchemy``'s ``Pipfile`` also had two more unrelated problems: ``psycopg2-binary`` instead of the ``psycopg`` (v3) driver the adapter actually targets, and ``flask-migrate``, which nothing imports anywhere — dropped. ``mongodb``'s ``Pipfile`` carried ``mongoengine``/``flask-pymongo``/``motor``/``mongomock``, none of which anything in the template imports either — trimmed to just ``pymongo``, its one real dependency. - Every template's ``Pipfile`` (where one exists) now mirrors its ``pyproject.toml`` fragment's exact PEP 508 version range instead of an unconstrained ``"*"`` or a stale pin, so ``read_dependencies()`` resolves the same effective dependency set regardless of which format a project started from. - No template ships a ``Pipfile`` any more: every one that had one (``ci_cd``, ``data_analysis``, ``flask_ui``, ``locales``, ``logs``, ``machine_learning``, ``mongodb``, ``prompt_toolkit``, ``sqlalchemy``, ``tests``, ``websockets``) had its content migrated into a ``pyproject.toml`` fragment and the ``Pipfile`` deleted (``machine_learning``/``tests``/``websockets`` previously had no ``pyproject.toml`` at all). This was safe to do unconditionally: the per-template ``Pipfile``-merge path had never actually worked, since ``TemplateABC.package_manager`` always defaults to ``PoetryManager()`` and no template (not even ``pipenv``/``uv`` themselves) ever overrode it, so ``TemplateAdder``'s Pipenv-specific branches never fired for any template — confirmed empirically (``add_template mongodb`` against an already-Pipenv-converted project left its ``Pipfile`` completely unchanged). ``TemplateAdder.merge_pm_files`` and the dead ``Pipfile``/``Pipfile.lock`` skip-condition in ``_copy_directory_contents`` were removed along with it; ``TemplateABC.install()`` now calls ``TemplateAdder.merge_toml()`` directly. A Pipenv-managed project's actual ``Pipfile`` still exists, same as before — it's ``pipenv``'s own *generated* output (written by its ``pre_install`` from the merged ``pyproject.toml``), never a file copied from a template. - ``appcraft/utils/dependency_convert.py`` only read/wrote three hardcoded URL labels (``Homepage``/``Repository``/``Documentation``), so any other ``[project.urls]`` entry (e.g. an ``Issues`` link) was silently dropped by any package-manager conversion. Generalized to a single ``metadata["urls"]`` dict carrying every label through — ``poetry``'s ``pre_install`` now keeps its three first-class fields for those three labels and puts everything else in the (deprecated but still functional) ``[tool.poetry.urls]`` table, confirmed to actually work via ``poetry check``; ``uv``'s writes the whole dict straight to ``[project.urls]``, since PEP 621 already supports arbitrary labels natively. Verified end-to-end with a real ``Issues`` URL surviving a full poetry → uv → poetry round-trip. - ``uv``'s ``pre_install`` wrote ``license`` as the legacy ``{text = "..."}`` table form instead of a plain PEP 639 SPDX string, even though ``metadata["license"]`` is always already just an SPDX expression (e.g. ``"MIT"``) regardless of source format — inconsistent with every other place in the codebase that was deliberately moved to the modern string form this same week. Fixed to write the bare string. ``license-files`` also now round-trips through a poetry→uv (or any format with a native ``[project]`` table) leg, via the same generic ``metadata`` dict — it still doesn't survive a leg through Poetry itself, which has no ``[tool.poetry]`` equivalent field (unchanged, accepted, decorative only given ``package-mode = false``). - ``pyright`` itself was never declared as a dependency anywhere, despite being this project's (and every generated project's) type checker — it only ever worked because it happened to be installed globally (confirmed: present on the system PATH but absent from the Poetry-managed venv). Added to both the CLI's own ``dev`` group and ``base``'s, so ``poetry install --with dev`` (or a bare ``poetry install``, since groups install by default) gets a working, reproducible ``pyright`` inside the project's own venv rather than relying on whatever happens to already be on a given machine. - Several dependency ranges had quietly fallen behind what's on PyPI, to the point their declared upper bound now excludes the actual latest release: ``sphinx`` ``<9.0.0`` (latest 9.1.0, both the CLI's own and ``base``'s), ``gunicorn`` ``<24.0.0`` (latest 26.2.0, ``flask``), ``flake8`` ``<7.0.0`` (latest 7.4.1, ``base``), ``isort`` ``<9.0.0`` (latest 9.0.2, ``base``), and ``mysqlclient``'s exact ``==2.2.4`` pin (latest 2.3.0, ``sqlalchemy``) — all bumped, each verified against the actual generated-project code that uses it (gunicorn's ``BaseApplication``/``load_config``/``load`` embedding API unchanged; flake8/isort flag the exact same pre-existing violations before and after, confirmed by diffing counts against the old version side by side, so CI silence about them isn't new; sphinx 9 builds the scaffolded docs with the same single pre-existing warning). ``selectolax`` had gone further still — ``<0.5.0`` against a real ``1.0.0`` release — and that one *is* a breaking change: 1.0 removes the old Modest-backed ``selectolax.parser`` module entirely in favor of ``selectolax.lexbor``. Confirmed the replacement (``LexborHTMLParser``/``LexborNode``) exposes the exact same shape (``css_first``/``css``/``.html``/``.text()``/``.attributes.get()``) the ``httpx``/``curl_cffi`` parser wrapper already relies on, so only the import line needed to change, in both templates' byte-identical copies. - Bumping ``selectolax`` surfaced a deeper, separate issue: ``poetry``'s ``pre_install`` always wrote ``python = "^{floor}"`` from just the bare version floor (``bare_python_version()`` strips any explicit upper bound before this point), so a real ``<4.0.0``-wide range got reconstructed regardless of what ``base`` originally declared — any dependency with its own upper Python bound (``selectolax``, previously capped at ``<3.15``, now ``<3.16``) could conflict with that unconditionally-wide reconstructed range under Poetry specifically, independent of which version was pinned; ``uv`` didn't hit this the same way since it wasn't validating against as wide an implied ceiling. Fixed by giving ``DependencySnapshot`` a real ``requires_python`` range (plus a per-dependency ``main_python`` marker, for cases like ``browser_manager``'s own ``>=3.13`` restriction) instead of just the bare floor, parsed via a new ``_pep508_parts()`` that splits a PEP 508 requirement into name/constraint/python-version marker. ``poetry``'s ``pre_install`` now uses ``requires_python`` directly instead of caret-synthesizing, writing a dependency with its own narrower range in Poetry's table form (``{version, python}``); ``uv``'s does the same for its PEP 508 dependency strings. ``base``'s own ``requires-python`` is narrowed to ``>=3.12,<3.16`` to match ``selectolax``'s real ceiling. Verified end-to-end: ``appcraft init selenium playwright httpx curl_cffi`` now locks and installs cleanly with Poetry. - ``git``'s ``post_install`` ran ``subprocess.check_call(["python", "tools", "git", "init", ...])`` directly — "python" there resolves to whichever interpreter is running ``appcraft`` itself, not the generated project's own (Poetry/Pipenv/uv) venv, so any dependency only installed in *that* venv (e.g. ``structlog`` from the ``logs`` template) raised ``ModuleNotFoundError`` the moment the subprocess imported anything needing it — reproduced with a real ``init git ... logs ...`` combo. ``playwright``'s ``post_install`` had the identical bug calling the bare ``playwright`` CLI. Both now go through ``PackageManagerBase().run_command()`` — the mechanism every other direct-subprocess call in the framework already uses, which wraps the command per active manager (``poetry run ...``/``pipenv run ...``/``uv run ...``) — instead of a raw ``subprocess.check_call``. Verified with a real ``init`` combining ``git``, ``github``, ``flask_api``, ``flask_ui``, ``sqlalchemy``, ``mongodb``, ``logs``, ``locales``, ``prompt_toolkit`` and ``docker`` together: branches get created, the app boots and serves a request. - ``sphinx_rtd_theme`` — the theme ``docs//source/conf.py`` actually sets (``html_theme``) and loads as an extension — was never declared as a dependency anywhere, the same class of gap as ``pyright`` above; it only ever built because something had it installed globally. Added to the CLI's own ``dev`` group. Verified by building the docs from a venv containing only the declared dev dependencies, nothing pre-installed. - ``TemplateCreator._create_pyproject()`` (the scaffolding step ``python -m appcraft.utils.template.creator `` runs, and the one the Contributing guide tells new contributors to use) wrote a ``[tool.appcraft.]`` stub — not a valid dependency declaration in any format ``dependency_convert.py`` reads, so a brand new template's own ``pyproject.toml`` fragment silently declared nothing, no matter what a contributor added under it unless they first noticed and replaced the whole table. Now generates a proper, empty ``[project]\ndependencies = [...]`` fragment with a comment pointing at the delivery-surface-group convention. - ``project_init.py`` passed a plain ``str`` where ``TemplateABC.install()`` (and ``TemplateAdder`` beneath it) expect ``Path | None`` — harmless at runtime (``TemplateAdder.__init__`` wraps it in ``Path(...)`` regardless) but a real strict-mode type error, caught sweeping ``pyright`` across the CLI's own ``scripts``/``utils`` and every template ``__init__.py`` rather than just the files touched directly this session. Wrapped in ``Path(...)`` at the call site. - Several ``docs/agents/*.md`` pages had drifted from the current code: ``templates-database.md`` described ``sqlalchemy``'s and ``mongodb``'s runners only in prose, without naming ``show_tables``/``show_columns``/``seed``/``list`` — a real gap against the "every runner must be documented" rule in ``AGENTS.md`` (the *user*-facing docs for both already named them correctly; only the agent docs were behind). ``templates-web-scraping.md`` had the same gap for ``search_template_docs``/``get_links``/``delegate_session.py``'s ``run()``. ``architecture.md`` and ``templates-misc.md`` still described ``TemplateAdder`` as merging "``pyproject.toml``/``Pipfile``" via ``merge_pm_files`` and CI as ``.github/workflows/static.yml`` — both stale after this same session's own earlier changes (``merge_pm_files`` removed, the workflow is ``docs.yml``). ``AGENTS.md`` still listed a ``realtime`` dependency-group that had already been folded into ``web``. The user-facing Contributing guide separately described ``locales``/``logs``/``ci_cd``/``data_analysis`` as having an unfixed ``Pipfile``-only dependency gap, and ``machine_learning``/``websockets``/``tests`` as having no dependency declaration at all — both already fixed earlier this session, just never reflected back into that page. 0.6.0b0 ---------- Added ~~~~~~~~ - ``docker`` template activated: a single Dockerfile (installs through whichever package manager is active, non-root user, skips dev-only deps in production) and a ``docker-compose.yml`` generated automatically from every deployable runner. - ``mongodb`` template. - ``pipenv``/``uv`` package-manager templates, swappable with ``poetry`` at any time (``appcraft init uv`` or ``appcraft add_template pipenv``), converting existing dependencies along the way. - ``web_scraping`` rebuilt as a shared base template with interchangeable engine adapters (``selenium``, ``playwright``, ``httpx``, ``curl_cffi``), including session handoff between an interactive (browser) engine and a faster reading-only one. - ``AGENTS.md`` as the canonical, cross-tool project-instructions file (also read by Cursor, GitHub Copilot, Windsurf, Codex, and others), with ``CLAUDE.md`` importing it for full Claude Code semantics. - Documentation moved to the repository root and versioned (``docs//``), with a version-selector page. - This Changelog and a Contact page. - ``LICENSE`` file (the project has always been MIT-licensed per its package metadata; the license text itself was missing). Changed ~~~~~~~~ - ``sqlalchemy`` adapter rebuilt around ``DatabasePort``/ports/use-cases; the stale, unused ``App`` ORM model removed. - ``flask`` split into a shared, non-standalone base plus ``flask_api``/``flask_ui``, composed via ``is_installed()`` instead of template-generation-time branching. - Package-manager ownership of ``pyproject.toml`` moved to ``base``; ``poetry`` demoted from a hard dependency to a swappable (still default) template. - Database/web-scraping ``Provider`` functions (``get_default_database_adapter()``, ``get_default_interactive_adapter()``, etc.) moved from ``application/providers/`` to ``infrastructure//provider.py``, correcting an ``application`` → ``infrastructure`` layering inversion. - ``web_scraping`` runners consolidated: one shared ``docs_search``/``page_links`` runner per action (default adapter picked via config or direct injection) instead of one runner per engine. - ``Repository``/``RepositoryBase`` now derive ``model``/``new_model`` automatically from their generic subscript, matching how ``BaseMapper`` already derived ``model``/``schema`` — a concrete repository no longer redeclares either. - Pydantic usage inside ``domain/`` centralized to a single file (``domain/models/core/pydantic.py``); every other ``domain/`` file imports Pydantic-derived names from there instead of ``pydantic`` directly, so dropping Pydantic later (e.g. for a MicroPython target) only means rewriting that one file. Fixed ~~~~~~~~ - ``appcraft init ``-triggered template swaps (e.g. ``appcraft init uv`` on a fresh project) crashing on a ``str``/``Path`` mismatch. - ``HTTPXAdapter``: ``RuntimeError: Event loop is closed`` raised from ``finish()`` when driven by synchronous code (a runner, or the benchmark tool). - ``init``/``add_template --help`` text hardcoding an outdated default-template list. - ``SearchDocsUseCase`` not actually working against async (Playwright) adapters despite its typing suggesting it did. - A broken "Contribute" link in this README. Removed ~~~~~~~~ - ``git``/``github`` templates deactivated pending migration off the pre-``Port``/``use_cases`` architecture (they still work, just hidden from ``list_templates``/``init`` unless ``--install-inactive`` is passed). - ``locales``/``logs`` templates deactivated (their dependencies were declared Pipfile-only, so a default Poetry project silently never installed them). - Per-engine ``web_scraping`` runner files and several dead, unused type aliases left over from before the runner consolidation above.