{"id":50561814,"url":"https://github.com/simtabi/gendia","last_synced_at":"2026-06-04T12:01:09.752Z","repository":{"id":356328838,"uuid":"1231225890","full_name":"simtabi/gendia","owner":"simtabi","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-07T16:39:25.000Z","size":105,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-07T16:42:58.053Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simtabi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-06T18:55:08.000Z","updated_at":"2026-05-07T16:41:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/simtabi/gendia","commit_stats":null,"previous_names":["simtabi/gendia"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/simtabi/gendia","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simtabi%2Fgendia","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simtabi%2Fgendia/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simtabi%2Fgendia/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simtabi%2Fgendia/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simtabi","download_url":"https://codeload.github.com/simtabi/gendia/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simtabi%2Fgendia/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33903134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-04T12:01:08.243Z","updated_at":"2026-06-04T12:01:09.741Z","avatar_url":"https://github.com/simtabi.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gendia\n\nA CI/CD-friendly Packagist (and npm + PyPI) dev-workflow handler for polyrepo ecosystems across GitHub orgs, GitLab groups, and Bitbucket workspaces.\n\n`gendia` is what stands between *your local repo state* and *the world seeing it*: it pushes pending commits and tags, fires the Packagist update webhook (or `npm publish` / `twine upload`), bumps changelogs, runs your verify pipeline, and audits the fleet for hygiene drift. One config file, one tool, one CLI verb per intent. Works the same on a developer laptop, on a VPS cron, and inside a Docker-based CI runner.\n\n```text\n$ gendia status\nstatus — ok\n  ✓ core                   main clean ahead=0 behind=0 latest_tag=v1.0.0\n  ✓ browser                main clean ahead=0 behind=0 latest_tag=v1.0.0\n  ✓ documentation          main clean ahead=0 behind=0 latest_tag=v1.0.0\n  ✓ tabler-icons           main clean ahead=0 behind=0 latest_tag=v1.0.0\n  ✓ bundled-icons          main clean ahead=0 behind=0 latest_tag=v1.0.0\n  ✓ metronic-icons         main clean ahead=0 behind=0 latest_tag=v1.0.0\n```\n\n## Why\n\nMaintaining a polyrepo Composer / npm / Python ecosystem means juggling: per-repo `composer.json` validation, CHANGELOG bumps, semver tagging, `git push --tags`, the Packagist update webhook, occasional Docker-image republishing, sometimes a parallel `npm publish` to a private registry — all wired together with bash scripts that drift out of sync the moment a repo gets renamed.\n\nExisting multi-repo tools (`mr`, `mu-repo`, `gita`) stop at clone / pull / status; none of them know about Packagist webhooks, npm tokens, or CHANGELOG-bump conventions. `gendia` fills that gap. It treats a release as a *workflow* (verify → bump → tag → push → notify) and keeps the dev surface and the CI surface identical — `gendia release myorg/core 1.0.1` does the same thing on your laptop and inside a CI runner.\n\n## Highlights\n\n- **CI/CD-first design**: identical CLI on dev box, VPS cron, and Docker-based runners. JSON log format for structured log shippers; exit code reflects all-ok / failed.\n- **Packagist-aware out of the box**: `sync` and `release` know how to fire the Packagist update endpoint when tags get pushed. npm and PyPI included as first-class push registries.\n- **Three platforms from day one**: GitHub, GitLab (cloud or self-hosted), Bitbucket Cloud.\n- **Two transports**: SSH (with optional explicit private-key path, `IdentitiesOnly=yes` to block agent enumeration) or HTTPS-with-API-token (via `GIT_ASKPASS`, never via URL embed, never in argv).\n- **Multi-org / multi-account**: any number of accounts in one config; each repo binds to one account. A leak in one account's credentials can't touch another's repos.\n- **Fourteen first-class verbs**: `status`, `sync`, `inventory`, `audit`, `cleanup`, `verify`, `release`, `mirror`, `init`, `config`, `setup`, `doctor`, `identity`, `conventions` — fleet operations, first-run wizards, per-account git-identity management, and JSON-driven repo-hygiene linting.\n- **Dry-run on everything**: `--dry-run` is honoured by every mutating operation.\n- **Zero runtime dependencies**: stdlib only. `keyring` is an optional extra for OS-keychain-backed secrets.\n- **Docker-native**: slim image (~80 MB), runs as non-root, mounts your `~/.config/gendia` and SSH keys; `docker compose run gendia sync` is the deployable unit.\n\n## Install\n\n```bash\n# Recommended: uv (fast, isolated)\nuv tool install gendia\n\n# Or pip\npip install gendia\n\n# From source (development)\ngit clone git@github.com:simtabi/gendia.git\ncd gendia\nmake install-dev\n```\n\n## Configure\n\nTwo files, both JSON. The machine-wide one declares accounts and registries; the per-project one declares the repos.\n\n**`~/.config/gendia/gendia.json`** (machine-wide):\n\n```json\n{\n  \"accounts\": {\n    \"myorg\":     { \"platform\": \"github\",    \"org\": \"myorg\",                  \"credential_ref\": \"GITHUB_TOKEN_MYORG\" },\n    \"personal\":  { \"platform\": \"github\",    \"username\": \"yourhandle\",         \"credential_ref\": \"GITHUB_TOKEN_PERSONAL\" },\n    \"internal\":  { \"platform\": \"gitlab\",    \"host\": \"gitlab.example.com\",     \"group\": \"tools\", \"credential_ref\": \"GITLAB_TOKEN_INTERNAL\" },\n    \"client-x\":  { \"platform\": \"bitbucket\", \"workspace\": \"client-x-workspace\",\"credential_ref\": \"BITBUCKET_APP_PASSWORD_CLIENT_X\" }\n  },\n  \"registries\": {\n    \"packagist\": { \"kind\": \"packagist\", \"username\": \"your-packagist-handle\", \"credential_ref\": \"PACKAGIST_API_TOKEN\" },\n    \"npm\":        { \"kind\": \"npm\",       \"credential_ref\": \"NPM_TOKEN\" },\n    \"pypi\":       { \"kind\": \"pypi\",      \"credential_ref\": \"PYPI_API_TOKEN\" }\n  },\n  \"defaults\": { \"concurrency\": 4, \"log_format\": \"human\" }\n}\n```\n\n**`./gendia.json`** (one per project, lives at the project root):\n\n```json\n{\n  \"name\": \"myorg\",\n  \"account\": \"myorg\",\n  \"registry\": \"packagist\",\n  \"root\": \"~/projects/myorg\",\n  \"repos\": [\n    { \"dir\": \"core\",        \"package\": \"myorg/core\",        \"verify\": [\"composer validate\", \"vendor/bin/pest\"] },\n    { \"dir\": \"browser\",     \"package\": \"myorg/browser\",     \"verify\": [\"composer validate\", \"vendor/bin/pest\"] },\n    { \"dir\": \"tabler-icons\",\"package\": \"myorg/tabler-icons\",\"verify\": [\"composer validate\", \"vendor/bin/pest\"] }\n  ],\n  \"cleanup_globs\": [\"vendor\", \"node_modules\", \".phpunit.cache\", \".DS_Store\"]\n}\n```\n\n**Secrets** never live in the JSON files. One `.env` covers every deployment shape — local dev, VPS, Docker, Kubernetes — by mixing two patterns side by side.\n\n### Where does the `.env` live?\n\n| Use case | Location | How gendia finds it |\n|---|---|---|\n| Local dev (default) | `~/.config/gendia/.env` | XDG-compliant default; no setup |\n| VPS / system-wide | `/etc/gendia/.env` (or anywhere) | `export GENDIA_ENV_FILE=/etc/gendia/.env` |\n| Per-project override | any path, e.g. `\u003cproject\u003e/.env.gendia` | `GENDIA_ENV_FILE` in that shell / Makefile |\n| Docker / Compose | host file bind-mounted at `/config/.env` | image sets `GENDIA_ENV_FILE=/config/.env` |\n| Kubernetes | secrets at `/run/secrets/\u003cname\u003e` | use `\u003cKEY\u003e_FILE=/run/secrets/\u003cname\u003e` in any of the above |\n\n**The file in this repo at `examples/.env.example` is a TEMPLATE only — never put real secrets there.** It's tracked by git. Copy it to the runtime location:\n\n```sh\nmkdir -p ~/.config/gendia\ncp examples/.env.example ~/.config/gendia/.env\nchmod 600 ~/.config/gendia/.env\n$EDITOR ~/.config/gendia/.env\n```\n\n`gendia-env` warns if your `.env` has looser permissions than 0640.\n\nInside the file:\n\n- **Direct value** for local dev: `GITHUB_TOKEN_MYORG=ghp_...`\n- **`\u003cKEY\u003e_FILE`** for Docker / Compose / Kubernetes secret mounts: `GITHUB_TOKEN_MYORG_FILE=/run/secrets/github_token_myorg`. The file's contents become the value of `\u003cKEY\u003e` at runtime — the secret never appears in env vars or process listings. Both forms can coexist; `*_FILE` wins when both are set.\n\nA small companion script ships in `bin/gendia-env` (also baked into the Docker image) that handles all of this for you. It sources the env file, resolves any `*_FILE` references, and runs whatever you exec it with — including `docker compose`, so `${VAR}` interpolation in `docker-compose.yml` Just Works:\n\n```bash\ngendia-env docker compose run --rm gendia status        # one-shot\ngendia-env --check                                      # show keys + masked previews\ngendia-env --docker-args                                # prints --env-file=PATH for docker\neval \"$(gendia-env --export)\"                           # source into current shell\n```\n\nA third optional resolver (when the `keyring` extra is installed): `pip install gendia[keyring]` then `keyring set gendia GITHUB_TOKEN_MYORG`. Skipped automatically when the package isn't installed (e.g., on a VPS).\n\nProcess environment variables override `.env` file values. The `--no-keyring` flag forces env-only resolution.\n\n## Auth: SSH vs API token\n\nTwo distinct credential types are at play. `gendia` keeps them separate.\n\n- **API token** (`credential_ref` in account config): used for HTTPS REST calls to GitHub / GitLab / Bitbucket APIs and Packagist's update endpoint. Always required.\n- **SSH key** (`ssh_key_path` in account config, optional): used for git over SSH. When unset, the user's default SSH agent and `~/.ssh/config` apply — the right answer for dev machines.\n\nEach account can pick its git transport via `git_auth`:\n\n| `git_auth` | Behaviour |\n|---|---|\n| `\"ssh\"` (default) | Use SSH agent / `~/.ssh/config` / explicit `ssh_key_path` |\n| `\"https\"` | Use the API token via `GIT_ASKPASS`; ideal for containers without an SSH agent |\n| `\"auto\"` | SSH when a key is available, else HTTPS-with-token |\n\n**Tokens are never embedded in URLs.** When `git_auth=https`, the token is supplied via a temporary `GIT_ASKPASS` script that prints the token to stdin. The token never appears in `argv` (visible to `ps`) or in the remote URL (visible in reflogs).\n\nFor a VPS deployment, the typical pattern is `git_auth: \"auto\"` with an explicit `ssh_key_path: /etc/gendia/keys/deploy_ed25519` — falls through to HTTPS-with-token if the key is missing.\n\n## Commands\n\n```bash\n# Fleet operations (read your project + iterate every repo):\ngendia status                                  # one-line summary per repo\ngendia sync                                    # push pending commits + tags + notify webhook registry\ngendia inventory                               # categorise every package per account (synced / pending / untracked / missing / ignored)\ngendia release \u003crepo\u003e \u003cversion\u003e                # bump CHANGELOG, tag, push, notify\ngendia audit                                   # hygiene checks\ngendia cleanup                                 # rm cleanup_globs across all repos\ngendia mirror --to ~/repos                     # clone every repo defined in config\ngendia verify                                  # run each repo's verify[] commands\n\n# Workstation setup (manage gendia's own config + the host's git identity):\ngendia init [--out FILE] [--force]             # scaffold a fresh gendia.json\ngendia setup [local|vps|project|docker|k8s|ci] # interactive first-run wizard, autodetects shape\ngendia config {list|get|set|unset|edit|path|doctor}   # manage the .env that holds tokens + paths\ngendia doctor                                  # full preflight: env file, git/ssh binaries, agent, credentials\ngendia identity {list|check|apply|setup|init}  # per-account git user.name / user.email / signing key\n\n# Quality control:\ngendia conventions [PATH] [--rules FILE] [--strict] [--json]\n                                               # lint repo hygiene: GitHub-special files, naming,\n                                               # ban-list glyphs, spec filenames, sub-folder readmes,\n                                               # shell-script shebangs (JSON-driven, fully tunable)\n\n# Modifiers (work with most commands):\n--dry-run                  show what would happen, do nothing\n--only repo-a,repo-b       operate on a subset\n--skip repo-c\n--concurrency 8            override default parallelism\n--log-format json          for CI consumption\n--no-keyring               skip the OS keyring backend; env / .env only\n--config PATH              point at a specific gendia.json\n```\n\n## Quality control: `gendia conventions`\n\nMost polyrepo ecosystems develop a \"house style\": which files must exist at the root, how markdown is named, which glyphs and emojis are off-limits, where specs live, what the readme has to declare. `gendia conventions` lints these the same way `audit` lints git hygiene — one tool, one config, identical on dev and CI.\n\n```bash\n# Single-repo run\ngendia conventions /path/to/repo\n\n# Walk every repo declared in the project's gendia.json\ncd ~/projects/myorg \u0026\u0026 gendia conventions\n\n# Custom rules (anything you omit falls back to the built-in defaults)\ngendia conventions --rules examples/conventions.json\n\n# CI mode: JSON output, warnings count as failures\ngendia conventions --json --strict\n```\n\nThe default rules cover ten categories:\n\n| Rule | Severity | What it catches |\n|---|---|---|\n| `github-special` | error / ok | Missing `LICENSE`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`. Reports `CODEOWNERS`, `SECURITY.md`, `SUPPORT.md` when present. |\n| `md-kebab-case` | error | Markdown filenames that aren't lowercase-kebab-case (with sensible exemptions for `README.md`, `CHANGELOG.md`, etc.). |\n| `forbidden-chars` | warn | Em-dash `—` (U+2014) and any other glyph you ban via `forbidden_chars`. |\n| `readme-frontmatter` | warn | Optional: require `**Owner:**` and `**Last Updated:**` in the readme head. |\n| `decorator-emojis` | warn | Ornamental emojis (⭐ 🎯 💼 ✨ 🚀 etc.). Status emojis (✅ ⏳ 📋 ⛔ 🔴 🟠 🟡 🟢) are deliberately allowed. |\n| `spec-date-prefix` | error | Date-prefixed spec filenames like `2026-05-08-thing.md` (the date belongs in frontmatter). |\n| `spec-number-prefix` | error | Number-prefixed spec filenames like `001-thing.md` (sequential IDs go in the readme index). |\n| `subfolder-readme` | warn | Sub-folder `readme.md` files (one tier index per repo). |\n| `stale-link-pattern` | warn | Markdown links that target the patterns above (numbered specs, sub-folder readmes). |\n| `sh-shebang` | error / warn | `*.sh` files without a shebang (error) or without the executable bit (warn). |\n\nOptional rules off by default:\n- `forbid_trailing_whitespace_md` — flags trailing whitespace in markdown.\n\nOverride anything via JSON. Every key is optional; omit it to keep the default. Full schema lives in `examples/conventions.json`:\n\n```json\n{\n  \"github_special_required\": [\"LICENSE\", \"CONTRIBUTING.md\"],\n  \"forbidden_chars\": [\"—\", \"“\", \"”\"],\n  \"decorator_emojis_exempt\": [\"CLAUDE.md\", \"changelog.md\", \"ADR-*.md\"],\n  \"spec_dir\": \"docs/specs\",\n  \"require_readme_frontmatter\": true,\n  \"forbid_trailing_whitespace_md\": true,\n  \"exclude_dirs\": [\".git\", \"node_modules\", \"vendor\", \"build\", \"dist\", \".venv\"]\n}\n```\n\nExit codes match `gendia doctor`: `0` = ok, `1` = warnings only (with `--strict`), `2` = at least one error.\n\n## Make targets\n\n```bash\nmake help              # list targets\nmake install           # uv tool install (or pip fallback)\nmake install-dev       # editable install with [dev] extras\nmake test              # pytest\nmake lint              # ruff\nmake typecheck         # mypy\nmake sync              # gendia sync (extra args via ARGS=...)\nmake release REPO=core VERSION=1.0.1\nmake audit / cleanup / verify / mirror / init / status / inventory\nmake docker-build / docker-shell / docker-push REGISTRY=ghcr.io/your-org\n```\n\n## Docker / VPS\n\n`gendia` ships a slim Python image (~80 MB) with the `gendia-env` helper baked in. The image's `ENTRYPOINT` is `gendia-env gendia`, so anything you put in CMD runs after the env file has been sourced and `*_FILE` secrets have been resolved.\n\n```bash\n# One-shot from the project root:\nmake docker-build\ndocker run --rm \\\n  --env-file ~/.config/gendia/.env \\\n  -v ~/.config/gendia:/config:ro \\\n  -v ~/.ssh:/home/gendia/.ssh:ro \\\n  -v \"$PWD\":/work \\\n  gendia:latest status\n```\n\nOr via Compose (recommended):\n\n```bash\ndocker compose run --rm gendia status\ndocker compose run --rm gendia sync\ndocker compose run --rm gendia release myorg/core 1.0.1\n```\n\nOr via the Make targets:\n\n```bash\nmake docker-build\nmake docker-shell                   # interactive bash inside the container\nmake docker-run VERB=sync           # one-shot any verb\n```\n\nVPS cron (every 30 minutes):\n\n```cron\n*/30 * * * * docker run --rm \\\n    --env-file /home/ops/.config/gendia/.env \\\n    -v /home/ops/.config/gendia:/config:ro \\\n    -v /home/ops/.ssh:/home/gendia/.ssh:ro \\\n    -v /home/ops/repos:/work \\\n    gendia:latest sync --log-format json \\\n    \u003e\u003e /var/log/gendia/sync.log 2\u003e\u00261\n```\n\nFor Kubernetes, mount each secret at `/run/secrets/\u003cname\u003e` via a Secret volume and set `\u003cKEY\u003e_FILE=/run/secrets/\u003cname\u003e` in the env. See the comments at the top of `examples/.env.example` for a complete recipe.\n\n## Architecture\n\n```\ngendia/\n├── src/gendia/\n│   ├── auth/         credential resolvers: env, .env, Docker secrets, keyring\n│   ├── config/       frozen dataclasses + JSON loader\n│   ├── git/          GitRepo wrapper, safe subprocess shell, transport-auth env\n│   ├── providers/    GitProvider abstract base + GitHub / GitLab / Bitbucket\n│   ├── registries/   PackageRegistry hierarchy (Webhook / Publish capable)\n│   ├── operations/   Operation abstract base + 9 fleet verbs + conventions linter\n│   ├── state/        sync-state store (~/.cache/gendia/sync-state.json)\n│   ├── observability logger.py (human + JSON formatters)\n│   └── cli/          argparse setup + dispatcher + sidecar commands (config/setup/doctor/identity)\n├── tests/unit/       schema, loader, auth, git_repo, git_auth, concurrency, identity, sync state, conventions\n├── examples/         single-org.json, multi-org.json, conventions.json, .env.example\n├── bin/gendia        bash shim for development use\n├── Dockerfile        multi-stage, ~80 MB\n├── docker-compose.yml\n└── Makefile\n```\n\nEach layer depends only on the layer below; SOLID applied across the board. Adding a new VCS = one file in `providers/`. Adding a new operation = one file in `operations/`.\n\n## Contributing\n\n```bash\ngit clone git@github.com:simtabi/gendia.git\ncd gendia\nmake install-dev\nmake test\n```\n\nTests must pass before merging. `make lint` and `make typecheck` should produce no errors. Conventional Commit messages preferred (`feat:`, `fix:`, `docs:`, `refactor:`, etc.).\n\n## Security\n\n- Tokens are never written to JSON config or argv.\n- SSH transport uses `IdentitiesOnly=yes` to prevent SSH agent enumeration.\n- HTTPS transport disables the system credential helper to prevent token caching.\n- The Docker image runs as a non-root user.\n- Report vulnerabilities to security@simtabi.com — not via public issues.\n\n## License\n\nThis project is licensed under the MIT License.\n\n© Simtabi LLC\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimtabi%2Fgendia","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimtabi%2Fgendia","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimtabi%2Fgendia/lists"}