From cc01b1ab755f7da4bdb80f0ef9e960c78bd90bd5 Mon Sep 17 00:00:00 2001 From: Mike Kelly Date: Thu, 9 Oct 2025 14:58:37 -0400 Subject: [PATCH 1/2] Adding support for remote catalogue of constitutions --- CHANGELOG.md | 23 ++ README.md | 19 ++ docs/example-constitutions-repo.md | 368 ++++++++++++++++++++++++++ docs/remote-constitutions.md | 411 +++++++++++++++++++++++++++++ docs/toc.yml | 4 + src/specify_cli/__init__.py | 258 +++++++++++++++++- 6 files changed, 1081 insertions(+), 2 deletions(-) create mode 100644 docs/example-constitutions-repo.md create mode 100644 docs/remote-constitutions.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4706b826b..6cc291a87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,29 @@ All notable changes to the Specify CLI and templates are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- **Remote Constitutions Feature**: New capability to fetch and apply constitutions from remote GitHub repositories during project initialization + - `list-constitutions` command to browse available constitutions from a GitHub repository + - `--constitution-repo` option for `init` command to specify a GitHub repository containing constitutions + - `--constitution-name` option to directly specify which constitution to use + - `--constitution-interactive` flag to interactively select from available constitutions + - `--constitution-path` option to specify subdirectory within the constitution repository + - `--constitution-branch` option to fetch from a specific branch (default: main) + - Support for both public and private repositories (with GitHub token authentication) + - Comprehensive documentation in `docs/remote-constitutions.md` + - Example constitution repository guide in `docs/example-constitutions-repo.md` +- New helper functions `fetch_remote_constitutions_list()` and `fetch_remote_constitution_content()` to handle GitHub API interactions +- Constitution fetching integrated into the project initialization workflow with progress tracking + +### Changed + +- Enhanced `init` command help text to include remote constitution examples +- Updated README.md with remote constitution feature documentation and usage examples +- Added remote constitution options to CLI reference table in README.md + ## [0.0.18] - 2025-10-06 ### Added diff --git a/README.md b/README.md index 4f9da1c57..39a84ace6 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ The `specify` command supports the following options: |-------------|----------------------------------------------------------------| | `init` | Initialize a new Specify project from the latest template | | `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`) | +| `list-constitutions` | List available constitutions from a remote GitHub repository | ### `specify init` Arguments & Options @@ -166,6 +167,11 @@ The `specify` command supports the following options: | `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) | | `--debug` | Flag | Enable detailed debug output for troubleshooting | | `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) | +| `--constitution-repo` | Option | GitHub repository containing constitutions (format: 'owner/repo') | +| `--constitution-name` | Option | Name of the constitution to use from the remote repository | +| `--constitution-path` | Option | Path within constitution repository where constitutions are stored | +| `--constitution-branch`| Option | Branch to fetch constitution from (default: main) | +| `--constitution-interactive` | Flag | Interactively select a constitution from the remote repository | ### Examples @@ -198,6 +204,19 @@ specify init --here --force --ai copilot # Skip git initialization specify init my-project --ai gemini --no-git +# Use a remote constitution from your company's repository +specify init my-project --constitution-repo myorg/constitutions --constitution-name python-microservices + +# Interactively select a remote constitution +specify init my-project --constitution-repo myorg/constitutions --constitution-interactive + +# List available constitutions +specify list-constitutions myorg/constitutions +specify list-constitutions myorg/constitutions --path backend +``` + +For more details on remote constitutions, see the [Remote Constitutions Guide](docs/remote-constitutions.md). + # Enable debug output for troubleshooting specify init my-project --ai claude --debug diff --git a/docs/example-constitutions-repo.md b/docs/example-constitutions-repo.md new file mode 100644 index 000000000..1329dc7c4 --- /dev/null +++ b/docs/example-constitutions-repo.md @@ -0,0 +1,368 @@ +# Example Constitution Repository + +This is an example structure for creating a constitution repository that can be used with spec-kit's remote constitutions feature. + +## Repository Structure + +``` +my-constitutions/ +├── README.md # Overview and usage guide +├── python-microservices.md # Python microservices constitution +├── golang-api.md # Go API services constitution +├── react-frontend.md # React frontend constitution +├── nodejs-backend.md # Node.js backend constitution +├── data-pipeline.md # Data pipeline constitution +└── mobile-app.md # Mobile app constitution +``` + +## Example Constitution Files + +### python-microservices.md + +```markdown +# Python Microservices Constitution + +## Core Principles + +### I. API-First Design +Every microservice must define its API contract using OpenAPI 3.0 specification before implementation begins. APIs must be versioned and maintain backward compatibility. + +### II. Test-Driven Development (NON-NEGOTIABLE) +- Unit test coverage must be >= 80% +- Integration tests required for all API endpoints +- Tests must pass before merging to main branch +- TDD cycle: Red → Green → Refactor + +### III. Observability +All services must implement: +- Structured logging using Python's `structlog` library (JSON format) +- Distributed tracing via OpenTelemetry +- Metrics exposure in Prometheus format +- Health check endpoints (`/health`, `/ready`) +- Request ID propagation across service boundaries + +### IV. Cloud Native Standards +Services must: +- Run in Docker containers (Alpine-based for size optimization) +- Follow 12-factor app principles +- Use Kubernetes for orchestration +- Store configuration in environment variables +- Be stateless and horizontally scalable + +### V. Security Requirements +- All external communications must use TLS 1.3+ +- Secrets managed via HashiCorp Vault or Kubernetes Secrets +- No credentials in code, config files, or container images +- Regular dependency scanning (weekly) using `safety` and `snyk` +- Input validation on all API endpoints +- Rate limiting on public endpoints + +### VI. Code Quality Standards +- Type hints required for all functions (use `mypy` for static checking) +- Code formatted with `black` (line length: 100) +- Import sorting with `isort` +- Linting with `ruff` (or `pylint` + `flake8`) +- Pre-commit hooks enforced for all the above + +### VII. Dependency Management +- Use `Poetry` or `uv` for dependency management +- Pin all dependencies to specific versions +- Monthly dependency updates +- Security patches applied within 72 hours of disclosure + +## Development Workflow + +### Branching Strategy +- Main branch protected, requires PR +- Feature branches: `feature/description` +- Bug fixes: `fix/description` +- Hotfixes: `hotfix/description` + +### Code Review Process +- Minimum 2 approvers required +- Architecture review for new services or major changes +- Security review for authentication/authorization changes +- Performance review for database-heavy features + +### Testing Requirements +- Unit tests: pytest with fixtures +- Integration tests: testcontainers for dependencies +- Contract tests: Pact for service-to-service +- Load tests: Locust for performance validation + +### CI/CD Pipeline +- Automated testing on every PR +- Automated security scanning (SAST, dependency check) +- Automated Docker image building and scanning +- Blue-green deployments for zero downtime +- Automated rollback on health check failures + +## Technology Stack + +### Required +- **Language**: Python 3.11+ +- **Framework**: FastAPI or Flask +- **ORM**: SQLAlchemy +- **Database**: PostgreSQL (primary), Redis (cache/queue) +- **Message Queue**: RabbitMQ or Apache Kafka +- **Observability**: OpenTelemetry + Prometheus + Grafana + +### Recommended +- **Testing**: pytest, pytest-asyncio, httpx (for async tests) +- **Validation**: Pydantic for data validation +- **Documentation**: Swagger UI (auto-generated from OpenAPI) + +## Governance + +This constitution supersedes all other development practices for Python microservices. Amendments require approval from: +- Tech Lead +- Architecture Review Board +- Security Team representative + +All amendments must be documented with: +- Rationale for change +- Impact assessment +- Migration plan for existing services + +**Version**: 1.0.0 | **Ratified**: 2025-01-15 | **Last Amended**: 2025-01-15 +``` + +### react-frontend.md + +```markdown +# React Frontend Constitution + +## Core Principles + +### I. Component-First Development +- Build reusable, self-contained components +- Follow atomic design principles (atoms, molecules, organisms) +- Components must be documented with Storybook +- Prop types required (TypeScript interfaces) + +### II. Type Safety (NON-NEGOTIABLE) +- TypeScript strict mode enabled +- No `any` types allowed (use `unknown` if necessary) +- All props, state, and API responses must be typed +- Type definitions in separate `.types.ts` files + +### III. Testing Strategy +- Unit tests for business logic (>= 80% coverage) +- Component tests using React Testing Library +- E2E tests for critical user flows (Playwright) +- Visual regression tests for UI components (Chromatic) + +### IV. Performance Standards +- Lighthouse score >= 90 (all categories) +- Time to Interactive (TTI) < 3.5s on 4G +- First Contentful Paint (FCP) < 1.8s +- Cumulative Layout Shift (CLS) < 0.1 +- Lazy loading for routes and heavy components +- Code splitting at route level minimum + +### V. Accessibility (NON-NEGOTIABLE) +- WCAG 2.1 Level AA compliance required +- Semantic HTML elements +- Proper ARIA labels where needed +- Keyboard navigation support +- Screen reader tested +- Color contrast ratios compliant + +### VI. State Management +- Use React Context for simple, localized state +- Use Zustand or Redux Toolkit for complex global state +- Server state managed by TanStack Query (React Query) +- Form state with React Hook Form +- URL as source of truth for navigation state + +### VII. Code Quality +- ESLint with Airbnb or Standard config +- Prettier for formatting (single quotes, semicolons) +- Husky for pre-commit hooks +- No console.log statements in production code +- Custom hooks for shared logic + +## Development Workflow + +### Project Structure +``` +src/ +├── components/ # Reusable UI components +├── features/ # Feature-specific modules +├── hooks/ # Custom React hooks +├── pages/ # Route components +├── services/ # API clients +├── store/ # Global state +├── types/ # TypeScript types +├── utils/ # Utility functions +└── styles/ # Global styles +``` + +### Component Guidelines +- One component per file +- Use functional components with hooks +- Extract custom hooks for complex logic +- Props destructured in function signature +- Default exports for components + +### Styling Approach +- Tailwind CSS for utility-first styling +- CSS Modules for component-specific styles +- styled-components for dynamic styling needs +- No inline styles except for dynamic values + +### API Integration +- Centralize API calls in service modules +- Use TanStack Query for data fetching +- Implement request/response interceptors +- Handle loading, error, and success states +- Optimistic updates where appropriate + +### Code Review Process +- Minimum 1 reviewer (2 for major changes) +- UI/UX review for new components +- Accessibility review for interactive elements +- Performance review for data-heavy features + +### CI/CD Pipeline +- Type checking with TypeScript +- Linting with ESLint +- Unit and component tests +- Build verification +- Deploy preview for every PR +- Automated deployment to staging + +## Technology Stack + +### Required +- **Framework**: React 18+ +- **Language**: TypeScript 5+ +- **Build Tool**: Vite +- **Styling**: Tailwind CSS +- **State**: Zustand + TanStack Query +- **Routing**: React Router v6 +- **Forms**: React Hook Form + Zod +- **Testing**: Vitest + React Testing Library + Playwright + +### Recommended +- **UI Library**: shadcn/ui or Radix UI +- **Icons**: Lucide React or Heroicons +- **Date Handling**: date-fns +- **HTTP Client**: ky or axios + +## Governance + +This constitution applies to all React-based frontends. Amendments require approval from: +- Frontend Tech Lead +- UX Lead +- Accessibility Champion + +All amendments must include: +- Rationale and benefits +- Migration guide for existing code +- Update to component library if needed + +**Version**: 1.2.0 | **Ratified**: 2024-09-01 | **Last Amended**: 2025-01-20 +``` + +## Using These Constitutions + +1. Create a GitHub repository (public or private) +2. Add your constitution markdown files +3. Team members can list them: + ```bash + specify list-constitutions myorg/constitutions-repo + ``` + +4. Initialize projects with a constitution: + ```bash + specify init my-app --constitution-repo myorg/constitutions-repo --constitution-name react-frontend + ``` + +5. Or select interactively: + ```bash + specify init my-app --constitution-repo myorg/constitutions-repo --constitution-interactive + ``` + +## Best Practices for Constitution Repositories + +1. **Keep constitutions focused**: Each should target a specific use case or tech stack +2. **Version your constitutions**: Include version numbers and dates +3. **Document rationale**: Explain *why* rules exist, not just *what* they are +4. **Make them actionable**: Include specific tools, commands, and examples +5. **Review regularly**: Update constitutions as practices evolve +6. **Collect feedback**: Encourage teams to suggest improvements +7. **Maintain a changelog**: Track what changed and why +8. **Add a README**: Explain the purpose and usage of each constitution + +## Example README.md for Constitution Repository + +```markdown +# Engineering Constitutions + +This repository contains standardized project constitutions for [Your Company Name]. + +## Available Constitutions + +| Constitution | Use Case | Last Updated | +|--------------|----------|--------------| +| `python-microservices.md` | Backend microservices in Python | 2025-01-15 | +| `golang-api.md` | High-performance APIs in Go | 2025-01-10 | +| `react-frontend.md` | Web frontends using React | 2025-01-20 | +| `nodejs-backend.md` | Node.js backend services | 2025-01-12 | +| `data-pipeline.md` | ETL and data processing pipelines | 2024-12-20 | +| `mobile-app.md` | React Native mobile apps | 2025-01-05 | + +## Usage + +### List Available Constitutions + +```bash +specify list-constitutions yourorg/engineering-constitutions +``` + +### Initialize New Project with Constitution + +```bash +specify init my-new-service \ + --constitution-repo yourorg/engineering-constitutions \ + --constitution-name python-microservices +``` + +### Interactive Selection + +```bash +specify init my-new-service \ + --constitution-repo yourorg/engineering-constitutions \ + --constitution-interactive +``` + +## Contributing + +To propose changes to a constitution: + +1. Create a new branch +2. Update the relevant constitution file +3. Include rationale in commit message +4. Open a PR with: + - What changed + - Why it changed + - Impact on existing projects + - Migration guidance if needed + +## Approval Process + +Constitution changes require approval from: +- Tech Lead of relevant domain +- Architecture Review Board representative +- At least 2 engineers who use that constitution + +## Questions? + +Contact the Architecture Team at #architecture-help +``` + +## Additional Resources + +- [Remote Constitutions Guide](../docs/remote-constitutions.md) +- [spec-kit Documentation](../README.md) diff --git a/docs/remote-constitutions.md b/docs/remote-constitutions.md new file mode 100644 index 000000000..f2ccf5547 --- /dev/null +++ b/docs/remote-constitutions.md @@ -0,0 +1,411 @@ +# Remote Constitutions + +## Overview + +The Remote Constitutions feature allows organizations to maintain a centralized catalog of project constitutions in a GitHub repository. This enables teams to easily bootstrap new projects with pre-approved, standardized governance principles and development guidelines. + +## Why Remote Constitutions? + +At large companies or within development teams, there are often: +- **Standard practices** that should be followed across projects +- **Compliance requirements** that must be met +- **Technology standards** for cloud environments, CICD pipelines, etc. +- **Quality guidelines** that ensure consistency across the organization + +Instead of requiring developers to recreate or copy-paste these constitutions, remote constitutions allow them to: +1. Browse available constitutions from a central repository +2. Select the appropriate constitution for their project type +3. Automatically apply it during project initialization + +## Setting Up a Constitutions Repository + +### Repository Structure + +Create a GitHub repository to store your constitutions. The recommended structure is: + +``` +my-constitutions-repo/ +├── README.md +├── python-microservices.md +├── react-frontend.md +├── golang-api.md +├── data-pipeline.md +└── mobile-app.md +``` + +Or organize them in directories: + +``` +my-constitutions-repo/ +├── README.md +├── backend/ +│ ├── python-microservices.md +│ ├── golang-api.md +│ └── java-spring.md +├── frontend/ +│ ├── react-spa.md +│ └── vue-pwa.md +└── data/ + ├── batch-pipeline.md + └── streaming-pipeline.md +``` + +### Constitution File Format + +Each constitution file should be a markdown file (`.md`) following the structure defined in the spec-kit template. See `memory/constitution.md` in any initialized project for the template format. + +Example constitution for a Python microservices project: + +```markdown +# Python Microservices Constitution + +## Core Principles + +### I. API-First Design +Every microservice must define its API contract using OpenAPI 3.0 specification before implementation begins. APIs must be versioned and maintain backward compatibility. + +### II. Test-Driven Development (NON-NEGOTIABLE) +- Unit test coverage must be >= 80% +- Integration tests required for all API endpoints +- Tests must pass before merging to main branch + +### III. Observability +All services must implement: +- Structured logging (JSON format) +- Distributed tracing (OpenTelemetry) +- Metrics exposure (Prometheus format) +- Health check endpoints (/health, /ready) + +### IV. Cloud Native Standards +Services must: +- Run in Docker containers +- Follow 12-factor app principles +- Use Kubernetes for orchestration +- Store configs in environment variables + +### V. Security Requirements +- All external communications must use TLS +- Secrets managed via HashiCorp Vault +- No credentials in code or config files +- Regular dependency scanning for vulnerabilities + +## Development Workflow + +### Code Review Process +- Minimum 2 approvers required +- Architecture review for new services +- Security review for authentication changes + +### CI/CD Pipeline +- Automated testing on every PR +- Automated security scanning +- Blue-green deployments for zero downtime + +## Governance + +This constitution supersedes all other development practices. Amendments require approval from the architecture steering committee and must be documented with rationale and migration plans. + +**Version**: 1.0.0 | **Ratified**: 2025-01-15 | **Last Amended**: 2025-01-15 +``` + +### Access Control + +- **Public repositories**: Anyone can access the constitutions +- **Private repositories**: Users need a GitHub token with appropriate permissions + +## Using Remote Constitutions + +### Listing Available Constitutions + +To see what constitutions are available in a repository: + +```bash +specify list-constitutions myorg/constitutions-repo +``` + +With a custom path: + +```bash +specify list-constitutions myorg/constitutions-repo --path backend +``` + +From a specific branch: + +```bash +specify list-constitutions myorg/constitutions-repo --branch develop +``` + +For private repositories: + +```bash +specify list-constitutions myorg/private-constitutions --github-token $GITHUB_TOKEN +``` + +Or set the token as an environment variable: + +```bash +export GITHUB_TOKEN="ghp_your_token_here" +specify list-constitutions myorg/private-constitutions +``` + +### Applying a Constitution During Init + +#### Interactive Selection + +Browse and select interactively: + +```bash +specify init my-project --constitution-repo myorg/constitutions --constitution-interactive +``` + +#### Direct Specification + +Specify the constitution name directly: + +```bash +specify init my-project \ + --constitution-repo myorg/constitutions \ + --constitution-name python-microservices +``` + +#### With Custom Path + +If constitutions are in a subdirectory: + +```bash +specify init my-project \ + --constitution-repo myorg/constitutions \ + --constitution-path backend \ + --constitution-name python-microservices +``` + +#### From Non-Main Branch + +Use a specific branch: + +```bash +specify init my-project \ + --constitution-repo myorg/constitutions \ + --constitution-branch develop \ + --constitution-name experimental-python +``` + +#### Complete Example with All Options + +```bash +specify init my-new-api \ + --ai claude \ + --script sh \ + --constitution-repo mycompany/engineering-standards \ + --constitution-path constitutions/backend \ + --constitution-name python-microservices \ + --github-token $GITHUB_TOKEN +``` + +## Best Practices + +### For Constitution Repository Maintainers + +1. **Clear Naming**: Use descriptive, hyphenated names for constitution files + - ✅ `python-microservices.md` + - ✅ `react-spa-enterprise.md` + - ❌ `const1.md` + - ❌ `v2.md` + +2. **Version Your Constitutions**: Include version numbers in the constitution content itself + +3. **Add a README**: Create a comprehensive README.md explaining: + - Purpose of each constitution + - When to use which constitution + - How to contribute updates + - Change approval process + +4. **Use Examples**: Include practical examples in your constitutions + +5. **Keep Them Updated**: Regular reviews to ensure constitutions reflect current practices + +6. **Document Changes**: Use Git commit messages to explain constitution changes + +### For Constitution Users + +1. **Review Before Using**: Always review the constitution content before applying it + +2. **Customize as Needed**: Remote constitutions are starting points; customize them for your specific project needs after initialization + +3. **Stay Updated**: Periodically check if your constitution source has updates + +4. **Provide Feedback**: Report issues or suggest improvements to constitution maintainers + +## Security Considerations + +### GitHub Tokens + +When using private repositories, you'll need a GitHub Personal Access Token: + +1. **Create a token** at https://github.com/settings/tokens +2. **Required scopes**: `repo` (for private repos) or `public_repo` (for public repos) +3. **Store securely**: Use environment variables, never hardcode tokens + +```bash +# Set as environment variable +export GITHUB_TOKEN="ghp_your_token_here" + +# Or pass directly (less secure) +specify init my-project --constitution-repo myorg/repo --github-token "ghp_your_token_here" +``` + +### Constitution Content Security + +- **Review constitutions**: Ensure they come from trusted sources +- **Check for secrets**: Constitutions should never contain credentials or API keys +- **Validate practices**: Ensure recommended practices align with your security policies + +## Troubleshooting + +### "Repository or path not found" + +**Cause**: The repository URL or path is incorrect, or you don't have access. + +**Solutions**: +- Verify the repository exists: `https://github.com/owner/repo` +- Check if it's private and you need a token +- Verify the path within the repository is correct + +### "No constitutions found" + +**Cause**: No `.md` files in the specified path. + +**Solutions**: +- Check the `--path` parameter +- Verify files have `.md` extension +- Use `list-constitutions` command to see what's available + +### "Constitution 'name' not found" + +**Cause**: The specified constitution name doesn't match any files. + +**Solutions**: +- Run `list-constitutions` to see available names +- Constitution names are file names without the `.md` extension +- Check for typos in the name + +### Rate Limiting + +**Cause**: GitHub API has rate limits (60 requests/hour for unauthenticated users). + +**Solution**: +- Use a GitHub token to get higher limits (5000 requests/hour) +- Wait before retrying + +## Examples + +### Example 1: Company-Wide Standards + +**Scenario**: Your company has standardized on specific architectures and practices. + +**Setup**: Create a `engineering-standards` repository with constitutions for each tech stack. + +**Usage**: +```bash +# New Python microservice +specify init payment-service \ + --constitution-repo acme-corp/engineering-standards \ + --constitution-name python-microservices + +# New React frontend +specify init customer-portal \ + --constitution-repo acme-corp/engineering-standards \ + --constitution-name react-spa-enterprise +``` + +### Example 2: Team-Specific Templates + +**Scenario**: Your data engineering team has specific requirements. + +**Setup**: Create a `data-team-constitutions` repository with pipelines, schemas, etc. + +**Usage**: +```bash +specify init customer-analytics \ + --constitution-repo data-team/data-team-constitutions \ + --constitution-name spark-batch-pipeline \ + --constitution-interactive +``` + +### Example 3: Open Source Best Practices + +**Scenario**: You want to share best practices publicly. + +**Setup**: Create a public repository with example constitutions. + +**Usage**: +```bash +# Anyone can use without authentication +specify init my-open-source-lib \ + --constitution-repo awesome-org/spec-kit-constitutions \ + --constitution-name python-library-best-practices +``` + +## Integration with CI/CD + +You can automate project initialization with remote constitutions in your CI/CD pipelines: + +```yaml +# GitHub Actions example +name: Bootstrap New Service + +on: + workflow_dispatch: + inputs: + service_name: + description: 'Name of the new service' + required: true + constitution: + description: 'Constitution to use' + required: true + type: choice + options: + - python-microservices + - golang-api + - nodejs-service + +jobs: + bootstrap: + runs-on: ubuntu-latest + steps: + - name: Install specify-cli + run: uv tool install specify-cli --from git+https://github.com/github/spec-kit.git + + - name: Initialize project + run: | + specify init ${{ inputs.service_name }} \ + --ai copilot \ + --constitution-repo ${{ github.repository_owner }}/engineering-standards \ + --constitution-name ${{ inputs.constitution }} \ + --github-token ${{ secrets.GITHUB_TOKEN }} + + - name: Create repository + # ... additional steps to create and push to new repo +``` + +## Future Enhancements + +Potential future improvements to this feature: + +- **Constitution metadata**: Support for metadata files describing constitutions +- **Constitution search**: Search within constitution content +- **Version locking**: Pin specific versions of constitutions +- **Constitution validation**: Validate constitution format before applying +- **Diff preview**: Show what will change when applying a constitution +- **Constitution inheritance**: Base constitutions that extend others +- **Custom protocols**: Support for other sources beyond GitHub (GitLab, Bitbucket, etc.) + +## Contributing + +To contribute improvements to the remote constitutions feature, see [CONTRIBUTING.md](../CONTRIBUTING.md). + +## Related Documentation + +- [Installation](installation.md) +- [Quickstart](quickstart.md) +- [Constitution Command](../templates/commands/constitution.md) diff --git a/docs/toc.yml b/docs/toc.yml index ecabd1850..1a2a9d7cb 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -9,6 +9,10 @@ href: installation.md - name: Quick Start href: quickstart.md + - name: Remote Constitutions + href: remote-constitutions.md + - name: Example Constitution Repository + href: example-constitutions-repo.md # Development workflows - name: Development diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index a0652539b..37891c9c2 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -64,6 +64,94 @@ def _github_auth_headers(cli_token: str | None = None) -> dict: token = _github_token(cli_token) return {"Authorization": f"Bearer {token}"} if token else {} +def fetch_remote_constitutions_list(repo_url: str, branch: str = "main", path: str = "", github_token: str = None) -> list[dict]: + """ + Fetch a list of available constitutions from a remote GitHub repository. + + Args: + repo_url: GitHub repository URL (e.g., 'https://github.com/owner/repo' or 'owner/repo') + branch: Branch name to fetch from (default: 'main') + path: Path within the repository where constitutions are stored (default: root) + github_token: Optional GitHub token for private repositories + + Returns: + List of dicts with 'name', 'path', and 'download_url' keys + """ + # Parse repo_url to extract owner and repo + repo_url = repo_url.strip() + if repo_url.startswith("https://github.com/"): + repo_url = repo_url.replace("https://github.com/", "") + repo_url = repo_url.rstrip("/") + + parts = repo_url.split("/") + if len(parts) < 2: + raise ValueError(f"Invalid repository URL: {repo_url}. Expected format: 'owner/repo' or 'https://github.com/owner/repo'") + + owner, repo = parts[0], parts[1] + + # Construct API URL to list directory contents + api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}" + if branch != "main": + api_url += f"?ref={branch}" + + try: + response = client.get( + api_url, + timeout=30, + follow_redirects=True, + headers=_github_auth_headers(github_token), + ) + + if response.status_code == 404: + raise RuntimeError(f"Repository or path not found: {owner}/{repo}/{path}") + elif response.status_code != 200: + raise RuntimeError(f"GitHub API returned {response.status_code} for {api_url}") + + contents = response.json() + + # Filter for markdown files that look like constitutions + constitutions = [] + for item in contents: + if item["type"] == "file" and item["name"].endswith(".md"): + constitutions.append({ + "name": item["name"].replace(".md", ""), + "path": item["path"], + "download_url": item["download_url"], + "description": item["name"] # Can be enhanced with metadata + }) + + return constitutions + + except Exception as e: + raise RuntimeError(f"Error fetching constitutions from {owner}/{repo}: {e}") + +def fetch_remote_constitution_content(download_url: str, github_token: str = None) -> str: + """ + Fetch the content of a specific constitution from a remote URL. + + Args: + download_url: Direct download URL for the constitution file + github_token: Optional GitHub token for private repositories + + Returns: + Content of the constitution file as a string + """ + try: + response = client.get( + download_url, + timeout=30, + follow_redirects=True, + headers=_github_auth_headers(github_token), + ) + + if response.status_code != 200: + raise RuntimeError(f"Failed to download constitution. Status: {response.status_code}") + + return response.text + + except Exception as e: + raise RuntimeError(f"Error fetching constitution content: {e}") + AI_CHOICES = { "copilot": "GitHub Copilot", "claude": "Claude Code", @@ -731,6 +819,11 @@ def init( skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"), debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"), github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"), + constitution_repo: str = typer.Option(None, "--constitution-repo", help="GitHub repository containing constitutions (format: 'owner/repo')"), + constitution_name: str = typer.Option(None, "--constitution-name", help="Name of the constitution to use from the remote repository"), + constitution_path: str = typer.Option("", "--constitution-path", help="Path within constitution repository where constitutions are stored"), + constitution_branch: str = typer.Option("main", "--constitution-branch", help="Branch to fetch constitution from"), + constitution_interactive: bool = typer.Option(False, "--constitution-interactive", help="Interactively select a constitution from the remote repository"), ): """ Initialize a new Specify project from the latest template. @@ -740,20 +833,30 @@ def init( 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, or Amazon Q Developer CLI) 3. Download the appropriate template from GitHub 4. Extract the template to a new project directory or current directory - 5. Initialize a fresh git repository (if not --no-git and no existing repo) - 6. Optionally set up AI assistant commands + 5. Optionally fetch and apply a constitution from a remote repository + 6. Initialize a fresh git repository (if not --no-git and no existing repo) + 7. Optionally set up AI assistant commands Examples: + # Basic usage specify init my-project specify init my-project --ai claude specify init my-project --ai copilot --no-git specify init --ignore-agent-tools my-project + + # Current directory initialization specify init . --ai claude # Initialize in current directory specify init . # Initialize in current directory (interactive AI selection) specify init --here --ai claude # Alternative syntax for current directory specify init --here --ai codex specify init --here specify init --here --force # Skip confirmation when current directory not empty + + # Remote constitution usage + specify init my-project --constitution-repo myorg/constitutions + specify init my-project --constitution-repo myorg/constitutions --constitution-name python-microservices + specify init my-project --constitution-repo myorg/constitutions --constitution-interactive + specify init . --constitution-repo myorg/constitutions --constitution-path templates/constitutions """ show_banner() @@ -902,6 +1005,78 @@ def init( console.print(f"[cyan]Selected AI assistant:[/cyan] {selected_ai}") console.print(f"[cyan]Selected script type:[/cyan] {selected_script}") + # Handle remote constitution if specified + remote_constitution_content = None + selected_constitution_name = None + + if constitution_repo: + console.print(f"\n[cyan]Fetching constitution from remote repository:[/cyan] {constitution_repo}") + + try: + # Fetch list of available constitutions + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + console=console, + ) as progress: + task = progress.add_task("Fetching constitution list...", total=None) + constitutions = fetch_remote_constitutions_list( + constitution_repo, + constitution_branch, + constitution_path, + github_token + ) + progress.update(task, completed=1) + + if not constitutions: + console.print("[red]Error:[/red] No constitutions found in the specified repository") + raise typer.Exit(1) + + # Determine which constitution to use + if constitution_interactive or not constitution_name: + # Interactive selection + constitution_choices = {c["name"]: c["description"] for c in constitutions} + selected_constitution_name = select_with_arrows( + constitution_choices, + "Choose a constitution:", + list(constitution_choices.keys())[0] + ) + else: + # Use specified constitution name + selected_constitution_name = constitution_name + + # Find the selected constitution + selected_constitution = next( + (c for c in constitutions if c["name"] == selected_constitution_name), + None + ) + + if not selected_constitution: + console.print(f"[red]Error:[/red] Constitution '{selected_constitution_name}' not found") + console.print(f"[dim]Available constitutions:[/dim]") + for c in constitutions: + console.print(f" - {c['name']}") + raise typer.Exit(1) + + # Fetch the constitution content + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + console=console, + ) as progress: + task = progress.add_task(f"Downloading constitution '{selected_constitution_name}'...", total=None) + remote_constitution_content = fetch_remote_constitution_content( + selected_constitution["download_url"], + github_token + ) + progress.update(task, completed=1) + + console.print(f"[green]✓[/green] Constitution '{selected_constitution_name}' fetched successfully") + + except Exception as e: + console.print(f"[red]Error fetching remote constitution:[/red] {e}") + raise typer.Exit(1) + # Download and set up project # New tree-based progress (no emojis); include earlier substeps tracker = StepTracker("Initialize Specify Project") @@ -938,6 +1113,21 @@ def init( download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token) + # Apply remote constitution if one was fetched + if remote_constitution_content: + tracker.add("constitution", "Apply remote constitution") + tracker.start("constitution") + constitution_file = project_path / "memory" / "constitution.md" + try: + # Ensure memory directory exists + constitution_file.parent.mkdir(parents=True, exist_ok=True) + # Write the remote constitution + constitution_file.write_text(remote_constitution_content, encoding="utf-8") + tracker.complete("constitution", f"'{selected_constitution_name}' applied") + except Exception as e: + tracker.error("constitution", str(e)) + console.print(f"[yellow]Warning: Could not write constitution file: {e}[/yellow]") + # Ensure scripts are executable (POSIX) ensure_executable_scripts(project_path, tracker=tracker) @@ -1017,6 +1207,11 @@ def init( steps_lines.append("1. You're already in the project directory!") step_num = 2 + # Add note about remote constitution if used + if remote_constitution_content: + steps_lines.append(f"{step_num}. [green]✓[/green] Constitution '{selected_constitution_name}' has been applied to [cyan]memory/constitution.md[/cyan]") + step_num += 1 + # Add Codex-specific setup step if needed if selected_ai == "codex": codex_path = project_path / ".codex" @@ -1097,6 +1292,65 @@ def check(): if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or kilocode_ok or opencode_ok or codex_ok or auggie_ok or q_ok): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") +@app.command() +def list_constitutions( + repo: str = typer.Argument(..., help="GitHub repository containing constitutions (format: 'owner/repo' or full URL)"), + path: str = typer.Option("", "--path", help="Path within the repository where constitutions are stored"), + branch: str = typer.Option("main", "--branch", help="Branch to fetch from"), + github_token: str = typer.Option(None, "--github-token", help="GitHub token for private repositories"), +): + """ + List available constitutions from a remote GitHub repository. + + This command helps you discover pre-made constitutions that can be used + when initializing a new project. Useful for organizations that maintain + a catalog of standard constitutions for different project types. + + Examples: + specify list-constitutions myorg/constitutions-repo + specify list-constitutions myorg/constitutions-repo --path templates/constitutions + specify list-constitutions https://github.com/myorg/constitutions-repo --branch develop + """ + show_banner() + + console.print(f"[cyan]Fetching constitutions from:[/cyan] {repo}") + if path: + console.print(f"[cyan]Path:[/cyan] {path}") + console.print(f"[cyan]Branch:[/cyan] {branch}\n") + + try: + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + console=console, + ) as progress: + task = progress.add_task("Fetching constitution list...", total=None) + constitutions = fetch_remote_constitutions_list(repo, branch, path, github_token) + progress.update(task, completed=1) + + if not constitutions: + console.print("[yellow]No constitutions found in the specified repository path.[/yellow]") + console.print("[dim]Make sure the path contains .md files.[/dim]") + return + + console.print(f"[green]Found {len(constitutions)} constitution(s):[/green]\n") + + table = Table(show_header=True, header_style="bold cyan") + table.add_column("Name", style="cyan", no_wrap=True) + table.add_column("Path", style="dim") + + for const in constitutions: + table.add_row(const["name"], const["path"]) + + console.print(table) + + console.print("\n[dim]To use a constitution during project initialization:[/dim]") + console.print(f"[dim] specify init --constitution-repo {repo} --constitution-name [/dim]") + + except Exception as e: + console.print(f"[red]Error:[/red] {e}") + raise typer.Exit(1) + def main(): app() From 456490216f83b3a96f2f198df0cb2e114902750a Mon Sep 17 00:00:00 2001 From: Mike Kelly Date: Thu, 9 Oct 2025 16:01:45 -0400 Subject: [PATCH 2/2] Proper path for fetched constitution --- src/specify_cli/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 37891c9c2..3c875d074 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1117,7 +1117,7 @@ def init( if remote_constitution_content: tracker.add("constitution", "Apply remote constitution") tracker.start("constitution") - constitution_file = project_path / "memory" / "constitution.md" + constitution_file = project_path / ".specify" / "memory" / "constitution.md" try: # Ensure memory directory exists constitution_file.parent.mkdir(parents=True, exist_ok=True) @@ -1209,7 +1209,7 @@ def init( # Add note about remote constitution if used if remote_constitution_content: - steps_lines.append(f"{step_num}. [green]✓[/green] Constitution '{selected_constitution_name}' has been applied to [cyan]memory/constitution.md[/cyan]") + steps_lines.append(f"{step_num}. [green]✓[/green] Constitution '{selected_constitution_name}' has been applied to [cyan].specify/memory/constitution.md[/cyan]") step_num += 1 # Add Codex-specific setup step if needed