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.
Files changed (136) hide show
  1. snapstack-1.0.0/.github/workflows/ci.yml +32 -0
  2. snapstack-1.0.0/.github/workflows/publish.yml +76 -0
  3. snapstack-1.0.0/.gitignore +92 -0
  4. snapstack-1.0.0/.specify/memory/constitution.md +170 -0
  5. snapstack-1.0.0/.specify/scripts/powershell/check-prerequisites.ps1 +148 -0
  6. snapstack-1.0.0/.specify/scripts/powershell/common.ps1 +137 -0
  7. snapstack-1.0.0/.specify/scripts/powershell/create-new-feature.ps1 +305 -0
  8. snapstack-1.0.0/.specify/scripts/powershell/setup-plan.ps1 +61 -0
  9. snapstack-1.0.0/.specify/scripts/powershell/update-agent-context.ps1 +463 -0
  10. snapstack-1.0.0/.specify/templates/agent-file-template.md +28 -0
  11. snapstack-1.0.0/.specify/templates/checklist-template.md +40 -0
  12. snapstack-1.0.0/.specify/templates/constitution-template.md +50 -0
  13. snapstack-1.0.0/.specify/templates/plan-template.md +104 -0
  14. snapstack-1.0.0/.specify/templates/spec-template.md +115 -0
  15. snapstack-1.0.0/.specify/templates/tasks-template.md +251 -0
  16. snapstack-1.0.0/CONTRIBUTING.md +54 -0
  17. snapstack-1.0.0/LICENSE +21 -0
  18. snapstack-1.0.0/PKG-INFO +267 -0
  19. snapstack-1.0.0/README.md +237 -0
  20. snapstack-1.0.0/pyproject.toml +62 -0
  21. snapstack-1.0.0/pysnap/__init__.py +1 -0
  22. snapstack-1.0.0/pysnap/_shared/Dockerfile.j2 +34 -0
  23. snapstack-1.0.0/pysnap/_shared/ci.yml.j2 +53 -0
  24. snapstack-1.0.0/pysnap/_shared/docker-compose.yml.j2 +46 -0
  25. snapstack-1.0.0/pysnap/_shared/dockerignore.j2 +13 -0
  26. snapstack-1.0.0/pysnap/_shared/env_example.j2 +24 -0
  27. snapstack-1.0.0/pysnap/_shared/gitignore.j2 +15 -0
  28. snapstack-1.0.0/pysnap/commands/__init__.py +1 -0
  29. snapstack-1.0.0/pysnap/commands/add.py +171 -0
  30. snapstack-1.0.0/pysnap/commands/create.py +136 -0
  31. snapstack-1.0.0/pysnap/commands/templates_cmd.py +134 -0
  32. snapstack-1.0.0/pysnap/commands/update.py +133 -0
  33. snapstack-1.0.0/pysnap/community.py +113 -0
  34. snapstack-1.0.0/pysnap/config.py +76 -0
  35. snapstack-1.0.0/pysnap/generator.py +262 -0
  36. snapstack-1.0.0/pysnap/main.py +65 -0
  37. snapstack-1.0.0/pysnap/manifest.py +101 -0
  38. snapstack-1.0.0/pysnap/plugins.py +123 -0
  39. snapstack-1.0.0/pysnap/preview.py +131 -0
  40. snapstack-1.0.0/pysnap/prompts.py +217 -0
  41. snapstack-1.0.0/pysnap/registry.py +123 -0
  42. snapstack-1.0.0/pysnap/templates/django/.dockerignore.j2 +15 -0
  43. snapstack-1.0.0/pysnap/templates/django/.github/workflows/ci.yml.j2 +34 -0
  44. snapstack-1.0.0/pysnap/templates/django/.gitignore.j2 +14 -0
  45. snapstack-1.0.0/pysnap/templates/django/Dockerfile.j2 +14 -0
  46. snapstack-1.0.0/pysnap/templates/django/README.md.j2 +36 -0
  47. snapstack-1.0.0/pysnap/templates/django/apps/__init__.py.j2 +0 -0
  48. snapstack-1.0.0/pysnap/templates/django/apps/core/__init__.py.j2 +0 -0
  49. snapstack-1.0.0/pysnap/templates/django/apps/core/apps.py.j2 +6 -0
  50. snapstack-1.0.0/pysnap/templates/django/apps/core/urls.py.j2 +7 -0
  51. snapstack-1.0.0/pysnap/templates/django/apps/core/views.py.j2 +6 -0
  52. snapstack-1.0.0/pysnap/templates/django/apps/users/__init__.py.j2 +0 -0
  53. snapstack-1.0.0/pysnap/templates/django/apps/users/apps.py.j2 +6 -0
  54. snapstack-1.0.0/pysnap/templates/django/apps/users/models.py.j2 +14 -0
  55. snapstack-1.0.0/pysnap/templates/django/apps/users/serializers.py.j2 +13 -0
  56. snapstack-1.0.0/pysnap/templates/django/apps/users/urls.py.j2 +10 -0
  57. snapstack-1.0.0/pysnap/templates/django/apps/users/views.py.j2 +22 -0
  58. snapstack-1.0.0/pysnap/templates/django/config/__init__.py.j2 +0 -0
  59. snapstack-1.0.0/pysnap/templates/django/config/asgi.py.j2 +9 -0
  60. snapstack-1.0.0/pysnap/templates/django/config/settings.py.j2 +110 -0
  61. snapstack-1.0.0/pysnap/templates/django/config/urls.py.j2 +12 -0
  62. snapstack-1.0.0/pysnap/templates/django/config/wsgi.py.j2 +9 -0
  63. snapstack-1.0.0/pysnap/templates/django/docker-compose.yml.j2 +29 -0
  64. snapstack-1.0.0/pysnap/templates/django/manage.py.j2 +22 -0
  65. snapstack-1.0.0/pysnap/templates/django/pyproject.toml.j2 +40 -0
  66. snapstack-1.0.0/pysnap/templates/django/template.json +50 -0
  67. snapstack-1.0.0/pysnap/templates/django/tests/__init__.py.j2 +1 -0
  68. snapstack-1.0.0/pysnap/templates/django/tests/conftest.py.j2 +6 -0
  69. snapstack-1.0.0/pysnap/templates/django/tests/test_health.py.j2 +9 -0
  70. snapstack-1.0.0/pysnap/templates/fastapi/.dockerignore.j2 +8 -0
  71. snapstack-1.0.0/pysnap/templates/fastapi/.github/workflows/ci.yml.j2 +46 -0
  72. snapstack-1.0.0/pysnap/templates/fastapi/.gitignore.j2 +13 -0
  73. snapstack-1.0.0/pysnap/templates/fastapi/Dockerfile.j2 +14 -0
  74. snapstack-1.0.0/pysnap/templates/fastapi/README.md.j2 +57 -0
  75. snapstack-1.0.0/pysnap/templates/fastapi/api/__init__.py.j2 +0 -0
  76. snapstack-1.0.0/pysnap/templates/fastapi/api/routes/__init__.py.j2 +0 -0
  77. snapstack-1.0.0/pysnap/templates/fastapi/api/routes/auth.py.j2 +18 -0
  78. snapstack-1.0.0/pysnap/templates/fastapi/api/routes/health.py.j2 +8 -0
  79. snapstack-1.0.0/pysnap/templates/fastapi/app/__init__.py.j2 +1 -0
  80. snapstack-1.0.0/pysnap/templates/fastapi/core/__init__.py.j2 +0 -0
  81. snapstack-1.0.0/pysnap/templates/fastapi/core/config.py.j2 +26 -0
  82. snapstack-1.0.0/pysnap/templates/fastapi/core/security.py.j2 +22 -0
  83. snapstack-1.0.0/pysnap/templates/fastapi/db/__init__.py.j2 +0 -0
  84. snapstack-1.0.0/pysnap/templates/fastapi/db/base.py.j2 +5 -0
  85. snapstack-1.0.0/pysnap/templates/fastapi/db/session.py.j2 +15 -0
  86. snapstack-1.0.0/pysnap/templates/fastapi/docker-compose.yml.j2 +30 -0
  87. snapstack-1.0.0/pysnap/templates/fastapi/main.py.j2 +27 -0
  88. snapstack-1.0.0/pysnap/templates/fastapi/models/__init__.py.j2 +0 -0
  89. snapstack-1.0.0/pysnap/templates/fastapi/models/user.py.j2 +13 -0
  90. snapstack-1.0.0/pysnap/templates/fastapi/pyproject.toml.j2 +48 -0
  91. snapstack-1.0.0/pysnap/templates/fastapi/schemas/__init__.py.j2 +0 -0
  92. snapstack-1.0.0/pysnap/templates/fastapi/schemas/user.py.j2 +18 -0
  93. snapstack-1.0.0/pysnap/templates/fastapi/template.json +53 -0
  94. snapstack-1.0.0/pysnap/templates/fastapi/tests/__init__.py.j2 +0 -0
  95. snapstack-1.0.0/pysnap/templates/fastapi/tests/conftest.py.j2 +9 -0
  96. snapstack-1.0.0/pysnap/templates/fastapi/tests/test_health.py.j2 +9 -0
  97. snapstack-1.0.0/pysnap/templates/flask/.dockerignore.j2 +14 -0
  98. snapstack-1.0.0/pysnap/templates/flask/.github/workflows/ci.yml.j2 +34 -0
  99. snapstack-1.0.0/pysnap/templates/flask/.gitignore.j2 +13 -0
  100. snapstack-1.0.0/pysnap/templates/flask/Dockerfile.j2 +14 -0
  101. snapstack-1.0.0/pysnap/templates/flask/README.md.j2 +34 -0
  102. snapstack-1.0.0/pysnap/templates/flask/app/__init__.py.j2 +30 -0
  103. snapstack-1.0.0/pysnap/templates/flask/app/config.py.j2 +23 -0
  104. snapstack-1.0.0/pysnap/templates/flask/app/extensions.py.j2 +9 -0
  105. snapstack-1.0.0/pysnap/templates/flask/app/models/__init__.py.j2 +1 -0
  106. snapstack-1.0.0/pysnap/templates/flask/app/models/user.py.j2 +16 -0
  107. snapstack-1.0.0/pysnap/templates/flask/app/routes/__init__.py.j2 +1 -0
  108. snapstack-1.0.0/pysnap/templates/flask/app/routes/auth.py.j2 +31 -0
  109. snapstack-1.0.0/pysnap/templates/flask/app/routes/health.py.j2 +11 -0
  110. snapstack-1.0.0/pysnap/templates/flask/docker-compose.yml.j2 +29 -0
  111. snapstack-1.0.0/pysnap/templates/flask/pyproject.toml.j2 +39 -0
  112. snapstack-1.0.0/pysnap/templates/flask/template.json +44 -0
  113. snapstack-1.0.0/pysnap/templates/flask/tests/__init__.py.j2 +1 -0
  114. snapstack-1.0.0/pysnap/templates/flask/tests/conftest.py.j2 +16 -0
  115. snapstack-1.0.0/pysnap/templates/flask/tests/test_health.py.j2 +8 -0
  116. snapstack-1.0.0/pysnap/templates/flask/wsgi.py.j2 +8 -0
  117. snapstack-1.0.0/pysnap/validator.py +89 -0
  118. snapstack-1.0.0/specs/001-core-scaffolding/checklists/requirements.md +42 -0
  119. snapstack-1.0.0/specs/001-core-scaffolding/contracts/cli-contract.md +173 -0
  120. snapstack-1.0.0/specs/001-core-scaffolding/data-model.md +134 -0
  121. snapstack-1.0.0/specs/001-core-scaffolding/plan.md +379 -0
  122. snapstack-1.0.0/specs/001-core-scaffolding/quickstart.md +103 -0
  123. snapstack-1.0.0/specs/001-core-scaffolding/research.md +224 -0
  124. snapstack-1.0.0/specs/001-core-scaffolding/spec.md +452 -0
  125. snapstack-1.0.0/specs/001-core-scaffolding/tasks.md +386 -0
  126. snapstack-1.0.0/tests/__init__.py +1 -0
  127. snapstack-1.0.0/tests/conftest.py +62 -0
  128. snapstack-1.0.0/tests/test_add_update.py +81 -0
  129. snapstack-1.0.0/tests/test_create.py +220 -0
  130. snapstack-1.0.0/tests/test_generator.py +183 -0
  131. snapstack-1.0.0/tests/test_manifest.py +105 -0
  132. snapstack-1.0.0/tests/test_plugins.py +63 -0
  133. snapstack-1.0.0/tests/test_preview.py +65 -0
  134. snapstack-1.0.0/tests/test_registry.py +93 -0
  135. snapstack-1.0.0/tests/test_templates_cmd.py +29 -0
  136. 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
+