6. Changelog

Formal changelog tracking starts with 0.6.0 — for anything earlier, see the commit history on GitHub.

6.1. 1.0.0b2

6.1.1. Fixed

  • TemplateABC.package_manager and TemplateAdder’s own package_manager parameter both defaulted to PoetryManager() — a class attribute and a function parameter default are both evaluated once, at definition time, so this constructed a PoetryManager() the instant template_abc.py/adder.py were imported, essentially every appcraft invocation, regardless of which package manager the user actually wanted. PoetryManager.__init__ checks whether a venv is active and, if not, used to shell out to find or install Poetry — so a uv-only user with no Poetry installed and no active venv could hit a crash, or an unwanted automatic install attempt, before ever reaching their own template choices. Removed both attributes entirely: their only consumer was already-dead code (see below), so there was never a legitimate reason for the CLI engine to probe for or install Poetry on behalf of a project that may not even be using it.

  • check_and_install_package_manager() in poetry/uv/pipenv’s own package-manager adapters checked pip show <tool> and fell back to pip install <tool> if that failed — wrong on two counts: a standalone installer (Poetry’s own script, uv’s, pipx) never registers the tool as a pip package in the current interpreter, so a perfectly working install still read as “not found”; and the pip install fallback assumes pip itself is present, which a venv uv itself created deliberately isn’t. Now checks whether the command actually runs (<tool> --version), regardless of how it was installed, and only falls back to pip if pip itself is usable — otherwise it prints a clear message pointing at that tool’s own install docs instead of a bare traceback. Verified end-to-end: installed appcraft itself via uv into a venv with Poetry absent from PATH and no pip inside the venv, then ran appcraft init uv for real — created the project and generated uv.lock with no manual steps.

  • runners/tools/git.py’s release() created the platform release with notes=f"Release {tag}" — no changelog, no summary, no link anywhere. Now renders the actual Conventional Commits changelog for the release (the same renderer the changelog command already uses).

6.2. 1.0.0b1

6.2.1. 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.

6.2.2. 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 <group> / uv sync --no-group <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 <group> 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.

6.2.3. 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 <email>"] 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/<version>/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 <name> runs, and the one the Contributing guide tells new contributors to use) wrote a [tool.appcraft.<name>] 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.

6.3. 0.6.0b0

6.3.1. 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/<version>/), 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).

6.3.2. 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/<concept>/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.

6.3.3. Fixed

  • appcraft init <package-manager>-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.

6.3.4. 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.