Skip to content

Conversation

@housekelly
Copy link

The intention here is to allow support for fetching pre-made constitutions from a remote github repository. It would be beneficial to allow a dev group or company to have well curated constitutions that contain all of the steering guidelines, best practices, and company specifics to allow the specify-cli to read from a remote repo and allow the user to select one, instead of starting from scratch each time.

This feature adds a few new cli arguments to facilitate.

Screenshot in action.
image

File Modified: src/specify_cli/__init__.py

New Functions:

  • fetch_remote_constitutions_list() - Fetches list of constitutions from GitHub
  • fetch_remote_constitution_content() - Downloads specific constitution content

New Command:

  • list-constitutions - Browse available constitutions from a repository

Enhanced Command:

  • init - Now supports 5 new options for remote constitutions:
    • --constitution-repo
    • --constitution-name
    • --constitution-path
    • --constitution-branch
    • --constitution-interactive

@housekelly housekelly requested a review from localden as a code owner October 9, 2025 20:32
@Copilot Copilot AI review requested due to automatic review settings October 9, 2025 20:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds a remote constitution feature that allows teams to fetch pre-made project constitutions from GitHub repositories during initialization. This enables organizations to maintain centralized, standardized governance principles and development guidelines.

  • Adds new CLI command list-constitutions for browsing available constitutions
  • Extends init command with 5 new options for remote constitution management
  • Includes comprehensive documentation and example constitution repository templates

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/specify_cli/init.py Core implementation with new functions for GitHub API integration and enhanced init command
docs/toc.yml Added navigation entries for remote constitution documentation
docs/remote-constitutions.md Comprehensive guide on setting up and using remote constitutions
docs/example-constitutions-repo.md Example constitution templates and repository structure guide
README.md Updated CLI reference table and usage examples
CHANGELOG.md Documented new feature additions and changes

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

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]:
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter github_token should have a consistent type annotation. Use str | None to match the pattern used in _github_auth_headers function.

Suggested change
def fetch_remote_constitutions_list(repo_url: str, branch: str = "main", path: str = "", github_token: str = None) -> list[dict]:
def fetch_remote_constitutions_list(repo_url: str, branch: str = "main", path: str = "", github_token: str | None = None) -> list[dict]:

Copilot uses AI. Check for mistakes.
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:
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter github_token should have a consistent type annotation. Use str | None to match the pattern used in _github_auth_headers function.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +95
if branch != "main":
api_url += f"?ref={branch}"
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition should check if branch is not 'main' OR if there are other query parameters needed. Currently, if branch is 'main' but other parameters exist, the ref parameter won't be added. Consider using proper URL parameter handling.

Copilot uses AI. Check for mistakes.
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"),
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter github_token should have a consistent type annotation. Use str | None to match the pattern used in other functions.

Suggested change
github_token: str = typer.Option(None, "--github-token", help="GitHub token for private repositories"),
github_token: str | None = typer.Option(None, "--github-token", help="GitHub token for private repositories"),

Copilot uses AI. Check for mistakes.
selected_constitution_name = select_with_arrows(
constitution_choices,
"Choose a constitution:",
list(constitution_choices.keys())[0]
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default selection logic list(constitution_choices.keys())[0] could fail if constitutions list is empty. This case is already handled above with the early exit, but consider using constitution_choices.keys() and list(constitution_choices.keys())[0] or similar defensive programming.

Suggested change
list(constitution_choices.keys())[0]
next(iter(constitution_choices.keys()), None)

Copilot uses AI. Check for mistakes.
@housekelly
Copy link
Author

@localden Curious on your thoughts here. I feel it would be a positive feature for the project to support a remote catalogue of well crafted constitutions. Please let me know if there is anything you feel needs changing.

@dasiths
Copy link

dasiths commented Oct 15, 2025

@housekelly can we do this as part of #892

@MadHuslista
Copy link

@housekelly Nice one, kudos! I was questioning something like this while needing to switch between constitutions_c & constitution_py, in a Embedded C + Python codebase.

How would you manage a sort of constitution tree or principles composition? ( @localden )

I'm seeing that there are couple of layers that arise from multiple constitutions:

  • Universals (principles used constantly for all constitutions)
  • Codebase specifics (for this repo use these ones, for C those other ones, fro Py. those others, etc.. )
  • Other layers..

So following up with your idea, I was thinking about setting constitutions or principles layers (not sure about the best atomicity) and enable a sort of "pick your poison" workflow in the command.

I imagine something like:

  • Refresh availables => select const/principles => Trigger composition

What do you think would be the best approach to the issue?

@housekelly
Copy link
Author

@dasiths happy to update it to support a folder which can include other notable files like your issue suggests. I feel like this functionality (remote repo support) belongs in the core list of cli functions.

As to @MadHuslista 's point. I would prefer the tool not be very opinionated on the structure of the remote constitution for this very reason. People will care about team practices, cloud practices, federal steering, etc. If we support a remote repo i had envisioned writing some workflows that would combine those different layers into the final set of constitutions. Let each group of devs worry about their own heirarchy

@jedjohan
Copy link

Good stuff, exactly what our team starting spec-driven has been thinking about (we now have 2 repos frontend/backend) and they need to share a "domain knowledge" constitution. One follow-up idea: the new helpers in init.py (fetch_remote_constitutions_list, fetch_remote_constitution_content, and the CLI wiring) are tightly coupled to the GitHub REST API.

If we can carve out a small provider abstraction there (parsing repo URLs, routing to provider-specific fetchers), the same CLI could support for example Azure DevOps repos too—most of the wiring is already done. That would let us reuse the new options in both the init and list-constitutions commands, and mirror the docs updates (remote-constitutions.md, README.md, CHANGELOG) for Azure DevOps usage.

Long-term, having that abstraction in place would make it straightforward to plug in additional SCMs down the road (GitLab, Bitbucket, etc.), giving teams more flexibility without revisiting the CLI surfaces again

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}"
Copy link

@RoyceLeonD RoyceLeonD Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is a GH project, but could you not hardcode to github. Instead only support fqdn with raw text or tar distributions (tar is less important). And Instead of it being a folder have it be a single file manifest. Similar to openapispec, ideally this model follows standard artifact + cdn distribution thus I get to use those same tools to publish/ manage/ version control.

Example: we use Jfrog arifactory and would like to run our spec through CI/CD and then publish (thus we can security scan, we can have uptime healthchecks, retrieve clear usage, utilize our existing data-rentention policy), this is valuable for us since our developers' rbac is used in flight to hit a zero trust endpoint and only show specs related to their identify, we do a similar thing for packages. Not scope creeping. But rather would like to instead reduce the client side processing speckit is doing. Instead I think add a publish command/ action on github that treats specs like artifacts.

This would flow nicely into other things you do. I personally don't like tis distribution model. Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants