diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..88d2d9f --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,23 @@ +{ + "permissions": { + "allow": [ + "Bash(date:*)", + "Bash(echo:*)", + "Bash(cat:*)", + "Bash(ls:*)", + "Bash(mkdir:*)", + "Bash(wc:*)", + "Bash(head:*)", + "Bash(tail:*)", + "Bash(sort:*)", + "Bash(grep:*)", + "Bash(tr:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git status:*)", + "Bash(git log:*)", + "Bash(git diff:*)", + "Bash(git tag:*)" + ] + } +} diff --git a/.claude/skills/check/SKILL.md b/.claude/skills/check/SKILL.md new file mode 100644 index 0000000..a295f02 --- /dev/null +++ b/.claude/skills/check/SKILL.md @@ -0,0 +1,14 @@ +--- +name: check +description: Run repo checks (ruff + pytest). +disable-model-invocation: true +--- + +Run: +- Windows: powershell -ExecutionPolicy Bypass -File scripts/check.ps1 +- Linux/WSL: bash scripts/check.sh + +If a check fails: +- capture the error output +- propose the smallest safe fix +- re-run checks diff --git a/.claude/skills/contextpack/SKILL.md b/.claude/skills/contextpack/SKILL.md new file mode 100644 index 0000000..94363f8 --- /dev/null +++ b/.claude/skills/contextpack/SKILL.md @@ -0,0 +1,13 @@ +--- +name: contextpack +description: Generate a repo snapshot for LLMs (.planning/CONTEXTPACK.md). +disable-model-invocation: true +--- + +Run: +- python scripts/contextpack.py + +Then read: +- .planning/CONTEXTPACK.md + +Use this before planning work or when resuming after a break. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3f56a53 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +[*.py] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d7396f --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Python +__pycache__/ +*.py[cod] + +# venv +.venv/ +venv/ + +# tooling +.pytest_cache/ +.ruff_cache/ + +# OS +.DS_Store +Thumbs.db + +# generated +.planning/CONTEXTPACK.md diff --git a/.planning/.gitkeep b/.planning/.gitkeep new file mode 100644 index 0000000..33b4209 --- /dev/null +++ b/.planning/.gitkeep @@ -0,0 +1 @@ +(placeholder) diff --git a/.planning/research/.gitkeep b/.planning/research/.gitkeep new file mode 100644 index 0000000..33b4209 --- /dev/null +++ b/.planning/research/.gitkeep @@ -0,0 +1 @@ +(placeholder) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..057b52e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.9 + hooks: + - id: ruff + args: ["--fix"] + - id: ruff-format diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..22c03ad --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,31 @@ +# Project Rules (auto-loaded) + +## What matters most +- Keep changes small and safe. +- Prefer incremental improvements over rewrites. +- Add/adjust tests when behavior changes. +- Keep commits atomic and descriptive. + +## GSD workflow +- Use GSD as the source of truth generator: + - /gsd:new-project creates PROJECT.md, REQUIREMENTS.md, ROADMAP.md, STATE.md, and .planning/research/ + - /gsd:discuss-phase -> /gsd:plan-phase -> /gsd:execute-phase -> /gsd:verify-work + +## Repo quick commands +- Bootstrap: + - Windows: powershell -ExecutionPolicy Bypass -File scripts/bootstrap.ps1 + - Linux/WSL: bash scripts/bootstrap.sh + +- Run checks: + - Windows: powershell -ExecutionPolicy Bypass -File scripts/check.ps1 + - Linux/WSL: bash scripts/check.sh + +- Generate LLM context snapshot: + - python scripts/contextpack.py + - output: .planning/CONTEXTPACK.md + +## Definition of done +- ruff check . +- ruff format --check . +- pytest +- Update STATE.md if progress changed diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..4546efb --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,15 @@ +# Project + +(Generated/maintained by GSD) + +## One-liner +- + +## Purpose +- + +## Users +- + +## Non-goals +- diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md new file mode 100644 index 0000000..7731e9c --- /dev/null +++ b/REQUIREMENTS.md @@ -0,0 +1,17 @@ +# Requirements + +(Generated/maintained by GSD) + +## Must-have (v1) +- [ ] + +## Nice-to-have (v2) +- [ ] + +## Constraints +- Platform: +- Performance: +- Privacy/security: + +## Acceptance tests +- [ ] Given ____, when ____, then ____ diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..bd4ae74 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,12 @@ +# Roadmap + +(Generated/maintained by GSD) + +## Phase 1 +- [ ] + +## Phase 2 +- [ ] + +## Phase 3 +- [ ] diff --git a/STATE.md b/STATE.md new file mode 100644 index 0000000..9d90980 --- /dev/null +++ b/STATE.md @@ -0,0 +1,20 @@ +# State + +(Generated/maintained by GSD) + +## Current focus +- + +## What works +- + +## What’s broken +- + +## Next 3 tasks +1. +2. +3. + +## Decisions +- (date) decision -> why diff --git a/docs/CHECKLIST.md b/docs/CHECKLIST.md new file mode 100644 index 0000000..498509c --- /dev/null +++ b/docs/CHECKLIST.md @@ -0,0 +1,12 @@ +# Checklists + +## Pre-merge +- [ ] ruff check . +- [ ] ruff format --check . +- [ ] pytest +- [ ] docs updated if requirements/decisions changed + +## Release +- [ ] version bump +- [ ] changelog notes +- [ ] basic smoke test \ No newline at end of file diff --git a/docs/CONTEXTPACK.md b/docs/CONTEXTPACK.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/PROJECT.md b/docs/PROJECT.md new file mode 100644 index 0000000..f8cf3a8 --- /dev/null +++ b/docs/PROJECT.md @@ -0,0 +1,13 @@ +# Project + +## One-liner +What is this? + +## Purpose +Why does it exist? + +## Users +Who is it for? + +## Non-goals +What are we intentionally NOT doing? diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md new file mode 100644 index 0000000..587f675 --- /dev/null +++ b/docs/REQUIREMENTS.md @@ -0,0 +1,15 @@ +# Requirements + +## Must-have (v1) +- [ ] + +## Nice-to-have (v2) +- [ ] + +## Constraints +- Platform: +- Performance: +- Privacy/security: + +## Acceptance tests +- [ ] Given ____, when ____, then ____ \ No newline at end of file diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..eeb902c --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,10 @@ +# Roadmap + +## Phase 1: MVP +- [ ] + +## Phase 2: Quality + UX +- [ ] + +## Phase 3: Scale + polish +- [ ] \ No newline at end of file diff --git a/docs/STATE.md b/docs/STATE.md new file mode 100644 index 0000000..16286a9 --- /dev/null +++ b/docs/STATE.md @@ -0,0 +1,18 @@ +# State + +## Current focus +- + +## What works +- + +## What’s broken +- + +## Next 3 tasks +1. +2. +3. + +## Recent decisions +- (date) decision -> why \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4490e1a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "app" +version = "0.1.0" +description = "GSD-native Python template" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "ruff>=0.6", + "pre-commit>=3.0", +] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "B", "UP"] +ignore = [] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/scripts/bootstrap.ps1 b/scripts/bootstrap.ps1 new file mode 100644 index 0000000..5f71c71 --- /dev/null +++ b/scripts/bootstrap.ps1 @@ -0,0 +1,9 @@ +python -m venv .venv +.\.venv\Scripts\Activate.ps1 + +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" + +pre-commit install + +Write-Host "✅ Bootstrapped (.venv created, dev deps installed)" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100644 index 0000000..60baa27 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +PY=python +command -v python >/dev/null 2>&1 || PY=python3 + +$PY -m venv .venv +source .venv/bin/activate + +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" + +pre-commit install || true + +echo "✅ Bootstrapped (.venv created, dev deps installed)" diff --git a/scripts/check.ps1 b/scripts/check.ps1 new file mode 100644 index 0000000..aa6773c --- /dev/null +++ b/scripts/check.ps1 @@ -0,0 +1,7 @@ +.\.venv\Scripts\Activate.ps1 + +ruff check . +ruff format --check . +pytest -q + +Write-Host "✅ Checks passed" diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100644 index 0000000..da1d912 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +source .venv/bin/activate + +ruff check . +ruff format --check . +pytest -q + +echo "✅ Checks passed" diff --git a/scripts/contextpack.py b/scripts/contextpack.py new file mode 100644 index 0000000..81ca19d --- /dev/null +++ b/scripts/contextpack.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import subprocess +from pathlib import Path +from datetime import datetime + +ROOT = Path(".").resolve() +OUT = ROOT / ".planning" / "CONTEXTPACK.md" + +IGNORE_DIRS = { + ".git", + ".venv", + "venv", + "__pycache__", + ".pytest_cache", + ".ruff_cache", + "dist", + "build", + "node_modules", +} + +KEY_FILES = [ + "CLAUDE.md", + "PROJECT.md", + "REQUIREMENTS.md", + "ROADMAP.md", + "STATE.md", + "pyproject.toml", + ".pre-commit-config.yaml", +] + +def run(cmd: list[str]) -> str: + try: + return subprocess.check_output(cmd, cwd=ROOT, stderr=subprocess.STDOUT, text=True).strip() + except Exception as e: + return f"(failed: {' '.join(cmd)}): {e}" + +def tree(max_depth: int = 3) -> str: + lines: list[str] = [] + + def walk(path: Path, depth: int) -> None: + if depth > max_depth: + return + for p in sorted(path.iterdir(), key=lambda x: (x.is_file(), x.name.lower())): + if p.name in IGNORE_DIRS: + continue + rel = p.relative_to(ROOT) + indent = " " * depth + if p.is_dir(): + lines.append(f"{indent}📁 {rel}/") + walk(p, depth + 1) + else: + lines.append(f"{indent}📄 {rel}") + + walk(ROOT, 0) + return "\n".join(lines) + +def head(path: Path, n: int = 160) -> str: + try: + return "\n".join(path.read_text(encoding="utf-8", errors="replace").splitlines()[:n]) + except Exception as e: + return f"(failed reading {path}): {e}" + +def main() -> None: + OUT.parent.mkdir(parents=True, exist_ok=True) + + parts: list[str] = [] + parts.append("# Context Pack") + parts.append(f"_Generated: {datetime.now().isoformat(timespec='seconds')}_\n") + + parts.append("## Repo tree\n```text\n" + tree() + "\n```") + parts.append("## Git status\n```text\n" + run(["git", "status"]) + "\n```") + parts.append("## Recent commits\n```text\n" + run(["git", "--no-pager", "log", "-10", "--oneline"]) + "\n```") + + parts.append("## Key files (head)") + for f in KEY_FILES: + p = ROOT / f + if p.exists(): + parts.append(f"### {f}\n```text\n{head(p)}\n```") + + OUT.write_text("\n\n".join(parts) + "\n", encoding="utf-8") + print(f"✅ Wrote {OUT.relative_to(ROOT)}") + +if __name__ == "__main__": + main() diff --git a/src/app/__init__.py b/src/app/__init__.py new file mode 100644 index 0000000..a9a2c5b --- /dev/null +++ b/src/app/__init__.py @@ -0,0 +1 @@ +__all__ = [] diff --git a/src/app/__main__.py b/src/app/__main__.py new file mode 100644 index 0000000..e124382 --- /dev/null +++ b/src/app/__main__.py @@ -0,0 +1,6 @@ +def main() -> None: + print("Hello from app!") + + +if __name__ == "__main__": + main() diff --git a/src/app/main.py b/src/app/main.py new file mode 100644 index 0000000..e124382 --- /dev/null +++ b/src/app/main.py @@ -0,0 +1,6 @@ +def main() -> None: + print("Hello from app!") + + +if __name__ == "__main__": + main() diff --git a/tests/test_smoke.py b/tests/test_smoke.py new file mode 100644 index 0000000..4bbc146 --- /dev/null +++ b/tests/test_smoke.py @@ -0,0 +1,2 @@ +def test_smoke() -> None: + assert True