mirror of
https://github.com/jparkerweb/plan2code.git
synced 2026-09-17 16:22:23 -07:00
Compare commits
3 Commits
aedfe944b2
...
v1.16.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b18b42e30 | |||
| 8e457fa6e4 | |||
| 6740e26261 |
@@ -2,6 +2,15 @@
|
||||
|
||||
All notable changes to Plan2Code will be documented in this file.
|
||||
|
||||
## v1.16.1
|
||||
|
||||
### ✨ Added
|
||||
|
||||
- **Status line: git worktree awareness** — a session running in a linked git worktree now renders the project segment as `repo ⑂ worktree` (e.g. `plan2code ⑂ spike`) instead of only the worktree's directory name, which previously made the session look like an unrelated project
|
||||
- Repo identity resolved from `git rev-parse --git-common-dir`, so it is correct regardless of how the worktree directory was named (bare `<name>.git` main repos included)
|
||||
- A leading repo prefix is stripped from the worktree name (`plan2code-user-auth` → `user-auth`), and the name collapses to a bare `⑂` when it merely restates the branch already on screen — matched across `/ _ . -` separators and type prefixes like `feature/`, and only when the branch is actually displayed
|
||||
- Toggleable via the new `items.worktree` config flag (default on); one extra timeout-bounded `git` call, skipped outside git repos
|
||||
|
||||
## v1.16.0
|
||||
|
||||
### ✨ Added
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "plan2code",
|
||||
"version": "1.16.0",
|
||||
"version": "1.16.1",
|
||||
"private": true,
|
||||
"bin": {
|
||||
"plan2code": "./install.js"
|
||||
|
||||
@@ -20,6 +20,14 @@ A persistent three-line status bar for Claude Code that displays model info, pro
|
||||
╰─╯ 2m ($0.18) │ ▰▰▱▱▱▱▱▱▱▱▱▱ 18% (36k) │ 88k in · 3k out
|
||||
```
|
||||
|
||||
**Git worktree** — session running in a linked worktree at `C:\git\plan2code-user-auth`:
|
||||
|
||||
```
|
||||
╭─╮ Opus 4.6 | High │ plan2code ⑂ │ feature/user-auth │ +12 -3
|
||||
│★│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
|
||||
╰─╯ 3h 5m ($4.62) │ ▰▰▰▰▰▱▱▱▱▱▱▱ 42% (84k) │ 5h: 28% · 7d: 61%
|
||||
```
|
||||
|
||||
**Line 1:** Planny icon | Model name + reasoning effort | Project name | Git branch | Uncommitted changes
|
||||
**Line 2:** Planny icon | Gray separator
|
||||
**Line 3:** Planny icon | Session duration + cost | Context window bar | Usage (rate limits or token counts)
|
||||
@@ -65,6 +73,7 @@ Edit `~/.claude/statusline-config.json`:
|
||||
"model": true,
|
||||
"effort": true,
|
||||
"project": true,
|
||||
"worktree": true,
|
||||
"branch": true,
|
||||
"contextBar": true,
|
||||
"planUsage": true,
|
||||
@@ -83,6 +92,7 @@ Edit `~/.claude/statusline-config.json`:
|
||||
| `items.model` | `true` | Show model name (Opus, Sonnet, Haiku) |
|
||||
| `items.effort` | `true` | Append the current reasoning effort level (Low/Medium/High/XHigh/Max) to the model segment, e.g. `Sonnet 5 \| High`. Reads `effort.level` from stdin; hidden when the current model doesn't support an effort parameter (field absent from stdin). |
|
||||
| `items.project` | `true` | Show project directory name |
|
||||
| `items.worktree` | `true` | Detect [git worktrees](https://git-scm.com/docs/git-worktree) and render the project segment as `repo ⑂ worktree` instead of just the worktree's directory name. See [Worktree Display](#worktree-display). Requires `items.project`. |
|
||||
| `items.branch` | `true` | Show current git branch (falls back to short SHA when detached HEAD) |
|
||||
| `items.contextBar` | `true` | Show context window usage bar with percentage |
|
||||
| `items.contextTokens` | `true` | Append raw tokens used in the context window next to the percentage, e.g. `42% (84k)`. Reads `context_window.total_input_tokens` (the same input-token count `used_percentage` is derived from — excludes output tokens). Requires `items.contextBar` to also be enabled. |
|
||||
@@ -101,6 +111,27 @@ Malformed config falls back to defaults silently. Type errors on individual keys
|
||||
| Copilot CLI | Planned | Deferred — CLI lacks status line script support as of v0.0.421 |
|
||||
| Others | Not supported | Codex CLI, Gemini CLI have built-in status, not scriptable |
|
||||
|
||||
## Worktree Display
|
||||
|
||||
With `items.worktree` disabled, a session in a linked worktree shows only that worktree's directory name (`plan2code-user-auth`) — the underlying repository identity is lost, and the session looks like an unrelated project.
|
||||
|
||||
When enabled, the project segment becomes `repo ⑂ worktree`:
|
||||
|
||||
| Worktree directory | Branch | Rendered | Why |
|
||||
|--------------------|--------|----------|-----|
|
||||
| `plan2code` (primary checkout) | `main` | `plan2code` | Not a linked worktree — unchanged |
|
||||
| `plan2code-spike` | `spike-thing` | `plan2code ⑂ spike` | Repo prefix stripped from the worktree name |
|
||||
| `plan2code-user-auth` | `feature/user-auth` | `plan2code ⑂` | Name is redundant with the visible branch — collapses to the bare marker |
|
||||
| `plan2code-user-auth` | `feature/user-auth`, `items.branch: false` | `plan2code ⑂ user-auth` | No branch shown, so the name is kept |
|
||||
|
||||
Details:
|
||||
|
||||
- **Detection** — a single `git rev-parse --git-dir --git-common-dir`. The two paths differ only inside a linked worktree. The true repo name comes from `--git-common-dir` (its parent directory, or the `<name>.git` basename for a bare main repo), so it is correct regardless of how the worktree directory was named.
|
||||
- **Prefix stripping** — a leading repo name followed by `-`, `_`, or `.` is removed, so `plan2code-user-auth` reads as `user-auth`.
|
||||
- **Redundancy collapse** — worktree directories usually mirror their branch. The name is dropped (leaving `⑂`) when it matches the branch after normalizing case and `/ _ . -` separators, compared against both the full branch and its trailing segment so type prefixes like `feature/` don't defeat the match. This only applies when the branch is actually displayed.
|
||||
- **Color** — the repo keeps its bold silver; the `⑂` marker and worktree name render in amber so a non-primary checkout is obvious at a glance. Monochrome output is `repo ⑂ worktree`.
|
||||
- **Cost** — one extra `git` invocation, subject to the same 1.5s timeout, and skipped entirely outside git repos.
|
||||
|
||||
## Usage Display
|
||||
|
||||
The usage segment reads data directly from Claude Code's stdin JSON — no API calls, no auth needed, no background processes.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"model": true,
|
||||
"effort": true,
|
||||
"project": true,
|
||||
"worktree": true,
|
||||
"branch": true,
|
||||
"contextBar": true,
|
||||
"contextTokens": true,
|
||||
|
||||
@@ -28,6 +28,7 @@ const DEFAULTS = {
|
||||
model: true,
|
||||
effort: true,
|
||||
project: true,
|
||||
worktree: true,
|
||||
branch: true,
|
||||
contextBar: true,
|
||||
contextTokens: true,
|
||||
@@ -90,6 +91,7 @@ const C = {
|
||||
// Plan2Code brand
|
||||
teal: rgb(190, 192, 200), // Silver — repo
|
||||
sky: rgb(120, 195, 255), // Light sky blue — branch
|
||||
amber: rgb(235, 170, 95), // Amber — linked worktree marker
|
||||
muted: rgb(40, 120, 200), // Mid blue — model
|
||||
label: rgb(130, 135, 150), // Mid gray — field labels (5h, 7d)
|
||||
// Bar & quota thresholds
|
||||
@@ -150,6 +152,54 @@ function getGitBranch(stdinData) {
|
||||
return sha || '';
|
||||
}
|
||||
|
||||
// Linked worktrees have their own .git dir under the main repo's
|
||||
// .git/worktrees/<name>, while --git-common-dir still points at the main repo's
|
||||
// .git. Equal paths mean we're in the primary checkout.
|
||||
function getWorktreeInfo(cwd) {
|
||||
const raw = gitExec(['rev-parse', '--git-dir', '--git-common-dir'], cwd);
|
||||
if (!raw) return null;
|
||||
|
||||
const [gitDir, commonDir] = raw.split('\n').map((p) => path.resolve(cwd, p.trim()));
|
||||
if (!gitDir || !commonDir) return null;
|
||||
|
||||
const samePath = process.platform === 'win32'
|
||||
? gitDir.toLowerCase() === commonDir.toLowerCase()
|
||||
: gitDir === commonDir;
|
||||
if (samePath) return null;
|
||||
|
||||
// commonDir is normally <repo>/.git; bare repos use <name>.git directly
|
||||
const commonBase = path.basename(commonDir);
|
||||
const repoName = commonBase === '.git'
|
||||
? path.basename(path.dirname(commonDir))
|
||||
: commonBase.replace(/\.git$/i, '');
|
||||
|
||||
const toplevel = gitExec(['rev-parse', '--show-toplevel'], cwd);
|
||||
const worktreeName = path.basename(toplevel || cwd);
|
||||
if (!repoName || !worktreeName) return null;
|
||||
|
||||
return { repoName, worktreeName };
|
||||
}
|
||||
|
||||
// "plan2code-user-auth" in repo "plan2code" reads as just "user-auth"
|
||||
function stripRepoPrefix(worktreeName, repoName) {
|
||||
const lowerWt = worktreeName.toLowerCase();
|
||||
const lowerRepo = repoName.toLowerCase();
|
||||
if (!lowerWt.startsWith(lowerRepo) || lowerWt === lowerRepo) return worktreeName;
|
||||
const rest = worktreeName.slice(repoName.length);
|
||||
return /^[-_.]/.test(rest) ? rest.slice(1) : worktreeName;
|
||||
}
|
||||
|
||||
const normalizeName = (s) => s.toLowerCase().replace(/[/_.\s-]+/g, '-').replace(/^-|-$/g, '');
|
||||
|
||||
// Worktree dirs usually mirror their branch (plan2code-user-auth / feature/user-auth).
|
||||
// Compare against both the full branch and its trailing segment so the common
|
||||
// type prefixes (feature/, bugfix/, ...) don't defeat the match.
|
||||
function isRedundantWithBranch(worktreeName, branch) {
|
||||
if (!branch) return false;
|
||||
const candidates = new Set([normalizeName(branch), normalizeName(branch.split('/').pop())]);
|
||||
return candidates.has(normalizeName(worktreeName));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Formatters
|
||||
// ============================================================================
|
||||
@@ -192,6 +242,34 @@ function getProjectName(stdinData) {
|
||||
return path.basename(projectDir);
|
||||
}
|
||||
|
||||
// Returns the project segment: "plan2code" normally, "plan2code ⑂ spike" in a
|
||||
// linked worktree — the name collapsing to a bare ⑂ when the branch says it already.
|
||||
function formatProject(stdinData, config, branch) {
|
||||
const project = getProjectName(stdinData);
|
||||
if (!project) return null;
|
||||
const plain = (text) => (config.color ? `${C.bold}${C.teal}${text}${C.reset}` : text);
|
||||
|
||||
if (!config.items.worktree) return plain(project);
|
||||
|
||||
const cwd = stdinData?.workspace?.project_dir || process.cwd();
|
||||
if (!isGitRepo(cwd)) return plain(project);
|
||||
|
||||
const info = getWorktreeInfo(cwd);
|
||||
if (!info) return plain(project);
|
||||
|
||||
const shortName = stripRepoPrefix(info.worktreeName, info.repoName);
|
||||
// Only dedupe against a branch the user can actually see — otherwise the
|
||||
// worktree identity would vanish entirely.
|
||||
const visibleBranch = config.items.branch ? branch : '';
|
||||
const redundant = isRedundantWithBranch(shortName, visibleBranch)
|
||||
|| isRedundantWithBranch(info.worktreeName, visibleBranch)
|
||||
|| normalizeName(shortName) === normalizeName(info.repoName);
|
||||
const marker = redundant ? '⑂' : `⑂ ${shortName}`;
|
||||
|
||||
if (!config.color) return `${info.repoName} ${marker}`;
|
||||
return `${C.bold}${C.teal}${info.repoName}${C.reset} ${C.amber}${marker}${C.reset}`;
|
||||
}
|
||||
|
||||
function calculateContextPercent(stdinData, config) {
|
||||
const cw = stdinData?.context_window;
|
||||
if (!cw) return null;
|
||||
@@ -339,14 +417,16 @@ function formatLine1(stdinData, config) {
|
||||
}
|
||||
}
|
||||
|
||||
// Branch is resolved first — the project segment dedupes the worktree name against it
|
||||
const branch = config.items.branch || config.items.worktree ? getGitBranch(stdinData) : '';
|
||||
|
||||
if (config.items.project) {
|
||||
const project = getProjectName(stdinData);
|
||||
if (project) segments.push(config.color ? `${C.bold}${C.teal}${project}${C.reset}` : project);
|
||||
const project = formatProject(stdinData, config, branch);
|
||||
if (project) segments.push(project);
|
||||
}
|
||||
|
||||
if (config.items.branch) {
|
||||
const branch = getGitBranch(stdinData);
|
||||
if (branch) segments.push(config.color ? `${C.sky}${branch}${C.reset}` : branch);
|
||||
if (config.items.branch && branch) {
|
||||
segments.push(config.color ? `${C.sky}${branch}${C.reset}` : branch);
|
||||
}
|
||||
|
||||
if (config.items.linesChanged) {
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Plan2Code",
|
||||
"version": "1.16.0",
|
||||
"version": "1.16.1",
|
||||
"description": "A structured 4-step workflow methodology for AI-assisted software development",
|
||||
"keywords": [
|
||||
"ai",
|
||||
@@ -17,7 +17,7 @@
|
||||
"url": "https://github.com/jparkerweb/plan2code"
|
||||
},
|
||||
"homepage": "https://plan2code.jparkerweb.com",
|
||||
"releaseDate": "2026-07-20",
|
||||
"releaseDate": "2026-07-31",
|
||||
"mode": "utility"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user