From b04427cbed657385f1d578eb1689e697de6c4043 Mon Sep 17 00:00:00 2001 From: Roger Chappel Date: Thu, 3 Sep 2026 13:56:48 +1000 Subject: [PATCH 1/3] test: cover Python package name normalization --- scripts/smoke-init.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/smoke-init.sh b/scripts/smoke-init.sh index 5b91280..84cce9c 100755 --- a/scripts/smoke-init.sh +++ b/scripts/smoke-init.sh @@ -175,6 +175,15 @@ grep -q "Refusing to overwrite" overwrite.err node "$repo_root/dist/index.js" init oss-cli smoke-app --force > force.json node "$repo_root/dist/index.js" init python-api py-api > py.json test -f py-api/src/py_api/main.py +node "$repo_root/dist/index.js" init python-api 'My API' > python-spaced.json +test -f 'My API/src/my_api/main.py' +grep -q 'name = "my-api"' 'My API/pyproject.toml' +node "$repo_root/dist/index.js" init python-api '123 Weird.API!' > python-punctuated.json +test -f '123 Weird.API!/src/_123_weird_api/main.py' +grep -q 'name = "123-weird-api"' '123 Weird.API!/pyproject.toml' +python3 -m venv python-venv +python-venv/bin/python -m pip install './My API' './123 Weird.API!' +python-venv/bin/python -c 'from my_api.main import app; from _123_weird_api.main import app as numbered_app; assert app.title == "My API"; assert numbered_app.title == "123 Weird.API!"' node "$repo_root/dist/index.js" init next-app web-app > next.json test -f web-app/src/app/page.tsx test -f web-app/eslint.config.mjs From 288f30aed79cd7a41d53cfbcaa2b693723115274 Mon Sep 17 00:00:00 2001 From: Roger Chappel Date: Thu, 3 Sep 2026 13:56:48 +1000 Subject: [PATCH 2/3] fix: normalize Python distribution and module names --- src/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index c4781d7..5069881 100644 --- a/src/index.ts +++ b/src/index.ts @@ -288,10 +288,11 @@ function parseGithubVisibility(value: string): GithubVisibility { function buildVariables(projectName: string, overrides: string[]): Record { const packageSlug = slugify(projectName); + const packageModule = packageSlug.replaceAll('-', '_').replace(/^(?=\d)/, '_'); const values: Record = { PROJECT_NAME: projectName, PACKAGE_NAME: packageSlug, - PACKAGE_MODULE: packageSlug.replaceAll('-', '_'), + PACKAGE_MODULE: packageModule, PACKAGE_DESCRIPTION: `${projectName} generated by StackForge.`, PROJECT_DESCRIPTION: `${projectName} generated by StackForge.`, AUTHOR_NAME: 'StackForge User', @@ -602,7 +603,7 @@ function nextTypeScriptConfigTemplate(): string { function pythonProjectTemplate(): string { return `[project] -name = "{{PROJECT_NAME}}" +name = "{{PACKAGE_NAME}}" version = "0.1.0" description = "{{PROJECT_DESCRIPTION}}" requires-python = ">=3.11" @@ -611,5 +612,8 @@ dependencies = ["fastapi>=0.115.0", "uvicorn>=0.34.0"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/{{PACKAGE_MODULE}}"] `; } From c7a2e9c77e402b45bcd15cd406db390c921c3570 Mon Sep 17 00:00:00 2001 From: Roger Chappel Date: Thu, 3 Sep 2026 13:56:48 +1000 Subject: [PATCH 3/3] docs: explain Python naming normalization --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index cf3aace..7f0befe 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,13 @@ StackForge is local-first and review-friendly: - `next-app`: Next.js application - `python-api`: Python API service +Project directory and display names may contain spaces or punctuation. For the +`python-api` template, StackForge normalizes that name for Python packaging: +`My API` becomes distribution `my-api` and import module `my_api`. Import +modules whose normalized name begins with a digit receive a leading underscore +(for example, `123 API` becomes `_123_api`). The original name remains the +generated directory name and FastAPI display title. + Generated repositories include `scripts/validate.sh` as the default local verification path. It runs the repo's normal local package checks when they exist, including `release:check`, and only runs `agent-qc ready` when `agent-qc` is installed, so `agent-qc` stays optional. The `next-app` scaffold ships an npm lockfile, `.gitignore`, `next-env.d.ts`,