6. Changelog
Formal changelog tracking starts with 0.6.0 — for anything earlier, see the commit history on GitHub.
6.1. 1.0.0b1
6.1.1. Added
gittemplate 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}-alphaline, and acommit-msghook blocks direct breaking commits on major-version branches.Stage-based releases:
python tools git releaseproduces 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_repositorytemplate: the shared foundation for hosting platforms, with a genericcreate_repo/delete_reporunner and a provider that picks the default platform (config/remote_repository.toml).githubtemplate activated as the GitHub adapter forremote_repository, using theghCLI. 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 owngit/remote_repository/githubtemplates to release appcraft itself, configured for major-version branches withbeta/mainstages (noalphayet). 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, thegit/ghsubprocess adapters) has no such dependency, so this script reuses it directly (merging the four templates’files/trees into a throwaway scratch directory viaTemplateAdder, the same mechanismadd_templateitself 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’sget_default_logger(), following the sameis_installed()-based pattern as the database/web-scraping/remote-repository providers: resolves to thelogstemplate’s richer logger when installed, the zero-dependency default otherwise.Logger.named(name): a view of the same singleton logger that showsnamein 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.1.2. Changed
gitandgithubmigrated to the framework’s current conventions (Port/UseCase/Adapter). Their previous code usedapplication/services/andAdapterInterface, which didn’t exist anywhere in the framework.The project’s version lives only in
pyproject.toml([project]or[tool.poetry]).config/app.tomlno longer has aversionkey, andAppManager.versionreads it frompyproject.toml(APPCRAFT_VERSIONstill overrides it).The documentation is no longer kept in
docs/latest; it lives in the version folder of each release, andlatestonly redirects to the newest one.BaseMapperrenamed toMapperBase, matchingRepositoryBase’s suffix instead of prefixing “Base” — the two classes play the same role (derivingmodel/schemaormodel/new_modelfrom a generic subscript viaPropertyMeta) and are now named the same way.FilterInterfacerenamed toFilter, matching howValueObjectis already named: both are pure, zero-behavior contracts that already have a*Basesibling with the shared concrete implementation (FilterBase,ValueObjectBase), so the plain name goes to the contract instead of the suffix.LoggerInterfaceremoved and merged intoLoggerBase. UnlikeFilter/ValueObject, it wasn’t a pure contract to begin with — it already inheritedlogging.Loggerdirectly — and nothing in the framework subclassed it independently ofLoggerBase, so the separate class added a layer without adding a guarantee.The
HTMLElementInterface/HTMLReadingElementInterface/HTMLNavigationElementInterface/HTMLInteractiveElementInterfacechain renamed toHTMLElement/HTMLReadingElement/HTMLNavigationElement/HTMLInteractiveElement. None of the four had a*Basesibling, and the chain is already distinguished by its own words (Reading/Navigation/Interactive), not by the suffix.HTMLParserInterface(httpx,curl_cffi) renamed toHTMLParser. Theselectolaxbackend’s own import of its realHTMLParserclass is now aliased toLibHTMLParserto avoid colliding with it.LoggerBaserenamed toLogger(bare, no suffix): onceLoggerInterfacewas merged into it, nothing else needed telling apart from it. This required two supporting changes first —Initializer’s own emptyclass Logger(LoggerBase): passwas removed in favor of calling the newget_default_logger()provider directly, and thelogstemplate’s real, structlog-backedLoggerwas renamed toStructlogLogger— otherwise either one would have collided with the bare name.The
base/subfolder holding just one*Baseclass — never a documented convention, unlikeinterface/— collapsed into the package’s own__init__.pyin 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, andinfrastructure/utils/logger/base/__init__.py→infrastructure/utils/logger/__init__.py. Matches howapplication/repositories/__init__.pyalready heldRepository,RepositoryBaseandGenericRepositorytogether, with no subfolder.BaseConfigrenamed toConfigBase, matching its own siblings’ suffix style (TomlConfig,JsonConfig,EnvConfig,PyProjectConfig) instead of prefixing “Base”. UnlikeFilter/ValueObject, it keeps theBase/suffix shape rather than going bare —Configis already a different, concrete, heavily-used class (the composed config reader), not a synonym for the abstract root.domain/value_objects/base/__init__.pycollapsed intodomain/value_objects/__init__.pythe same way, now holdingValueObject,ValueObjectBase,IntValueObjectandStringValueObjecttogether.IAppProvider/AppProviderrenamed toAppPort/AppAdapterand moved toapplication/ports/app.py/infrastructure/app/adapter.py. This wasn’t just anI-prefix fix: it was the one remaining “Provider” implemented as an ABC-plus-one-Adapter class pair instead of a plainget_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 whatPort/Adapteralready name elsewhere in the framework. The now-emptyapplication/providers/package was removed.infrastructure/framework/appcraft/core/,app/andutils/moved toinfrastructure/core/,infrastructure/app/andinfrastructure/utils/— directly underinfrastructure/, no longer nested under the templating engine’s own namespace. None of the three is about appcraft’s templating mechanism:coreis cross-cutting runtime machinery (config, the runner system, error handling, package managers) with no single concept of its own,appis just another concept-specificPort/Adapterpair likedatabase/git/web_scrapingand belongs at the same depth as them, andutilsis generic helpers (Printer,ComponentPrinter, the logger).infrastructure/framework/appcraft/templates/— theis_installed()markers — is the one part that’s genuinely about the templating mechanism, and stayed put.git’s andremote_repository’s domain exceptions standardized on theErrorsuffix:GitException/NotAGitRepositoryError/NotAReleaseBranchError/FirstReleaseNotEntryErrormixedExceptionon the base class withErroron every child, same forRemoteRepositoryExceptionand its three children — both families now readGitError/RemoteRepositoryErrorat 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_objectsareError-suffixed throughout, with noException-suffixed root at all — so they were renamed too (WebScrapingError,NoSuchElementError,BrowserNotInstalledError,NoBrowsersInstalledError), makingErrorthe one suffix used everywhere in the framework.Every
pyproject.tomlin the repository — the CLI’s own root file and every template’s shippedfiles/pyproject.tomlfragment (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 adependencieslist 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 somethingpoetry/pipenv/uv’s ownpre_installwrites 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, plussqlalchemy’s ownflask-sqlalchemywhen both are installed, plus the unrelatedwebsocketsstub’s dependencies — a websocket server is another network-facing delivery surface, same bucket, even though neither template depends on the other),cli(prompt_toolkit). Aguigroup 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 madedatabase/web_scraping/i18n/observability/datatheir own excludable groups too, and gavetestsits 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 intodevinstead.) Every group installs by default (never marked optional) and is excludable withpoetry install --without <group>/uv sync --no-group <group>— e.g.--without webfor a CLI-only build that keeps every business dependency but drops the web server.uv’spre_installnow also sets[tool.uv].default-groups(listing every group,devincluded — 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-optionalgroups.pipenv’spre_installnow flattens every group,devincluded, into[packages]— Pipfile has no “default but individually excludable” named-group concept (a barepipenv installonly 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-pythonraised from>=3.9to>=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 rootpyproject.toml, so a contributor editing one template’s source couldpoetry 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 justdev(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 theappcraft/templates/temp/filesdev loop, which installs that template’s real dependencies into a separate venv of its own.)The CLI’s own root
pyproject.tomlgained a full[project.urls]table (Homepage/Repository/Documentation/Changelog/Issues, all pointing at the real GitHub repo and docs site — confirmed theChangelogURL matches a real built page),keywords, broaderclassifiers(specific Python versions,Environment :: Console,Intended Audience :: Developers,Topic :: Software Development :: Code Generators), and an explicit PEP 639license-files = ["LICENSE"]instead of relying on setuptools’ implicit auto-discovery. Verified by actually building a real sdist (python -m build --sdist) and inspectingPKG-INFO: every field lands correctly, includingLicense-Expression: MIT/License-File: LICENSE(Metadata-Version2.4, required for those two).[build-system].requires’"toml"entry was checked and kept, not removed —setup.pygenuinely imports it at build time to readpyproject.tomldirectly forpackage_data/the legacyurl=kwarg, confirmed by reading the file rather than assumed.base/files/pyproject.toml’s[project.urls]had a real bug:Repository/Documentationpointed atgithub.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 genericexample.complaceholders, matchingHomepage’s existing style. base also gained aLICENSEfile (MIT, placeholder copyright holder matching the existingauthorsplaceholder) andlicense-files = ["LICENSE"]— previouslylicense = "MIT"was declared with no actual license file shipped to back it. Note:license-filesdoesn’t survive Poetry’s own conversion (poetryhas no[tool.poetry]equivalent field) — accepted as-is rather than engineered around, since a Poetry-managed generated project also setspackage-mode = falseand 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 rootpyproject.toml(anIssueslink added; specific supported Python versions,Environment :: Console,Intended Audience :: Developersadded to classifiers;clean-architectureadded 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 - Betawas also changed to3 - Alpha: a project that was just scaffolded, with no code of the user’s own written yet, calling itself “Beta” was never accurate.
6.1.3. Fixed
docs/scripts/generate_latest_redirect.pyonly ever wrotebuild/latest/index.html, so a deep link likelatest/getting_started.htmlorlatest/templates/index.html404’d even thoughlatest/index.htmlitself 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.htmlpage found there.RepositoryBase.create()was an empty stub that returnedNoneinstead of forwarding to the adapter, unlike every other method on the class. A repository that didn’t overridecreate()itself silently created nothing.ConfigBase._load()was dead code — nothing called it — and was removed.JsonConfig.__init__was a no-op override ofConfigBase.__init__with the same signature and default; removed.Config.load_file()passed a full file path (e.g."config/database.toml") toPyProjectConfig.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 triespyproject.tomlbeforeload_file()is ever reached), so it was removed rather than fixed in place.locales,logsanddata_analysisdeclared their real dependencies only in aPipfile, so a default Poetry project never installed them — each now also ships apyproject.tomlfragment.logs’ fragment keeps onlystructlog(the one dependency the template’s code actually imports) rather than carrying overbetter-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 emptyPipfilewasn’t a bug: its runner has no real dependency yet.TemplateAdder.union_dictsdeduplicated merged lists withset(dict1[prop]), which crashed (unhashable type) the moment a merged list held tables instead of plain strings — exposed bybase’s ownpyproject.tomlswitching to PEP 621’sauthors = [{name = ..., email = ...}]form (the previous Poetry-styleauthors = ["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’spre_installusedpyproject.setdefault("build-system", ...), which only ever filled in a missing key — harmless whilebase’s ownpyproject.tomlhappened to already declare the matchingpoetry-corebackend, but oncebaseswitched to a plainsetuptoolsdefault (package-manager-agnostic, see above) thesetdefaultsilently stopped firing, leaving a Poetry-managed project with the wrong build backend. Now overwritten unconditionally.uv’s ownpre_installhad the mirror problem one level further: swapping touvleft whatever backend was already in[build-system]in place, anduv synctries 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 havinguv’spre_installset[tool.uv]\npackage = false(uv’s equivalent of Poetry’spackage-mode = false) and drop[build-system]entirely, since it’s irrelevant once the project itself is never built.flask-sqlalchemywas imported byinfrastructure/framework/flask/sqlalchemy.pybut declared in neitherflask’s norsqlalchemy’spyproject.toml— onlysqlalchemy’s oldPipfilehappened to carry it, so a default Poetry project combiningflask``+``sqlalchemynever actually installed it (silently masked:FlaskSQLAlchemy(self.app).init_app()catches the resulting error and just logs it). Now declared insqlalchemy’s manifest, in both formats.sqlalchemy’sPipfilealso had two more unrelated problems:psycopg2-binaryinstead of thepsycopg(v3) driver the adapter actually targets, andflask-migrate, which nothing imports anywhere — dropped.mongodb’sPipfilecarriedmongoengine/flask-pymongo/motor/mongomock, none of which anything in the template imports either — trimmed to justpymongo, its one real dependency.Every template’s
Pipfile(where one exists) now mirrors itspyproject.tomlfragment’s exact PEP 508 version range instead of an unconstrained"*"or a stale pin, soread_dependencies()resolves the same effective dependency set regardless of which format a project started from.No template ships a
Pipfileany 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 apyproject.tomlfragment and thePipfiledeleted (machine_learning/tests/websocketspreviously had nopyproject.tomlat all). This was safe to do unconditionally: the per-templatePipfile-merge path had never actually worked, sinceTemplateABC.package_manageralways defaults toPoetryManager()and no template (not evenpipenv/uvthemselves) ever overrode it, soTemplateAdder’s Pipenv-specific branches never fired for any template — confirmed empirically (add_template mongodbagainst an already-Pipenv-converted project left itsPipfilecompletely unchanged).TemplateAdder.merge_pm_filesand the deadPipfile/Pipfile.lockskip-condition in_copy_directory_contentswere removed along with it;TemplateABC.install()now callsTemplateAdder.merge_toml()directly. A Pipenv-managed project’s actualPipfilestill exists, same as before — it’spipenv’s own generated output (written by itspre_installfrom the mergedpyproject.toml), never a file copied from a template.appcraft/utils/dependency_convert.pyonly read/wrote three hardcoded URL labels (Homepage/Repository/Documentation), so any other[project.urls]entry (e.g. anIssueslink) was silently dropped by any package-manager conversion. Generalized to a singlemetadata["urls"]dict carrying every label through —poetry’spre_installnow 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 viapoetry 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 realIssuesURL surviving a full poetry → uv → poetry round-trip.uv’spre_installwrotelicenseas the legacy{text = "..."}table form instead of a plain PEP 639 SPDX string, even thoughmetadata["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-filesalso now round-trips through a poetry→uv (or any format with a native[project]table) leg, via the same genericmetadatadict — it still doesn’t survive a leg through Poetry itself, which has no[tool.poetry]equivalent field (unchanged, accepted, decorative only givenpackage-mode = false).pyrightitself 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 owndevgroup andbase’s, sopoetry install --with dev(or a barepoetry install, since groups install by default) gets a working, reproduciblepyrightinside 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 andbase’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), andmysqlclient’s exact==2.2.4pin (latest 2.3.0,sqlalchemy) — all bumped, each verified against the actual generated-project code that uses it (gunicorn’sBaseApplication/load_config/loadembedding 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).selectolaxhad gone further still —<0.5.0against a real1.0.0release — and that one is a breaking change: 1.0 removes the old Modest-backedselectolax.parsermodule entirely in favor ofselectolax.lexbor. Confirmed the replacement (LexborHTMLParser/LexborNode) exposes the exact same shape (css_first/css/.html/.text()/.attributes.get()) thehttpx/curl_cffiparser wrapper already relies on, so only the import line needed to change, in both templates’ byte-identical copies.Bumping
selectolaxsurfaced a deeper, separate issue:poetry’spre_installalways wrotepython = "^{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 whatbaseoriginally 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;uvdidn’t hit this the same way since it wasn’t validating against as wide an implied ceiling. Fixed by givingDependencySnapshota realrequires_pythonrange (plus a per-dependencymain_pythonmarker, for cases likebrowser_manager’s own>=3.13restriction) 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’spre_installnow usesrequires_pythondirectly 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 ownrequires-pythonis narrowed to>=3.12,<3.16to matchselectolax’s real ceiling. Verified end-to-end:appcraft init selenium playwright httpx curl_cffinow locks and installs cleanly with Poetry.git’spost_installransubprocess.check_call(["python", "tools", "git", "init", ...])directly — “python” there resolves to whichever interpreter is runningappcraftitself, not the generated project’s own (Poetry/Pipenv/uv) venv, so any dependency only installed in that venv (e.g.structlogfrom thelogstemplate) raisedModuleNotFoundErrorthe moment the subprocess imported anything needing it — reproduced with a realinit git ... logs ...combo.playwright’spost_installhad the identical bug calling the bareplaywrightCLI. Both now go throughPackageManagerBase().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 rawsubprocess.check_call. Verified with a realinitcombininggit,github,flask_api,flask_ui,sqlalchemy,mongodb,logs,locales,prompt_toolkitanddockertogether: branches get created, the app boots and serves a request.sphinx_rtd_theme— the themedocs/<version>/source/conf.pyactually sets (html_theme) and loads as an extension — was never declared as a dependency anywhere, the same class of gap aspyrightabove; it only ever built because something had it installed globally. Added to the CLI’s owndevgroup. Verified by building the docs from a venv containing only the declared dev dependencies, nothing pre-installed.TemplateCreator._create_pyproject()(the scaffolding steppython -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 formatdependency_convert.pyreads, so a brand new template’s ownpyproject.tomlfragment 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.pypassed a plainstrwhereTemplateABC.install()(andTemplateAdderbeneath it) expectPath | None— harmless at runtime (TemplateAdder.__init__wraps it inPath(...)regardless) but a real strict-mode type error, caught sweepingpyrightacross the CLI’s ownscripts/utilsand every template__init__.pyrather than just the files touched directly this session. Wrapped inPath(...)at the call site.Several
docs/agents/*.mdpages had drifted from the current code:templates-database.mddescribedsqlalchemy’s andmongodb’s runners only in prose, without namingshow_tables/show_columns/seed/list— a real gap against the “every runner must be documented” rule inAGENTS.md(the user-facing docs for both already named them correctly; only the agent docs were behind).templates-web-scraping.mdhad the same gap forsearch_template_docs/get_links/delegate_session.py’srun().architecture.mdandtemplates-misc.mdstill describedTemplateAdderas merging “pyproject.toml/Pipfile” viamerge_pm_filesand CI as.github/workflows/static.yml— both stale after this same session’s own earlier changes (merge_pm_filesremoved, the workflow isdocs.yml).AGENTS.mdstill listed arealtimedependency-group that had already been folded intoweb. The user-facing Contributing guide separately describedlocales/logs/ci_cd/data_analysisas having an unfixedPipfile-only dependency gap, andmachine_learning/websockets/testsas having no dependency declaration at all — both already fixed earlier this session, just never reflected back into that page.
6.2. 0.6.0b0
6.2.1. Added
dockertemplate activated: a single Dockerfile (installs through whichever package manager is active, non-root user, skips dev-only deps in production) and adocker-compose.ymlgenerated automatically from every deployable runner.mongodbtemplate.pipenv/uvpackage-manager templates, swappable withpoetryat any time (appcraft init uvorappcraft add_template pipenv), converting existing dependencies along the way.web_scrapingrebuilt 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.mdas the canonical, cross-tool project-instructions file (also read by Cursor, GitHub Copilot, Windsurf, Codex, and others), withCLAUDE.mdimporting 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.
LICENSEfile (the project has always been MIT-licensed per its package metadata; the license text itself was missing).
6.2.2. Changed
sqlalchemyadapter rebuilt aroundDatabasePort/ports/use-cases; the stale, unusedAppORM model removed.flasksplit into a shared, non-standalone base plusflask_api/flask_ui, composed viais_installed()instead of template-generation-time branching.Package-manager ownership of
pyproject.tomlmoved tobase;poetrydemoted from a hard dependency to a swappable (still default) template.Database/web-scraping
Providerfunctions (get_default_database_adapter(),get_default_interactive_adapter(), etc.) moved fromapplication/providers/toinfrastructure/<concept>/provider.py, correcting anapplication→infrastructurelayering inversion.web_scrapingrunners consolidated: one shareddocs_search/page_linksrunner per action (default adapter picked via config or direct injection) instead of one runner per engine.Repository/RepositoryBasenow derivemodel/new_modelautomatically from their generic subscript, matching howBaseMapperalready derivedmodel/schema— a concrete repository no longer redeclares either.Pydantic usage inside
domain/centralized to a single file (domain/models/core/pydantic.py); every otherdomain/file imports Pydantic-derived names from there instead ofpydanticdirectly, so dropping Pydantic later (e.g. for a MicroPython target) only means rewriting that one file.
6.2.3. Fixed
appcraft init <package-manager>-triggered template swaps (e.g.appcraft init uvon a fresh project) crashing on astr/Pathmismatch.HTTPXAdapter:RuntimeError: Event loop is closedraised fromfinish()when driven by synchronous code (a runner, or the benchmark tool).init/add_template --helptext hardcoding an outdated default-template list.SearchDocsUseCasenot actually working against async (Playwright) adapters despite its typing suggesting it did.A broken “Contribute” link in this README.
6.2.4. Removed
git/githubtemplates deactivated pending migration off the pre-Port/use_casesarchitecture (they still work, just hidden fromlist_templates/initunless--install-inactiveis passed).locales/logstemplates deactivated (their dependencies were declared Pipfile-only, so a default Poetry project silently never installed them).Per-engine
web_scrapingrunner files and several dead, unused type aliases left over from before the runner consolidation above.