Local dev guide and script updates

This commit is contained in:
Den Delimarsky 🌺
2025-09-11 22:06:04 -07:00
parent 0f0e19da33
commit f89361cd3d
4 changed files with 212 additions and 8 deletions

View File

@@ -12,6 +12,7 @@ Spec-Driven Development **flips the script** on traditional software development
- [Installation Guide](installation.md) - [Installation Guide](installation.md)
- [Quick Start Guide](quickstart.md) - [Quick Start Guide](quickstart.md)
- [Local Development](local-development.md)
## Core Philosophy ## Core Philosophy

131
docs/local-development.md Normal file
View File

@@ -0,0 +1,131 @@
# Local Development Guide
This guide shows how to iterate on the `specify` CLI locally without publishing a release or committing to `main` first.
## 1. Clone and Switch Branches
```bash
git clone https://github.com/github/spec-kit.git
cd spec-kit
# Work on a feature branch
git checkout -b your-feature-branch
```
## 2. Run the CLI Directly (Fastest Feedback)
You can execute the CLI via the module entrypoint without installing anything:
```bash
# From repo root
python -m src.specify_cli --help
python -m src.specify_cli init demo-project --ai claude --ignore-agent-tools
```
If you prefer invoking the script file style (uses shebang):
```bash
python src/specify_cli/__init__.py init demo-project
```
## 3. Use Editable Install (Isolated Environment)
Create an isolated environment using `uv` so dependencies resolve exactly like end users get them:
```bash
# Create & activate virtual env (uv auto-manages .venv)
uv venv
source .venv/bin/activate # or on Windows: .venv\\Scripts\\activate
# Install project in editable mode
uv pip install -e .
# Now 'specify' entrypoint is available
specify --help
```
Re-running after code edits requires no reinstall because of editable mode.
## 4. Invoke with uvx Directly From Git (Current Branch)
`uvx` can run from a local path (or a Git ref) to simulate user flows:
```bash
uvx --from . specify init demo-uvx --ai copilot --ignore-agent-tools
```
You can also point uvx at a specific branch without merging:
```bash
# Push your working branch first
git push origin your-feature-branch
uvx --from git+https://github.com/github/spec-kit.git@your-feature-branch specify init demo-branch-test
```
## 5. Testing Script Permission Logic
After running an `init`, check that shell scripts are executable on POSIX systems:
```bash
ls -l scripts | grep .sh
# Expect owner execute bit (e.g. -rwxr-xr-x)
```
On Windows this step is a no-op.
## 6. Run Lint / Basic Checks (Add Your Own)
Currently no enforced lint config is bundled, but you can quickly sanity check importability:
```bash
python -c "import specify_cli; print('Import OK')"
```
## 7. Build a Wheel Locally (Optional)
Validate packaging before publishing:
```bash
uv build
ls dist/
```
Install the built artifact into a fresh throwaway environment if needed.
## 8. Using a Temporary Workspace
When testing `init --here` in a dirty directory, create a temp workspace:
```bash
mkdir /tmp/spec-test && cd /tmp/spec-test
python -m src.specify_cli init --here --ai claude --ignore-agent-tools # if repo copied here
```
Or copy only the modified CLI portion if you want a lighter sandbox.
## 9. Debug Network / TLS Skips
If you need to bypass TLS validation while experimenting:
```bash
specify check --skip-tls
specify init demo --skip-tls --ai gemini --ignore-agent-tools
```
(Use only for local experimentation.)
## 10. Rapid Edit Loop Summary
| Action | Command |
|--------|---------|
| Run CLI directly | `python -m src.specify_cli --help` |
| Editable install | `uv pip install -e .` then `specify ...` |
| Local uvx run | `uvx --from . specify ...` |
| Git branch uvx | `uvx --from git+URL@branch specify ...` |
| Build wheel | `uv build` |
## 11. Cleaning Up
Remove build artifacts / virtual env quickly:
```bash
rm -rf .venv dist build *.egg-info
```
## 12. Common Issues
| Symptom | Fix |
|---------|-----|
| `ModuleNotFoundError: typer` | Run `uv pip install -e .` |
| Scripts not executable (Linux) | Re-run init (logic adds bits) or `chmod +x scripts/*.sh` |
| Git step skipped | You passed `--no-git` or Git not installed |
| TLS errors on corporate network | Try `--skip-tls` (not for production) |
## 13. Next Steps
- Update docs and run through Quick Start using your modified CLI
- Open a PR when satisfied
- (Optional) Tag a release once changes land in `main`
---
Feel free to expand this guide with additional local workflows (debugging, profiling, test automation) as the project matures.

View File

@@ -1,10 +1,17 @@
# Home page
- name: Home - name: Home
href: index.md href: index.md
- name: Installation
# Getting started section
- name: Getting Started
items:
- name: Installation
href: installation.md href: installation.md
- name: Quick Start - name: Quick Start
href: quickstart.md href: quickstart.md
- name: Contributing
href: CONTRIBUTING.md # Development workflows
- name: Support - name: Development
href: SUPPORT.md items:
- name: Local Development
href: local-development.md

View File

@@ -635,6 +635,67 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
return project_path return project_path
def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None:
"""Ensure POSIX .sh scripts in the project scripts directory have execute bits (no-op on Windows)."""
if os.name == "nt":
return # Windows: skip silently
scripts_dir = project_path / "scripts"
if not scripts_dir.is_dir():
return
failures: list[str] = []
updated = 0
for script in scripts_dir.glob("*.sh"):
try:
# Skip symlinks
if script.is_symlink():
continue
# Must be a regular file
if not script.is_file():
continue
# Quick shebang check
try:
with script.open("rb") as f:
first_two = f.read(2)
if first_two != b"#!":
continue
except Exception:
continue
st = script.stat()
mode = st.st_mode
# If already any execute bit set, skip
if mode & 0o111:
continue
# Only add execute bits that correspond to existing read bits
new_mode = mode
if mode & 0o400: # owner read
new_mode |= 0o100
if mode & 0o040: # group read
new_mode |= 0o010
if mode & 0o004: # other read
new_mode |= 0o001
# Fallback: ensure at least owner execute
if not (new_mode & 0o100):
new_mode |= 0o100
os.chmod(script, new_mode)
updated += 1
except Exception as e:
failures.append(f"{script.name}: {e}")
if tracker:
detail = f"{updated} updated" + (f", {len(failures)} failed" if failures else "")
tracker.add("chmod", "Set script permissions")
if failures:
tracker.error("chmod", detail)
else:
tracker.complete("chmod", detail)
else:
if updated:
console.print(f"[cyan]Updated execute permissions on {updated} script(s)[/cyan]")
if failures:
console.print("[yellow]Some scripts could not be updated:[/yellow]")
for f in failures:
console.print(f" - {f}")
@app.command() @app.command()
def init( def init(
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"), project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
@@ -761,6 +822,7 @@ def init(
("extract", "Extract template"), ("extract", "Extract template"),
("zip-list", "Archive contents"), ("zip-list", "Archive contents"),
("extracted-summary", "Extraction summary"), ("extracted-summary", "Extraction summary"),
("chmod", "Ensure scripts executable"),
("cleanup", "Cleanup"), ("cleanup", "Cleanup"),
("git", "Initialize git repository"), ("git", "Initialize git repository"),
("final", "Finalize") ("final", "Finalize")
@@ -778,6 +840,9 @@ def init(
download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker, client=local_client) download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker, client=local_client)
# Ensure scripts are executable (POSIX)
ensure_executable_scripts(project_path, tracker=tracker)
# Git step # Git step
if not no_git: if not no_git:
tracker.start("git") tracker.start("git")