Skip to content

Commit f89361c

Browse files
committed
Local dev guide and script updates
1 parent 0f0e19d commit f89361c

File tree

4 files changed

+212
-8
lines changed

4 files changed

+212
-8
lines changed

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Spec-Driven Development **flips the script** on traditional software development
1212

1313
- [Installation Guide](installation.md)
1414
- [Quick Start Guide](quickstart.md)
15+
- [Local Development](local-development.md)
1516

1617
## Core Philosophy
1718

docs/local-development.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Local Development Guide
2+
3+
This guide shows how to iterate on the `specify` CLI locally without publishing a release or committing to `main` first.
4+
5+
## 1. Clone and Switch Branches
6+
7+
```bash
8+
git clone https://github.com/github/spec-kit.git
9+
cd spec-kit
10+
# Work on a feature branch
11+
git checkout -b your-feature-branch
12+
```
13+
14+
## 2. Run the CLI Directly (Fastest Feedback)
15+
16+
You can execute the CLI via the module entrypoint without installing anything:
17+
18+
```bash
19+
# From repo root
20+
python -m src.specify_cli --help
21+
python -m src.specify_cli init demo-project --ai claude --ignore-agent-tools
22+
```
23+
24+
If you prefer invoking the script file style (uses shebang):
25+
26+
```bash
27+
python src/specify_cli/__init__.py init demo-project
28+
```
29+
30+
## 3. Use Editable Install (Isolated Environment)
31+
32+
Create an isolated environment using `uv` so dependencies resolve exactly like end users get them:
33+
34+
```bash
35+
# Create & activate virtual env (uv auto-manages .venv)
36+
uv venv
37+
source .venv/bin/activate # or on Windows: .venv\\Scripts\\activate
38+
39+
# Install project in editable mode
40+
uv pip install -e .
41+
42+
# Now 'specify' entrypoint is available
43+
specify --help
44+
```
45+
46+
Re-running after code edits requires no reinstall because of editable mode.
47+
48+
## 4. Invoke with uvx Directly From Git (Current Branch)
49+
50+
`uvx` can run from a local path (or a Git ref) to simulate user flows:
51+
52+
```bash
53+
uvx --from . specify init demo-uvx --ai copilot --ignore-agent-tools
54+
```
55+
56+
You can also point uvx at a specific branch without merging:
57+
58+
```bash
59+
# Push your working branch first
60+
git push origin your-feature-branch
61+
uvx --from git+https://github.com/github/spec-kit.git@your-feature-branch specify init demo-branch-test
62+
```
63+
64+
## 5. Testing Script Permission Logic
65+
After running an `init`, check that shell scripts are executable on POSIX systems:
66+
```bash
67+
ls -l scripts | grep .sh
68+
# Expect owner execute bit (e.g. -rwxr-xr-x)
69+
```
70+
On Windows this step is a no-op.
71+
72+
## 6. Run Lint / Basic Checks (Add Your Own)
73+
Currently no enforced lint config is bundled, but you can quickly sanity check importability:
74+
```bash
75+
python -c "import specify_cli; print('Import OK')"
76+
```
77+
78+
## 7. Build a Wheel Locally (Optional)
79+
Validate packaging before publishing:
80+
```bash
81+
uv build
82+
ls dist/
83+
```
84+
Install the built artifact into a fresh throwaway environment if needed.
85+
86+
## 8. Using a Temporary Workspace
87+
When testing `init --here` in a dirty directory, create a temp workspace:
88+
```bash
89+
mkdir /tmp/spec-test && cd /tmp/spec-test
90+
python -m src.specify_cli init --here --ai claude --ignore-agent-tools # if repo copied here
91+
```
92+
Or copy only the modified CLI portion if you want a lighter sandbox.
93+
94+
## 9. Debug Network / TLS Skips
95+
If you need to bypass TLS validation while experimenting:
96+
```bash
97+
specify check --skip-tls
98+
specify init demo --skip-tls --ai gemini --ignore-agent-tools
99+
```
100+
(Use only for local experimentation.)
101+
102+
## 10. Rapid Edit Loop Summary
103+
| Action | Command |
104+
|--------|---------|
105+
| Run CLI directly | `python -m src.specify_cli --help` |
106+
| Editable install | `uv pip install -e .` then `specify ...` |
107+
| Local uvx run | `uvx --from . specify ...` |
108+
| Git branch uvx | `uvx --from git+URL@branch specify ...` |
109+
| Build wheel | `uv build` |
110+
111+
## 11. Cleaning Up
112+
Remove build artifacts / virtual env quickly:
113+
```bash
114+
rm -rf .venv dist build *.egg-info
115+
```
116+
117+
## 12. Common Issues
118+
| Symptom | Fix |
119+
|---------|-----|
120+
| `ModuleNotFoundError: typer` | Run `uv pip install -e .` |
121+
| Scripts not executable (Linux) | Re-run init (logic adds bits) or `chmod +x scripts/*.sh` |
122+
| Git step skipped | You passed `--no-git` or Git not installed |
123+
| TLS errors on corporate network | Try `--skip-tls` (not for production) |
124+
125+
## 13. Next Steps
126+
- Update docs and run through Quick Start using your modified CLI
127+
- Open a PR when satisfied
128+
- (Optional) Tag a release once changes land in `main`
129+
130+
---
131+
Feel free to expand this guide with additional local workflows (debugging, profiling, test automation) as the project matures.

