spawnio 1.0.8__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spawnio-1.0.8 → spawnio-1.1.0}/CONTRIBUTING.md +10 -1
- {spawnio-1.0.8 → spawnio-1.1.0}/PKG-INFO +42 -4
- {spawnio-1.0.8 → spawnio-1.1.0}/README.md +41 -3
- spawnio-1.1.0/docs/architecture.md +290 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/docs/changelog.md +47 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/docs/commands.md +103 -23
- {spawnio-1.0.8 → spawnio-1.1.0}/docs/getting-started.md +3 -2
- spawnio-1.1.0/docs/inspection/inspection-contract.md +987 -0
- spawnio-1.1.0/docs/inspection/language-framework-handoff.md +57 -0
- spawnio-1.1.0/docs/inspection/safe-discovery.md +123 -0
- spawnio-1.1.0/docs/inspection/supported-technologies.md +182 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/pyproject.toml +12 -1
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/__init__.py +1 -1
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/cli/app.py +129 -66
- spawnio-1.1.0/src/spawn/cli/errors.py +29 -0
- spawnio-1.1.0/src/spawn/cli/inspect_render.py +287 -0
- spawnio-1.1.0/src/spawn/cli/noninteractive.py +125 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/cli/prompts.py +7 -11
- spawnio-1.1.0/src/spawn/core/exceptions.py +50 -0
- spawnio-1.1.0/src/spawn/core/models.py +58 -0
- spawnio-1.0.8/src/spawn/cli/noninteractive.py → spawnio-1.1.0/src/spawn/core/planning.py +33 -91
- spawnio-1.1.0/src/spawn/core/project.py +59 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/generators/custom_structure.py +38 -15
- spawnio-1.1.0/src/spawn/generators/destination.py +66 -0
- spawnio-1.1.0/src/spawn/generators/metadata.py +45 -0
- spawnio-1.1.0/src/spawn/generators/pipeline.py +32 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/generators/project_files.py +2 -2
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/generators/project_generator.py +44 -43
- spawnio-1.1.0/src/spawn/github/exceptions.py +5 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/github/publisher.py +2 -1
- spawnio-1.1.0/src/spawn/inspection/__init__.py +69 -0
- spawnio-1.1.0/src/spawn/inspection/_reading.py +21 -0
- spawnio-1.1.0/src/spawn/inspection/base.py +68 -0
- spawnio-1.1.0/src/spawn/inspection/dependencies.py +410 -0
- spawnio-1.1.0/src/spawn/inspection/discovery.py +280 -0
- spawnio-1.1.0/src/spawn/inspection/exceptions.py +14 -0
- spawnio-1.1.0/src/spawn/inspection/frameworks.py +255 -0
- spawnio-1.1.0/src/spawn/inspection/languages.py +195 -0
- spawnio-1.1.0/src/spawn/inspection/models.py +615 -0
- spawnio-1.1.0/src/spawn/inspection/orchestrator.py +294 -0
- spawnio-1.1.0/src/spawn/inspection/serialization.py +491 -0
- spawnio-1.1.0/src/spawn/inspection/tooling.py +469 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/git.py +9 -6
- spawnio-1.1.0/src/spawn/utils/redact.py +8 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/success.py +8 -2
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/uv.py +7 -5
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/validators.py +6 -4
- spawnio-1.1.0/tests/conftest.py +54 -0
- spawnio-1.1.0/tests/fixtures/__init__.py +0 -0
- spawnio-1.1.0/tests/fixtures/archetypes.py +11 -0
- spawnio-1.1.0/tests/fixtures/inspection_corpus.py +175 -0
- spawnio-1.1.0/tests/fixtures/make_sample_report.py +363 -0
- spawnio-1.1.0/tests/fixtures/static/broken_meta/.spawn/meta.json +1 -0
- spawnio-1.1.0/tests/fixtures/static/meta_unknown_intent/.spawn/meta.json +11 -0
- spawnio-1.1.0/tests/fixtures/static/non_spawn_python_repo/pyproject.toml +3 -0
- spawnio-1.1.0/tests/fixtures/static/non_spawn_python_repo/src/pkg/__init__.py +1 -0
- spawnio-1.1.0/tests/fixtures/static/non_spawn_python_repo/tests/test_x.py +2 -0
- spawnio-1.1.0/tests/fixtures/static/sample_report.json +316 -0
- spawnio-1.1.0/tests/fixtures/trees/agent.txt +20 -0
- spawnio-1.1.0/tests/fixtures/trees/automation.txt +20 -0
- spawnio-1.1.0/tests/fixtures/trees/backend-api.txt +16 -0
- spawnio-1.1.0/tests/fixtures/trees/chatbot.txt +22 -0
- spawnio-1.1.0/tests/fixtures/trees/cli.txt +12 -0
- spawnio-1.1.0/tests/fixtures/trees/custom.txt +4 -0
- spawnio-1.1.0/tests/fixtures/trees/data.txt +12 -0
- spawnio-1.1.0/tests/fixtures/trees/mcp.txt +11 -0
- spawnio-1.1.0/tests/fixtures/trees/rag.txt +21 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_app.py +35 -22
- spawnio-1.1.0/tests/test_boundaries.py +35 -0
- spawnio-1.1.0/tests/test_create_flags.py +203 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_custom_structure.py +5 -3
- spawnio-1.1.0/tests/test_destination.py +238 -0
- spawnio-1.1.0/tests/test_docs.py +44 -0
- spawnio-1.1.0/tests/test_errors.py +219 -0
- spawnio-1.1.0/tests/test_fixtures.py +158 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_generator.py +4 -2
- spawnio-1.1.0/tests/test_inspect_cli.py +350 -0
- spawnio-1.1.0/tests/test_inspection_dependencies.py +537 -0
- spawnio-1.1.0/tests/test_inspection_discovery.py +530 -0
- spawnio-1.1.0/tests/test_inspection_frameworks.py +327 -0
- spawnio-1.1.0/tests/test_inspection_languages.py +221 -0
- spawnio-1.1.0/tests/test_inspection_models.py +773 -0
- spawnio-1.1.0/tests/test_inspection_orchestrator.py +445 -0
- spawnio-1.1.0/tests/test_inspection_regression.py +477 -0
- spawnio-1.1.0/tests/test_inspection_sample.py +486 -0
- spawnio-1.1.0/tests/test_inspection_serialization.py +791 -0
- spawnio-1.1.0/tests/test_inspection_tooling.py +605 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_models.py +44 -0
- spawnio-1.1.0/tests/test_pipeline.py +135 -0
- spawnio-1.1.0/tests/test_planning.py +160 -0
- spawnio-1.1.0/tests/test_project.py +88 -0
- spawnio-1.0.8/.coderabbit.yaml +0 -105
- spawnio-1.0.8/.github/ISSUE_TEMPLATE/bug_report.yml +0 -77
- spawnio-1.0.8/.github/ISSUE_TEMPLATE/config.yml +0 -5
- spawnio-1.0.8/.github/ISSUE_TEMPLATE/feature_request.yml +0 -28
- spawnio-1.0.8/.github/PULL_REQUEST_TEMPLATE.md +0 -24
- spawnio-1.0.8/.github/labeler.yml +0 -25
- spawnio-1.0.8/.github/workflows/ci.yml +0 -29
- spawnio-1.0.8/.github/workflows/pr-check.yml +0 -60
- spawnio-1.0.8/.github/workflows/publish.yml +0 -24
- spawnio-1.0.8/.python-version +0 -1
- spawnio-1.0.8/assets/preview.png +0 -0
- spawnio-1.0.8/docs/architecture.md +0 -199
- spawnio-1.0.8/src/spawn/core/exceptions.py +0 -8
- spawnio-1.0.8/src/spawn/core/models.py +0 -22
- spawnio-1.0.8/src/spawn/github/exceptions.py +0 -5
- spawnio-1.0.8/uv.lock +0 -333
- {spawnio-1.0.8 → spawnio-1.1.0}/.gitignore +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/LICENSE +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/cli/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/core/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/core/registry.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/generators/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/github/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/github/validators.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/agent/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/agent/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/automation/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/automation/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/backend_api/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/backend_api/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/base.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/chatbot/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/chatbot/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/cli_application/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/cli_application/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/analysis/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/analysis/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/dashboard/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/dashboard/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/etl/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/etl/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/ml/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/ml/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/mcp_server/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/mcp_server/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/rag/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/rag/content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/shared_content.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/__init__.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/banner.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/console.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/doctor.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/doctor_fix.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/theme.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_agent_generator.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_agent_template.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_automation_generator.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_automation_template.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_chatbot_generator.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_chatbot_template.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_cli_application_generator.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_cli_application_template.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_console.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_custom_structure_integration.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_analysis.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_dashboard.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_etl.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_gitignore.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_ml.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_doctor.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_doctor_fix.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_git.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_github_publisher.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_github_validators.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_mcp_server_generator.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_mcp_server_template.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_noninteractive.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_project_files.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_prompts.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_rag_generator.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_rag_template.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_registry.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_templates.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_uv.py +0 -0
- {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_validators.py +0 -0
|
@@ -35,6 +35,15 @@ uv run pytest --cov=src/spawn --cov-report=term-missing
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
+
## Testing and fixtures
|
|
39
|
+
|
|
40
|
+
- **Factory:** the `spawn_project` fixture in `tests/conftest.py` generates a project offline: `spawn_project("backend-api", framework="fastapi")` returns its `Path`. The `no_toolchain` fixture stubs git, uv and package installs.
|
|
41
|
+
- **Golden trees:** `tests/fixtures/trees/<archetype>.txt` lists every file of a generated archetype. After an intentional output change, regenerate with `SPAWN_UPDATE_TREES=1 uv run pytest tests/test_fixtures.py` and review the diff.
|
|
42
|
+
- **Offline rule:** tests must not use the network, real `git` or real `uv`; use `no_toolchain`.
|
|
43
|
+
- **Adding an archetype:** add an entry to `tests/fixtures/archetypes.py`, run once with `SPAWN_UPDATE_TREES=1` to create its golden tree, and review it.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
38
47
|
## Linting and formatting
|
|
39
48
|
|
|
40
49
|
```bash
|
|
@@ -126,7 +135,7 @@ Add two test files mirroring the existing pattern:
|
|
|
126
135
|
| File | What to test |
|
|
127
136
|
|---|---|
|
|
128
137
|
| `tests/test_<slug>_template.py` | Template constants, `get_dependencies()`, `get_readme_content()`, `py_compile` on each Python starter file, no unescaped braces |
|
|
129
|
-
| `tests/test_<slug>_generator.py` | `ProjectGenerator().generate()` with mocked uv/git/install, directory structure, README content, `.spawn/meta.json` fields |
|
|
138
|
+
| `tests/test_<slug>_generator.py` | `generate_project(config)` (or `ProjectGenerator().generate()`) with mocked uv/git/install, directory structure, README content, `.spawn/meta.json` fields |
|
|
130
139
|
|
|
131
140
|
See `tests/test_mcp_server_template.py` and `tests/test_mcp_server_generator.py` for the exact mock patterns (`_mock_uv_and_install()` context manager, `_cfg()` helper, `patch.object(MyTemplate, "post_install")`).
|
|
132
141
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: spawnio
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Spawn a ready-to-use Python project in seconds
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -89,6 +89,7 @@ It's repetitive. It's inconsistent. And you haven't written a single line of *re
|
|
|
89
89
|
| **GitHub publishing** | Connects your project to an existing GitHub repo and pushes the initial commit |
|
|
90
90
|
| **spawn doctor** | Scores your project's health out of 100, with per-category breakdowns and a prioritized next step |
|
|
91
91
|
| **spawn doctor --fix** | Fixes missing files and config automatically — never overwrites, never installs packages |
|
|
92
|
+
| **spawn inspect** | Static, read-only summary of a repository: languages, frameworks, dependencies, package managers, tooling and layout, as text or JSON |
|
|
92
93
|
|
|
93
94
|
---
|
|
94
95
|
|
|
@@ -144,12 +145,13 @@ Running `spawn` with no arguments shows the banner, the installed version, and t
|
|
|
144
145
|
|
|
145
146
|
```
|
|
146
147
|
SPAWN — scaffold your next project
|
|
147
|
-
v1.0
|
|
148
|
+
v1.1.0
|
|
148
149
|
|
|
149
150
|
Commands
|
|
150
151
|
create Scaffold a new project
|
|
151
152
|
doctor Check the health of a project directory
|
|
152
153
|
version Show the installed version
|
|
154
|
+
inspect Inspect a repository (read-only)
|
|
153
155
|
|
|
154
156
|
Run spawn COMMAND --help for details on a command.
|
|
155
157
|
```
|
|
@@ -710,7 +712,7 @@ spawn create --name my-api --template backend-api --framework fastapi --extras r
|
|
|
710
712
|
spawn create --config spawn.json
|
|
711
713
|
```
|
|
712
714
|
|
|
713
|
-
`--config` takes precedence over individual flags. Add `--dry-run` to either form to validate the config and print it without creating any files.
|
|
715
|
+
`--config` takes precedence over individual flags (an explicit `--no-git`/`--no-uv` is ignored with a warning). Add `--dry-run` to either form to validate the config and print it without creating any files.
|
|
714
716
|
|
|
715
717
|
> Custom Structure is interactive-only in this version.
|
|
716
718
|
|
|
@@ -791,8 +793,42 @@ spawn doctor --fix --license mit
|
|
|
791
793
|
|
|
792
794
|
```bash
|
|
793
795
|
spawn version
|
|
794
|
-
# → Spawn v1.0
|
|
796
|
+
# → Spawn v1.1.0
|
|
797
|
+
|
|
798
|
+
spawn --version
|
|
799
|
+
# → Spawn v1.1.0
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### `spawn inspect`
|
|
803
|
+
|
|
804
|
+
Give Spawn a repository and receive a deterministic, evidence-backed summary of what it contains.
|
|
805
|
+
|
|
806
|
+
```bash
|
|
807
|
+
spawn inspect . # human-readable summary
|
|
808
|
+
spawn inspect . --json # full report on stdout, schema_version "1.0"
|
|
809
|
+
spawn inspect . --verbose # all dependencies, entries and evidence paths
|
|
810
|
+
```
|
|
811
|
+
|
|
795
812
|
```
|
|
813
|
+
⚡ SPAWN / INSPECT
|
|
814
|
+
|
|
815
|
+
demo
|
|
816
|
+
python project · 11 files · 3 directories
|
|
817
|
+
|
|
818
|
+
PROJECT OVERVIEW
|
|
819
|
+
Primary language python
|
|
820
|
+
Framework Not detected
|
|
821
|
+
Package manager Not detected
|
|
822
|
+
Origin spawn
|
|
823
|
+
|
|
824
|
+
✓ Inspection complete
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
**Scope.** 25 languages by extension and manifest; React, Next.js, Vue, Nuxt, Angular, Svelte, Express, FastAPI, Flask, Django, Starlette, Pydantic and Laravel; dependencies from `pyproject.toml`, `requirements*.txt` and `package.json`; package managers (uv, poetry, pipenv, pip, npm, yarn, pnpm, bun); tooling (ruff, black, flake8, mypy, pyright, pytest, eslint, prettier, jest, vitest, pre-commit, git), CI, containers and databases. Full matrix: [supported technologies](docs/inspection/supported-technologies.md).
|
|
828
|
+
|
|
829
|
+
**Limitations.** Detection is static and evidence-based: a finding means an indicator exists, not that the project builds or runs. Frameworks seen only in a dependency declaration are marked *inferred*; imports are matched by regex, so import-like text in comments or strings can false-positive. CI, container and pre-commit detection is presence-only. Databases are inferred from driver packages. Manifests such as `Pipfile`, `setup.py`, `Cargo.toml`, `go.mod`, `Gemfile` and `pom.xml` give no dependency findings. Directories named `build`, `dist`, `env`, `target`, `vendor` and the like are skipped at any depth.
|
|
830
|
+
|
|
831
|
+
**Safety.** `spawn inspect` is read-only: it never runs repository code, installs anything, uses the network or writes to the repository, and it does not follow symbolic links. It exits 0 for complete and partial reports and 7 for an invalid path or failed inspection. See the [command reference](docs/commands.md#spawn-inspect).
|
|
796
832
|
|
|
797
833
|
### Publish to GitHub
|
|
798
834
|
|
|
@@ -825,6 +861,8 @@ All tests should pass. If they don't, please [open an issue](https://github.com/
|
|
|
825
861
|
|
|
826
862
|
| Version | Highlight |
|
|
827
863
|
|---|---|
|
|
864
|
+
| **v1.1.0** | `spawn inspect`: static, read-only repository inspection (languages, frameworks, dependencies, package managers, tooling, layout) with human and `--json` output; `spawn --version` |
|
|
865
|
+
| **v1.0.9** | Core hardening: documented exit-code contract (generation, toolchain and publish failures exit non-zero), `--no-uv` honored for templates (file-only extras still written), path-traversal protection for custom structures, `read_project_metadata` extension boundary |
|
|
828
866
|
| **v1.0.8** | `spawn doctor --fix` (`--dry-run`, `--yes`, `--license`), `--license` flag and prompt on `create`, `CHANGELOG.md` generated for every project, `pre-commit` and `mypy` extras across all templates, Custom Structure Mypy option |
|
|
829
867
|
| **v1.0.7** | Fixed UTF-8 console crash on Windows when stdout is redirected/piped, added Windows reserved device name validation (`CON`, `PRN`, `AUX`, `NUL`, `COM1`–`9`, `LPT1`–`9`), clarified Python 3.12 requirement |
|
|
830
868
|
| **v1.0.6** | Removed duplicate banner from `spawn create`; `spawn` alone now shows the banner and command overview |
|
|
@@ -78,6 +78,7 @@ It's repetitive. It's inconsistent. And you haven't written a single line of *re
|
|
|
78
78
|
| **GitHub publishing** | Connects your project to an existing GitHub repo and pushes the initial commit |
|
|
79
79
|
| **spawn doctor** | Scores your project's health out of 100, with per-category breakdowns and a prioritized next step |
|
|
80
80
|
| **spawn doctor --fix** | Fixes missing files and config automatically — never overwrites, never installs packages |
|
|
81
|
+
| **spawn inspect** | Static, read-only summary of a repository: languages, frameworks, dependencies, package managers, tooling and layout, as text or JSON |
|
|
81
82
|
|
|
82
83
|
---
|
|
83
84
|
|
|
@@ -133,12 +134,13 @@ Running `spawn` with no arguments shows the banner, the installed version, and t
|
|
|
133
134
|
|
|
134
135
|
```
|
|
135
136
|
SPAWN — scaffold your next project
|
|
136
|
-
v1.0
|
|
137
|
+
v1.1.0
|
|
137
138
|
|
|
138
139
|
Commands
|
|
139
140
|
create Scaffold a new project
|
|
140
141
|
doctor Check the health of a project directory
|
|
141
142
|
version Show the installed version
|
|
143
|
+
inspect Inspect a repository (read-only)
|
|
142
144
|
|
|
143
145
|
Run spawn COMMAND --help for details on a command.
|
|
144
146
|
```
|
|
@@ -699,7 +701,7 @@ spawn create --name my-api --template backend-api --framework fastapi --extras r
|
|
|
699
701
|
spawn create --config spawn.json
|
|
700
702
|
```
|
|
701
703
|
|
|
702
|
-
`--config` takes precedence over individual flags. Add `--dry-run` to either form to validate the config and print it without creating any files.
|
|
704
|
+
`--config` takes precedence over individual flags (an explicit `--no-git`/`--no-uv` is ignored with a warning). Add `--dry-run` to either form to validate the config and print it without creating any files.
|
|
703
705
|
|
|
704
706
|
> Custom Structure is interactive-only in this version.
|
|
705
707
|
|
|
@@ -780,8 +782,42 @@ spawn doctor --fix --license mit
|
|
|
780
782
|
|
|
781
783
|
```bash
|
|
782
784
|
spawn version
|
|
783
|
-
# → Spawn v1.0
|
|
785
|
+
# → Spawn v1.1.0
|
|
786
|
+
|
|
787
|
+
spawn --version
|
|
788
|
+
# → Spawn v1.1.0
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### `spawn inspect`
|
|
792
|
+
|
|
793
|
+
Give Spawn a repository and receive a deterministic, evidence-backed summary of what it contains.
|
|
794
|
+
|
|
795
|
+
```bash
|
|
796
|
+
spawn inspect . # human-readable summary
|
|
797
|
+
spawn inspect . --json # full report on stdout, schema_version "1.0"
|
|
798
|
+
spawn inspect . --verbose # all dependencies, entries and evidence paths
|
|
799
|
+
```
|
|
800
|
+
|
|
784
801
|
```
|
|
802
|
+
⚡ SPAWN / INSPECT
|
|
803
|
+
|
|
804
|
+
demo
|
|
805
|
+
python project · 11 files · 3 directories
|
|
806
|
+
|
|
807
|
+
PROJECT OVERVIEW
|
|
808
|
+
Primary language python
|
|
809
|
+
Framework Not detected
|
|
810
|
+
Package manager Not detected
|
|
811
|
+
Origin spawn
|
|
812
|
+
|
|
813
|
+
✓ Inspection complete
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
**Scope.** 25 languages by extension and manifest; React, Next.js, Vue, Nuxt, Angular, Svelte, Express, FastAPI, Flask, Django, Starlette, Pydantic and Laravel; dependencies from `pyproject.toml`, `requirements*.txt` and `package.json`; package managers (uv, poetry, pipenv, pip, npm, yarn, pnpm, bun); tooling (ruff, black, flake8, mypy, pyright, pytest, eslint, prettier, jest, vitest, pre-commit, git), CI, containers and databases. Full matrix: [supported technologies](docs/inspection/supported-technologies.md).
|
|
817
|
+
|
|
818
|
+
**Limitations.** Detection is static and evidence-based: a finding means an indicator exists, not that the project builds or runs. Frameworks seen only in a dependency declaration are marked *inferred*; imports are matched by regex, so import-like text in comments or strings can false-positive. CI, container and pre-commit detection is presence-only. Databases are inferred from driver packages. Manifests such as `Pipfile`, `setup.py`, `Cargo.toml`, `go.mod`, `Gemfile` and `pom.xml` give no dependency findings. Directories named `build`, `dist`, `env`, `target`, `vendor` and the like are skipped at any depth.
|
|
819
|
+
|
|
820
|
+
**Safety.** `spawn inspect` is read-only: it never runs repository code, installs anything, uses the network or writes to the repository, and it does not follow symbolic links. It exits 0 for complete and partial reports and 7 for an invalid path or failed inspection. See the [command reference](docs/commands.md#spawn-inspect).
|
|
785
821
|
|
|
786
822
|
### Publish to GitHub
|
|
787
823
|
|
|
@@ -814,6 +850,8 @@ All tests should pass. If they don't, please [open an issue](https://github.com/
|
|
|
814
850
|
|
|
815
851
|
| Version | Highlight |
|
|
816
852
|
|---|---|
|
|
853
|
+
| **v1.1.0** | `spawn inspect`: static, read-only repository inspection (languages, frameworks, dependencies, package managers, tooling, layout) with human and `--json` output; `spawn --version` |
|
|
854
|
+
| **v1.0.9** | Core hardening: documented exit-code contract (generation, toolchain and publish failures exit non-zero), `--no-uv` honored for templates (file-only extras still written), path-traversal protection for custom structures, `read_project_metadata` extension boundary |
|
|
817
855
|
| **v1.0.8** | `spawn doctor --fix` (`--dry-run`, `--yes`, `--license`), `--license` flag and prompt on `create`, `CHANGELOG.md` generated for every project, `pre-commit` and `mypy` extras across all templates, Custom Structure Mypy option |
|
|
818
856
|
| **v1.0.7** | Fixed UTF-8 console crash on Windows when stdout is redirected/piped, added Windows reserved device name validation (`CON`, `PRN`, `AUX`, `NUL`, `COM1`–`9`, `LPT1`–`9`), clarified Python 3.12 requirement |
|
|
819
857
|
| **v1.0.6** | Removed duplicate banner from `spawn create`; `spawn` alone now shows the banner and command overview |
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
|
|
5
|
+
Spawn is a Python CLI tool built with Typer and Rich. It scaffolds new Python projects from templates, optionally publishes them to GitHub, and scores existing projects for environment health.
|
|
6
|
+
|
|
7
|
+
The three main capabilities are **project generation** (`spawn create`), **GitHub publishing** (optional post-create flow), and **environment health checking** (`spawn doctor`).
|
|
8
|
+
|
|
9
|
+
## 2. Technology Stack
|
|
10
|
+
|
|
11
|
+
| Tool | Version | Role |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Python | 3.12+ | Core language |
|
|
14
|
+
| Typer | 0.26.7+ | CLI framework |
|
|
15
|
+
| Rich | 15.0.0+ | Terminal UI |
|
|
16
|
+
| uv | any | Environment management |
|
|
17
|
+
| Git | any | Version control |
|
|
18
|
+
| Hatchling | any | Build backend |
|
|
19
|
+
|
|
20
|
+
Entry point: `spawn.cli.app:main` (defined in `pyproject.toml` as `[project.scripts]`).
|
|
21
|
+
|
|
22
|
+
## 3. Repository Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
src/spawn/
|
|
26
|
+
├── __init__.py # __version__ via importlib.metadata, fallback "1.1.0" (keep in sync with pyproject.toml)
|
|
27
|
+
├── cli/
|
|
28
|
+
│ ├── app.py # Typer app: create, version (and --version), doctor, inspect commands
|
|
29
|
+
│ ├── inspect_render.py # Rich rendering of an InspectionReport for `spawn inspect`
|
|
30
|
+
│ ├── noninteractive.py # Build config from CLI flags or JSON config file
|
|
31
|
+
│ └── prompts.py # Interactive prompts; derives menu from registry
|
|
32
|
+
├── core/
|
|
33
|
+
│ ├── exceptions.py # SpawnError — base exception
|
|
34
|
+
│ ├── models.py # ProjectConfig dataclass (name, template, use_git, framework, extras, license)
|
|
35
|
+
│ └── registry.py # TemplateMetadata, TEMPLATES dict, get_template(),
|
|
36
|
+
│ # instantiate_template(), get_metadata(), list_templates()
|
|
37
|
+
├── generators/
|
|
38
|
+
│ ├── custom_structure.py # Custom structure parser and generator
|
|
39
|
+
│ ├── project_files.py # Standalone file writers for LICENSE, README, configs, CI
|
|
40
|
+
│ └── project_generator.py # Orchestrates generation, dependency install, meta.json write
|
|
41
|
+
├── github/
|
|
42
|
+
│ ├── exceptions.py # GitHubPublishError
|
|
43
|
+
│ ├── publisher.py # GitHubPublisher — stages, commits, pushes
|
|
44
|
+
│ └── validators.py # GitHub URL validation
|
|
45
|
+
├── inspection/ # Read-only repository inspection (never imports spawn.cli)
|
|
46
|
+
│ ├── models.py # Report/finding/evidence/diagnostic models, SCHEMA_VERSION, validate_report
|
|
47
|
+
│ ├── base.py # Analyzer protocol
|
|
48
|
+
│ ├── discovery.py # validate_inspection_root, discover_repository (single inventory scan)
|
|
49
|
+
│ ├── languages.py # LanguageDetector
|
|
50
|
+
│ ├── frameworks.py # FrameworkDetector
|
|
51
|
+
│ ├── dependencies.py # DependencyAnalyzer (dependencies, package managers)
|
|
52
|
+
│ ├── tooling.py # ToolingAnalyzer (tools, CI, containers, databases, structure)
|
|
53
|
+
│ ├── orchestrator.py # run_inspection: runs analyzers, aggregates, builds the report
|
|
54
|
+
│ ├── serialization.py # to_json / from_json
|
|
55
|
+
│ ├── exceptions.py # InspectionError (exit code 7)
|
|
56
|
+
│ └── _reading.py # Bounded file reads
|
|
57
|
+
├── templates/
|
|
58
|
+
│ ├── base.py # BaseTemplate dataclass
|
|
59
|
+
│ ├── shared_content.py # README_CONTENT, GITIGNORE_CONTENT, CI, and tool configs
|
|
60
|
+
│ ├── backend_api/
|
|
61
|
+
│ │ ├── __init__.py # BackendAPITemplate (dispatches by framework)
|
|
62
|
+
│ │ └── content.py # All FastAPI/Flask/Django/Docker/CI content strings
|
|
63
|
+
│ ├── cli_application/
|
|
64
|
+
│ │ ├── __init__.py # CLITemplate (dispatches by framework + cli_type)
|
|
65
|
+
│ │ └── content.py # All framework × cli_type content strings
|
|
66
|
+
│ ├── automation/
|
|
67
|
+
│ │ ├── __init__.py # AutomationTemplate
|
|
68
|
+
│ │ └── content.py # Workflow, task, logger, test, README content strings
|
|
69
|
+
│ ├── chatbot/
|
|
70
|
+
│ │ ├── __init__.py # ChatbotTemplate (branches on framework)
|
|
71
|
+
│ │ └── content.py # LLM provider, chat, prompt, test, README strings
|
|
72
|
+
│ ├── python_script/ # Reserved — not in active menu
|
|
73
|
+
│ ├── data_science/ # Reserved — not in active menu
|
|
74
|
+
│ └── ml_project/ # Reserved — not in active menu
|
|
75
|
+
└── utils/
|
|
76
|
+
├── banner.py # show_banner() — ASCII wordmark with ice-fade colours
|
|
77
|
+
├── console.py # Shared Rich Console instance
|
|
78
|
+
├── doctor.py # ProjectHealthChecker, run_health_check()
|
|
79
|
+
├── doctor_fix.py # FixAction, FixResult, plan_fixes(), apply_fixes(), tool_hints()
|
|
80
|
+
├── git.py # initialize_git(), run_git_command(), add_all(), commit() etc.
|
|
81
|
+
├── success.py # show_success() — renders the success panel
|
|
82
|
+
├── theme.py # UI theme colors and styles
|
|
83
|
+
├── uv.py # initialize_uv(), install_packages()
|
|
84
|
+
└── validators.py # validate_project_name()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 4. Key Flows
|
|
88
|
+
|
|
89
|
+
### 4a. Project Creation Flow
|
|
90
|
+
|
|
91
|
+
1. User runs `spawn create`
|
|
92
|
+
2. `app.create()` builds a `ProjectConfig`:
|
|
93
|
+
- non-interactive (`--name` or `--config`): `cli/noninteractive.py` calls `core/planning.py::plan_project`, which validates the name, rejects an existing destination, and resolves template options, extras and license. Config-file mode ignores explicit `--no-git`/`--no-uv` with a warning.
|
|
94
|
+
- interactive: `cli/prompts.py::get_project_config()` collects the answers (menus derived from the registry) and builds the config directly
|
|
95
|
+
3. `--dry-run` prints the resolved config and stops; nothing is written
|
|
96
|
+
4. `generators/pipeline.py::generate_project(config)` dispatches on `config.template`:
|
|
97
|
+
`ProjectGenerator.generate()` for registry templates, `CustomStructureGenerator.generate()` for `"custom"`
|
|
98
|
+
5. Both generators call `generators/destination.py`: `resolve_destination` (single-directory name check), `assert_available` (fails with `FilesystemError` if the path exists in any form, including symlinks) and, for custom structures, `safe_join` (rejects traversal and absolute paths)
|
|
99
|
+
6. `ProjectGenerator` resolves the template via `instantiate_template(config)`, creates directories, writes starter files, README and `.gitignore`, runs `initialize_git()`, `initialize_uv()`, `install_packages()` and `post_install()` (the uv steps are skipped with `--no-uv`; file-only extras are still written and the rest are reported in one "Skipped without uv" warning), then `generators/metadata.py::write_project_meta`
|
|
100
|
+
7. `show_success()` renders the success panel
|
|
101
|
+
8. If Git was enabled and not in non-interactive/`--yes` mode, optionally `GitHubPublisher.publish()` stages, commits, and pushes
|
|
102
|
+
|
|
103
|
+
**Cleanup rule:** on any failure a generator removes the project directory only if that run created it (`destination.cleanup_created`, which also refuses symlinks, the cwd and its parents, the home directory and filesystem roots). A pre-existing directory is never touched.
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
spawn create
|
|
107
|
+
│
|
|
108
|
+
▼
|
|
109
|
+
show_banner() banner.py
|
|
110
|
+
│
|
|
111
|
+
▼
|
|
112
|
+
plan_project() planning.py (flags / --config)
|
|
113
|
+
get_project_config() prompts.py (interactive)
|
|
114
|
+
│ → ProjectConfig
|
|
115
|
+
▼
|
|
116
|
+
generate_project() pipeline.py
|
|
117
|
+
├── ProjectGenerator / CustomStructureGenerator
|
|
118
|
+
│ ├── resolve_destination + assert_available destination.py
|
|
119
|
+
│ ├── instantiate_template() registry.py (forwards framework + extras)
|
|
120
|
+
│ ├── mkdir + write files, README, .gitignore
|
|
121
|
+
│ ├── initialize_git() git.py (if use_git)
|
|
122
|
+
│ ├── initialize_uv() / install_packages() (if use_uv)
|
|
123
|
+
│ └── post_install() template (ruff/pytest config, Docker, CI)
|
|
124
|
+
└── write_project_meta() metadata.py → .spawn/meta.json
|
|
125
|
+
(called by ProjectGenerator, or by generate_project for custom)
|
|
126
|
+
│
|
|
127
|
+
▼
|
|
128
|
+
show_success() success.py
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 4b. Template System
|
|
132
|
+
|
|
133
|
+
`BaseTemplate` is a dataclass with these fields:
|
|
134
|
+
|
|
135
|
+
| Field | Type | Purpose |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `name` | `str` | Display name shown in the success panel |
|
|
138
|
+
| `folders` | `list[str]` | Directories created under the project root |
|
|
139
|
+
| `starter_files` | `list[tuple[str, str]]` | `(relative_path, content_template)` pairs; `{project_name}` substituted at write time |
|
|
140
|
+
| `next_steps` | `list[str]` | Run commands shown after creation; `{project_name}` substituted at display time |
|
|
141
|
+
|
|
142
|
+
`BaseTemplate` also provides overridable methods:
|
|
143
|
+
|
|
144
|
+
| Method | Default | Purpose |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `generate(project_path, context)` | Creates folders and writes starter files | Template-specific generation |
|
|
147
|
+
| `get_readme_content(context)` | Returns `None` (use shared README) | Custom README per template |
|
|
148
|
+
| `get_dependencies()` | Returns `[]` | Packages to install via `uv add` |
|
|
149
|
+
| `post_install(project_path)` | No-op | Config writes, Docker files, CI files |
|
|
150
|
+
|
|
151
|
+
Each template lives in its own subdirectory with an `__init__.py` (class) and `content.py` (string constants).
|
|
152
|
+
|
|
153
|
+
#### Registry
|
|
154
|
+
|
|
155
|
+
`registry.py` holds `TEMPLATES: dict[str, TemplateMetadata]`. Current entries in order:
|
|
156
|
+
|
|
157
|
+
| Key | Template class | Frameworks | Extras |
|
|
158
|
+
|---|---|---|---|
|
|
159
|
+
| `backend-api` | `BackendAPITemplate` | fastapi, flask, django | ruff, pytest, docker, github-actions, pre-commit, mypy |
|
|
160
|
+
| `cli` | `CLITemplate` | typer, click, argparse | ruff, pytest, github-actions, pre-commit, mypy |
|
|
161
|
+
| `automation` | `AutomationTemplate` | none | ruff, pytest, github-actions, pre-commit, mypy |
|
|
162
|
+
| `chatbot` | `ChatbotTemplate` | pydantic-ai, openai-sdk, litellm | ruff, pytest, rich, github-actions, pre-commit, mypy |
|
|
163
|
+
| `agent` | `AgentTemplate` | pydantic-ai, openai-agents | ruff, pytest, github-actions, pre-commit, mypy |
|
|
164
|
+
| `rag` | `RAGTemplate` | none | ruff, pytest, github-actions, pre-commit, mypy |
|
|
165
|
+
| `data` | `DataProjectTemplate` | none | ruff, pytest, github-actions, pre-commit, mypy |
|
|
166
|
+
| `mcp` | `MCPServerTemplate` | none | ruff, pytest, github-actions, pre-commit, mypy |
|
|
167
|
+
|
|
168
|
+
`get_template(slug)` returns a default-constructed instance. `instantiate_template(config)` forwards `framework`, `extras`, and `cli_type` from `ProjectConfig` to templates whose constructors accept them, using signature introspection.
|
|
169
|
+
|
|
170
|
+
`_REMOVED_SLUGS = {"fastapi", "python", "data-science", "ml"}` documents slugs that existed in previous versions but are no longer registered.
|
|
171
|
+
|
|
172
|
+
### 4c. Backend API Dispatch
|
|
173
|
+
|
|
174
|
+
`BackendAPITemplate.__init__` branches on `framework` to select the correct folder list,
|
|
175
|
+
starter files, and next steps:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
framework == "flask" → FLASK_FOLDERS, FLASK_FILES, flask next steps
|
|
179
|
+
framework == "django" → DJANGO_FOLDERS, DJANGO_FILES, django next steps
|
|
180
|
+
else (None/"fastapi") → FASTAPI_FOLDERS, FASTAPI_FILES, fastapi next steps
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`get_dependencies()` returns base deps for the framework plus any selected extras.
|
|
184
|
+
`post_install()` writes ruff config, pytest filterwarnings, Dockerfile, `.dockerignore`,
|
|
185
|
+
and/or `.github/workflows/ci.yml` depending on `self.extras`.
|
|
186
|
+
|
|
187
|
+
### 4d. Error Handling
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
SpawnError exit 1 core/exceptions.py
|
|
191
|
+
├── InvalidInputError exit 1
|
|
192
|
+
│ └── StructureParseError exit 1
|
|
193
|
+
├── ConfigError exit 1
|
|
194
|
+
├── FilesystemError exit 3
|
|
195
|
+
├── ToolchainError exit 4
|
|
196
|
+
├── GenerationError exit 5
|
|
197
|
+
│ └── TemplateError exit 5
|
|
198
|
+
└── PublishError exit 6
|
|
199
|
+
└── GitHubPublishError exit 6 github/exceptions.py
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Generators convert `OSError` to `FilesystemError`, remove only the directory the current run created, and re-raise. `cli/errors.py` owns the CLI handlers: `fail(e)` prints `❌ message` in red and exits with `e.exit_code`; `report_unexpected(e)` handles any other exception, printing a short message and exiting 10 (`UNEXPECTED_EXIT_CODE`), or re-raising for a full traceback when `SPAWN_DEBUG` is set to anything other than empty/`0`/`false`/`no`. Ctrl+C, EOF and declined prompts exit 130; Click usage errors exit 2. See [Exit codes](commands.md#exit-codes).
|
|
203
|
+
|
|
204
|
+
## 5. Adding a New Template
|
|
205
|
+
|
|
206
|
+
1. Create `src/spawn/templates/your_intent/` with `__init__.py` and `content.py`
|
|
207
|
+
2. Subclass `BaseTemplate`; set `name`, `folders`, `starter_files`, `next_steps`
|
|
208
|
+
3. Override `get_dependencies()`, `post_install()`, `get_readme_content()` as needed
|
|
209
|
+
4. Register in `registry.py` — add a `TemplateMetadata` entry to `TEMPLATES`
|
|
210
|
+
5. Write tests in `test_templates.py` and `test_registry.py`
|
|
211
|
+
|
|
212
|
+
## 6. Running Tests
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
uv run pytest
|
|
216
|
+
uv run pytest -v
|
|
217
|
+
uv run pytest tests/test_generator.py
|
|
218
|
+
uv run ruff check .
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## 7. Pipeline
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
input -> plan_project -> ProjectConfig -> generate_project -> filesystem -> meta -> publish (optional)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| Step | Owner |
|
|
228
|
+
|---|---|
|
|
229
|
+
| Input (flags, `--config` JSON, prompts) | `cli/noninteractive.py`, `cli/prompts.py` |
|
|
230
|
+
| Plan: validate and resolve options | `core/planning.py::plan_project` |
|
|
231
|
+
| Canonical model | `core/models.py::ProjectConfig` |
|
|
232
|
+
| Generate: folders, files, git, uv, rollback | `generators/pipeline.py::generate_project` |
|
|
233
|
+
| Meta: `.spawn/meta.json` | `generators/metadata.py::write_project_meta` |
|
|
234
|
+
| Publish | `github/publisher.py::GitHubPublisher` (called from `cli/app.py`) |
|
|
235
|
+
|
|
236
|
+
## 8. Canonical model
|
|
237
|
+
|
|
238
|
+
`ProjectConfig` is the single model passed between stages.
|
|
239
|
+
|
|
240
|
+
| Field | Meaning |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `name`, `template` | Project name; template slug (`"custom"` for pasted structures) |
|
|
243
|
+
| `framework`, `provider`, `cli_type`, `data_type` | Template options |
|
|
244
|
+
| `extras`, `license`, `generate_claude_md` | Optional tooling, license (`mit`/`none`), also write `CLAUDE.md` |
|
|
245
|
+
| `use_git`, `use_uv` | Toolchain steps |
|
|
246
|
+
| `custom_*` | Custom-structure entries, dependencies, dev setup, gitignore extras, source format |
|
|
247
|
+
| `destination` | Target directory (`Path` or `None`); excluded from equality |
|
|
248
|
+
|
|
249
|
+
`to_dict()` returns a JSON-serializable dict of every field (`destination` as a string).
|
|
250
|
+
|
|
251
|
+
`.spawn/meta.json` is a **historical record, not authoritative**: it stores `intent`, `framework`, `provider`, `spawn_version`, `created_at`, `generator`, `git`, `uv` and `source` only. It does not record `name`, `cli_type`, `data_type`, `extras`, `license` or `generate_claude_md`, and the project may have changed since. `ProjectConfig` is generation intent only; it cannot be reconstructed from metadata.
|
|
252
|
+
|
|
253
|
+
## 9. Extension boundary
|
|
254
|
+
|
|
255
|
+
Code that analyses existing projects must not depend on the CLI. It obtains state through `core/`, `generators/` and `templates/`:
|
|
256
|
+
|
|
257
|
+
| Need | Use |
|
|
258
|
+
|---|---|
|
|
259
|
+
| State of an existing project | `core.project.read_project_metadata(path)`: a `ProjectMetadata`, or `None` if there is no `.spawn/meta.json`; raises `ConfigError` if the file is unreadable or invalid. Read-only; does not validate the intent against the registry |
|
|
260
|
+
| Resolve options into a config | `core.planning.plan_project(...)` |
|
|
261
|
+
| Template catalogue | `core.registry.get_metadata()`, `list_templates()` |
|
|
262
|
+
| Create a project | `generators.pipeline.generate_project(config)` |
|
|
263
|
+
|
|
264
|
+
**Rule:** no module under `core/`, `generators/`, `templates/`, `utils/` or `github/` may import `spawn.cli`. `tests/test_boundaries.py` enforces this.
|
|
265
|
+
|
|
266
|
+
To add a template: subclass `BaseTemplate` in `templates/<slug>/` and add a `TemplateMetadata` entry to `TEMPLATES` (see section 5).
|
|
267
|
+
|
|
268
|
+
## 10. Error model
|
|
269
|
+
|
|
270
|
+
All Spawn errors derive from `SpawnError` (`core/exceptions.py`); each class carries the CLI `exit_code`. The CLI prints `❌ message` and exits with that code. See [Exit codes](commands.md#exit-codes).
|
|
271
|
+
|
|
272
|
+
## 11. Inspection pipeline
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
spawn inspect -> run_inspection -> validate root -> discover once -> analyzers -> aggregate/dedupe/sort -> InspectionReport -> human or JSON
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
| Step | Owner |
|
|
279
|
+
|---|---|
|
|
280
|
+
| CLI: argument, `--json`, exit codes | `cli/app.py::inspect_command` |
|
|
281
|
+
| Validate root (bad root raises `InspectionError`, exit 7) | `inspection/discovery.py::validate_inspection_root` |
|
|
282
|
+
| Discover once: sorted files, directories, skipped, diagnostics; symlinks never followed | `inspection/discovery.py::discover_repository` |
|
|
283
|
+
| Analyzers, fixed order: language, framework, dependency, tooling; each gets the same immutable inventory | `inspection/orchestrator.py::ANALYZERS` |
|
|
284
|
+
| Isolate: a crashing or invalid analyzer becomes a `failed` result, others continue | `orchestrator._run_one` |
|
|
285
|
+
| Aggregate: validate findings, merge by `(category, subject)`, sort; derive summary and structure | `orchestrator._aggregate`, `_summary` |
|
|
286
|
+
| Render: Rich output, or `to_json` on stdout | `cli/inspect_render.py`, `inspection/serialization.py` |
|
|
287
|
+
|
|
288
|
+
Analyzers only consume the inventory (plus bounded reads of files it lists); they do not walk the tree. Nothing is executed, installed, fetched or written. `.spawn/meta.json` is read at most once, as untrusted recorded metadata.
|
|
289
|
+
|
|
290
|
+
**Module boundary:** `inspection/` never imports `spawn.cli`; `tests/test_boundaries.py` enforces this. Statuses and semantics are in [the inspection contract](inspection/inspection-contract.md); what is detected is in [supported technologies](inspection/supported-technologies.md).
|
|
@@ -3,6 +3,53 @@
|
|
|
3
3
|
All notable changes to Spawn are documented here.
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
5
5
|
|
|
6
|
+
## v1.1.0 — 2026
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`spawn inspect [PATH] [--json] [--verbose]`** — read-only, static inspection of a repository. Prints a human summary (overview, technology, tooling, structure, diagnostics) or, with `--json`, the full report on stdout. Exits 0 for complete and partial reports and 7 for an invalid path or failed inspection (see `docs/commands.md`).
|
|
11
|
+
- **`spawn --version`** — prints the same line as `spawn version`.
|
|
12
|
+
- **Inspection contract** — `spawn.inspection` models (`InspectionReport`, `Finding`, `Evidence`, `Diagnostic`), `SCHEMA_VERSION` `"1.0"`, `validate_report`, and `to_json` / `from_json`. See `docs/inspection/inspection-contract.md`.
|
|
13
|
+
- **Safe discovery** — one sorted inventory scan; symlinks and junctions are never followed, default exclusions (`.git`, `node_modules`, `build`, `dist`, `venv`, `env`, `target`, ...) match at any depth, errors below the root become diagnostics.
|
|
14
|
+
- **Language and framework detection** — 25 languages by extension and manifest; React, Next.js, Vue, Nuxt, Angular, Svelte, Express, FastAPI, Flask, Django, Starlette, Pydantic and Laravel from manifests (inferred), imports and config files (detected).
|
|
15
|
+
- **Dependency and package-manager analyzer** — direct dependencies from `pyproject.toml`, `requirements*.txt` and `package.json`; uv, poetry, pipenv, pip, npm, yarn, pnpm and bun.
|
|
16
|
+
- **Tooling analyzer** — Python and JavaScript tools, git, CI systems, Docker and Compose, database drivers, `tests-directory`, `src-layout` and `spawn-generated` markers.
|
|
17
|
+
- **Orchestrator** — runs the analyzers in a fixed order with per-analyzer isolation, merges findings by `(category, subject)`, derives the summary and structure, and reads `.spawn/meta.json` as untrusted recorded metadata.
|
|
18
|
+
- **Test corpus** — generated fixture repositories and a regression suite (`tests/test_inspection_*.py`).
|
|
19
|
+
- Release smoke test now runs `spawn inspect . --json` against a generated project and checks it is unchanged.
|
|
20
|
+
|
|
21
|
+
### Known limitations
|
|
22
|
+
|
|
23
|
+
- Excluded directory basenames (`build`, `env`, `target`, `vendor`, `dist`, `coverage`, ...) match at any depth.
|
|
24
|
+
- `.git` is detected through discovery's skipped list only.
|
|
25
|
+
- Framework imports are regex-matched: import-like text in comments or strings can false-positive; multi-line and destructured imports are missed.
|
|
26
|
+
- Frameworks found only in a dependency declaration are inferred, and `primary_framework` may be inferred.
|
|
27
|
+
- CI, container and pre-commit detection is presence-only (no YAML parsing).
|
|
28
|
+
- Databases are inferred from driver packages only.
|
|
29
|
+
- A pip and an npm package with the same name share one finding.
|
|
30
|
+
- `Pipfile`, `setup.py`, `Cargo.toml`, `go.mod`, `Gemfile`, `composer.json`, `pom.xml` and Gradle files produce no dependency findings; Rails is not detected.
|
|
31
|
+
- `summary.name` is the directory basename.
|
|
32
|
+
|
|
33
|
+
## v1.0.9 — 2026
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **Canonical `ProjectConfig`** — gains `destination` and `to_dict()`; `core/planning.py::plan_project` resolves options into a config and `generators/pipeline.py::generate_project` builds it.
|
|
38
|
+
- **`core/project.py::read_project_metadata`** — CLI-free, read-only `ProjectMetadata` from `.spawn/meta.json` (`None` if absent, `ConfigError` if invalid). Metadata is historical, not authoritative; `ProjectConfig` is generation intent only.
|
|
39
|
+
- **Fixtures and regression tests** — archetype factory, golden file trees (`SPAWN_UPDATE_TREES=1`), and an import-boundary test (nothing outside `cli/` imports `spawn.cli`).
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- `--no-uv` is now honored for template projects: uv init, `uv add` and extras that need `pyproject.toml` are skipped (one yellow "Skipped without uv: ..." line lists them); file-only extras such as `pre-commit` are still written.
|
|
44
|
+
- Interactive `spawn create --dry-run` now validates and prints the config without writing anything.
|
|
45
|
+
- `--config` combined with an explicit `--no-git`/`--no-uv` prints a warning that the config file takes precedence.
|
|
46
|
+
- **Error and exit-code contract** — generation, toolchain (git/uv) and publish failures now exit non-zero (3–6) instead of 0; unexpected errors exit 10 with a short message (`SPAWN_DEBUG=1` shows the traceback). Declining the final custom-structure prompt now cancels cleanly (130) instead of raising a traceback. Git/GitHub error text no longer includes URL credentials. See the Exit codes table in `docs/commands.md`.
|
|
47
|
+
|
|
48
|
+
### Security
|
|
49
|
+
|
|
50
|
+
- Custom-structure paths that escape the project directory (path traversal) are rejected.
|
|
51
|
+
- Failure cleanup only removes directories created by the current run.
|
|
52
|
+
|
|
6
53
|
## v1.0.8 — 2026
|
|
7
54
|
|
|
8
55
|
### Added
|