npm package: @algorithmikos/bethink
Description: Language-agnostic CLI for versioning, changelog, commit, and tag automation with LLM polish
Author: Umar al-Fayiz
Latest version: 1.5.1

# bethink

> A language-agnostic CLI for versioning, changelog authoring, and release tagging — with LLM polish and a resilient offline fallback.

---

## Table of Contents

- [Overview](#overview)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [CLI Flags](#cli-flags)
- [Workflow](#workflow)
  - [Action Selection](#action-selection)
  - [Entry Collection](#entry-collection)
  - [Version Bump](#version-bump)
  - [LLM Features](#llm-features)
  - [Fail-Safe & Smart Copy](#fail-safe--smart-copy)
  - [Session State & Post-Bump Gap](#session-state--post-bump-gap)
- [Manifest Support](#manifest-support)
- [Configuration](#configuration)
- [Prompt Customisation](#prompt-customisation)
- [Architecture](#architecture)
- [File Reference](#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

```bash
npm install -g bethink
```

Node.js ≥ 18 is required.

On first run, `bethink --config` opens the provider wizard and writes `~/.bethink.json`.

---

## Quick Start

```bash
cd /path/to/your/project
bethink
```

The 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:
1. Keep it as-is.
2. 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 `removed` entry → **major**.
- Any `added`, `changed`, or `deprecated` entry (no `removed`) → **minor**.
- Only `fixed`, `security`, or `build` entries → **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:

1. **Continue with raw messages** — proceed without LLM polish.
2. **Smart Copy** — the full session context (entries, commits since the last tag, `git diff --stat`, branch, version) is aggregated using `clipboard-fallback.txt` and copied to the system clipboard. The user pastes this into any web-based LLM, then pastes the JSON result back into the CLI.
3. **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:

```json
{
  "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.js` owns the state machine and `ctx` object. It calls executors and renders nothing itself except the banner, entry list, and summary.
- `workflow-orchestrator.js` owns the entry collection loop and LLM enrichment per entry. It does not know about versioning or Git tags.
- `action-executor.js` owns write operations (manifest, changelog, git). It does not prompt.
- `llm-client.js` owns 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 |