codebyplan 1.13.23 → 1.13.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +445 -187
- package/package.json +2 -2
- package/templates/agents/cbp-cc-executor.md +7 -7
- package/templates/agents/cbp-improve-round.md +2 -2
- package/templates/agents/cbp-round-executor.md +20 -4
- package/templates/agents/cbp-testing-qa-agent.md +3 -3
- package/templates/hooks/README.md +1 -1
- package/templates/hooks/cbp-statusline.mjs +106 -11
- package/templates/hooks/cbp-statusline.py +79 -13
- package/templates/hooks/cbp-statusline.sh +97 -17
- package/templates/hooks/validate-structure-patterns.sh +1 -1
- package/templates/skills/cbp-checkpoint-check/SKILL.md +2 -2
- package/templates/skills/cbp-checkpoint-complete/SKILL.md +2 -2
- package/templates/skills/cbp-merge-main/SKILL.md +1 -1
- package/templates/skills/cbp-round-end/SKILL.md +12 -35
- package/templates/skills/cbp-round-end/reference/findings-presentation.md +76 -3
- package/templates/skills/cbp-round-execute/SKILL.md +13 -60
- package/templates/skills/cbp-round-start/SKILL.md +3 -1
- package/templates/skills/cbp-round-update/SKILL.md +1 -1
- package/templates/skills/cbp-session-start/SKILL.md +2 -0
- package/templates/skills/cbp-ship-configure/SKILL.md +1 -1
- package/templates/skills/cbp-ship-configure/reference/supabase.md +2 -2
- package/templates/skills/cbp-ship-main/SKILL.md +2 -0
- package/templates/skills/cbp-standalone-task-create/SKILL.md +1 -1
- package/templates/skills/cbp-task-check/SKILL.md +1 -1
- package/templates/skills/cbp-task-complete/SKILL.md +1 -1
- package/templates/skills/cbp-task-create/SKILL.md +50 -1
- package/templates/skills/cbp-task-start/SKILL.md +2 -2
- package/templates/skills/cbp-task-testing/SKILL.md +2 -2
- package/templates/skills/cbp-todo/SKILL.md +36 -3
- package/templates/skills/cbp-todo/qa-regression.md +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebyplan",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.25",
|
|
4
4
|
"description": "CLI for CodeByPlan — AI-powered development planning and tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc --project tsconfig.build.json",
|
|
16
16
|
"build:npm": "node esbuild.npm.mjs",
|
|
17
|
-
"
|
|
17
|
+
"prepare": "node -e \"const v=process.env.CI;process.exit(v&&v!=='false'&&v!=='0'?0:1)\" || node esbuild.npm.mjs",
|
|
18
18
|
"lint": "eslint",
|
|
19
19
|
"lint:fix": "eslint --fix",
|
|
20
20
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
scope: org-shared
|
|
3
3
|
name: cbp-cc-executor
|
|
4
|
-
description: Authoring executor for `.claude/` infrastructure. Applies approved changes across rules, skills, agents, context, CLAUDE.md, settings, hooks, and auto-memory — with update-first discipline, scope-marker enforcement, and length-limit awareness. Callable by the main conversation
|
|
4
|
+
description: Authoring executor for `.claude/` infrastructure. Applies approved changes across rules, skills, agents, context, CLAUDE.md, settings, hooks, and auto-memory — with update-first discipline, scope-marker enforcement, and length-limit awareness. Callable by the main conversation, `/cbp-checkpoint-end`, and `round-executor` (for in-scope `.claude/` infra deliverables).
|
|
5
5
|
tools: Read, Write, Edit, Glob, Grep, Skill, Task, AskUserQuestion, mcp__codebyplan__create_task
|
|
6
6
|
model: sonnet
|
|
7
7
|
effort: xhigh
|
|
@@ -28,15 +28,16 @@ One reusable primitive for applying batches of approved `.claude/` changes. Cent
|
|
|
28
28
|
| ---------------------------- | ----------------------------------------------------- | -------------------------------------------------- |
|
|
29
29
|
| Main conversation | User directly asks Claude to update a rule/skill/etc. | Claude spawns the agent instead of editing by hand |
|
|
30
30
|
| `/cbp-checkpoint-end` | Post-ship documentation / CLAUDE.md updates | Future |
|
|
31
|
+
| `round-executor` | A round's planned deliverables include `.claude/` infra | Executor delegates the batch here (`source: 'round-executor'`) instead of looping `/cbp-build-cc-*` per file |
|
|
31
32
|
|
|
32
|
-
**`round-executor`
|
|
33
|
+
**`round-executor` MAY call this agent** to apply IN-SCOPE `.claude/` infrastructure deliverables — paths already in the round's approved `files_to_modify[]`. This is distinct from the immediate-issue-capture path: infrastructure needs *discovered* mid-round that fall OUTSIDE the round's scope are still captured as tasks per `cbp-task-create` Step 3.5 "Immediate Issue Capture Contract" — never routed through this agent.
|
|
33
34
|
|
|
34
35
|
## Input Contract
|
|
35
36
|
|
|
36
37
|
```yaml
|
|
37
38
|
input:
|
|
38
39
|
repo_id: string
|
|
39
|
-
source: 'main' | 'checkpoint-end' # additional internal sources may exist in your CBP setup; extend as needed
|
|
40
|
+
source: 'main' | 'checkpoint-end' | 'round-executor' # additional internal sources may exist in your CBP setup; extend as needed
|
|
40
41
|
changes:
|
|
41
42
|
- id: string | number
|
|
42
43
|
type: 'rule' | 'skill' | 'agent' | 'context' | 'architecture' | 'CLAUDE.md' | 'settings' | 'hook' | 'memory'
|
|
@@ -102,7 +103,7 @@ For each change in `input.changes`, pre-flight in order:
|
|
|
102
103
|
|
|
103
104
|
| `source` | Expected | On missing/generic |
|
|
104
105
|
| ------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
-
| `main` / `checkpoint-end` | Often absent — caller is ad-hoc | Derive them here: use Phase 1 inventory to list candidates, and fill `why_not_existing` with the agent's own reasoning. Do not reject. |
|
|
106
|
+
| `main` / `checkpoint-end` / `round-executor` | Often absent — caller is ad-hoc or plan-driven | Derive them here: use Phase 1 inventory to list candidates, and fill `why_not_existing` with the agent's own reasoning. Do not reject. |
|
|
106
107
|
|
|
107
108
|
4. **Length pre-flight** — for `update`, read current length; reject if the requested change would exceed the block limit in `validate-structure-lengths.sh`. Offer a split proposal via `AskUserQuestion` instead.
|
|
108
109
|
|
|
@@ -134,7 +135,7 @@ Record every applied change with `authored_via` and `status`.
|
|
|
134
135
|
|
|
135
136
|
- **Downgrade `create` → `update`** — apply silently, note in output.
|
|
136
137
|
- **Unclear fit between two existing files** — `AskUserQuestion` with the two candidates and their descriptions.
|
|
137
|
-
- **Change exceeds this invocation's scope** (e.g. proposal implies a broader refactor) — create a task via MCP `create_task` per `
|
|
138
|
+
- **Change exceeds this invocation's scope** (e.g. proposal implies a broader refactor) — create a task via MCP `create_task` per `cbp-task-create` Step 3.5 "Immediate Issue Capture Contract", record in `deferred_changes` with `task_id_created`.
|
|
138
139
|
- **Hook or settings change with cross-environment implications** — require explicit `scope` field from caller; if missing or ambiguous, ask.
|
|
139
140
|
|
|
140
141
|
### Phase 5: Post-Apply Sanity
|
|
@@ -204,8 +205,7 @@ Block-limit violations are non-negotiable — split before applying.
|
|
|
204
205
|
|
|
205
206
|
## Integration
|
|
206
207
|
|
|
207
|
-
- **Spawned by**: main conversation (ad-hoc), `/cbp-checkpoint-end` (future)
|
|
208
|
-
- **Never spawned by**: `round-executor` (round execution stays code-only)
|
|
208
|
+
- **Spawned by**: main conversation (ad-hoc), `/cbp-checkpoint-end` (future), `round-executor` (in-scope `.claude/` infra deliverables, `source: 'round-executor'`)
|
|
209
209
|
- **Reads**: `.claude/` inventory, `validate-structure-lengths.sh`, target files
|
|
210
210
|
- **Writes**: `.claude/` files (via `/cbp-build-cc-*` skills for creates, direct Edit for updates)
|
|
211
211
|
- **Calls skills**: `/cbp-build-cc-rule`, `/cbp-build-cc-skill`, `/cbp-build-cc-claude-file`, `/cbp-build-cc-settings`, `/cbp-build-cc-memory`
|
|
@@ -237,9 +237,9 @@ For each issue found:
|
|
|
237
237
|
**Corrective-depth advisory**: Before emitting findings, check `round.number` and round provenance:
|
|
238
238
|
- IF `round.number >= 3` AND the round is corrective (round requirements contain improvement/correction verbs: "fix", "address", "correct", "resolve" against a prior finding)
|
|
239
239
|
- THEN prepend to the Phase 6 output: `> [advisory] This is round N. Each successive corrective round increases ship-delay risk; consider deferring low/medium findings to a follow-up TASK in the current checkpoint (not a standalone task). Findings still listed in full — your call.`
|
|
240
|
-
- Findings remain unchanged; this is informational only. Pairs with
|
|
240
|
+
- Findings remain unchanged; this is informational only. Pairs with the planner's Path B trivial-corrective bypass (which keeps trivial corrective rounds cheap) — together they bound corrective-chain depth.
|
|
241
241
|
|
|
242
|
-
**Scope-routing recommendation**: For each finding that exceeds the current round's scope, populate `finding.routing_recommendation` per `
|
|
242
|
+
**Scope-routing recommendation**: For each finding that exceeds the current round's scope, populate `finding.routing_recommendation` per `cbp-task-create` Step 3.5 "Immediate Issue Capture Contract — How to Capture":
|
|
243
243
|
|
|
244
244
|
| Finding shape | `routing_recommendation` |
|
|
245
245
|
|---------------|--------------------------|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
scope: org-shared
|
|
3
3
|
name: cbp-round-executor
|
|
4
4
|
description: Execute approved plan. Receives pre-analyzed deliverables and files list. Focuses on quality implementation. Communicates with user when blocked or needs decisions.
|
|
5
|
-
tools: Read, Write, Edit, Glob, Grep, Bash, TaskUpdate, AskUserQuestion, Skill
|
|
5
|
+
tools: Read, Write, Edit, Glob, Grep, Bash, TaskUpdate, AskUserQuestion, Skill, Task
|
|
6
6
|
model: sonnet
|
|
7
7
|
effort: xhigh
|
|
8
8
|
---
|
|
@@ -94,6 +94,7 @@ output:
|
|
|
94
94
|
| TaskUpdate | Mark todos as complete |
|
|
95
95
|
| AskUserQuestion | **Critical** - Ask user when blocked or need decisions |
|
|
96
96
|
| Skill | **Required** - Invoke routing commands for managed files |
|
|
97
|
+
| Task | Spawn sub-executor agents (Step 3.5): `cbp-database-agent`, `general-purpose`, `cbp-cc-executor` — NEVER `cbp-e2e-*` (orchestrator-owned) |
|
|
97
98
|
|
|
98
99
|
**Key Principle:** If something is unclear or you're blocked, ASK the user. Don't make assumptions.
|
|
99
100
|
|
|
@@ -140,6 +141,8 @@ Action: {Use Skill tool for routing / Proceed with direct edits}
|
|
|
140
141
|
|
|
141
142
|
**Critical:** If ANY file requires routing, you MUST use the Skill tool. NEVER use Edit/Write on managed files.
|
|
142
143
|
|
|
144
|
+
**Batch alternative:** when `files_to_modify[]` contains MULTIPLE `.claude/` infra files, you MAY delegate the whole batch to `cbp-cc-executor` (Step 3.5) instead of looping the build-cc skills here — cc-executor applies the same routing with centralized update-first / scope-marker / length discipline. Single-file managed edits stay on the Skill-tool routing above.
|
|
145
|
+
|
|
143
146
|
#### Step 0.1: Scope-Leak Guard (MANDATORY)
|
|
144
147
|
|
|
145
148
|
Before ANY Write/Edit invocation during execution, the target path MUST appear in the active scope. When running in wave mode (`wave` input is present), the scope is `wave.files[]`; otherwise it is `approved_plan.files_to_modify[].path`. Silent absorption of an out-of-scope file is forbidden — every absorbed file dilutes the round's diff and erodes plan-as-contract guarantees.
|
|
@@ -183,7 +186,7 @@ Two categories of work are NOT performed by this agent and must be returned to t
|
|
|
183
186
|
| Action | Why excluded | Where it goes |
|
|
184
187
|
|--------|--------------|---------------|
|
|
185
188
|
| MCP `create_task`, `update_task`, `complete_task`, `add_round`, etc. (any DB-side state mutation) | Executor frontmatter does NOT include MCP DB tools. Tool-not-available errors force orchestrator improvisation. | Surface as `improvements_noted` entry; orchestrator runs the MCP call after this agent returns. Executor never tries to invoke MCP DB tools. |
|
|
186
|
-
| Spawning `cbp-e2e-*` specialist agents |
|
|
189
|
+
| Spawning `cbp-e2e-*` specialist agents | E2E execution is **orchestrator-owned by design** — the `cbp-e2e-*` specialist agents (dispatched per `context/testing/e2e.md`) are spawned by `/cbp-round-execute` Step 5 (parallel with `cbp-testing-qa-agent`), NOT by this executor. The executor's `Task` tool exists ONLY for the Step 3.5 sub-executor delegations (`cbp-database-agent`, `general-purpose`, `cbp-cc-executor`) — it is never used to spawn an e2e specialist even though it now physically could. | Set `specialist_needs.review_needed.ux_review` / `ui_review` if applicable. Do NOT attempt to spawn any e2e agent from inside the executor. |
|
|
187
190
|
|
|
188
191
|
If the plan implies either action, complete the rest of the work and surface the carved-out steps in `improvements_noted[]` for the orchestrator to handle.
|
|
189
192
|
|
|
@@ -305,6 +308,7 @@ When the approved plan includes specialized work, delegate to sub-executor agent
|
|
|
305
308
|
|-----------|-------|-----------------|
|
|
306
309
|
| Supabase migrations, RLS, types | `cbp-database-agent` | Plan includes DB schema changes, RLS policies, or type generation |
|
|
307
310
|
| Batch identical-structure file writes (≥4 files) | `general-purpose` (background) | Plan has 4+ independent files, no shared state, no ordered dependency |
|
|
311
|
+
| `.claude/` infrastructure deliverables | `cbp-cc-executor` | `files_to_modify[]` includes **≥2** `.claude/` files (rules, skills, agents, context, hooks, settings, CLAUDE.md). A single `.claude/` file edit stays on Step 0 Skill-tool routing |
|
|
308
312
|
|
|
309
313
|
**How to delegate to `cbp-database-agent`:**
|
|
310
314
|
1. Collect all DB-related steps from the plan
|
|
@@ -316,6 +320,18 @@ When the approved plan includes specialized work, delegate to sub-executor agent
|
|
|
316
320
|
- Simple Supabase queries in application code (executor handles these)
|
|
317
321
|
- Only delegate schema/migration/RLS/type generation work
|
|
318
322
|
|
|
323
|
+
**How to delegate to `cbp-cc-executor`:**
|
|
324
|
+
1. Collect every `files_to_modify[]` entry whose path is under `.claude/` (or is `CLAUDE.md`).
|
|
325
|
+
2. Build cc-executor's `input.changes[]` — one entry per file (`type`, `target`, `action`, `description`, `reasoning`, `source_of_proposal`).
|
|
326
|
+
3. Spawn `cbp-cc-executor` via Agent tool with `source: 'round-executor'` and those changes.
|
|
327
|
+
4. Merge its `applied_changes` / `files_changed` into executor output; surface any `deferred_changes` / `conflicts` in `improvements_noted[]`.
|
|
328
|
+
|
|
329
|
+
> **Agent-create guard**: cc-executor REJECTS `type: 'agent'` with `action: 'create'` (its No-Go table — agent creation is a planning-level decision). If `files_to_modify[]` contains an agent **create**, surface it via `AskUserQuestion` BEFORE spawning rather than letting it come back as `status: 'rejected'`. Agent **updates** delegate normally.
|
|
330
|
+
|
|
331
|
+
**When NOT to delegate to `cbp-cc-executor`:**
|
|
332
|
+
- A single `.claude/` file edit — the Step 0 build-cc routing (Skill tool) handles it; reserve cc-executor for multi-file batches or when its centralized update-first / scope / length discipline adds value.
|
|
333
|
+
- Infra needs *discovered* mid-round that are OUTSIDE `files_to_modify[]` — those become tasks per `cbp-task-create` Step 3.5, NEVER a cc-executor call.
|
|
334
|
+
|
|
319
335
|
#### Background General-Purpose Delegation
|
|
320
336
|
|
|
321
337
|
**Trigger** — all of the following must hold:
|
|
@@ -585,7 +601,7 @@ Which would you prefer?
|
|
|
585
601
|
- **Spawned by**: `/cbp-round-execute` Step 3 (single-wave 3-AGENT path or per-wave 3-WAVE path)
|
|
586
602
|
- **Returns to**: `/cbp-round-execute` which collects output and runs per-wave `cbp-testing-qa-agent`
|
|
587
603
|
- **Depends on**: `cbp-task-planner` agent (provides approved plan)
|
|
588
|
-
- **May spawn**: `cbp-database-agent`
|
|
604
|
+
- **May spawn**: `cbp-database-agent` (Supabase operations), `general-purpose` (background batch writes), and `cbp-cc-executor` (in-scope `.claude/` infra deliverables, `source: 'round-executor'`) as sub-executors. (NOT any `cbp-e2e-*` specialist — e2e is orchestrator-owned, spawned by `/cbp-round-execute` Step 5 per the Step 0.2 carve-out.)
|
|
589
605
|
|
|
590
606
|
## Structure Knowledge
|
|
591
607
|
|
|
@@ -603,4 +619,4 @@ improvements_noted:
|
|
|
603
619
|
suggestion: 'Add [pattern] to [skill-name] or create new skill'
|
|
604
620
|
```
|
|
605
621
|
|
|
606
|
-
**Do NOT
|
|
622
|
+
**Do NOT make self-directed rules/skills edits based on gaps you discover mid-execution** — those go through `cbp-improve-claude` after task completion. This is distinct from approved `.claude/` deliverables already in `files_to_modify[]`, which ARE applied this round via the Step 3.5 `cbp-cc-executor` delegation path (or Step 0 Skill-tool routing for a single file).
|
|
@@ -300,11 +300,11 @@ Mandatory dependency vulnerability scan:
|
|
|
300
300
|
|
|
301
301
|
### Phase 3.8: Capture Unrelated Issues — Default to Current Scope
|
|
302
302
|
|
|
303
|
-
For each entry in `unrelated_issues[]` with severity `warning` or `critical`, route per `
|
|
303
|
+
For each entry in `unrelated_issues[]` with severity `warning` or `critical`, route per `cbp-task-create` Step 3.5 "Immediate Issue Capture Contract — How to Capture" — DO NOT default to standalone task creation.
|
|
304
304
|
|
|
305
305
|
**Routing logic** (walk top-down; use the first row that fits):
|
|
306
306
|
|
|
307
|
-
1. **Trivial inline fix** (≤5 min, mechanical, scope-clean per `
|
|
307
|
+
1. **Trivial inline fix** (≤5 min, mechanical, scope-clean per `cbp-round-end` reference `findings-presentation.md` "Infra Issue Absorption Contract — Trivial-Resolution Exception") — leave the issue in `unrelated_issues[]` with `routing: "inline"` and let the orchestrator absorb it into the current round before `/cbp-round-end`.
|
|
308
308
|
|
|
309
309
|
2. **Related to current task's domain** (most cases) — emit the finding in `unrelated_issues[]` with `routing: "new_round_in_current_task"`. The agent does NOT call `create_task`. `/cbp-round-end` consumes these and includes them as requirements for the next round of the current task.
|
|
310
310
|
|
|
@@ -318,7 +318,7 @@ For routings 1-4, include each finding in `unrelated_issues[]` with the routing
|
|
|
318
318
|
|
|
319
319
|
The agent's job is **classification + recommendation**, not unilateral task creation. Standalone creation outside the timed-re-check case requires explicit user confirmation at `/cbp-round-end`.
|
|
320
320
|
|
|
321
|
-
This aligns with `
|
|
321
|
+
This aligns with `cbp-task-create` Step 3.5 "Immediate Issue Capture Contract" (resolve-in-current-scope by default; standalone is rare) and `cbp-round-end` reference `findings-presentation.md` "Infra Issue Absorption Contract" (absorb-by-default since the flip from defer-by-default).
|
|
322
322
|
|
|
323
323
|
### Phase 4: QA Generation
|
|
324
324
|
|
|
@@ -18,7 +18,7 @@ Renders up to 6 structured lines below Claude Code's prompt area. Reads JSON fro
|
|
|
18
18
|
|
|
19
19
|
**Render lines:**
|
|
20
20
|
|
|
21
|
-
- **Line 1 — Identity**: **folder name** (basename of `cwd`) + **git branch** (always shown — taken from `worktree.branch`, else derived via `git -C <cwd> rev-parse --abbrev-ref HEAD`, so it appears even without a registered worktree); single prefix (`wt:NAME` / `session:NAME` / `agent:NAME`, priority order); model **display name only**; `effort:LEVEL` (when set); `
|
|
21
|
+
- **Line 1 — Identity**: **folder name** (basename of `cwd`) + **git branch** (always shown — taken from `worktree.branch`, else derived via `git -C <cwd> rev-parse --abbrev-ref HEAD`, so it appears even without a registered worktree); single prefix (`wt:NAME` / `session:NAME` / `agent:NAME`, priority order); model **display name only**; `effort:LEVEL` (when set); `style:NAME` (when output style is not "default"); `[VIM_MODE]`
|
|
22
22
|
- **Line 2 — Context**: 20-character progress bar (green / yellow / red at 50% / 75%); `used% / ctx_size`; per-call token breakdown — `in:N out:N cache_cr:N cache_rd:N` (from `context_window.current_usage`); `⚠ 200k+` banner when `exceeds_200k_tokens` is true
|
|
23
23
|
- **Line 3 — Cost**: session cost in USD; total wall duration (`dur:`) and API-only duration (`api:`) via human-readable format; lines added/removed
|
|
24
24
|
- **Line 4 — Rate limits**: `5h: PCT% (resets in Xh)` and `7d: PCT% (resets in Xd)` with relative-time helper; colour-coded green / yellow / red at 60% / 80%; emitted only when at least one window has a non-zero `resets_at` epoch
|
|
@@ -77,7 +77,6 @@ function main() {
|
|
|
77
77
|
);
|
|
78
78
|
const EXCEEDS_200K = get(data, ["exceeds_200k_tokens"], false);
|
|
79
79
|
const EFFORT = get(data, ["effort", "level"], "");
|
|
80
|
-
const THINKING = get(data, ["thinking", "enabled"], false);
|
|
81
80
|
const RATE_5H_PCT = get(
|
|
82
81
|
data,
|
|
83
82
|
["rate_limits", "five_hour", "used_percentage"],
|
|
@@ -119,6 +118,7 @@ function main() {
|
|
|
119
118
|
repo_pr: true,
|
|
120
119
|
worktree: true,
|
|
121
120
|
infra_drift: true,
|
|
121
|
+
package_freshness: true,
|
|
122
122
|
no_color: false,
|
|
123
123
|
};
|
|
124
124
|
try {
|
|
@@ -138,6 +138,7 @@ function main() {
|
|
|
138
138
|
"repo_pr",
|
|
139
139
|
"worktree",
|
|
140
140
|
"infra_drift",
|
|
141
|
+
"package_freshness",
|
|
141
142
|
]) {
|
|
142
143
|
if (typeof parsed.lines[k] === "boolean") cfg[k] = parsed.lines[k];
|
|
143
144
|
}
|
|
@@ -190,6 +191,9 @@ function main() {
|
|
|
190
191
|
return String(n);
|
|
191
192
|
};
|
|
192
193
|
|
|
194
|
+
// Percentage formatter (integer round-half-up; cross-runtime identical).
|
|
195
|
+
const fmtPct = (n) => String(Math.floor(Number(n) + 0.5));
|
|
196
|
+
|
|
193
197
|
const fmtK = (val) => {
|
|
194
198
|
const v = Number(val);
|
|
195
199
|
if (v >= 1000000) {
|
|
@@ -203,9 +207,9 @@ function main() {
|
|
|
203
207
|
};
|
|
204
208
|
|
|
205
209
|
const fmtCost = (c) => {
|
|
206
|
-
const n = Math.floor(Number(c) *
|
|
207
|
-
const frac = String(n %
|
|
208
|
-
return `$${Math.floor(n /
|
|
210
|
+
const n = Math.floor(Number(c) * 100 + 0.5);
|
|
211
|
+
const frac = String(n % 100).padStart(2, "0");
|
|
212
|
+
return `$${Math.floor(n / 100)}.${frac}`;
|
|
209
213
|
};
|
|
210
214
|
|
|
211
215
|
const fmtDur = (ms) => {
|
|
@@ -281,7 +285,6 @@ function main() {
|
|
|
281
285
|
L1 += `${C.BOLD}${C.CYAN}${MODEL_ID}${C.RST}`;
|
|
282
286
|
}
|
|
283
287
|
if (EFFORT) L1 += ` ${C.DIM}effort:${C.RST}${EFFORT}`;
|
|
284
|
-
if (THINKING === true) L1 += ` ${C.YELLOW}thinking:on${C.RST}`;
|
|
285
288
|
if (OUTPUT_STYLE && OUTPUT_STYLE !== "default")
|
|
286
289
|
L1 += ` ${C.DIM}style:${C.RST}${OUTPUT_STYLE}`;
|
|
287
290
|
if (VIM_MODE) L1 += ` ${C.DIM}[${VIM_MODE}]${C.RST}`;
|
|
@@ -325,18 +328,20 @@ function main() {
|
|
|
325
328
|
if (has5h || has7d) {
|
|
326
329
|
let L4 = "";
|
|
327
330
|
if (has5h) {
|
|
331
|
+
const r5 = fmtPct(RATE_5H_PCT);
|
|
328
332
|
let c5;
|
|
329
|
-
if (gte(
|
|
330
|
-
else if (gte(
|
|
333
|
+
if (gte(r5, 80)) c5 = C.RED;
|
|
334
|
+
else if (gte(r5, 60)) c5 = C.YELLOW;
|
|
331
335
|
else c5 = C.GREEN;
|
|
332
|
-
L4 = `${C.DIM}5h:${C.RST}${c5}${
|
|
336
|
+
L4 = `${C.DIM}5h:${C.RST}${c5}${r5}%${C.RST} ${C.DIM}(resets in ${fmtRelTime(RATE_5H_RESETS)})${C.RST}`;
|
|
333
337
|
}
|
|
334
338
|
if (has7d) {
|
|
339
|
+
const r7 = fmtPct(RATE_7D_PCT);
|
|
335
340
|
let c7;
|
|
336
|
-
if (gte(
|
|
337
|
-
else if (gte(
|
|
341
|
+
if (gte(r7, 80)) c7 = C.RED;
|
|
342
|
+
else if (gte(r7, 60)) c7 = C.YELLOW;
|
|
338
343
|
else c7 = C.GREEN;
|
|
339
|
-
const seg7 = `${C.DIM}7d:${C.RST}${c7}${
|
|
344
|
+
const seg7 = `${C.DIM}7d:${C.RST}${c7}${r7}%${C.RST} ${C.DIM}(resets in ${fmtRelTime(RATE_7D_RESETS)})${C.RST}`;
|
|
340
345
|
L4 = L4 ? `${L4} ${C.DIM}|${C.RST} ${seg7}` : seg7;
|
|
341
346
|
}
|
|
342
347
|
out.push(L4);
|
|
@@ -418,6 +423,96 @@ function main() {
|
|
|
418
423
|
}
|
|
419
424
|
}
|
|
420
425
|
|
|
426
|
+
// ============================================================
|
|
427
|
+
// LINE 8 — Package freshness (codebyplan version / sync state)
|
|
428
|
+
// ============================================================
|
|
429
|
+
// Source: .codebyplan/claude-status.local.json (written by background refresh).
|
|
430
|
+
// Inline fallback (cache absent): read .claude/.cbp.manifest.json vs
|
|
431
|
+
// node_modules/codebyplan/package.json. HIDE when guarded (canonical_source /
|
|
432
|
+
// no_manifest / unknown) or when manifest absent (not a managed consumer).
|
|
433
|
+
if (shouldShow("PACKAGE_FRESHNESS", cfg.package_freshness)) {
|
|
434
|
+
let guarded = false;
|
|
435
|
+
let installed = "";
|
|
436
|
+
let newer = false;
|
|
437
|
+
let latest = "";
|
|
438
|
+
let inSync = true;
|
|
439
|
+
|
|
440
|
+
const cachePath = path.join(
|
|
441
|
+
root,
|
|
442
|
+
".codebyplan",
|
|
443
|
+
"claude-status.local.json"
|
|
444
|
+
);
|
|
445
|
+
if (fs.existsSync(cachePath)) {
|
|
446
|
+
try {
|
|
447
|
+
const cacheRaw = fs.readFileSync(cachePath, "utf8");
|
|
448
|
+
const cache = JSON.parse(cacheRaw);
|
|
449
|
+
if (cache && typeof cache === "object") {
|
|
450
|
+
const gr = cache.guard_reason;
|
|
451
|
+
if (
|
|
452
|
+
gr === "canonical_source" ||
|
|
453
|
+
gr === "no_manifest" ||
|
|
454
|
+
gr === "unknown"
|
|
455
|
+
) {
|
|
456
|
+
guarded = true;
|
|
457
|
+
} else {
|
|
458
|
+
installed =
|
|
459
|
+
typeof cache.installed === "string" ? cache.installed : "";
|
|
460
|
+
newer = cache.newer === true;
|
|
461
|
+
latest = typeof cache.latest === "string" ? cache.latest : "";
|
|
462
|
+
inSync = cache.in_sync !== false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
// Unreadable / invalid → keep guarded=false, installed=""
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
// Inline fallback: no cache — read-only file reads only, no network.
|
|
470
|
+
const manifestPath = path.join(root, ".claude", ".cbp.manifest.json");
|
|
471
|
+
const pkgPath = path.join(
|
|
472
|
+
root,
|
|
473
|
+
"node_modules",
|
|
474
|
+
"codebyplan",
|
|
475
|
+
"package.json"
|
|
476
|
+
);
|
|
477
|
+
if (!fs.existsSync(manifestPath)) {
|
|
478
|
+
// No manifest → not a managed consumer → hide.
|
|
479
|
+
guarded = true;
|
|
480
|
+
} else {
|
|
481
|
+
try {
|
|
482
|
+
const mRaw = fs.readFileSync(manifestPath, "utf8");
|
|
483
|
+
const mParsed = JSON.parse(mRaw);
|
|
484
|
+
const mVer =
|
|
485
|
+
typeof mParsed?.version === "string" ? mParsed.version : "";
|
|
486
|
+
const pRaw = fs.readFileSync(pkgPath, "utf8");
|
|
487
|
+
const pParsed = JSON.parse(pRaw);
|
|
488
|
+
const iVer =
|
|
489
|
+
typeof pParsed?.version === "string" ? pParsed.version : "";
|
|
490
|
+
installed = iVer;
|
|
491
|
+
if (mVer && iVer && mVer !== iVer) {
|
|
492
|
+
// manifest ≠ installed → .claude is out of sync → ⟳ run claude update
|
|
493
|
+
// (mirrors the doctor's version_skip → in_sync:false). No npm info in
|
|
494
|
+
// the offline fallback, so never the ↑ newer-available marker.
|
|
495
|
+
inSync = false;
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
498
|
+
// Can't read files → hide segment.
|
|
499
|
+
guarded = true;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!guarded && installed) {
|
|
505
|
+
let L8 = `${C.DIM}cbp${C.RST} ${installed}`;
|
|
506
|
+
if (newer && latest) {
|
|
507
|
+
L8 += ` ${C.YELLOW}↑${latest}${C.RST}`;
|
|
508
|
+
}
|
|
509
|
+
if (!inSync) {
|
|
510
|
+
L8 += ` ${C.YELLOW}⟳ run claude update${C.RST}`;
|
|
511
|
+
}
|
|
512
|
+
out.push(L8);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
421
516
|
process.stdout.write(out.length ? out.join("\n") + "\n" : "");
|
|
422
517
|
}
|
|
423
518
|
|
|
@@ -60,7 +60,6 @@ def main():
|
|
|
60
60
|
CACHE_READ = _get(data, ["context_window", "current_usage", "cache_read_input_tokens"], 0)
|
|
61
61
|
EXCEEDS_200K = _get(data, ["exceeds_200k_tokens"], False)
|
|
62
62
|
EFFORT = _get(data, ["effort", "level"], "")
|
|
63
|
-
THINKING = _get(data, ["thinking", "enabled"], False)
|
|
64
63
|
RATE_5H_PCT = _get(data, ["rate_limits", "five_hour", "used_percentage"], "")
|
|
65
64
|
RATE_5H_RESETS = _get(data, ["rate_limits", "five_hour", "resets_at"], 0)
|
|
66
65
|
RATE_7D_PCT = _get(data, ["rate_limits", "seven_day", "used_percentage"], "")
|
|
@@ -81,7 +80,7 @@ def main():
|
|
|
81
80
|
cfg = {
|
|
82
81
|
"identity": True, "context": True, "cost": True,
|
|
83
82
|
"rate_limits": True, "repo_pr": True, "worktree": True,
|
|
84
|
-
"infra_drift": True, "no_color": False,
|
|
83
|
+
"infra_drift": True, "package_freshness": True, "no_color": False,
|
|
85
84
|
}
|
|
86
85
|
try:
|
|
87
86
|
with open(os.path.join(root, ".codebyplan", "statusline.json"), "r", encoding="utf-8") as fh:
|
|
@@ -91,7 +90,7 @@ def main():
|
|
|
91
90
|
cfg["no_color"] = parsed["no_color"]
|
|
92
91
|
lines = parsed.get("lines")
|
|
93
92
|
if isinstance(lines, dict):
|
|
94
|
-
for k in ["identity", "context", "cost", "rate_limits", "repo_pr", "worktree", "infra_drift"]:
|
|
93
|
+
for k in ["identity", "context", "cost", "rate_limits", "repo_pr", "worktree", "infra_drift", "package_freshness"]:
|
|
95
94
|
if isinstance(lines.get(k), bool):
|
|
96
95
|
cfg[k] = lines[k]
|
|
97
96
|
except Exception:
|
|
@@ -134,6 +133,10 @@ def main():
|
|
|
134
133
|
return str(int(x))
|
|
135
134
|
return str(n)
|
|
136
135
|
|
|
136
|
+
# Percentage formatter (integer round-half-up; cross-runtime identical).
|
|
137
|
+
def fmt_pct(n):
|
|
138
|
+
return "%d" % int(float(n) + 0.5)
|
|
139
|
+
|
|
137
140
|
def fmt_k(val):
|
|
138
141
|
v = float(val)
|
|
139
142
|
if v >= 1000000:
|
|
@@ -145,8 +148,8 @@ def main():
|
|
|
145
148
|
return str(int(v))
|
|
146
149
|
|
|
147
150
|
def fmt_cost(c):
|
|
148
|
-
n = math.floor(float(c) *
|
|
149
|
-
return "$%d.%
|
|
151
|
+
n = math.floor(float(c) * 100 + 0.5)
|
|
152
|
+
return "$%d.%02d" % (n // 100, n % 100)
|
|
150
153
|
|
|
151
154
|
def fmt_dur(ms):
|
|
152
155
|
secs = math.trunc(float(ms) / 1000)
|
|
@@ -217,8 +220,6 @@ def main():
|
|
|
217
220
|
l1 += "%s%s%s%s" % (BOLD, CYAN, MODEL_ID, RST)
|
|
218
221
|
if EFFORT:
|
|
219
222
|
l1 += " %seffort:%s%s" % (DIM, RST, EFFORT)
|
|
220
|
-
if THINKING is True:
|
|
221
|
-
l1 += " %sthinking:on%s" % (YELLOW, RST)
|
|
222
223
|
if OUTPUT_STYLE and OUTPUT_STYLE != "default":
|
|
223
224
|
l1 += " %sstyle:%s%s" % (DIM, RST, OUTPUT_STYLE)
|
|
224
225
|
if VIM_MODE:
|
|
@@ -273,24 +274,26 @@ def main():
|
|
|
273
274
|
if has_5h or has_7d:
|
|
274
275
|
l4 = ""
|
|
275
276
|
if has_5h:
|
|
276
|
-
|
|
277
|
+
r5 = fmt_pct(RATE_5H_PCT)
|
|
278
|
+
if gte(r5, 80):
|
|
277
279
|
c5 = RED
|
|
278
|
-
elif gte(
|
|
280
|
+
elif gte(r5, 60):
|
|
279
281
|
c5 = YELLOW
|
|
280
282
|
else:
|
|
281
283
|
c5 = GREEN
|
|
282
284
|
l4 = "%s5h:%s%s%s%%%s %s(resets in %s)%s" % (
|
|
283
|
-
DIM, RST, c5,
|
|
285
|
+
DIM, RST, c5, r5, RST, DIM, fmt_rel_time(RATE_5H_RESETS), RST,
|
|
284
286
|
)
|
|
285
287
|
if has_7d:
|
|
286
|
-
|
|
288
|
+
r7 = fmt_pct(RATE_7D_PCT)
|
|
289
|
+
if gte(r7, 80):
|
|
287
290
|
c7 = RED
|
|
288
|
-
elif gte(
|
|
291
|
+
elif gte(r7, 60):
|
|
289
292
|
c7 = YELLOW
|
|
290
293
|
else:
|
|
291
294
|
c7 = GREEN
|
|
292
295
|
seg7 = "%s7d:%s%s%s%%%s %s(resets in %s)%s" % (
|
|
293
|
-
DIM, RST, c7,
|
|
296
|
+
DIM, RST, c7, r7, RST, DIM, fmt_rel_time(RATE_7D_RESETS), RST,
|
|
294
297
|
)
|
|
295
298
|
l4 = ("%s %s|%s %s" % (l4, DIM, RST, seg7)) if l4 else seg7
|
|
296
299
|
out.append(l4)
|
|
@@ -343,6 +346,69 @@ def main():
|
|
|
343
346
|
if behind > 0:
|
|
344
347
|
out.append("%s⚠ infra %d behind%s %s→ /cbp-refresh-infra%s" % (YELLOW, behind, RST, DIM, RST))
|
|
345
348
|
|
|
349
|
+
# ===== LINE 8 — Package freshness (codebyplan version / sync state) =====
|
|
350
|
+
# Source: .codebyplan/claude-status.local.json (written by background refresh).
|
|
351
|
+
# Inline fallback (cache absent): read .claude/.cbp.manifest.json vs
|
|
352
|
+
# node_modules/codebyplan/package.json. HIDE when guarded (canonical_source /
|
|
353
|
+
# no_manifest / unknown) or when manifest absent (not a managed consumer).
|
|
354
|
+
if should_show("PACKAGE_FRESHNESS", cfg["package_freshness"]):
|
|
355
|
+
_guarded = False
|
|
356
|
+
_installed = ""
|
|
357
|
+
_newer = False
|
|
358
|
+
_latest = ""
|
|
359
|
+
_in_sync = True
|
|
360
|
+
|
|
361
|
+
cache_path = os.path.join(root, ".codebyplan", "claude-status.local.json")
|
|
362
|
+
if os.path.isfile(cache_path):
|
|
363
|
+
try:
|
|
364
|
+
with open(cache_path, "r", encoding="utf-8") as fh:
|
|
365
|
+
cache = json.load(fh)
|
|
366
|
+
if isinstance(cache, dict):
|
|
367
|
+
gr = cache.get("guard_reason")
|
|
368
|
+
if gr in ("canonical_source", "no_manifest", "unknown"):
|
|
369
|
+
_guarded = True
|
|
370
|
+
else:
|
|
371
|
+
_installed = cache.get("installed") if isinstance(cache.get("installed"), str) else ""
|
|
372
|
+
_newer = cache.get("newer") is True
|
|
373
|
+
_latest = cache.get("latest") if isinstance(cache.get("latest"), str) else ""
|
|
374
|
+
_in_sync = cache.get("in_sync") is not False
|
|
375
|
+
except Exception:
|
|
376
|
+
pass # Unreadable / invalid → keep _installed=""
|
|
377
|
+
else:
|
|
378
|
+
# Inline fallback: no cache — read-only file reads only, no network.
|
|
379
|
+
manifest_path = os.path.join(root, ".claude", ".cbp.manifest.json")
|
|
380
|
+
pkg_path = os.path.join(root, "node_modules", "codebyplan", "package.json")
|
|
381
|
+
if not os.path.isfile(manifest_path):
|
|
382
|
+
# No manifest → not a managed consumer → hide.
|
|
383
|
+
_guarded = True
|
|
384
|
+
else:
|
|
385
|
+
try:
|
|
386
|
+
with open(manifest_path, "r", encoding="utf-8") as fh:
|
|
387
|
+
m_data = json.load(fh)
|
|
388
|
+
m_ver = m_data.get("version") if isinstance(m_data, dict) else ""
|
|
389
|
+
m_ver = m_ver if isinstance(m_ver, str) else ""
|
|
390
|
+
with open(pkg_path, "r", encoding="utf-8") as fh:
|
|
391
|
+
p_data = json.load(fh)
|
|
392
|
+
i_ver = p_data.get("version") if isinstance(p_data, dict) else ""
|
|
393
|
+
i_ver = i_ver if isinstance(i_ver, str) else ""
|
|
394
|
+
_installed = i_ver
|
|
395
|
+
if m_ver and i_ver and m_ver != i_ver:
|
|
396
|
+
# manifest != installed -> .claude is out of sync -> run claude update
|
|
397
|
+
# (mirrors the doctor's version_skip -> in_sync:false). No npm info
|
|
398
|
+
# in the offline fallback, so never the up-arrow newer marker.
|
|
399
|
+
_in_sync = False
|
|
400
|
+
except Exception:
|
|
401
|
+
# Can't read files → hide segment.
|
|
402
|
+
_guarded = True
|
|
403
|
+
|
|
404
|
+
if not _guarded and _installed:
|
|
405
|
+
l8 = "%scbp%s %s" % (DIM, RST, _installed)
|
|
406
|
+
if _newer and _latest:
|
|
407
|
+
l8 += " %s↑%s%s" % (YELLOW, _latest, RST)
|
|
408
|
+
if not _in_sync:
|
|
409
|
+
l8 += " %s⟳ run claude update%s" % (YELLOW, RST)
|
|
410
|
+
out.append(l8)
|
|
411
|
+
|
|
346
412
|
sys.stdout.write(("\n".join(out) + "\n") if out else "")
|
|
347
413
|
|
|
348
414
|
|