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.
Files changed (178) hide show
  1. {spawnio-1.0.8 → spawnio-1.1.0}/CONTRIBUTING.md +10 -1
  2. {spawnio-1.0.8 → spawnio-1.1.0}/PKG-INFO +42 -4
  3. {spawnio-1.0.8 → spawnio-1.1.0}/README.md +41 -3
  4. spawnio-1.1.0/docs/architecture.md +290 -0
  5. {spawnio-1.0.8 → spawnio-1.1.0}/docs/changelog.md +47 -0
  6. {spawnio-1.0.8 → spawnio-1.1.0}/docs/commands.md +103 -23
  7. {spawnio-1.0.8 → spawnio-1.1.0}/docs/getting-started.md +3 -2
  8. spawnio-1.1.0/docs/inspection/inspection-contract.md +987 -0
  9. spawnio-1.1.0/docs/inspection/language-framework-handoff.md +57 -0
  10. spawnio-1.1.0/docs/inspection/safe-discovery.md +123 -0
  11. spawnio-1.1.0/docs/inspection/supported-technologies.md +182 -0
  12. {spawnio-1.0.8 → spawnio-1.1.0}/pyproject.toml +12 -1
  13. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/__init__.py +1 -1
  14. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/cli/app.py +129 -66
  15. spawnio-1.1.0/src/spawn/cli/errors.py +29 -0
  16. spawnio-1.1.0/src/spawn/cli/inspect_render.py +287 -0
  17. spawnio-1.1.0/src/spawn/cli/noninteractive.py +125 -0
  18. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/cli/prompts.py +7 -11
  19. spawnio-1.1.0/src/spawn/core/exceptions.py +50 -0
  20. spawnio-1.1.0/src/spawn/core/models.py +58 -0
  21. spawnio-1.0.8/src/spawn/cli/noninteractive.py → spawnio-1.1.0/src/spawn/core/planning.py +33 -91
  22. spawnio-1.1.0/src/spawn/core/project.py +59 -0
  23. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/generators/custom_structure.py +38 -15
  24. spawnio-1.1.0/src/spawn/generators/destination.py +66 -0
  25. spawnio-1.1.0/src/spawn/generators/metadata.py +45 -0
  26. spawnio-1.1.0/src/spawn/generators/pipeline.py +32 -0
  27. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/generators/project_files.py +2 -2
  28. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/generators/project_generator.py +44 -43
  29. spawnio-1.1.0/src/spawn/github/exceptions.py +5 -0
  30. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/github/publisher.py +2 -1
  31. spawnio-1.1.0/src/spawn/inspection/__init__.py +69 -0
  32. spawnio-1.1.0/src/spawn/inspection/_reading.py +21 -0
  33. spawnio-1.1.0/src/spawn/inspection/base.py +68 -0
  34. spawnio-1.1.0/src/spawn/inspection/dependencies.py +410 -0
  35. spawnio-1.1.0/src/spawn/inspection/discovery.py +280 -0
  36. spawnio-1.1.0/src/spawn/inspection/exceptions.py +14 -0
  37. spawnio-1.1.0/src/spawn/inspection/frameworks.py +255 -0
  38. spawnio-1.1.0/src/spawn/inspection/languages.py +195 -0
  39. spawnio-1.1.0/src/spawn/inspection/models.py +615 -0
  40. spawnio-1.1.0/src/spawn/inspection/orchestrator.py +294 -0
  41. spawnio-1.1.0/src/spawn/inspection/serialization.py +491 -0
  42. spawnio-1.1.0/src/spawn/inspection/tooling.py +469 -0
  43. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/git.py +9 -6
  44. spawnio-1.1.0/src/spawn/utils/redact.py +8 -0
  45. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/success.py +8 -2
  46. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/uv.py +7 -5
  47. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/validators.py +6 -4
  48. spawnio-1.1.0/tests/conftest.py +54 -0
  49. spawnio-1.1.0/tests/fixtures/__init__.py +0 -0
  50. spawnio-1.1.0/tests/fixtures/archetypes.py +11 -0
  51. spawnio-1.1.0/tests/fixtures/inspection_corpus.py +175 -0
  52. spawnio-1.1.0/tests/fixtures/make_sample_report.py +363 -0
  53. spawnio-1.1.0/tests/fixtures/static/broken_meta/.spawn/meta.json +1 -0
  54. spawnio-1.1.0/tests/fixtures/static/meta_unknown_intent/.spawn/meta.json +11 -0
  55. spawnio-1.1.0/tests/fixtures/static/non_spawn_python_repo/pyproject.toml +3 -0
  56. spawnio-1.1.0/tests/fixtures/static/non_spawn_python_repo/src/pkg/__init__.py +1 -0
  57. spawnio-1.1.0/tests/fixtures/static/non_spawn_python_repo/tests/test_x.py +2 -0
  58. spawnio-1.1.0/tests/fixtures/static/sample_report.json +316 -0
  59. spawnio-1.1.0/tests/fixtures/trees/agent.txt +20 -0
  60. spawnio-1.1.0/tests/fixtures/trees/automation.txt +20 -0
  61. spawnio-1.1.0/tests/fixtures/trees/backend-api.txt +16 -0
  62. spawnio-1.1.0/tests/fixtures/trees/chatbot.txt +22 -0
  63. spawnio-1.1.0/tests/fixtures/trees/cli.txt +12 -0
  64. spawnio-1.1.0/tests/fixtures/trees/custom.txt +4 -0
  65. spawnio-1.1.0/tests/fixtures/trees/data.txt +12 -0
  66. spawnio-1.1.0/tests/fixtures/trees/mcp.txt +11 -0
  67. spawnio-1.1.0/tests/fixtures/trees/rag.txt +21 -0
  68. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_app.py +35 -22
  69. spawnio-1.1.0/tests/test_boundaries.py +35 -0
  70. spawnio-1.1.0/tests/test_create_flags.py +203 -0
  71. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_custom_structure.py +5 -3
  72. spawnio-1.1.0/tests/test_destination.py +238 -0
  73. spawnio-1.1.0/tests/test_docs.py +44 -0
  74. spawnio-1.1.0/tests/test_errors.py +219 -0
  75. spawnio-1.1.0/tests/test_fixtures.py +158 -0
  76. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_generator.py +4 -2
  77. spawnio-1.1.0/tests/test_inspect_cli.py +350 -0
  78. spawnio-1.1.0/tests/test_inspection_dependencies.py +537 -0
  79. spawnio-1.1.0/tests/test_inspection_discovery.py +530 -0
  80. spawnio-1.1.0/tests/test_inspection_frameworks.py +327 -0
  81. spawnio-1.1.0/tests/test_inspection_languages.py +221 -0
  82. spawnio-1.1.0/tests/test_inspection_models.py +773 -0
  83. spawnio-1.1.0/tests/test_inspection_orchestrator.py +445 -0
  84. spawnio-1.1.0/tests/test_inspection_regression.py +477 -0
  85. spawnio-1.1.0/tests/test_inspection_sample.py +486 -0
  86. spawnio-1.1.0/tests/test_inspection_serialization.py +791 -0
  87. spawnio-1.1.0/tests/test_inspection_tooling.py +605 -0
  88. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_models.py +44 -0
  89. spawnio-1.1.0/tests/test_pipeline.py +135 -0
  90. spawnio-1.1.0/tests/test_planning.py +160 -0
  91. spawnio-1.1.0/tests/test_project.py +88 -0
  92. spawnio-1.0.8/.coderabbit.yaml +0 -105
  93. spawnio-1.0.8/.github/ISSUE_TEMPLATE/bug_report.yml +0 -77
  94. spawnio-1.0.8/.github/ISSUE_TEMPLATE/config.yml +0 -5
  95. spawnio-1.0.8/.github/ISSUE_TEMPLATE/feature_request.yml +0 -28
  96. spawnio-1.0.8/.github/PULL_REQUEST_TEMPLATE.md +0 -24
  97. spawnio-1.0.8/.github/labeler.yml +0 -25
  98. spawnio-1.0.8/.github/workflows/ci.yml +0 -29
  99. spawnio-1.0.8/.github/workflows/pr-check.yml +0 -60
  100. spawnio-1.0.8/.github/workflows/publish.yml +0 -24
  101. spawnio-1.0.8/.python-version +0 -1
  102. spawnio-1.0.8/assets/preview.png +0 -0
  103. spawnio-1.0.8/docs/architecture.md +0 -199
  104. spawnio-1.0.8/src/spawn/core/exceptions.py +0 -8
  105. spawnio-1.0.8/src/spawn/core/models.py +0 -22
  106. spawnio-1.0.8/src/spawn/github/exceptions.py +0 -5
  107. spawnio-1.0.8/uv.lock +0 -333
  108. {spawnio-1.0.8 → spawnio-1.1.0}/.gitignore +0 -0
  109. {spawnio-1.0.8 → spawnio-1.1.0}/LICENSE +0 -0
  110. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/cli/__init__.py +0 -0
  111. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/core/__init__.py +0 -0
  112. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/core/registry.py +0 -0
  113. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/generators/__init__.py +0 -0
  114. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/github/__init__.py +0 -0
  115. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/github/validators.py +0 -0
  116. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/__init__.py +0 -0
  117. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/agent/__init__.py +0 -0
  118. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/agent/content.py +0 -0
  119. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/automation/__init__.py +0 -0
  120. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/automation/content.py +0 -0
  121. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/backend_api/__init__.py +0 -0
  122. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/backend_api/content.py +0 -0
  123. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/base.py +0 -0
  124. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/chatbot/__init__.py +0 -0
  125. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/chatbot/content.py +0 -0
  126. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/cli_application/__init__.py +0 -0
  127. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/cli_application/content.py +0 -0
  128. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/__init__.py +0 -0
  129. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/analysis/__init__.py +0 -0
  130. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/analysis/content.py +0 -0
  131. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/dashboard/__init__.py +0 -0
  132. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/dashboard/content.py +0 -0
  133. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/etl/__init__.py +0 -0
  134. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/etl/content.py +0 -0
  135. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/ml/__init__.py +0 -0
  136. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/data_project/ml/content.py +0 -0
  137. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/mcp_server/__init__.py +0 -0
  138. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/mcp_server/content.py +0 -0
  139. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/rag/__init__.py +0 -0
  140. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/rag/content.py +0 -0
  141. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/templates/shared_content.py +0 -0
  142. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/__init__.py +0 -0
  143. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/banner.py +0 -0
  144. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/console.py +0 -0
  145. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/doctor.py +0 -0
  146. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/doctor_fix.py +0 -0
  147. {spawnio-1.0.8 → spawnio-1.1.0}/src/spawn/utils/theme.py +0 -0
  148. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_agent_generator.py +0 -0
  149. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_agent_template.py +0 -0
  150. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_automation_generator.py +0 -0
  151. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_automation_template.py +0 -0
  152. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_chatbot_generator.py +0 -0
  153. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_chatbot_template.py +0 -0
  154. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_cli_application_generator.py +0 -0
  155. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_cli_application_template.py +0 -0
  156. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_console.py +0 -0
  157. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_custom_structure_integration.py +0 -0
  158. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_analysis.py +0 -0
  159. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_dashboard.py +0 -0
  160. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_etl.py +0 -0
  161. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_gitignore.py +0 -0
  162. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_data_project_ml.py +0 -0
  163. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_doctor.py +0 -0
  164. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_doctor_fix.py +0 -0
  165. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_git.py +0 -0
  166. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_github_publisher.py +0 -0
  167. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_github_validators.py +0 -0
  168. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_mcp_server_generator.py +0 -0
  169. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_mcp_server_template.py +0 -0
  170. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_noninteractive.py +0 -0
  171. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_project_files.py +0 -0
  172. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_prompts.py +0 -0
  173. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_rag_generator.py +0 -0
  174. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_rag_template.py +0 -0
  175. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_registry.py +0 -0
  176. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_templates.py +0 -0
  177. {spawnio-1.0.8 → spawnio-1.1.0}/tests/test_uv.py +0 -0
  178. {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.8
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.8
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.8
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.8
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.8
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