docs/toc.yml

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
# Home page
12
- name: Home
23
href: index.md
3-
- name: Installation
4-
href: installation.md
5-
- name: Quick Start
6-
href: quickstart.md
7-
- name: Contributing
8-
href: CONTRIBUTING.md
9-
- name: Support
10-
href: SUPPORT.md
4+
5+
# Getting started section
6+
- name: Getting Started
7+
items:
8+
- name: Installation
9+
href: installation.md
10+
- name: Quick Start
11+
href: quickstart.md
12+
13+
# Development workflows
14+
- name: Development
15+
items:
16+
- name: Local Development
17+
href: local-development.md

src/specify_cli/__init__.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,67 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
635635
return project_path
636636

637637

638+
def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None:
639+
"""Ensure POSIX .sh scripts in the project scripts directory have execute bits (no-op on Windows)."""
640+
if os.name == "nt":
641+
return # Windows: skip silently
642+
scripts_dir = project_path / "scripts"
643+
if not scripts_dir.is_dir():
644+
return
645+
failures: list[str] = []
646+
updated = 0
647+
for script in scripts_dir.glob("*.sh"):
648+
try:
649+
# Skip symlinks
650+
if script.is_symlink():
651+
continue
652+
# Must be a regular file
653+
if not script.is_file():
654+
continue
655+
# Quick shebang check
656+
try:
657+
with script.open("rb") as f:
658+
first_two = f.read(2)
659+
if first_two != b"#!":
660+
continue
661+
except Exception:
662+
continue
663+
st = script.stat()
664+
mode = st.st_mode
665+
# If already any execute bit set, skip
666+
if mode & 0o111:
667+
continue
668+
# Only add execute bits that correspond to existing read bits
669+
new_mode = mode
670+
if mode & 0o400: # owner read
671+
new_mode |= 0o100
672+
if mode & 0o040: # group read
673+
new_mode |= 0o010
674+
if mode & 0o004: # other read
675+
new_mode |= 0o001
676+
# Fallback: ensure at least owner execute
677+
if not (new_mode & 0o100):
678+
new_mode |= 0o100
679+
os.chmod(script, new_mode)
680+
updated += 1
681+
except Exception as e:
682+
failures.append(f"{script.name}: {e}")
683+
if tracker:
684+
detail = f"{updated} updated" + (f", {len(failures)} failed" if failures else "")
685+
tracker.add("chmod", "Set script permissions")
686+
if failures:
687+
tracker.error("chmod", detail)
688+
else:
689+
tracker.complete("chmod", detail)
690+
else:
691+
if updated:
692+
console.print(f"[cyan]Updated execute permissions on {updated} script(s)[/cyan]")
693+
if failures:
694+
console.print("[yellow]Some scripts could not be updated:[/yellow]")
695+
for f in failures:
696+
console.print(f" - {f}")
697+
698+
638699
@app.command()
639700
def init(
640701
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
@@ -761,6 +822,7 @@ def init(
761822
("extract", "Extract template"),
762823
("zip-list", "Archive contents"),
763824
("extracted-summary", "Extraction summary"),
825+
("chmod", "Ensure scripts executable"),
764826
("cleanup", "Cleanup"),
765827
("git", "Initialize git repository"),
766828
("final", "Finalize")
@@ -778,6 +840,9 @@ def init(
778840

779841
download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker, client=local_client)
780842

843+
# Ensure scripts are executable (POSIX)
844+
ensure_executable_scripts(project_path, tracker=tracker)
845+
781846
# Git step
782847
if not no_git:
783848
tracker.start("git")

0 commit comments

Comments
 (0)