codebyplan 1.5.1 → 1.9.0
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 +4462 -748
- package/package.json +5 -1
- package/templates/.gitkeep +0 -0
- package/templates/README.md +20 -0
- package/templates/agents/cbp-cc-executor.md +213 -0
- package/templates/agents/cbp-database-agent.md +229 -0
- package/templates/agents/cbp-improve-claude.md +245 -0
- package/templates/agents/cbp-improve-round.md +284 -0
- package/templates/agents/cbp-mechanical-edits.md +111 -0
- package/templates/agents/cbp-research.md +282 -0
- package/templates/agents/cbp-round-executor.md +604 -0
- package/templates/agents/cbp-security-agent.md +134 -0
- package/templates/agents/cbp-task-check.md +213 -0
- package/templates/agents/cbp-task-planner.md +582 -0
- package/templates/agents/cbp-test-e2e-agent.md +363 -0
- package/templates/agents/cbp-testing-qa-agent.md +400 -0
- package/templates/context/mcp-docs.md +139 -0
- package/templates/hooks/README.md +236 -0
- package/templates/hooks/cbp-auto-test-hooks.sh +44 -0
- package/templates/hooks/cbp-lint-format-on-edit.sh +159 -0
- package/templates/hooks/cbp-maestro-yaml-validate.sh +100 -0
- package/templates/hooks/cbp-mcp-migration-guard.sh +32 -0
- package/templates/hooks/cbp-mcp-round-sync.sh +79 -0
- package/templates/hooks/cbp-mcp-worktree-inject.sh +76 -0
- package/templates/hooks/cbp-notify.sh +68 -0
- package/templates/hooks/cbp-plugin-dispatch.sh +29 -0
- package/templates/hooks/cbp-pre-commit-quality-gate.sh +204 -0
- package/templates/hooks/cbp-statusline.sh +347 -0
- package/templates/hooks/cbp-subagent-statusline.sh +182 -0
- package/templates/hooks/cbp-test-coverage-gate.sh +144 -0
- package/templates/hooks/cbp-test-hooks.sh +320 -0
- package/templates/hooks/hooks.json +85 -0
- package/templates/hooks/validate-context-usage.sh +59 -0
- package/templates/hooks/validate-git-commit.sh +78 -0
- package/templates/hooks/validate-git-stash-deny.sh +32 -0
- package/templates/hooks/validate-structure-lengths.sh +57 -0
- package/templates/hooks/validate-structure-lib.sh +104 -0
- package/templates/hooks/validate-structure-patterns.sh +54 -0
- package/templates/hooks/validate-structure-scope.sh +33 -0
- package/templates/hooks/validate-structure-smoke.sh +95 -0
- package/templates/hooks/validate-structure-templates.sh +34 -0
- package/templates/hooks/validate-structure.sh +69 -0
- package/templates/rules/.gitkeep +0 -0
- package/templates/rules/README.md +47 -0
- package/templates/rules/context-file-loading.md +52 -0
- package/templates/rules/scope-vocabulary.md +64 -0
- package/templates/rules/todo-backend.md +109 -0
- package/templates/settings.project.base.json +55 -0
- package/templates/settings.user.base.json +25 -0
- package/templates/skills/cbp-build-cc-agent/SKILL.md +139 -0
- package/templates/skills/cbp-build-cc-agent/examples/read-only-reviewer.md +32 -0
- package/templates/skills/cbp-build-cc-agent/examples/with-hooks.md +41 -0
- package/templates/skills/cbp-build-cc-agent/examples/with-skills-preload.md +25 -0
- package/templates/skills/cbp-build-cc-agent/reference/cbp-quality.md +153 -0
- package/templates/skills/cbp-build-cc-agent/reference/frontmatter-fields.md +37 -0
- package/templates/skills/cbp-build-cc-agent/reference/permission-modes.md +18 -0
- package/templates/skills/cbp-build-cc-agent/scripts/validate-agent.sh +67 -0
- package/templates/skills/cbp-build-cc-agent/templates/agent.md +66 -0
- package/templates/skills/cbp-build-cc-claude-file/SKILL.md +178 -0
- package/templates/skills/cbp-build-cc-claude-file/examples/minimal-project.md +33 -0
- package/templates/skills/cbp-build-cc-claude-file/examples/monorepo-with-imports.md +39 -0
- package/templates/skills/cbp-build-cc-claude-file/reference/imports.md +72 -0
- package/templates/skills/cbp-build-cc-claude-file/reference/what-belongs.md +39 -0
- package/templates/skills/cbp-build-cc-claude-file/templates/project-claude-md.md +48 -0
- package/templates/skills/cbp-build-cc-claude-file/templates/user-claude-md.md +22 -0
- package/templates/skills/cbp-build-cc-memory/SKILL.md +201 -0
- package/templates/skills/cbp-build-cc-memory/examples/feedback-memory.md +11 -0
- package/templates/skills/cbp-build-cc-memory/examples/project-memory.md +11 -0
- package/templates/skills/cbp-build-cc-memory/examples/reference-memory.md +13 -0
- package/templates/skills/cbp-build-cc-memory/examples/user-memory.md +14 -0
- package/templates/skills/cbp-build-cc-memory/reference/memory-types.md +59 -0
- package/templates/skills/cbp-build-cc-memory/reference/when-to-save.md +62 -0
- package/templates/skills/cbp-build-cc-memory/templates/MEMORY-index.md +4 -0
- package/templates/skills/cbp-build-cc-memory/templates/memory-entry.md +15 -0
- package/templates/skills/cbp-build-cc-mode/SKILL.md +99 -0
- package/templates/skills/cbp-build-cc-rule/SKILL.md +176 -0
- package/templates/skills/cbp-build-cc-rule/examples/global-rule.md +19 -0
- package/templates/skills/cbp-build-cc-rule/examples/scoped-rule.md +41 -0
- package/templates/skills/cbp-build-cc-rule/reference/paths-patterns.md +48 -0
- package/templates/skills/cbp-build-cc-rule/templates/rule.md +32 -0
- package/templates/skills/cbp-build-cc-settings/SKILL.md +220 -0
- package/templates/skills/cbp-build-cc-settings/examples/hooks-config.json +64 -0
- package/templates/skills/cbp-build-cc-settings/examples/permissions-config.json +34 -0
- package/templates/skills/cbp-build-cc-settings/examples/sandbox-config.json +42 -0
- package/templates/skills/cbp-build-cc-settings/reference/cbp-conventions.md +104 -0
- package/templates/skills/cbp-build-cc-settings/reference/permission-rules.md +61 -0
- package/templates/skills/cbp-build-cc-settings/reference/scope-precedence.md +73 -0
- package/templates/skills/cbp-build-cc-settings/reference/settings-fields.md +166 -0
- package/templates/skills/cbp-build-cc-settings/templates/settings.json +23 -0
- package/templates/skills/cbp-build-cc-settings/templates/settings.local.json +10 -0
- package/templates/skills/cbp-build-cc-skill/SKILL.md +154 -0
- package/templates/skills/cbp-build-cc-skill/examples/dynamic-context.md +31 -0
- package/templates/skills/cbp-build-cc-skill/examples/fork-skill.md +22 -0
- package/templates/skills/cbp-build-cc-skill/examples/knowledge-skill.md +25 -0
- package/templates/skills/cbp-build-cc-skill/examples/task-skill.md +29 -0
- package/templates/skills/cbp-build-cc-skill/reference/cbp-quality.md +157 -0
- package/templates/skills/cbp-build-cc-skill/reference/frontmatter-fields.md +35 -0
- package/templates/skills/cbp-build-cc-skill/reference/string-substitutions.md +60 -0
- package/templates/skills/cbp-build-cc-skill/scripts/validate-skill.sh +90 -0
- package/templates/skills/cbp-build-cc-skill/templates/skill.md +51 -0
- package/templates/skills/cbp-checkpoint-check/SKILL.md +156 -0
- package/templates/skills/cbp-checkpoint-complete/SKILL.md +109 -0
- package/templates/skills/cbp-checkpoint-create/SKILL.md +116 -0
- package/templates/skills/cbp-checkpoint-end/SKILL.md +241 -0
- package/templates/skills/cbp-checkpoint-plan/SKILL.md +137 -0
- package/templates/skills/cbp-checkpoint-plan/reference/alternative-comparison-template.md +54 -0
- package/templates/skills/cbp-checkpoint-plan/reference/dep-decision-rubric.md +50 -0
- package/templates/skills/cbp-checkpoint-plan/reference/e2e-discovery-probe.md +57 -0
- package/templates/skills/cbp-checkpoint-plan/reference/gap-analysis-playbook.md +47 -0
- package/templates/skills/cbp-checkpoint-start/SKILL.md +84 -0
- package/templates/skills/cbp-checkpoint-update/SKILL.md +115 -0
- package/templates/skills/cbp-frontend-a11y/SKILL.md +109 -0
- package/templates/skills/cbp-frontend-a11y/reference/aria-roles-states.md +130 -0
- package/templates/skills/cbp-frontend-a11y/reference/contrast-visual.md +122 -0
- package/templates/skills/cbp-frontend-a11y/reference/keyboard-patterns.md +154 -0
- package/templates/skills/cbp-frontend-a11y/reference/semantic-html.md +111 -0
- package/templates/skills/cbp-frontend-design/SKILL.md +145 -0
- package/templates/skills/cbp-frontend-design/reference/nextjs-scss.md +118 -0
- package/templates/skills/cbp-frontend-design/reference/rn-expo.md +101 -0
- package/templates/skills/cbp-frontend-design/reference/tauri-react.md +82 -0
- package/templates/skills/cbp-frontend-ui/SKILL.md +262 -0
- package/templates/skills/cbp-frontend-ui/reference/ui-label-maps.md +42 -0
- package/templates/skills/cbp-frontend-ui/reference/ui-layout-patterns.md +105 -0
- package/templates/skills/cbp-frontend-ui/reference/variant-defaults.md +149 -0
- package/templates/skills/cbp-frontend-ux/SKILL.md +181 -0
- package/templates/skills/cbp-git-branch-feat-create/SKILL.md +115 -0
- package/templates/skills/cbp-git-commit/SKILL.md +278 -0
- package/templates/skills/cbp-git-worktree-create/SKILL.md +226 -0
- package/templates/skills/cbp-git-worktree-remove/SKILL.md +145 -0
- package/templates/skills/cbp-merge-main/SKILL.md +228 -0
- package/templates/skills/cbp-round-check/SKILL.md +104 -0
- package/templates/skills/cbp-round-end/SKILL.md +183 -0
- package/templates/skills/cbp-round-end/reference/findings-presentation.md +44 -0
- package/templates/skills/cbp-round-end/reference/inline-fallback.md +35 -0
- package/templates/skills/cbp-round-execute/SKILL.md +211 -0
- package/templates/skills/cbp-round-execute/reference/inline-fallback.md +59 -0
- package/templates/skills/cbp-round-input/SKILL.md +165 -0
- package/templates/skills/cbp-round-start/SKILL.md +222 -0
- package/templates/skills/cbp-round-update/SKILL.md +163 -0
- package/templates/skills/cbp-session-end/SKILL.md +187 -0
- package/templates/skills/cbp-session-start/SKILL.md +155 -0
- package/templates/skills/cbp-ship/SKILL.md +332 -0
- package/templates/skills/cbp-ship/reference/changesets-overview.md +120 -0
- package/templates/skills/cbp-ship/reference/eas-cli-overview.md +60 -0
- package/templates/skills/cbp-ship/reference/gh-cli-overview.md +135 -0
- package/templates/skills/cbp-ship/reference/gh-cli-shipment-commands.md +283 -0
- package/templates/skills/cbp-ship/reference/npm-publish-monorepo.md +252 -0
- package/templates/skills/cbp-ship/reference/npm-publish-oidc-trusted.md +157 -0
- package/templates/skills/cbp-ship/reference/npm-publish-overview.md +171 -0
- package/templates/skills/cbp-ship/reference/preflight-checklist.md +88 -0
- package/templates/skills/cbp-ship/reference/railway-nestjs-deployment.md +169 -0
- package/templates/skills/cbp-ship/reference/railway-overview.md +120 -0
- package/templates/skills/cbp-ship/reference/railway-troubleshooting.md +168 -0
- package/templates/skills/cbp-ship/reference/release-please-overview.md +99 -0
- package/templates/skills/cbp-ship/reference/surface-expo-eas.md +155 -0
- package/templates/skills/cbp-ship/reference/surface-npm.md +180 -0
- package/templates/skills/cbp-ship/reference/surface-railway.md +152 -0
- package/templates/skills/cbp-ship/reference/surface-supabase.md +178 -0
- package/templates/skills/cbp-ship/reference/surface-tauri.md +138 -0
- package/templates/skills/cbp-ship/reference/surface-vercel.md +124 -0
- package/templates/skills/cbp-ship/reference/surface-vscode-ext.md +144 -0
- package/templates/skills/cbp-ship/reference/surfaces.md +60 -0
- package/templates/skills/cbp-ship/reference/testflight-automation.md +215 -0
- package/templates/skills/cbp-ship/reference/testflight-internal-vs-external.md +69 -0
- package/templates/skills/cbp-ship/reference/testflight-overview.md +98 -0
- package/templates/skills/cbp-ship/reference/versioning.md +116 -0
- package/templates/skills/cbp-ship/scripts/detect-surfaces.sh +217 -0
- package/templates/skills/cbp-ship/scripts/verify-expo-eas.sh +35 -0
- package/templates/skills/cbp-ship/scripts/verify-npm.sh +21 -0
- package/templates/skills/cbp-ship/scripts/verify-railway.sh +41 -0
- package/templates/skills/cbp-ship/scripts/verify-supabase.sh +19 -0
- package/templates/skills/cbp-ship/scripts/verify-tauri.sh +24 -0
- package/templates/skills/cbp-ship/scripts/verify-vercel.sh +32 -0
- package/templates/skills/cbp-ship/scripts/verify-vscode-ext.sh +25 -0
- package/templates/skills/cbp-ship/templates/eas.json +66 -0
- package/templates/skills/cbp-ship/templates/railway.toml +15 -0
- package/templates/skills/cbp-ship/templates/release-please-config.json +17 -0
- package/templates/skills/cbp-ship/templates/vercel.json +19 -0
- package/templates/skills/cbp-ship/templates/vscodeignore +21 -0
- package/templates/skills/cbp-ship/templates/workflow-changesets.yml +41 -0
- package/templates/skills/cbp-ship/templates/workflow-eas-submit.yml +53 -0
- package/templates/skills/cbp-ship/templates/workflow-npm-publish.yml +36 -0
- package/templates/skills/cbp-ship/templates/workflow-release-please.yml +21 -0
- package/templates/skills/cbp-ship/templates/workflow-tauri-release.yml +69 -0
- package/templates/skills/cbp-ship/templates/workflow-vsce-publish.yml +31 -0
- package/templates/skills/cbp-ship-configure/SKILL.md +296 -0
- package/templates/skills/cbp-ship-configure/reference/expo-mobile.md +204 -0
- package/templates/skills/cbp-ship-configure/reference/npm-package.md +165 -0
- package/templates/skills/cbp-ship-configure/reference/railway-backend.md +199 -0
- package/templates/skills/cbp-ship-configure/reference/supabase.md +200 -0
- package/templates/skills/cbp-ship-configure/reference/tauri-desktop.md +181 -0
- package/templates/skills/cbp-ship-configure/reference/vercel.md +117 -0
- package/templates/skills/cbp-ship-configure/reference/vscode-ext.md +155 -0
- package/templates/skills/cbp-ship-main/SKILL.md +65 -0
- package/templates/skills/cbp-supabase-branch-check/SKILL.md +337 -0
- package/templates/skills/cbp-supabase-branch-check/reference/dag-steps.md +29 -0
- package/templates/skills/cbp-supabase-migrate/SKILL.md +314 -0
- package/templates/skills/cbp-supabase-migrate/reference/advisor-triage.md +70 -0
- package/templates/skills/cbp-supabase-migrate/reference/cli-fallback.md +87 -0
- package/templates/skills/cbp-supabase-migrate/reference/preflight-dry-run.md +58 -0
- package/templates/skills/cbp-supabase-setup/SKILL.md +239 -0
- package/templates/skills/cbp-supabase-setup/reference/branching-setup.md +121 -0
- package/templates/skills/cbp-supabase-setup/reference/cli-fallback.md +109 -0
- package/templates/skills/cbp-task-check/SKILL.md +166 -0
- package/templates/skills/cbp-task-complete/SKILL.md +206 -0
- package/templates/skills/cbp-task-complete/reference/checkpoint-done-branching.md +48 -0
- package/templates/skills/cbp-task-complete/reference/next-step-heuristic.md +56 -0
- package/templates/skills/cbp-task-create/SKILL.md +167 -0
- package/templates/skills/cbp-task-start/SKILL.md +239 -0
- package/templates/skills/cbp-task-testing/SKILL.md +277 -0
- package/templates/skills/cbp-todo/SKILL.md +111 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @hook: NOT-A-HOOK (statusLine renderer, invoked by settings.json statusLine.command)
|
|
3
|
+
# Claude Code Status Line - All fields visible
|
|
4
|
+
# Purpose: Renders up to 6 structured lines of Claude Code status from stdin JSON.
|
|
5
|
+
# Not a PreToolUse/PostToolUse/Notification hook — do NOT register in hooks[].
|
|
6
|
+
# Shows: Identity | Context | Cost | Rate Limits | Repo/PR | Worktree
|
|
7
|
+
#
|
|
8
|
+
# --------------------------------------------------------------------------
|
|
9
|
+
# ENV-VAR TOGGLES — set to 1 to suppress a render section
|
|
10
|
+
# CBP_STATUSLINE_HIDE_IDENTITY=1 suppress line 1 (model, effort, session, agent)
|
|
11
|
+
# CBP_STATUSLINE_HIDE_CONTEXT=1 suppress line 2 (context bar, tokens, cache)
|
|
12
|
+
# CBP_STATUSLINE_HIDE_COST=1 suppress line 3 (cost, duration, lines)
|
|
13
|
+
# CBP_STATUSLINE_HIDE_RATE_LIMITS=1 suppress line 4 (5h / 7d rate limits)
|
|
14
|
+
# CBP_STATUSLINE_HIDE_REPO_PR=1 suppress line 5 (repo host/owner/name, PR)
|
|
15
|
+
# CBP_STATUSLINE_HIDE_WORKTREE=1 suppress line 6 (worktree name/branch/path)
|
|
16
|
+
# CBP_STATUSLINE_NO_COLOR=1 strip all ANSI colour codes (also honoured by $NO_COLOR)
|
|
17
|
+
# --------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
INPUT=$(cat)
|
|
20
|
+
|
|
21
|
+
# ---- One jq invocation captures all ~40 fields --------------------------------
|
|
22
|
+
eval "$(echo "$INPUT" | jq -r '
|
|
23
|
+
@sh "MODEL_ID=\(.model.id // "")",
|
|
24
|
+
@sh "MODEL_NAME=\(.model.display_name // "")",
|
|
25
|
+
@sh "CWD=\(.cwd // "")",
|
|
26
|
+
@sh "WS_CURRENT_DIR=\(.workspace.current_dir // "")",
|
|
27
|
+
@sh "WS_PROJECT_DIR=\(.workspace.project_dir // "")",
|
|
28
|
+
@sh "WS_ADDED_DIRS_LEN=\((.workspace.added_dirs // []) | length)",
|
|
29
|
+
@sh "WS_GIT_WORKTREE=\(.workspace.git_worktree // "")",
|
|
30
|
+
@sh "WS_REPO_HOST=\(.workspace.repo.host // "")",
|
|
31
|
+
@sh "WS_REPO_OWNER=\(.workspace.repo.owner // "")",
|
|
32
|
+
@sh "WS_REPO_NAME=\(.workspace.repo.name // "")",
|
|
33
|
+
@sh "COST=\(.cost.total_cost_usd // 0)",
|
|
34
|
+
@sh "DURATION=\(.cost.total_duration_ms // 0)",
|
|
35
|
+
@sh "API_DURATION=\(.cost.total_api_duration_ms // 0)",
|
|
36
|
+
@sh "LINES_ADD=\(.cost.total_lines_added // 0)",
|
|
37
|
+
@sh "LINES_DEL=\(.cost.total_lines_removed // 0)",
|
|
38
|
+
@sh "CTX_TOT_IN=\(.context_window.total_input_tokens // 0)",
|
|
39
|
+
@sh "CTX_TOT_OUT=\(.context_window.total_output_tokens // 0)",
|
|
40
|
+
@sh "CTX_SIZE=\(.context_window.context_window_size // 200000)",
|
|
41
|
+
@sh "CTX_PCT=\(.context_window.used_percentage // 0)",
|
|
42
|
+
@sh "CTX_REM=\(.context_window.remaining_percentage // 100)",
|
|
43
|
+
@sh "CUR_IN=\(.context_window.current_usage.input_tokens // 0)",
|
|
44
|
+
@sh "CUR_OUT=\(.context_window.current_usage.output_tokens // 0)",
|
|
45
|
+
@sh "CACHE_CREATE=\(.context_window.current_usage.cache_creation_input_tokens // 0)",
|
|
46
|
+
@sh "CACHE_READ=\(.context_window.current_usage.cache_read_input_tokens // 0)",
|
|
47
|
+
@sh "EXCEEDS_200K=\(.exceeds_200k_tokens // false)",
|
|
48
|
+
@sh "EFFORT=\(.effort.level // "")",
|
|
49
|
+
@sh "THINKING=\(.thinking.enabled // false)",
|
|
50
|
+
@sh "RATE_5H_PCT=\(.rate_limits.five_hour.used_percentage // "")",
|
|
51
|
+
@sh "RATE_5H_RESETS=\(.rate_limits.five_hour.resets_at // 0)",
|
|
52
|
+
@sh "RATE_7D_PCT=\(.rate_limits.seven_day.used_percentage // "")",
|
|
53
|
+
@sh "RATE_7D_RESETS=\(.rate_limits.seven_day.resets_at // 0)",
|
|
54
|
+
# Note: RATE_*_PCT use `// ""` (not `// 0`) so the `[ -n "$RATE_*_PCT" ]` absence gate
|
|
55
|
+
# below distinguishes "API said 0% used" from "rate_limits object absent". Do not
|
|
56
|
+
# change to `// 0` — it would silently suppress the rate-limit line on subscriber
|
|
57
|
+
# accounts at 0% usage at session start.
|
|
58
|
+
@sh "SESSION_ID=\(.session_id // "")",
|
|
59
|
+
@sh "SESSION_NAME=\(.session_name // "")",
|
|
60
|
+
@sh "TRANSCRIPT_PATH=\(.transcript_path // "")",
|
|
61
|
+
@sh "VERSION=\(.version // "")",
|
|
62
|
+
@sh "OUTPUT_STYLE=\(.output_style.name // "")",
|
|
63
|
+
@sh "VIM_MODE=\(.vim.mode // "")",
|
|
64
|
+
@sh "AGENT_NAME=\(.agent.name // "")",
|
|
65
|
+
@sh "PR_NUMBER=\(.pr.number // "")",
|
|
66
|
+
@sh "PR_URL=\(.pr.url // "")",
|
|
67
|
+
@sh "PR_REVIEW_STATE=\(.pr.review_state // "")",
|
|
68
|
+
@sh "WT_NAME=\(.worktree.name // "")",
|
|
69
|
+
@sh "WT_PATH=\(.worktree.path // "")",
|
|
70
|
+
@sh "WT_BRANCH=\(.worktree.branch // "")",
|
|
71
|
+
@sh "WT_ORIG_CWD=\(.worktree.original_cwd // "")",
|
|
72
|
+
@sh "WT_ORIG_BRANCH=\(.worktree.original_branch // "")"
|
|
73
|
+
')"
|
|
74
|
+
|
|
75
|
+
# ---- Colour setup ------------------------------------------------------------
|
|
76
|
+
# Strip colours when NO_COLOR or CBP_STATUSLINE_NO_COLOR is set
|
|
77
|
+
if [ -n "${NO_COLOR:-}" ] || [ "${CBP_STATUSLINE_NO_COLOR:-0}" = "1" ]; then
|
|
78
|
+
RST=''; DIM=''; BOLD=''; GREEN=''; YELLOW=''; RED=''; CYAN=''; MAGENTA=''; BLUE=''
|
|
79
|
+
else
|
|
80
|
+
RST='\033[0m'
|
|
81
|
+
DIM='\033[2m'
|
|
82
|
+
BOLD='\033[1m'
|
|
83
|
+
GREEN='\033[32m'
|
|
84
|
+
YELLOW='\033[33m'
|
|
85
|
+
RED='\033[31m'
|
|
86
|
+
CYAN='\033[36m'
|
|
87
|
+
MAGENTA='\033[35m'
|
|
88
|
+
BLUE='\033[34m'
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# ---- Float-safe percentage comparison ----------------------------------------
|
|
92
|
+
# Usage: awk_gte VALUE THRESHOLD (returns 0 = true, 1 = false)
|
|
93
|
+
awk_gte() { awk -v v="$1" -v t="$2" 'BEGIN{exit !(v+0 >= t+0)}'; }
|
|
94
|
+
|
|
95
|
+
# ---- Token/size formatter (K / M) -------------------------------------------
|
|
96
|
+
fmt_k() {
|
|
97
|
+
local val=$1
|
|
98
|
+
if [ "$val" -ge 1000000 ] 2>/dev/null; then
|
|
99
|
+
printf "%.1fM" "$(echo "$val / 1000000" | bc -l)"
|
|
100
|
+
elif [ "$val" -ge 1000 ] 2>/dev/null; then
|
|
101
|
+
printf "%.1fK" "$(echo "$val / 1000" | bc -l)"
|
|
102
|
+
else
|
|
103
|
+
printf "%d" "$val"
|
|
104
|
+
fi
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# ---- Duration formatter (ms → Xh Xm Xs) ------------------------------------
|
|
108
|
+
fmt_dur() {
|
|
109
|
+
local ms=$1
|
|
110
|
+
local secs=$(( ms / 1000 ))
|
|
111
|
+
if [ "$secs" -ge 3600 ]; then
|
|
112
|
+
printf "%dh%dm" $(( secs / 3600 )) $(( (secs % 3600) / 60 ))
|
|
113
|
+
elif [ "$secs" -ge 60 ]; then
|
|
114
|
+
printf "%dm%ds" $(( secs / 60 )) $(( secs % 60 ))
|
|
115
|
+
else
|
|
116
|
+
printf "%ds" "$secs"
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# ---- Relative-time formatter (epoch → "now" / Xm / Xh / Xd) ----------------
|
|
121
|
+
# Callers should gate on `epoch != 0` upstream (the absence sentinel from the
|
|
122
|
+
# `// 0` jq fallback). Passing epoch=0 here yields "now" by the delta<=0 branch.
|
|
123
|
+
fmt_rel_time() {
|
|
124
|
+
local epoch=$1
|
|
125
|
+
local now
|
|
126
|
+
now=$(date +%s)
|
|
127
|
+
local delta=$(( epoch - now ))
|
|
128
|
+
if [ "$delta" -le 0 ]; then
|
|
129
|
+
printf "now"
|
|
130
|
+
elif [ "$delta" -ge 86400 ]; then
|
|
131
|
+
printf "%dd" $(( delta / 86400 ))
|
|
132
|
+
elif [ "$delta" -ge 3600 ]; then
|
|
133
|
+
printf "%dh" $(( delta / 3600 ))
|
|
134
|
+
else
|
|
135
|
+
printf "%dm" $(( delta / 60 ))
|
|
136
|
+
fi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# ============================================================
|
|
140
|
+
# LINE 1 — Identity
|
|
141
|
+
# ============================================================
|
|
142
|
+
if [ "${CBP_STATUSLINE_HIDE_IDENTITY:-0}" != "1" ]; then
|
|
143
|
+
L1=""
|
|
144
|
+
|
|
145
|
+
# Prefix: wt > session > agent (pick first present)
|
|
146
|
+
if [ -n "$WT_NAME" ]; then
|
|
147
|
+
L1="${DIM}wt:${RST}${MAGENTA}${WT_NAME}${RST} "
|
|
148
|
+
elif [ -n "$SESSION_NAME" ]; then
|
|
149
|
+
L1="${DIM}session:${RST}${MAGENTA}${SESSION_NAME}${RST} "
|
|
150
|
+
elif [ -n "$AGENT_NAME" ]; then
|
|
151
|
+
L1="${DIM}agent:${RST}${MAGENTA}${AGENT_NAME}${RST} "
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# Model (display_name + id in parens when id differs or both present)
|
|
155
|
+
if [ -n "$MODEL_NAME" ] && [ -n "$MODEL_ID" ] && [ "$MODEL_NAME" != "$MODEL_ID" ]; then
|
|
156
|
+
L1="${L1}${BOLD}${CYAN}${MODEL_NAME}${RST} ${DIM}(${MODEL_ID})${RST}"
|
|
157
|
+
elif [ -n "$MODEL_NAME" ]; then
|
|
158
|
+
L1="${L1}${BOLD}${CYAN}${MODEL_NAME}${RST}"
|
|
159
|
+
elif [ -n "$MODEL_ID" ]; then
|
|
160
|
+
L1="${L1}${BOLD}${CYAN}${MODEL_ID}${RST}"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# Effort level (when present)
|
|
164
|
+
if [ -n "$EFFORT" ]; then
|
|
165
|
+
L1="${L1} ${DIM}effort:${RST}${EFFORT}"
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# Thinking (only when explicitly true)
|
|
169
|
+
if [ "$THINKING" = "true" ]; then
|
|
170
|
+
L1="${L1} ${YELLOW}thinking:on${RST}"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Output style (when present and not "default")
|
|
174
|
+
if [ -n "$OUTPUT_STYLE" ] && [ "$OUTPUT_STYLE" != "default" ]; then
|
|
175
|
+
L1="${L1} ${DIM}style:${RST}${OUTPUT_STYLE}"
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# Version
|
|
179
|
+
if [ -n "$VERSION" ]; then
|
|
180
|
+
L1="${L1} ${DIM}v:${VERSION}${RST}"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Vim mode
|
|
184
|
+
if [ -n "$VIM_MODE" ]; then
|
|
185
|
+
L1="${L1} ${DIM}[${VIM_MODE}]${RST}"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# Session id (short 8-char prefix — distinguishes parallel sessions)
|
|
189
|
+
if [ -n "$SESSION_ID" ]; then
|
|
190
|
+
L1="${L1} ${DIM}sid:${SESSION_ID:0:8}${RST}"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Transcript file (basename only — full path would blow out the line)
|
|
194
|
+
if [ -n "$TRANSCRIPT_PATH" ]; then
|
|
195
|
+
L1="${L1} ${DIM}log:$(basename "$TRANSCRIPT_PATH")${RST}"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Only print when we have actual content
|
|
199
|
+
if [ -n "$L1" ]; then
|
|
200
|
+
printf "%b\n" "$L1"
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# ============================================================
|
|
205
|
+
# LINE 2 — Context window
|
|
206
|
+
# ============================================================
|
|
207
|
+
if [ "${CBP_STATUSLINE_HIDE_CONTEXT:-0}" != "1" ]; then
|
|
208
|
+
# Context bar colour by usage
|
|
209
|
+
if awk_gte "$CTX_PCT" 75; then
|
|
210
|
+
BAR_COLOR="$RED"
|
|
211
|
+
elif awk_gte "$CTX_PCT" 50; then
|
|
212
|
+
BAR_COLOR="$YELLOW"
|
|
213
|
+
else
|
|
214
|
+
BAR_COLOR="$GREEN"
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# 20-char progress bar (ceiling division so 99% shows 20/20, not 19/20)
|
|
218
|
+
FILLED=$(( (CTX_PCT + 4) / 5 ))
|
|
219
|
+
[ "$FILLED" -gt 20 ] && FILLED=20
|
|
220
|
+
EMPTY=$(( 20 - FILLED ))
|
|
221
|
+
BAR=""
|
|
222
|
+
for ((i=0; i<FILLED; i++)); do BAR+="▓"; done
|
|
223
|
+
for ((i=0; i<EMPTY; i++)); do BAR+="░"; done
|
|
224
|
+
|
|
225
|
+
CTX_SIZE_F=$(fmt_k "$CTX_SIZE")
|
|
226
|
+
CUR_IN_F=$(fmt_k "$CUR_IN")
|
|
227
|
+
CUR_OUT_F=$(fmt_k "$CUR_OUT")
|
|
228
|
+
CACHE_CR_F=$(fmt_k "$CACHE_CREATE")
|
|
229
|
+
CACHE_RD_F=$(fmt_k "$CACHE_READ")
|
|
230
|
+
|
|
231
|
+
L2="${BAR_COLOR}${BAR}${RST} ${BAR_COLOR}${CTX_PCT}%${RST}${DIM}/${CTX_SIZE_F}${RST}"
|
|
232
|
+
L2="${L2} ${DIM}in:${RST}${BLUE}${CUR_IN_F}${RST} ${DIM}out:${RST}${MAGENTA}${CUR_OUT_F}${RST} ${DIM}cache_cr:${RST}${CACHE_CR_F} ${DIM}cache_rd:${RST}${CACHE_RD_F}"
|
|
233
|
+
|
|
234
|
+
if [ "$EXCEEDS_200K" = "true" ]; then
|
|
235
|
+
L2="${L2} ${YELLOW}⚠ 200k+${RST}"
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
printf "%b\n" "$L2"
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# ============================================================
|
|
242
|
+
# LINE 3 — Cost
|
|
243
|
+
# ============================================================
|
|
244
|
+
if [ "${CBP_STATUSLINE_HIDE_COST:-0}" != "1" ]; then
|
|
245
|
+
COST_F=$(printf '$%.4f' "$COST")
|
|
246
|
+
DUR_F=$(fmt_dur "$DURATION")
|
|
247
|
+
API_DUR_F=$(fmt_dur "$API_DURATION")
|
|
248
|
+
|
|
249
|
+
L3="${GREEN}${COST_F}${RST} ${DIM}dur:${RST}${DUR_F} ${DIM}api:${RST}${API_DUR_F} ${GREEN}+${LINES_ADD}${RST} ${RED}-${LINES_DEL}${RST} ${DIM}lines${RST}"
|
|
250
|
+
printf "%b\n" "$L3"
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# ============================================================
|
|
254
|
+
# LINE 4 — Rate limits
|
|
255
|
+
# ============================================================
|
|
256
|
+
if [ "${CBP_STATUSLINE_HIDE_RATE_LIMITS:-0}" != "1" ]; then
|
|
257
|
+
# Emit this line only when at least one window has resets_at != 0
|
|
258
|
+
HAVE_RATE=0
|
|
259
|
+
[ -n "$RATE_5H_PCT" ] && [ "$RATE_5H_RESETS" != "0" ] && HAVE_RATE=1
|
|
260
|
+
[ -n "$RATE_7D_PCT" ] && [ "$RATE_7D_RESETS" != "0" ] && HAVE_RATE=1
|
|
261
|
+
|
|
262
|
+
if [ "$HAVE_RATE" = "1" ]; then
|
|
263
|
+
L4=""
|
|
264
|
+
|
|
265
|
+
if [ -n "$RATE_5H_PCT" ] && [ "$RATE_5H_RESETS" != "0" ]; then
|
|
266
|
+
if awk_gte "$RATE_5H_PCT" 80; then
|
|
267
|
+
C5="$RED"
|
|
268
|
+
elif awk_gte "$RATE_5H_PCT" 60; then
|
|
269
|
+
C5="$YELLOW"
|
|
270
|
+
else
|
|
271
|
+
C5="$GREEN"
|
|
272
|
+
fi
|
|
273
|
+
REL5=$(fmt_rel_time "$RATE_5H_RESETS")
|
|
274
|
+
L4="${DIM}5h:${RST}${C5}${RATE_5H_PCT}%${RST} ${DIM}(resets in ${REL5})${RST}"
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
if [ -n "$RATE_7D_PCT" ] && [ "$RATE_7D_RESETS" != "0" ]; then
|
|
278
|
+
if awk_gte "$RATE_7D_PCT" 80; then
|
|
279
|
+
C7="$RED"
|
|
280
|
+
elif awk_gte "$RATE_7D_PCT" 60; then
|
|
281
|
+
C7="$YELLOW"
|
|
282
|
+
else
|
|
283
|
+
C7="$GREEN"
|
|
284
|
+
fi
|
|
285
|
+
REL7=$(fmt_rel_time "$RATE_7D_RESETS")
|
|
286
|
+
SEG7="${DIM}7d:${RST}${C7}${RATE_7D_PCT}%${RST} ${DIM}(resets in ${REL7})${RST}"
|
|
287
|
+
if [ -n "$L4" ]; then
|
|
288
|
+
L4="${L4} ${DIM}|${RST} ${SEG7}"
|
|
289
|
+
else
|
|
290
|
+
L4="$SEG7"
|
|
291
|
+
fi
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
printf "%b\n" "$L4"
|
|
295
|
+
fi
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
# ============================================================
|
|
299
|
+
# LINE 5 — Repo / PR
|
|
300
|
+
# ============================================================
|
|
301
|
+
if [ "${CBP_STATUSLINE_HIDE_REPO_PR:-0}" != "1" ]; then
|
|
302
|
+
L5=""
|
|
303
|
+
|
|
304
|
+
# Repo segment (host/owner/name when host is present)
|
|
305
|
+
if [ -n "$WS_REPO_HOST" ]; then
|
|
306
|
+
L5="${DIM}${WS_REPO_HOST}/${WS_REPO_OWNER}/${WS_REPO_NAME}${RST}"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# PR segment
|
|
310
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
311
|
+
PR_SEG="${DIM}PR${RST} ${CYAN}#${PR_NUMBER}${RST}"
|
|
312
|
+
if [ -n "$PR_REVIEW_STATE" ]; then
|
|
313
|
+
PR_SEG="${PR_SEG} ${DIM}${PR_REVIEW_STATE}${RST}"
|
|
314
|
+
fi
|
|
315
|
+
if [ -n "$PR_URL" ]; then
|
|
316
|
+
PR_SEG="${PR_SEG} ${BLUE}${PR_URL}${RST}"
|
|
317
|
+
fi
|
|
318
|
+
if [ -n "$L5" ]; then
|
|
319
|
+
L5="${L5} ${DIM}|${RST} ${PR_SEG}"
|
|
320
|
+
else
|
|
321
|
+
L5="$PR_SEG"
|
|
322
|
+
fi
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
if [ -n "$L5" ]; then
|
|
326
|
+
printf "%b\n" "$L5"
|
|
327
|
+
fi
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
# ============================================================
|
|
331
|
+
# LINE 6 — Worktree
|
|
332
|
+
# ============================================================
|
|
333
|
+
if [ "${CBP_STATUSLINE_HIDE_WORKTREE:-0}" != "1" ]; then
|
|
334
|
+
if [ -n "$WT_NAME" ]; then
|
|
335
|
+
L6="${MAGENTA}${WT_NAME}${RST} ${DIM}@${RST} ${CYAN}${WT_BRANCH}${RST}"
|
|
336
|
+
if [ -n "$WT_ORIG_BRANCH" ] && [ "$WT_ORIG_BRANCH" != "$WT_BRANCH" ]; then
|
|
337
|
+
L6="${L6} ${DIM}(was: ${WT_ORIG_BRANCH})${RST}"
|
|
338
|
+
fi
|
|
339
|
+
# Truncate path if longer than 60 chars
|
|
340
|
+
WT_PATH_DISP="$WT_PATH"
|
|
341
|
+
if [ "${#WT_PATH}" -gt 60 ]; then
|
|
342
|
+
WT_PATH_DISP="...${WT_PATH: -57}"
|
|
343
|
+
fi
|
|
344
|
+
L6="${L6} ${DIM}${WT_PATH_DISP}${RST}"
|
|
345
|
+
printf "%b\n" "$L6"
|
|
346
|
+
fi
|
|
347
|
+
fi
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Portability: bash 3.2+ (macOS default /bin/bash). UTF-8 multibyte chars in
|
|
3
|
+
# user-controlled fields are byte-iterated under 3.2 (over-counts visible width
|
|
4
|
+
# for non-ASCII content); acceptable safe-direction approximation.
|
|
5
|
+
# @hook: NOT-A-HOOK (subagentStatusLine renderer, invoked by settings.json subagentStatusLine.command)
|
|
6
|
+
# Claude Code Subagent Status Line — one JSON line per active subagent
|
|
7
|
+
# Purpose: Renders one row per running subagent. Output protocol per upstream docs:
|
|
8
|
+
# {"id": "<task_id>", "content": "<rendered_body>"}
|
|
9
|
+
# - Omitted tasks keep their default Claude Code rendering
|
|
10
|
+
# - Empty content strings hide the row
|
|
11
|
+
# NOT a PreToolUse/PostToolUse/Notification hook — do NOT register in hooks[].
|
|
12
|
+
#
|
|
13
|
+
# ENV-VAR TOGGLES — set to 1 to suppress a column / color
|
|
14
|
+
# CBP_SUBAGENT_STATUSLINE_HIDE_DESCRIPTION=1 omit DESCRIPTION segment
|
|
15
|
+
# CBP_SUBAGENT_STATUSLINE_HIDE_TOKENS=1 omit tokens:N segment
|
|
16
|
+
# CBP_SUBAGENT_STATUSLINE_NO_COLOR=1 strip all ANSI codes (also honoured by $NO_COLOR)
|
|
17
|
+
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
|
|
20
|
+
# One jq pass for base fields (cwd, session_id, columns)
|
|
21
|
+
eval "$(echo "$INPUT" | jq -r '
|
|
22
|
+
@sh "CWD=\(.cwd // "")",
|
|
23
|
+
@sh "SESSION_ID=\(.session_id // "")",
|
|
24
|
+
@sh "COLUMNS=\(.columns // 0)"
|
|
25
|
+
')"
|
|
26
|
+
|
|
27
|
+
# ---- Colour setup ----------------------------------------------------------
|
|
28
|
+
# Only the colours referenced by the render block below are defined here.
|
|
29
|
+
if [ -n "${NO_COLOR:-}" ] || [ "${CBP_SUBAGENT_STATUSLINE_NO_COLOR:-0}" = "1" ]; then
|
|
30
|
+
RST=''; DIM=''; BOLD=''; GREEN=''; RED=''; BLUE=''
|
|
31
|
+
else
|
|
32
|
+
RST='\033[0m'
|
|
33
|
+
DIM='\033[2m'
|
|
34
|
+
BOLD='\033[1m'
|
|
35
|
+
GREEN='\033[32m'
|
|
36
|
+
RED='\033[31m'
|
|
37
|
+
BLUE='\033[34m'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# ---- Token/size formatter K / M (verbatim from cbp-statusline.sh L96-105) ---
|
|
41
|
+
fmt_k() {
|
|
42
|
+
local val=$1
|
|
43
|
+
if [ "$val" -ge 1000000 ] 2>/dev/null; then
|
|
44
|
+
printf "%.1fM" "$(echo "$val / 1000000" | bc -l)"
|
|
45
|
+
elif [ "$val" -ge 1000 ] 2>/dev/null; then
|
|
46
|
+
printf "%.1fK" "$(echo "$val / 1000" | bc -l)"
|
|
47
|
+
else
|
|
48
|
+
printf "%d" "$val"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ---- Age formatter: epoch seconds → "Xs" / "XmYs" / "XhYm" -----------------
|
|
53
|
+
# NEW helper — cannot reuse fmt_dur (which divides by 1000 for ms input).
|
|
54
|
+
# startTime is a Unix epoch in seconds, same as rate_limits.*.resets_at.
|
|
55
|
+
fmt_age_secs() {
|
|
56
|
+
local start=$1
|
|
57
|
+
local now
|
|
58
|
+
now=$(date +%s)
|
|
59
|
+
local delta=$(( now - start ))
|
|
60
|
+
[ "$delta" -lt 0 ] && delta=0
|
|
61
|
+
if [ "$delta" -ge 3600 ]; then
|
|
62
|
+
printf "%dh%dm" $(( delta / 3600 )) $(( (delta % 3600) / 60 ))
|
|
63
|
+
elif [ "$delta" -ge 60 ]; then
|
|
64
|
+
printf "%dm%ds" $(( delta / 60 )) $(( delta % 60 ))
|
|
65
|
+
else
|
|
66
|
+
printf "%ds" "$delta"
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ---- Strip ANSI codes for visible-length measurement (column truncation) ----
|
|
71
|
+
strip_ansi() {
|
|
72
|
+
printf '%s' "$1" | sed $'s/\033\\[[0-9;]*m//g'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# ---- JSON-string-escape a bash variable (embed in {"id":..., "content":...}) -
|
|
76
|
+
json_escape() {
|
|
77
|
+
printf '%s' "$1" | jq -Rs '.'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ---- Per-task render: one jq TSV emission, looped in bash ------------------
|
|
81
|
+
# Single jq pass for tasks[] (locked decision #3 — two jq calls total).
|
|
82
|
+
echo "$INPUT" | jq -r '
|
|
83
|
+
.tasks // [] | .[] | [
|
|
84
|
+
.id // "",
|
|
85
|
+
.name // "",
|
|
86
|
+
.type // "",
|
|
87
|
+
.status // "",
|
|
88
|
+
.description // "",
|
|
89
|
+
(.startTime // 0 | tostring),
|
|
90
|
+
(.tokenCount // 0 | tostring)
|
|
91
|
+
] | @tsv
|
|
92
|
+
' | while IFS=$'\t' read -r T_ID T_NAME T_TYPE T_STATUS T_DESC T_START T_TOKENS; do
|
|
93
|
+
# Skip blank rows (empty tasks[] → no output)
|
|
94
|
+
[ -z "$T_ID" ] && continue
|
|
95
|
+
|
|
96
|
+
# Status icon + colour
|
|
97
|
+
case "$T_STATUS" in
|
|
98
|
+
running) ICON="${GREEN}●${RST}" ;;
|
|
99
|
+
pending) ICON="${DIM}○${RST}" ;;
|
|
100
|
+
completed) ICON="${BLUE}✓${RST}" ;;
|
|
101
|
+
failed) ICON="${RED}✗${RST}" ;;
|
|
102
|
+
*) ICON="${DIM}•${RST}" ;;
|
|
103
|
+
esac
|
|
104
|
+
|
|
105
|
+
# Build content segments
|
|
106
|
+
ROW="${ICON} ${BOLD}${T_NAME}${RST}"
|
|
107
|
+
[ -n "$T_TYPE" ] && ROW="${ROW} ${DIM}(${T_TYPE})${RST}"
|
|
108
|
+
|
|
109
|
+
if [ "${CBP_SUBAGENT_STATUSLINE_HIDE_DESCRIPTION:-0}" != "1" ] && [ -n "$T_DESC" ]; then
|
|
110
|
+
ROW="${ROW} ${DIM}—${RST} ${T_DESC}"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
if [ "${CBP_SUBAGENT_STATUSLINE_HIDE_TOKENS:-0}" != "1" ] && [ "$T_TOKENS" -gt 0 ] 2>/dev/null; then
|
|
114
|
+
TOKENS_F=$(fmt_k "$T_TOKENS")
|
|
115
|
+
ROW="${ROW} ${DIM}·${RST} ${DIM}tokens:${RST}${TOKENS_F}"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
if [ "$T_START" -gt 0 ] 2>/dev/null; then
|
|
119
|
+
AGE_F=$(fmt_age_secs "$T_START")
|
|
120
|
+
ROW="${ROW} ${DIM}·${RST} ${DIM}age:${RST}${AGE_F}"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Apply printf to interpret escape sequences, then truncate to $COLUMNS
|
|
124
|
+
RENDERED=$(printf '%b' "$ROW")
|
|
125
|
+
if [ "$COLUMNS" -gt 0 ] 2>/dev/null; then
|
|
126
|
+
# ANSI-aware iterative truncation: walk RENDERED char-by-char treating
|
|
127
|
+
# \033[...letter escape sequences as zero-width and ordinary chars as
|
|
128
|
+
# one-width. Stop when the visible counter reaches COLUMNS - 3, then
|
|
129
|
+
# append RST + ellipsis. Correct under any ANSI-density distribution,
|
|
130
|
+
# unlike a single OVERHEAD-add formula which assumes uniform spread.
|
|
131
|
+
# Pure bash (no awk/perl subprocess); byte-based slicing means non-ASCII
|
|
132
|
+
# multi-byte UTF-8 in user-controlled descriptions counts as multiple
|
|
133
|
+
# visible units — acceptable approximation for the ASCII-dominant case.
|
|
134
|
+
TARGET=$(( COLUMNS - 3 ))
|
|
135
|
+
[ "$TARGET" -lt 0 ] && TARGET=0
|
|
136
|
+
RAW_LEN=${#RENDERED}
|
|
137
|
+
vis=0
|
|
138
|
+
i=0
|
|
139
|
+
in_esc=0
|
|
140
|
+
while [ $i -lt $RAW_LEN ] && [ $vis -lt $TARGET ]; do
|
|
141
|
+
ch="${RENDERED:$i:1}"
|
|
142
|
+
if [ "$in_esc" = "1" ]; then
|
|
143
|
+
case "$ch" in
|
|
144
|
+
[a-zA-Z]) in_esc=0 ;;
|
|
145
|
+
esac
|
|
146
|
+
elif [ "$ch" = $'\033' ]; then
|
|
147
|
+
in_esc=1
|
|
148
|
+
else
|
|
149
|
+
vis=$(( vis + 1 ))
|
|
150
|
+
fi
|
|
151
|
+
i=$(( i + 1 ))
|
|
152
|
+
done
|
|
153
|
+
# Post-loop drain: every rendered row ends with a trailing ${RST} from the
|
|
154
|
+
# last segment. Without this drain, a row whose visible content fits within
|
|
155
|
+
# COLUMNS still has `i < RAW_LEN` (pointing inside the trailing reset),
|
|
156
|
+
# which would spuriously trigger truncation. Walk past any in-progress
|
|
157
|
+
# escape and any further escape sequences until we hit a real visible byte
|
|
158
|
+
# or end-of-string.
|
|
159
|
+
while [ $i -lt $RAW_LEN ]; do
|
|
160
|
+
ch="${RENDERED:$i:1}"
|
|
161
|
+
if [ "$in_esc" = "1" ]; then
|
|
162
|
+
case "$ch" in
|
|
163
|
+
[a-zA-Z]) in_esc=0 ;;
|
|
164
|
+
esac
|
|
165
|
+
i=$(( i + 1 ))
|
|
166
|
+
elif [ "$ch" = $'\033' ]; then
|
|
167
|
+
in_esc=1
|
|
168
|
+
i=$(( i + 1 ))
|
|
169
|
+
else
|
|
170
|
+
break
|
|
171
|
+
fi
|
|
172
|
+
done
|
|
173
|
+
if [ $i -lt $RAW_LEN ]; then
|
|
174
|
+
# printf '%b' converts the literal-text RST (`\033[0m`) into an actual
|
|
175
|
+
# ESC byte sequence, matching the byte semantics of RENDERED's prefix.
|
|
176
|
+
RENDERED="${RENDERED:0:$i}$(printf '%b' "$RST")..."
|
|
177
|
+
fi
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Emit one JSON line per task (upstream protocol: JSON-per-line)
|
|
181
|
+
printf '{"id":%s,"content":%s}\n' "$(json_escape "$T_ID")" "$(json_escape "$RENDERED")"
|
|
182
|
+
done
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @hook: PreToolUse Bash
|
|
3
|
+
# Hook: PreToolUse for Bash (git commit commands)
|
|
4
|
+
# Purpose: Block git commits when new source files lack test companions
|
|
5
|
+
# Exit 0 = allow, Exit 2 = block (stderr sent to Claude)
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
echo "[HOOK START] test-coverage-gate"
|
|
10
|
+
|
|
11
|
+
# Read JSON input from stdin
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
|
|
14
|
+
# Extract command from tool input
|
|
15
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
|
16
|
+
|
|
17
|
+
if [ -z "$COMMAND" ]; then
|
|
18
|
+
echo "[HOOK END] test-coverage-gate (no command)"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Only check git commit commands
|
|
23
|
+
if ! echo "$COMMAND" | grep -qE 'git\s+commit'; then
|
|
24
|
+
echo "[HOOK END] test-coverage-gate (not git commit)"
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Get repo root
|
|
29
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
|
|
30
|
+
if [ -z "$REPO_ROOT" ]; then
|
|
31
|
+
echo "[HOOK END] test-coverage-gate (not a git repo)"
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Skip merge commits — the diff inherits files from the other parent that
|
|
36
|
+
# arrived without test companions; flagging them here is a false positive
|
|
37
|
+
# (the coverage gap belongs to whichever upstream branch introduced them).
|
|
38
|
+
# Use `git rev-parse --git-dir` to resolve the real git dir (handles worktrees,
|
|
39
|
+
# where .git is a file pointing at the main repo's worktrees/<name>/ dir).
|
|
40
|
+
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || echo "")
|
|
41
|
+
if [ -n "$GIT_DIR" ] && [ -f "${GIT_DIR}/MERGE_HEAD" ]; then
|
|
42
|
+
echo "[HOOK END] test-coverage-gate (merge in progress; skipping)"
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Get staged files (new or modified source files)
|
|
47
|
+
# Only check NEW files (Added), not modifications to existing files.
|
|
48
|
+
# Modified files that lack tests are a pre-existing gap — captured by
|
|
49
|
+
# test-coverage tooling, not blocked at commit time.
|
|
50
|
+
STAGED_SOURCE=$(git diff --cached --name-only --diff-filter=A | grep -E '\.(ts|tsx|js|jsx)$' || true)
|
|
51
|
+
|
|
52
|
+
if [ -z "$STAGED_SOURCE" ]; then
|
|
53
|
+
echo "[HOOK END] test-coverage-gate (no source files staged)"
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
MISSING_TESTS=""
|
|
58
|
+
CHECKED=0
|
|
59
|
+
SKIPPED=0
|
|
60
|
+
|
|
61
|
+
while IFS= read -r FILE; do
|
|
62
|
+
# Skip test files themselves
|
|
63
|
+
if echo "$FILE" | grep -qE '\.(test|spec)\.(ts|tsx|js|jsx)$'; then
|
|
64
|
+
continue
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Skip infrastructure / generated / config files (generic skips — harmless if user doesn't have these dirs)
|
|
68
|
+
if echo "$FILE" | grep -qE '^\.claude/|^docs/|^supabase/|\.config\.|\.json$|\.md$|\.ya?ml$|\.sh$|\.scss$|\.css$'; then
|
|
69
|
+
continue
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Skip type definition files
|
|
73
|
+
if echo "$FILE" | grep -qE '\.d\.ts$|/types\.ts$|/types/'; then
|
|
74
|
+
continue
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Skip barrel exports (index.ts with only re-exports)
|
|
78
|
+
if echo "$FILE" | grep -qE '/index\.(ts|tsx)$'; then
|
|
79
|
+
SKIPPED=$((SKIPPED + 1))
|
|
80
|
+
continue
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Skip Next.js App Router framework files — page/layout/loading/error/route/
|
|
84
|
+
# etc. are framework-convention wrappers, not logic-bearing modules. Same
|
|
85
|
+
# category as the barrel/index skip above: behavior lives in the components
|
|
86
|
+
# they delegate to, which carry their own tests.
|
|
87
|
+
if echo "$FILE" | grep -qE '/app/(.*/)?(page|layout|loading|error|global-error|not-found|template|default|route|forbidden|unauthorized)\.(tsx?|jsx?)$'; then
|
|
88
|
+
SKIPPED=$((SKIPPED + 1))
|
|
89
|
+
continue
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
CHECKED=$((CHECKED + 1))
|
|
93
|
+
|
|
94
|
+
# Derive expected test file locations
|
|
95
|
+
DIR=$(dirname "$FILE")
|
|
96
|
+
BASE=$(basename "$FILE" | sed -E 's/\.(ts|tsx|js|jsx)$//')
|
|
97
|
+
EXT=$(echo "$FILE" | grep -oE '\.(ts|tsx|js|jsx)$')
|
|
98
|
+
|
|
99
|
+
# Check multiple conventions:
|
|
100
|
+
# 1. Sibling: dir/name.test.ext
|
|
101
|
+
# 2. Co-located: dir/__tests__/name.test.ext
|
|
102
|
+
# 3. Spec variant: dir/name.spec.ext
|
|
103
|
+
# 4. Co-located spec: dir/__tests__/name.spec.ext
|
|
104
|
+
FOUND=0
|
|
105
|
+
for PATTERN in \
|
|
106
|
+
"${DIR}/${BASE}.test${EXT}" \
|
|
107
|
+
"${DIR}/__tests__/${BASE}.test${EXT}" \
|
|
108
|
+
"${DIR}/${BASE}.spec${EXT}" \
|
|
109
|
+
"${DIR}/__tests__/${BASE}.spec${EXT}" \
|
|
110
|
+
"${DIR}/${BASE}.test.ts" \
|
|
111
|
+
"${DIR}/__tests__/${BASE}.test.ts" \
|
|
112
|
+
"${DIR}/${BASE}.test.tsx" \
|
|
113
|
+
"${DIR}/__tests__/${BASE}.test.tsx"; do
|
|
114
|
+
if [ -f "${REPO_ROOT}/${PATTERN}" ]; then
|
|
115
|
+
FOUND=1
|
|
116
|
+
break
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
|
|
120
|
+
# Also check if test file is being staged in this same commit
|
|
121
|
+
if [ "$FOUND" -eq 0 ]; then
|
|
122
|
+
if echo "$STAGED_SOURCE" | grep -qE "${BASE}\.(test|spec)\.(ts|tsx|js|jsx)$"; then
|
|
123
|
+
FOUND=1
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
if [ "$FOUND" -eq 0 ]; then
|
|
128
|
+
MISSING_TESTS="${MISSING_TESTS} - ${FILE}\n"
|
|
129
|
+
fi
|
|
130
|
+
done <<< "$STAGED_SOURCE"
|
|
131
|
+
|
|
132
|
+
if [ -n "$MISSING_TESTS" ]; then
|
|
133
|
+
echo "" >&2
|
|
134
|
+
echo "TEST COVERAGE GATE: Cannot commit — source files without test companions:" >&2
|
|
135
|
+
echo -e "$MISSING_TESTS" >&2
|
|
136
|
+
echo "Write tests for these files before committing." >&2
|
|
137
|
+
echo "Checked ${CHECKED} source files, skipped ${SKIPPED} (index/barrel/framework)." >&2
|
|
138
|
+
echo "" >&2
|
|
139
|
+
echo "[HOOK END] test-coverage-gate (BLOCKED)"
|
|
140
|
+
exit 2
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
echo "[HOOK END] test-coverage-gate (${CHECKED} files checked, all have tests)"
|
|
144
|
+
exit 0
|