snapstack 1.0.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.
- snapstack-1.0.0/.github/workflows/ci.yml +32 -0
- snapstack-1.0.0/.github/workflows/publish.yml +76 -0
- snapstack-1.0.0/.gitignore +92 -0
- snapstack-1.0.0/.specify/memory/constitution.md +170 -0
- snapstack-1.0.0/.specify/scripts/powershell/check-prerequisites.ps1 +148 -0
- snapstack-1.0.0/.specify/scripts/powershell/common.ps1 +137 -0
- snapstack-1.0.0/.specify/scripts/powershell/create-new-feature.ps1 +305 -0
- snapstack-1.0.0/.specify/scripts/powershell/setup-plan.ps1 +61 -0
- snapstack-1.0.0/.specify/scripts/powershell/update-agent-context.ps1 +463 -0
- snapstack-1.0.0/.specify/templates/agent-file-template.md +28 -0
- snapstack-1.0.0/.specify/templates/checklist-template.md +40 -0
- snapstack-1.0.0/.specify/templates/constitution-template.md +50 -0
- snapstack-1.0.0/.specify/templates/plan-template.md +104 -0
- snapstack-1.0.0/.specify/templates/spec-template.md +115 -0
- snapstack-1.0.0/.specify/templates/tasks-template.md +251 -0
- snapstack-1.0.0/CONTRIBUTING.md +54 -0
- snapstack-1.0.0/LICENSE +21 -0
- snapstack-1.0.0/PKG-INFO +267 -0
- snapstack-1.0.0/README.md +237 -0
- snapstack-1.0.0/pyproject.toml +62 -0
- snapstack-1.0.0/pysnap/__init__.py +1 -0
- snapstack-1.0.0/pysnap/_shared/Dockerfile.j2 +34 -0
- snapstack-1.0.0/pysnap/_shared/ci.yml.j2 +53 -0
- snapstack-1.0.0/pysnap/_shared/docker-compose.yml.j2 +46 -0
- snapstack-1.0.0/pysnap/_shared/dockerignore.j2 +13 -0
- snapstack-1.0.0/pysnap/_shared/env_example.j2 +24 -0
- snapstack-1.0.0/pysnap/_shared/gitignore.j2 +15 -0
- snapstack-1.0.0/pysnap/commands/__init__.py +1 -0
- snapstack-1.0.0/pysnap/commands/add.py +171 -0
- snapstack-1.0.0/pysnap/commands/create.py +136 -0
- snapstack-1.0.0/pysnap/commands/templates_cmd.py +134 -0
- snapstack-1.0.0/pysnap/commands/update.py +133 -0
- snapstack-1.0.0/pysnap/community.py +113 -0
- snapstack-1.0.0/pysnap/config.py +76 -0
- snapstack-1.0.0/pysnap/generator.py +262 -0
- snapstack-1.0.0/pysnap/main.py +65 -0
- snapstack-1.0.0/pysnap/manifest.py +101 -0
- snapstack-1.0.0/pysnap/plugins.py +123 -0
- snapstack-1.0.0/pysnap/preview.py +131 -0
- snapstack-1.0.0/pysnap/prompts.py +217 -0
- snapstack-1.0.0/pysnap/registry.py +123 -0
- snapstack-1.0.0/pysnap/templates/django/.dockerignore.j2 +15 -0
- snapstack-1.0.0/pysnap/templates/django/.github/workflows/ci.yml.j2 +34 -0
- snapstack-1.0.0/pysnap/templates/django/.gitignore.j2 +14 -0
- snapstack-1.0.0/pysnap/templates/django/Dockerfile.j2 +14 -0
- snapstack-1.0.0/pysnap/templates/django/README.md.j2 +36 -0
- snapstack-1.0.0/pysnap/templates/django/apps/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/django/apps/core/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/django/apps/core/apps.py.j2 +6 -0
- snapstack-1.0.0/pysnap/templates/django/apps/core/urls.py.j2 +7 -0
- snapstack-1.0.0/pysnap/templates/django/apps/core/views.py.j2 +6 -0
- snapstack-1.0.0/pysnap/templates/django/apps/users/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/django/apps/users/apps.py.j2 +6 -0
- snapstack-1.0.0/pysnap/templates/django/apps/users/models.py.j2 +14 -0
- snapstack-1.0.0/pysnap/templates/django/apps/users/serializers.py.j2 +13 -0
- snapstack-1.0.0/pysnap/templates/django/apps/users/urls.py.j2 +10 -0
- snapstack-1.0.0/pysnap/templates/django/apps/users/views.py.j2 +22 -0
- snapstack-1.0.0/pysnap/templates/django/config/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/django/config/asgi.py.j2 +9 -0
- snapstack-1.0.0/pysnap/templates/django/config/settings.py.j2 +110 -0
- snapstack-1.0.0/pysnap/templates/django/config/urls.py.j2 +12 -0
- snapstack-1.0.0/pysnap/templates/django/config/wsgi.py.j2 +9 -0
- snapstack-1.0.0/pysnap/templates/django/docker-compose.yml.j2 +29 -0
- snapstack-1.0.0/pysnap/templates/django/manage.py.j2 +22 -0
- snapstack-1.0.0/pysnap/templates/django/pyproject.toml.j2 +40 -0
- snapstack-1.0.0/pysnap/templates/django/template.json +50 -0
- snapstack-1.0.0/pysnap/templates/django/tests/__init__.py.j2 +1 -0
- snapstack-1.0.0/pysnap/templates/django/tests/conftest.py.j2 +6 -0
- snapstack-1.0.0/pysnap/templates/django/tests/test_health.py.j2 +9 -0
- snapstack-1.0.0/pysnap/templates/fastapi/.dockerignore.j2 +8 -0
- snapstack-1.0.0/pysnap/templates/fastapi/.github/workflows/ci.yml.j2 +46 -0
- snapstack-1.0.0/pysnap/templates/fastapi/.gitignore.j2 +13 -0
- snapstack-1.0.0/pysnap/templates/fastapi/Dockerfile.j2 +14 -0
- snapstack-1.0.0/pysnap/templates/fastapi/README.md.j2 +57 -0
- snapstack-1.0.0/pysnap/templates/fastapi/api/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/fastapi/api/routes/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/fastapi/api/routes/auth.py.j2 +18 -0
- snapstack-1.0.0/pysnap/templates/fastapi/api/routes/health.py.j2 +8 -0
- snapstack-1.0.0/pysnap/templates/fastapi/app/__init__.py.j2 +1 -0
- snapstack-1.0.0/pysnap/templates/fastapi/core/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/fastapi/core/config.py.j2 +26 -0
- snapstack-1.0.0/pysnap/templates/fastapi/core/security.py.j2 +22 -0
- snapstack-1.0.0/pysnap/templates/fastapi/db/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/fastapi/db/base.py.j2 +5 -0
- snapstack-1.0.0/pysnap/templates/fastapi/db/session.py.j2 +15 -0
- snapstack-1.0.0/pysnap/templates/fastapi/docker-compose.yml.j2 +30 -0
- snapstack-1.0.0/pysnap/templates/fastapi/main.py.j2 +27 -0
- snapstack-1.0.0/pysnap/templates/fastapi/models/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/fastapi/models/user.py.j2 +13 -0
- snapstack-1.0.0/pysnap/templates/fastapi/pyproject.toml.j2 +48 -0
- snapstack-1.0.0/pysnap/templates/fastapi/schemas/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/fastapi/schemas/user.py.j2 +18 -0
- snapstack-1.0.0/pysnap/templates/fastapi/template.json +53 -0
- snapstack-1.0.0/pysnap/templates/fastapi/tests/__init__.py.j2 +0 -0
- snapstack-1.0.0/pysnap/templates/fastapi/tests/conftest.py.j2 +9 -0
- snapstack-1.0.0/pysnap/templates/fastapi/tests/test_health.py.j2 +9 -0
- snapstack-1.0.0/pysnap/templates/flask/.dockerignore.j2 +14 -0
- snapstack-1.0.0/pysnap/templates/flask/.github/workflows/ci.yml.j2 +34 -0
- snapstack-1.0.0/pysnap/templates/flask/.gitignore.j2 +13 -0
- snapstack-1.0.0/pysnap/templates/flask/Dockerfile.j2 +14 -0
- snapstack-1.0.0/pysnap/templates/flask/README.md.j2 +34 -0
- snapstack-1.0.0/pysnap/templates/flask/app/__init__.py.j2 +30 -0
- snapstack-1.0.0/pysnap/templates/flask/app/config.py.j2 +23 -0
- snapstack-1.0.0/pysnap/templates/flask/app/extensions.py.j2 +9 -0
- snapstack-1.0.0/pysnap/templates/flask/app/models/__init__.py.j2 +1 -0
- snapstack-1.0.0/pysnap/templates/flask/app/models/user.py.j2 +16 -0
- snapstack-1.0.0/pysnap/templates/flask/app/routes/__init__.py.j2 +1 -0
- snapstack-1.0.0/pysnap/templates/flask/app/routes/auth.py.j2 +31 -0
- snapstack-1.0.0/pysnap/templates/flask/app/routes/health.py.j2 +11 -0
- snapstack-1.0.0/pysnap/templates/flask/docker-compose.yml.j2 +29 -0
- snapstack-1.0.0/pysnap/templates/flask/pyproject.toml.j2 +39 -0
- snapstack-1.0.0/pysnap/templates/flask/template.json +44 -0
- snapstack-1.0.0/pysnap/templates/flask/tests/__init__.py.j2 +1 -0
- snapstack-1.0.0/pysnap/templates/flask/tests/conftest.py.j2 +16 -0
- snapstack-1.0.0/pysnap/templates/flask/tests/test_health.py.j2 +8 -0
- snapstack-1.0.0/pysnap/templates/flask/wsgi.py.j2 +8 -0
- snapstack-1.0.0/pysnap/validator.py +89 -0
- snapstack-1.0.0/specs/001-core-scaffolding/checklists/requirements.md +42 -0
- snapstack-1.0.0/specs/001-core-scaffolding/contracts/cli-contract.md +173 -0
- snapstack-1.0.0/specs/001-core-scaffolding/data-model.md +134 -0
- snapstack-1.0.0/specs/001-core-scaffolding/plan.md +379 -0
- snapstack-1.0.0/specs/001-core-scaffolding/quickstart.md +103 -0
- snapstack-1.0.0/specs/001-core-scaffolding/research.md +224 -0
- snapstack-1.0.0/specs/001-core-scaffolding/spec.md +452 -0
- snapstack-1.0.0/specs/001-core-scaffolding/tasks.md +386 -0
- snapstack-1.0.0/tests/__init__.py +1 -0
- snapstack-1.0.0/tests/conftest.py +62 -0
- snapstack-1.0.0/tests/test_add_update.py +81 -0
- snapstack-1.0.0/tests/test_create.py +220 -0
- snapstack-1.0.0/tests/test_generator.py +183 -0
- snapstack-1.0.0/tests/test_manifest.py +105 -0
- snapstack-1.0.0/tests/test_plugins.py +63 -0
- snapstack-1.0.0/tests/test_preview.py +65 -0
- snapstack-1.0.0/tests/test_registry.py +93 -0
- snapstack-1.0.0/tests/test_templates_cmd.py +29 -0
- snapstack-1.0.0/tests/test_validator.py +30 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: pip install -e ".[dev]"
|
|
27
|
+
|
|
28
|
+
- name: Lint
|
|
29
|
+
run: ruff check .
|
|
30
|
+
|
|
31
|
+
- name: Test
|
|
32
|
+
run: pytest --tb=short -q
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Build distribution
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.11"
|
|
22
|
+
|
|
23
|
+
- name: Install build tools
|
|
24
|
+
run: pip install --upgrade pip build
|
|
25
|
+
|
|
26
|
+
- name: Build package
|
|
27
|
+
run: python -m build
|
|
28
|
+
|
|
29
|
+
- name: Upload build artifacts
|
|
30
|
+
uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: python-package-distributions
|
|
33
|
+
path: dist/
|
|
34
|
+
|
|
35
|
+
publish-pypi:
|
|
36
|
+
name: Publish to PyPI
|
|
37
|
+
needs: build
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
environment:
|
|
40
|
+
name: pypi
|
|
41
|
+
url: https://pypi.org/p/snapstack
|
|
42
|
+
permissions:
|
|
43
|
+
id-token: write # Trusted publishing (no API token needed)
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- name: Download build artifacts
|
|
47
|
+
uses: actions/download-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: python-package-distributions
|
|
50
|
+
path: dist/
|
|
51
|
+
|
|
52
|
+
- name: Publish to PyPI
|
|
53
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
54
|
+
|
|
55
|
+
publish-testpypi:
|
|
56
|
+
name: Publish to TestPyPI
|
|
57
|
+
needs: build
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
if: github.event.release.prerelease == true
|
|
60
|
+
environment:
|
|
61
|
+
name: testpypi
|
|
62
|
+
url: https://test.pypi.org/p/snapstack
|
|
63
|
+
permissions:
|
|
64
|
+
id-token: write
|
|
65
|
+
|
|
66
|
+
steps:
|
|
67
|
+
- name: Download build artifacts
|
|
68
|
+
uses: actions/download-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: python-package-distributions
|
|
71
|
+
path: dist/
|
|
72
|
+
|
|
73
|
+
- name: Publish to TestPyPI
|
|
74
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
75
|
+
with:
|
|
76
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.pyd
|
|
7
|
+
*.pdb
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
ENV/
|
|
14
|
+
.env/
|
|
15
|
+
|
|
16
|
+
# Distribution / packaging
|
|
17
|
+
dist/
|
|
18
|
+
build/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
*.egg
|
|
21
|
+
.eggs/
|
|
22
|
+
MANIFEST
|
|
23
|
+
|
|
24
|
+
# Installer logs
|
|
25
|
+
pip-log.txt
|
|
26
|
+
pip-delete-this-directory.txt
|
|
27
|
+
|
|
28
|
+
# Unit test / coverage
|
|
29
|
+
.tox/
|
|
30
|
+
.coverage
|
|
31
|
+
.coverage.*
|
|
32
|
+
htmlcov/
|
|
33
|
+
.pytest_cache/
|
|
34
|
+
.cache/
|
|
35
|
+
nosetests.xml
|
|
36
|
+
coverage.xml
|
|
37
|
+
*.cover
|
|
38
|
+
.hypothesis/
|
|
39
|
+
|
|
40
|
+
# Linting
|
|
41
|
+
.ruff_cache/
|
|
42
|
+
.mypy_cache/
|
|
43
|
+
|
|
44
|
+
# pysnap cache
|
|
45
|
+
.cache/pysnap/
|
|
46
|
+
|
|
47
|
+
# Environment files
|
|
48
|
+
.env
|
|
49
|
+
.env.*
|
|
50
|
+
!.env.example
|
|
51
|
+
|
|
52
|
+
# IDE / Editor
|
|
53
|
+
.vscode/
|
|
54
|
+
.idea/
|
|
55
|
+
*.sublime-project
|
|
56
|
+
*.sublime-workspace
|
|
57
|
+
*.swp
|
|
58
|
+
*.swo
|
|
59
|
+
*~
|
|
60
|
+
|
|
61
|
+
# OS
|
|
62
|
+
.DS_Store
|
|
63
|
+
.DS_Store?
|
|
64
|
+
._*
|
|
65
|
+
.Spotlight-V100
|
|
66
|
+
.Trashes
|
|
67
|
+
ehthumbs.db
|
|
68
|
+
Thumbs.db
|
|
69
|
+
|
|
70
|
+
# Logs
|
|
71
|
+
*.log
|
|
72
|
+
logs/
|
|
73
|
+
|
|
74
|
+
# Temporary files
|
|
75
|
+
*.tmp
|
|
76
|
+
*.bak
|
|
77
|
+
tmp/
|
|
78
|
+
|
|
79
|
+
# Smoke test output
|
|
80
|
+
my-api/
|
|
81
|
+
|
|
82
|
+
# Root-level Docker files (pysnap is a CLI tool, Docker templates are inside pysnap/templates/)
|
|
83
|
+
/Dockerfile
|
|
84
|
+
/docker-compose.yml
|
|
85
|
+
/.dockerignore
|
|
86
|
+
|
|
87
|
+
# Speckit agent internals
|
|
88
|
+
.agent/
|
|
89
|
+
|
|
90
|
+
# Lock files (pysnap is a library/CLI, consumers pin their own deps)
|
|
91
|
+
uv.lock
|
|
92
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Sync Impact Report
|
|
3
|
+
===================
|
|
4
|
+
- Version change: N/A (initial) -> 1.0.0
|
|
5
|
+
- Modified principles: N/A (first constitution)
|
|
6
|
+
- Added sections:
|
|
7
|
+
- Core Principles (6 principles: Developer Experience, Correctness,
|
|
8
|
+
Backward Compatibility, Extensibility, Minimal Magic,
|
|
9
|
+
Testing as a First-Class Citizen)
|
|
10
|
+
- Technical Constraints
|
|
11
|
+
- Development Workflow
|
|
12
|
+
- Governance
|
|
13
|
+
- Removed sections: None
|
|
14
|
+
- Templates requiring updates:
|
|
15
|
+
- .specify/templates/plan-template.md ✅ reviewed (Constitution Check
|
|
16
|
+
section aligns with new principles; no changes needed)
|
|
17
|
+
- .specify/templates/spec-template.md ✅ reviewed (scope/requirements
|
|
18
|
+
structure compatible; no changes needed)
|
|
19
|
+
- .specify/templates/tasks-template.md ✅ reviewed (task categorization
|
|
20
|
+
compatible; testing tasks align with Principle VI)
|
|
21
|
+
- Follow-up TODOs: None
|
|
22
|
+
-->
|
|
23
|
+
|
|
24
|
+
# Pysnap Constitution
|
|
25
|
+
|
|
26
|
+
## Core Principles
|
|
27
|
+
|
|
28
|
+
### I. Developer Experience
|
|
29
|
+
|
|
30
|
+
Every interaction with pysnap MUST feel polished, fast, and intuitive.
|
|
31
|
+
This is the highest-priority principle; all other principles are
|
|
32
|
+
subordinate when trade-offs arise.
|
|
33
|
+
|
|
34
|
+
- CLI output MUST use clear formatting (colors, spinners, structured
|
|
35
|
+
messages via Rich) so the developer always knows what is happening.
|
|
36
|
+
- Interactive prompts MUST have sensible defaults. A developer MUST be
|
|
37
|
+
able to scaffold a working project by pressing Enter through every
|
|
38
|
+
prompt.
|
|
39
|
+
- Error messages MUST be actionable: state what went wrong, why, and
|
|
40
|
+
what the developer can do to fix it.
|
|
41
|
+
- Project generation MUST complete in under 5 seconds on commodity
|
|
42
|
+
hardware.
|
|
43
|
+
- The `--help` output for every command MUST be self-documenting and
|
|
44
|
+
sufficient to use the tool without reading external docs.
|
|
45
|
+
|
|
46
|
+
### II. Correctness
|
|
47
|
+
|
|
48
|
+
Generated project code MUST always be runnable on first try with zero
|
|
49
|
+
manual edits.
|
|
50
|
+
|
|
51
|
+
- Every generated project MUST pass `pip install -e .` (or the
|
|
52
|
+
equivalent for the chosen package manager) without errors.
|
|
53
|
+
- Every generated project MUST start its development server
|
|
54
|
+
successfully on the first run.
|
|
55
|
+
- Every generated project's test suite MUST pass on first run with
|
|
56
|
+
zero failures.
|
|
57
|
+
- Template variables MUST never leak into generated output. All
|
|
58
|
+
placeholders MUST be resolved before files are written.
|
|
59
|
+
- CI/CD workflows included in generated projects MUST be valid and
|
|
60
|
+
functional against their target platforms (GitHub Actions, etc.).
|
|
61
|
+
|
|
62
|
+
### III. Backward Compatibility
|
|
63
|
+
|
|
64
|
+
Existing pysnap commands MUST never break between versions.
|
|
65
|
+
|
|
66
|
+
- Public CLI commands (e.g., `pysnap create`) MUST NOT change their
|
|
67
|
+
argument signatures or default behavior in minor or patch releases.
|
|
68
|
+
- Removal or renaming of a command MUST only occur in a major version
|
|
69
|
+
bump and MUST be preceded by at least one release with a deprecation
|
|
70
|
+
warning.
|
|
71
|
+
- Configuration file formats MUST remain backward-compatible within a
|
|
72
|
+
major version series.
|
|
73
|
+
- The pysnap public Python API (if any) follows semantic versioning:
|
|
74
|
+
breaking changes require a major version bump.
|
|
75
|
+
|
|
76
|
+
### IV. Extensibility
|
|
77
|
+
|
|
78
|
+
Every feature MUST be designed so community contributors can add new
|
|
79
|
+
framework templates without touching core logic.
|
|
80
|
+
|
|
81
|
+
- Framework scaffolding MUST be implemented through a template registry
|
|
82
|
+
pattern. Adding a new framework (e.g., Litestar, Starlette) MUST NOT
|
|
83
|
+
require modifications to the CLI command layer or the generation
|
|
84
|
+
engine.
|
|
85
|
+
- Templates MUST be self-contained directories that declare their own
|
|
86
|
+
metadata (name, description, prompts, default values) via a manifest
|
|
87
|
+
file.
|
|
88
|
+
- A `pysnap use <template-url>` command MUST support community
|
|
89
|
+
templates hosted externally (Git repos, tarballs).
|
|
90
|
+
- Hook points (pre-generation, post-generation) MUST be available for
|
|
91
|
+
templates to run custom setup logic.
|
|
92
|
+
|
|
93
|
+
### V. Minimal Magic
|
|
94
|
+
|
|
95
|
+
Generated projects MUST be readable and educational. Developers MUST
|
|
96
|
+
learn from the output rather than being confused by it.
|
|
97
|
+
|
|
98
|
+
- Generated code MUST NOT use metaprogramming, dynamic imports, or
|
|
99
|
+
code generation at runtime unless the target framework requires it.
|
|
100
|
+
- Every generated file MUST contain a clear purpose visible from its
|
|
101
|
+
name and a brief docstring or header comment.
|
|
102
|
+
- Directory structures MUST follow the conventions of the target
|
|
103
|
+
framework's official documentation.
|
|
104
|
+
- Dependencies in generated `pyproject.toml` MUST be pinned to
|
|
105
|
+
compatible ranges (e.g., `>=X.Y,<X+1`) with no unnecessary
|
|
106
|
+
transitive dependencies.
|
|
107
|
+
- Generated code SHOULD prefer explicit imports, explicit
|
|
108
|
+
configuration, and standard-library solutions over third-party
|
|
109
|
+
abstractions.
|
|
110
|
+
|
|
111
|
+
### VI. Testing as a First-Class Citizen
|
|
112
|
+
|
|
113
|
+
Every generated project includes a working test suite by default, and
|
|
114
|
+
pysnap itself MUST have full test coverage.
|
|
115
|
+
|
|
116
|
+
- Every generated project MUST include a `tests/` directory with at
|
|
117
|
+
least one passing test (e.g., a health-check endpoint test).
|
|
118
|
+
- Generated test suites MUST include a `conftest.py` with reusable
|
|
119
|
+
fixtures (app client, database session, etc.) appropriate to the
|
|
120
|
+
chosen framework.
|
|
121
|
+
- Pysnap's own codebase MUST maintain >= 90% line coverage as measured
|
|
122
|
+
by `pytest --cov`.
|
|
123
|
+
- Every new pysnap feature MUST ship with unit tests covering its
|
|
124
|
+
happy path and at least one error/edge case.
|
|
125
|
+
- Integration tests MUST verify that generated projects pass their own
|
|
126
|
+
test suites end-to-end (generate -> install -> test).
|
|
127
|
+
|
|
128
|
+
## Technical Constraints
|
|
129
|
+
|
|
130
|
+
- **Language/Version**: Python >= 3.9 (as declared in `pyproject.toml`).
|
|
131
|
+
- **CLI Framework**: Typer (with Rich for output formatting).
|
|
132
|
+
- **Templating Engine**: Jinja2 for all code generation.
|
|
133
|
+
- **Interactive Prompts**: Questionary for user input during scaffolding.
|
|
134
|
+
- **Build System**: Hatchling.
|
|
135
|
+
- **Linting/Formatting**: Ruff.
|
|
136
|
+
- **License**: MIT.
|
|
137
|
+
- **Supported Frameworks**: FastAPI (stable), Django (planned),
|
|
138
|
+
Flask (planned), community templates via registry.
|
|
139
|
+
|
|
140
|
+
## Development Workflow
|
|
141
|
+
|
|
142
|
+
- All changes MUST pass `ruff check` and `ruff format --check` before
|
|
143
|
+
merge.
|
|
144
|
+
- All changes MUST pass the full `pytest` suite.
|
|
145
|
+
- New features MUST include tests that cover both pysnap internals and
|
|
146
|
+
the correctness of generated output.
|
|
147
|
+
- Pull requests MUST reference the relevant principle(s) from this
|
|
148
|
+
constitution when introducing architectural decisions.
|
|
149
|
+
- Deprecations MUST follow the process defined in Principle III:
|
|
150
|
+
deprecation warning in release N, removal no earlier than release
|
|
151
|
+
N+1 (major).
|
|
152
|
+
|
|
153
|
+
## Governance
|
|
154
|
+
|
|
155
|
+
- This constitution is the authoritative source of design and
|
|
156
|
+
development policy for pysnap. It supersedes informal conventions,
|
|
157
|
+
ad-hoc decisions, and any conflicting documentation.
|
|
158
|
+
- Amendments to this constitution require:
|
|
159
|
+
1. A written proposal describing the change and its rationale.
|
|
160
|
+
2. Review and approval by at least one project maintainer.
|
|
161
|
+
3. A version bump to this document following semantic versioning
|
|
162
|
+
(MAJOR for principle removals/redefinitions, MINOR for new
|
|
163
|
+
principles or material expansions, PATCH for clarifications).
|
|
164
|
+
4. A migration plan if the amendment affects existing templates,
|
|
165
|
+
commands, or contributor workflows.
|
|
166
|
+
- All pull requests and code reviews MUST verify compliance with the
|
|
167
|
+
principles defined above. Non-compliance MUST be justified in
|
|
168
|
+
writing with a reference to the specific trade-off.
|
|
169
|
+
|
|
170
|
+
**Version**: 1.0.0 | **Ratified**: 2026-03-04 | **Last Amended**: 2026-03-04
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
|
|
3
|
+
# Consolidated prerequisite checking script (PowerShell)
|
|
4
|
+
#
|
|
5
|
+
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
|
|
6
|
+
# It replaces the functionality previously spread across multiple scripts.
|
|
7
|
+
#
|
|
8
|
+
# Usage: ./check-prerequisites.ps1 [OPTIONS]
|
|
9
|
+
#
|
|
10
|
+
# OPTIONS:
|
|
11
|
+
# -Json Output in JSON format
|
|
12
|
+
# -RequireTasks Require tasks.md to exist (for implementation phase)
|
|
13
|
+
# -IncludeTasks Include tasks.md in AVAILABLE_DOCS list
|
|
14
|
+
# -PathsOnly Only output path variables (no validation)
|
|
15
|
+
# -Help, -h Show help message
|
|
16
|
+
|
|
17
|
+
[CmdletBinding()]
|
|
18
|
+
param(
|
|
19
|
+
[switch]$Json,
|
|
20
|
+
[switch]$RequireTasks,
|
|
21
|
+
[switch]$IncludeTasks,
|
|
22
|
+
[switch]$PathsOnly,
|
|
23
|
+
[switch]$Help
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
$ErrorActionPreference = 'Stop'
|
|
27
|
+
|
|
28
|
+
# Show help if requested
|
|
29
|
+
if ($Help) {
|
|
30
|
+
Write-Output @"
|
|
31
|
+
Usage: check-prerequisites.ps1 [OPTIONS]
|
|
32
|
+
|
|
33
|
+
Consolidated prerequisite checking for Spec-Driven Development workflow.
|
|
34
|
+
|
|
35
|
+
OPTIONS:
|
|
36
|
+
-Json Output in JSON format
|
|
37
|
+
-RequireTasks Require tasks.md to exist (for implementation phase)
|
|
38
|
+
-IncludeTasks Include tasks.md in AVAILABLE_DOCS list
|
|
39
|
+
-PathsOnly Only output path variables (no prerequisite validation)
|
|
40
|
+
-Help, -h Show this help message
|
|
41
|
+
|
|
42
|
+
EXAMPLES:
|
|
43
|
+
# Check task prerequisites (plan.md required)
|
|
44
|
+
.\check-prerequisites.ps1 -Json
|
|
45
|
+
|
|
46
|
+
# Check implementation prerequisites (plan.md + tasks.md required)
|
|
47
|
+
.\check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
|
|
48
|
+
|
|
49
|
+
# Get feature paths only (no validation)
|
|
50
|
+
.\check-prerequisites.ps1 -PathsOnly
|
|
51
|
+
|
|
52
|
+
"@
|
|
53
|
+
exit 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Source common functions
|
|
57
|
+
. "$PSScriptRoot/common.ps1"
|
|
58
|
+
|
|
59
|
+
# Get feature paths and validate branch
|
|
60
|
+
$paths = Get-FeaturePathsEnv
|
|
61
|
+
|
|
62
|
+
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit:$paths.HAS_GIT)) {
|
|
63
|
+
exit 1
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# If paths-only mode, output paths and exit (support combined -Json -PathsOnly)
|
|
67
|
+
if ($PathsOnly) {
|
|
68
|
+
if ($Json) {
|
|
69
|
+
[PSCustomObject]@{
|
|
70
|
+
REPO_ROOT = $paths.REPO_ROOT
|
|
71
|
+
BRANCH = $paths.CURRENT_BRANCH
|
|
72
|
+
FEATURE_DIR = $paths.FEATURE_DIR
|
|
73
|
+
FEATURE_SPEC = $paths.FEATURE_SPEC
|
|
74
|
+
IMPL_PLAN = $paths.IMPL_PLAN
|
|
75
|
+
TASKS = $paths.TASKS
|
|
76
|
+
} | ConvertTo-Json -Compress
|
|
77
|
+
} else {
|
|
78
|
+
Write-Output "REPO_ROOT: $($paths.REPO_ROOT)"
|
|
79
|
+
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
|
80
|
+
Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)"
|
|
81
|
+
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
|
82
|
+
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
|
83
|
+
Write-Output "TASKS: $($paths.TASKS)"
|
|
84
|
+
}
|
|
85
|
+
exit 0
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Validate required directories and files
|
|
89
|
+
if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
|
|
90
|
+
Write-Output "ERROR: Feature directory not found: $($paths.FEATURE_DIR)"
|
|
91
|
+
Write-Output "Run /speckit.specify first to create the feature structure."
|
|
92
|
+
exit 1
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
|
|
96
|
+
Write-Output "ERROR: plan.md not found in $($paths.FEATURE_DIR)"
|
|
97
|
+
Write-Output "Run /speckit.plan first to create the implementation plan."
|
|
98
|
+
exit 1
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Check for tasks.md if required
|
|
102
|
+
if ($RequireTasks -and -not (Test-Path $paths.TASKS -PathType Leaf)) {
|
|
103
|
+
Write-Output "ERROR: tasks.md not found in $($paths.FEATURE_DIR)"
|
|
104
|
+
Write-Output "Run /speckit.tasks first to create the task list."
|
|
105
|
+
exit 1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Build list of available documents
|
|
109
|
+
$docs = @()
|
|
110
|
+
|
|
111
|
+
# Always check these optional docs
|
|
112
|
+
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
|
|
113
|
+
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
|
|
114
|
+
|
|
115
|
+
# Check contracts directory (only if it exists and has files)
|
|
116
|
+
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) {
|
|
117
|
+
$docs += 'contracts/'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }
|
|
121
|
+
|
|
122
|
+
# Include tasks.md if requested and it exists
|
|
123
|
+
if ($IncludeTasks -and (Test-Path $paths.TASKS)) {
|
|
124
|
+
$docs += 'tasks.md'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Output results
|
|
128
|
+
if ($Json) {
|
|
129
|
+
# JSON output
|
|
130
|
+
[PSCustomObject]@{
|
|
131
|
+
FEATURE_DIR = $paths.FEATURE_DIR
|
|
132
|
+
AVAILABLE_DOCS = $docs
|
|
133
|
+
} | ConvertTo-Json -Compress
|
|
134
|
+
} else {
|
|
135
|
+
# Text output
|
|
136
|
+
Write-Output "FEATURE_DIR:$($paths.FEATURE_DIR)"
|
|
137
|
+
Write-Output "AVAILABLE_DOCS:"
|
|
138
|
+
|
|
139
|
+
# Show status of each potential document
|
|
140
|
+
Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null
|
|
141
|
+
Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null
|
|
142
|
+
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null
|
|
143
|
+
Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null
|
|
144
|
+
|
|
145
|
+
if ($IncludeTasks) {
|
|
146
|
+
Test-FileExists -Path $paths.TASKS -Description 'tasks.md' | Out-Null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
# Common PowerShell functions analogous to common.sh
|
|
3
|
+
|
|
4
|
+
function Get-RepoRoot {
|
|
5
|
+
try {
|
|
6
|
+
$result = git rev-parse --show-toplevel 2>$null
|
|
7
|
+
if ($LASTEXITCODE -eq 0) {
|
|
8
|
+
return $result
|
|
9
|
+
}
|
|
10
|
+
} catch {
|
|
11
|
+
# Git command failed
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Fall back to script location for non-git repos
|
|
15
|
+
return (Resolve-Path (Join-Path $PSScriptRoot "../../..")).Path
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Get-CurrentBranch {
|
|
19
|
+
# First check if SPECIFY_FEATURE environment variable is set
|
|
20
|
+
if ($env:SPECIFY_FEATURE) {
|
|
21
|
+
return $env:SPECIFY_FEATURE
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Then check git if available
|
|
25
|
+
try {
|
|
26
|
+
$result = git rev-parse --abbrev-ref HEAD 2>$null
|
|
27
|
+
if ($LASTEXITCODE -eq 0) {
|
|
28
|
+
return $result
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
# Git command failed
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# For non-git repos, try to find the latest feature directory
|
|
35
|
+
$repoRoot = Get-RepoRoot
|
|
36
|
+
$specsDir = Join-Path $repoRoot "specs"
|
|
37
|
+
|
|
38
|
+
if (Test-Path $specsDir) {
|
|
39
|
+
$latestFeature = ""
|
|
40
|
+
$highest = 0
|
|
41
|
+
|
|
42
|
+
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
|
|
43
|
+
if ($_.Name -match '^(\d{3})-') {
|
|
44
|
+
$num = [int]$matches[1]
|
|
45
|
+
if ($num -gt $highest) {
|
|
46
|
+
$highest = $num
|
|
47
|
+
$latestFeature = $_.Name
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if ($latestFeature) {
|
|
53
|
+
return $latestFeature
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Final fallback
|
|
58
|
+
return "main"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Test-HasGit {
|
|
62
|
+
try {
|
|
63
|
+
git rev-parse --show-toplevel 2>$null | Out-Null
|
|
64
|
+
return ($LASTEXITCODE -eq 0)
|
|
65
|
+
} catch {
|
|
66
|
+
return $false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function Test-FeatureBranch {
|
|
71
|
+
param(
|
|
72
|
+
[string]$Branch,
|
|
73
|
+
[bool]$HasGit = $true
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# For non-git repos, we can't enforce branch naming but still provide output
|
|
77
|
+
if (-not $HasGit) {
|
|
78
|
+
Write-Warning "[specify] Warning: Git repository not detected; skipped branch validation"
|
|
79
|
+
return $true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ($Branch -notmatch '^[0-9]{3}-') {
|
|
83
|
+
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
|
84
|
+
Write-Output "Feature branches should be named like: 001-feature-name"
|
|
85
|
+
return $false
|
|
86
|
+
}
|
|
87
|
+
return $true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Get-FeatureDir {
|
|
91
|
+
param([string]$RepoRoot, [string]$Branch)
|
|
92
|
+
Join-Path $RepoRoot "specs/$Branch"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function Get-FeaturePathsEnv {
|
|
96
|
+
$repoRoot = Get-RepoRoot
|
|
97
|
+
$currentBranch = Get-CurrentBranch
|
|
98
|
+
$hasGit = Test-HasGit
|
|
99
|
+
$featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
|
|
100
|
+
|
|
101
|
+
[PSCustomObject]@{
|
|
102
|
+
REPO_ROOT = $repoRoot
|
|
103
|
+
CURRENT_BRANCH = $currentBranch
|
|
104
|
+
HAS_GIT = $hasGit
|
|
105
|
+
FEATURE_DIR = $featureDir
|
|
106
|
+
FEATURE_SPEC = Join-Path $featureDir 'spec.md'
|
|
107
|
+
IMPL_PLAN = Join-Path $featureDir 'plan.md'
|
|
108
|
+
TASKS = Join-Path $featureDir 'tasks.md'
|
|
109
|
+
RESEARCH = Join-Path $featureDir 'research.md'
|
|
110
|
+
DATA_MODEL = Join-Path $featureDir 'data-model.md'
|
|
111
|
+
QUICKSTART = Join-Path $featureDir 'quickstart.md'
|
|
112
|
+
CONTRACTS_DIR = Join-Path $featureDir 'contracts'
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function Test-FileExists {
|
|
117
|
+
param([string]$Path, [string]$Description)
|
|
118
|
+
if (Test-Path -Path $Path -PathType Leaf) {
|
|
119
|
+
Write-Output " ✓ $Description"
|
|
120
|
+
return $true
|
|
121
|
+
} else {
|
|
122
|
+
Write-Output " ✗ $Description"
|
|
123
|
+
return $false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function Test-DirHasFiles {
|
|
128
|
+
param([string]$Path, [string]$Description)
|
|
129
|
+
if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer } | Select-Object -First 1)) {
|
|
130
|
+
Write-Output " ✓ $Description"
|
|
131
|
+
return $true
|
|
132
|
+
} else {
|
|
133
|
+
Write-Output " ✗ $Description"
|
|
134
|
+
return $false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|