@algorithmikos/bethink
v1.5.1
Published
Language-agnostic CLI for versioning, changelog, commit, and tag automation with LLM polish
Maintainers
Readme
bethink
A language-agnostic CLI for versioning, changelog authoring, and release tagging — with LLM polish and a resilient offline fallback.
Table of Contents
- Overview
- Installation
- Quick Start
- CLI Flags
- Workflow
- Manifest Support
- Configuration
- Prompt Customisation
- Architecture
- File Reference
Overview
bethink is invoked from the root of any Git repository. It guides the user through three independently selectable atomic actions:
| Action | What it does |
|--------|-------------|
| Bump | Reads the current version from the detected manifest, calculates the next semver, writes CHANGELOG.md and RELEASE-NOTES.md, and updates the manifest. |
| Commit | Stages all working-tree changes and creates a Git commit with an LLM-generated conventional commit message. |
| Tag | Creates an annotated Git tag at HEAD with an LLM-generated annotation. |
Any combination of these three actions is valid. The tool operates on one repository, one project — there is no multi-package or monorepo mode.
Installation
npm install -g bethinkNode.js ≥ 18 is required.
On first run, bethink --config opens the provider wizard and writes ~/.bethink.json.
Quick Start
cd /path/to/your/project
bethinkThe tool detects your manifest automatically, checks your configured LLM provider, and begins the interactive workflow.
CLI Flags
| Flag | Description |
|------|-------------|
| (none) | Run the interactive release workflow. |
| --config | Open the provider configuration wizard. |
| --state | Inspect the saved session state for the current branch. |
| --help | Display usage information. |
Workflow
Action Selection
After provider and branch selection, a checkbox prompt allows any combination of Bump, Commit, and Tag to be toggled. Common combinations:
- Bump + Commit + Tag — full release in one shot.
- Bump + Commit — bump and commit now; tag after a build or QA step.
- Tag only — cut a tag from a prior bump+commit session.
- Commit only — commit without any version change.
Entry Collection
Changelog entries are collected interactively. Each entry has a type:
| Type | Semver impact | Default public inclusion |
|------|--------------|--------------------------|
| added | minor | likely |
| changed | minor | unlikely |
| deprecated | minor | unlikely |
| removed | major | unlikely |
| fixed | patch | likely |
| security | patch | likely |
| build | patch | never |
After each raw message is entered, the user may:
- Keep it as-is.
- Send it to the LLM for rephrasing.
The LLM then evaluates whether the entry belongs in the public-facing RELEASE-NOTES.md and offers a plain-language rewrite. The user confirms, edits, or excludes it.
The session may be saved at any point and resumed on the next run.
Version Bump
The bump level is auto-calculated from the entry types collected:
- Any
removedentry → major. - Any
added,changed, ordeprecatedentry (noremoved) → minor. - Only
fixed,security, orbuildentries → patch.
The user may override this at the confirmation prompt. Pre-release channels (alpha, beta, rc, or a custom identifier) are supported.
If the version was already bumped in a prior session, the 📌 Already bumped option skips re-writing the manifest and uses the on-disk version.
LLM Features
All LLM interactions are powered by externally editable prompt templates in config/prompts/. The following tasks are LLM-assisted:
| Task | Prompt file |
|------|------------|
| Rephrase a changelog entry | rephrase-entry.txt |
| Suggest public release note inclusion | public-entry-suggestion.txt |
| Generate commit message and tag annotation | commit-and-tag.txt |
| Clipboard fallback context aggregation | clipboard-fallback.txt |
Every LLM result is shown to the user before it is applied. The user may keep, edit, or retry with the same or a different provider.
Fail-Safe & Smart Copy
The process never terminates due to an LLM failure. When a provider is unreachable, the user chooses one of three paths:
- Continue with raw messages — proceed without LLM polish.
- Smart Copy — the full session context (entries, commits since the last tag,
git diff --stat, branch, version) is aggregated usingclipboard-fallback.txtand copied to the system clipboard. The user pastes this into any web-based LLM, then pastes the JSON result back into the CLI. - Abort — exit cleanly without writing anything.
Session State & Post-Bump Gap
~/.bethink.state.json persists the following between sessions, scoped to projectRoot + branch:
pendingEntries— entries collected but not yet committed.bumpedVersion— the version written to the manifest in a prior Bump action that has not yet been tagged.commitHash— the hash of the last associated commit, used for rollback and post-bump gap detection.status—'pending'|'bumped'|'committed'.
Post-bump gap detection: if commits were added to the branch after a version was bumped but before a tag was created, those commits are detected (git log <bumpCommitHash>..HEAD) and surfaced as a warning. The user is expected to account for them in the current session's entries.
State is cleared automatically after a successful Tag action. It can also be manually cleared via the Rollback option (which performs either git revert or git reset --soft on the saved commit hash).
Manifest Support
The manifest is auto-detected by probing the project directory in the following order:
| File | Ecosystem | Version field | Write behaviour |
|------|-----------|--------------|-----------------|
| package.json | Node.js | version | JSON field updated in-place. |
| pyproject.toml | Python | version = "..." | TOML scalar replaced via regex. |
| composer.json | PHP | version | JSON field updated in-place. |
| go.mod | Go | (none — Git tags only) | Write is a no-op; the Tag action is the version. |
Configuration
~/.bethink.json is written by bethink --config. The current schema:
{
"providers": [
{ "type": "ollama", "model": "qwen2.5:14b" },
{ "type": "gemini", "model": "gemini-1.5-flash", "apiKey": "AIza..." }
]
}The first provider in the array is the default. Multiple providers are supported; the user can switch between them during a retry cycle.
Supported provider types:
| Type | Required fields | Notes |
|------|----------------|-------|
| ollama | model | Ollama must be running locally on port 11434. |
| gemini | model, apiKey | Google Generative Language API. |
Prompt Customisation
All LLM prompts are plain .txt files in config/prompts/. Variables are interpolated with {{VARIABLE_NAME}} syntax at runtime. Editing these files changes the LLM's linguistic behaviour without touching source code.
| File | Variables |
|------|-----------|
| rephrase-entry.txt | {{VERBS}}, {{TYPE_LABEL}}, {{EXAMPLE}}, {{RAW_MESSAGE}} |
| public-entry-suggestion.txt | {{TYPE}}, {{RAW_MESSAGE}}, {{DEV_REPHRASED}}, {{LIKELINESS}} |
| commit-and-tag.txt | {{ENTRIES_LIST}}, {{TAG_NOTE}} |
| clipboard-fallback.txt | {{PROJECT_ROOT}}, {{BRANCH}}, {{CURRENT_VERSION}}, {{COMMITS_SINCE_TAG}}, {{ENTRIES_LIST}}, {{DIFF_STAT}}, {{TASKS}} |
Architecture
bethink/
├── bin/
│ ├── cli.js Entry point. Linear state machine (PROVIDER → … → DONE).
│ └── config-wizard.js Interactive provider setup wizard.
├── config/
│ └── prompts/
│ ├── rephrase-entry.txt
│ ├── public-entry-suggestion.txt
│ ├── commit-and-tag.txt
│ └── clipboard-fallback.txt
└── src/
├── core/
│ ├── git-engine.js All Git subprocess calls. Single responsibility.
│ ├── changelog-writer.js Writes CHANGELOG.md and RELEASE-NOTES.md.
│ ├── version-engine.js Pure semver calculation. Stateless.
│ ├── config-manager.js ~/.bethink.json loading and schema migration.
│ ├── state-store.js ~/.bethink.state.json persistence (bump→tag bridge).
│ ├── workflow-orchestrator.js Entry collection loop, LLM enrichment, fallback routing.
│ └── action-executor.js executeBump / executeCommit / executeTag — one each.
├── adapters/
│ └── manifest-adapter.js Strategy Pattern: package.json, pyproject.toml,
│ composer.json, go.mod.
├── llm/
│ └── llm-client.js Provider-agnostic completion layer. Prompt file loading.
│ All functions return { fromLLM: bool } — never throw.
└── ui/
├── ui-renderer.js Barrel re-export of display-renderer + prompt-handler.
├── display-renderer.js Spinner, printInfo/Success/Warning/Error. No inquirer.
└── prompt-handler.js All inquirer interactive prompts. No business logic.Separation of concerns:
cli.jsowns the state machine andctxobject. It calls executors and renders nothing itself except the banner, entry list, and summary.workflow-orchestrator.jsowns the entry collection loop and LLM enrichment per entry. It does not know about versioning or Git tags.action-executor.jsowns write operations (manifest, changelog, git). It does not prompt.llm-client.jsowns all network I/O to LLM providers. It does not know about Git or the UI.ui/owns all terminal output and prompting. It does not perform any I/O except stdout and stdin.
File Reference
| File | Lines | Purpose |
|------|-------|---------|
| bin/cli.js | ~490 | State machine entry point (justified exception to 300-line limit — shared ctx across states makes splitting harmful) |
| bin/config-wizard.js | ~205 | Provider wizard |
| src/core/git-engine.js | ~221 | Git operations |
| src/core/changelog-writer.js | ~142 | Changelog file writer |
| src/core/version-engine.js | ~81 | Semver calculation |
| src/core/config-manager.js | ~90 | Config file management |
| src/core/state-store.js | ~117 | Session state persistence |
| src/core/workflow-orchestrator.js | ~265 | Entry collection + LLM routing |
| src/core/action-executor.js | ~175 | Bump / Commit / Tag execution |
| src/adapters/manifest-adapter.js | ~200 | Manifest strategy pattern |
| src/llm/llm-client.js | ~316 | LLM provider abstraction |
| src/ui/display-renderer.js | ~103 | Chalk output, spinner |
| src/ui/prompt-handler.js | ~472 | Inquirer prompts (justified exception — 16 uniform independent functions) |
| src/ui/ui-renderer.js | ~14 | Barrel re-export |
