Planhub
Planhub is a CLI app that syncs local planning artifacts (plans, issues,
milestones written as .md files) to GitHub Issues via the API.
Goals
- Keep planning artifacts in git and reviewable.
- Generate/update GitHub issues and milestones from local files.
- Make planning conversational: we can edit
.mdfiles and sync later.
How It Works
Planhub keeps planning data next to the code it describes. Each repository gets
a .plan/ folder containing milestones and issues as Markdown files with YAML
front matter. You edit these files like normal docs, then planhub sync maps
them to GitHub issues and milestones.
Installation
Global install from PyPI (recommended for CLI usage). pipx gives you a clean
CLI install without polluting your system Python, and puts planhub on your
PATH:
pipx install planhub
Project-local install with uv (inside your repo). This keeps the tool in a virtual environment for that repo:
uv venv
uv pip install planhub
First Run
Run planhub init in your repo. It creates the .plan/ layout, a repo-local
config file, and interactively prompts for a few common sync defaults:
planhub init
Commands
planhub init- Creates the standard
.plan/structure and.plan/config.yaml(if missing) in the current repo. - Use
--dry-runto preview the folders and config questions without writing anything or reading stdin. - Interactively prompts for default assignees, default labels, closed-issue policy, and sync verbosity, showing current values as defaults. Prompts run only in an interactive shell; a non-interactive shell (CI, redirected stdin) skips them automatically.
- Use
--yes/-yto skip the prompts and keep current values (a no-op write on an already-configured repo); it also accepts the skills prompt's default when--skills/--no-skillsis omitted. - Offers to install a
planhub-plan-artifactsskill for Claude Code, Cursor and Codex (.claude/skills/,.cursor/skills/and.codex/skills/) that teaches the agent.plan/layout, front matter, and to never runplanhub syncunasked. - Use
--skillsto install/update without prompting, or--no-skillsto skip without prompting. Omit both to be asked (interactive shells only — non-interactive runs skip by default). - Re-running
planhub init --skillsrefreshes any installed skill file whose content is out of date with the bundled template.
- Creates the standard
planhub issue <title>- Creates a new GitHub issue with the given title.
- Requires credentials and a GitHub
remote.origin.url.
planhub pull- Imports GitHub issues and milestones into
.plan/. Never writes to GitHub. - Imports all GitHub milestones into
.plan/(including open milestones with only closed issues, empty milestones, and closed milestones under.plan/archive/milestones). Closed milestone issues are imported into the milestone folder; closed backlog/root issues are not imported as files. - GitHub is also the source of truth for milestone
state; local milestone files are reconciled from GitHub before archive moves run. - Use
--forceto overwrite an already-imported local issue file's title/body/labels/assignees/milestone/state from its current GitHub content. Local-only front matter keys (e.g.id) are preserved. Without--force, an already-imported issue's content is left untouched. - Use
--dry-runto preview imports/overwrites without writing changes. - Requires credentials and a GitHub
remote.origin.urlto import; without them, pull still runs local reconcile and exits 0.
- Imports GitHub issues and milestones into
planhub push- Reads
.plan/files and creates or updates GitHub issues and milestones. Never imports from GitHub. - Prints explicit operation counts for create/update/archive/delete.
- Writes the GitHub
numberback into each file after creation. - GitHub is the source of truth for issue state during push; push does not
send local
stateorstate_reasonto GitHub, it reconciles those fields from the GitHub response. - Push runs in three phases: parse files, build a sync plan, then apply it.
- Use
--dry-runto validate files without writing changes. - Use
--verbosefor path-level planned changes, or--compactfor concise output. CLI flags override config. - Requires credentials and a GitHub
remote.origin.urlonly when there are pending creates/updates; exits non-zero in that case if missing. - Closed synced root issues are archived under
.plan/archive/issuesby default (or deleted whensync.closed_issues.policy: deleteis configured). - Milestone issues remain inside their milestone folder. When
milestone.mdhasstate: "closed", push moves the whole milestone directory to.plan/archive/milestones. If set back tostate: "open", push moves that directory back under.plan/milestones.
- Reads
planhub sync- Runs
planhub pullthenplanhub push— the same combined behavior as beforepull/pushexisted as separate commands. - Use
--dry-run,--verbose, or--compactas withpush.
- Runs
Credentials
Planhub can reuse your GitHub CLI session or a token stored in the environment.
The simplest path is to authenticate once with gh, and the CLI will fetch a
token automatically.
gh auth login
Alternatively, create a personal access token and export it:
- GitHub → Settings → Developer settings → Personal access tokens.
- Create a fine-grained token for the target repo(s).
- Grant Issues: Read (and Write if you will push updates later).
export GITHUB_TOKEN=ghp_your_token_here
Suggested Data Repo Layout
.plan/
issues/
20260127-backlog-issue.md
milestones/
stage-1/
milestone.md
issues/
issue-001.md
assets/
diagram.png
Front Matter Tips
milestonecan be a title or a number; usemilestone: nullto clear it.- Use
labels: []orassignees: []to remove them on GitHub. state_reasonrequiresstate: "closed".
Config
- All config lives in
.plan/config.yaml, per repository. There is no global config file —planhub initprompts interactively for common defaults instead (see First Run above). - Set
sync.behavior.verbositytocompact(default) orverbose.
Development
Setup
Install development dependencies:
uv sync --dev
Pre-commit Hooks
This project uses pre-commit to run code quality checks before commits. Install the hooks:
uv run pre-commit install
The hooks will automatically run on git commit. They check for:
- Code formatting and linting (ruff)
- Trailing whitespace and end-of-file fixes
- YAML, JSON, and TOML syntax
- Merge conflicts and other common issues
To run hooks manually:
uv run pre-commit run --all-files
Version Bumping
This project includes a grow.py script for bumping versions and creating releases:
./grow.py
The script will:
- Read the current version from
pyproject.toml - Show commits since the last tag
- Prompt for the new version
- Update the version in
pyproject.toml - Create a commit with a changelog of commits since the last release
- Create an annotated tag with the new version
- Optionally push commits and tags to the remote repository
The changelog commit includes all commit messages since the last tag, making it easy to see what changed in each release on GitHub.
License
Planhub is licensed under the GNU Affero General Public License v3.0
(AGPL-3.0). See LICENSE for the full text.
Next Steps
- Implement the parsing + GitHub sync logic.
- Add CI to validate file formats.
Metadata
Release files for planhub 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| planhub-0.4.1.tar.gz | 160.5 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| planhub-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 211.2 kB
Release files / planhub-0.4.1.tar.gz
| Download URL | planhub-0.4.1.tar.gz |
|---|---|
| Size | 160.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3390514110095b105f03686427b96f91fe03c0693a06c4fded2e3d1243628cfe
|
|
BLAKE2b-256 checksum How to use checksums |
a8b8369280a46388bb1ad24ebd94b6b1f9b2cb01af96c04c205583dbc0605c64
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / planhub-0.4.1-py3-none-any.whl
| Download URL | planhub-0.4.1-py3-none-any.whl |
|---|---|
| Size | 50.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
be2746ec5285e19453e75f70c6072758127d07d5f2c2b7526b6d72893210bc3d
|
|
BLAKE2b-256 checksum How to use checksums |
96a581cf89d669fb603359c9467395fea448a9ecc38410a9ec9ea9a8a2ba8d1b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logSecurity for planhub 0.4.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for planhub-0.4.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.