@macintacos/caret 1.0.2 → 1.1.1
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/.claude-plugin/plugin.json +1 -1
- package/README.md +19 -9
- package/bin/caret +1 -1
- package/bin/caret-launcher +48 -23
- package/commands/doctor.md +140 -0
- package/commands/plan.md +25 -0
- package/dist/cli.js +2863 -1413
- package/opencode/caret.plugin.ts +6 -5
- package/opencode/commands/doctor.md +121 -0
- package/opencode/commands/plan.md +24 -0
- package/package.json +1 -1
- package/ui/dist/assets/{angular-html-CNIx69MF.js → angular-html-BECXXPyr.js} +1 -1
- package/ui/dist/assets/{angular-ts-B6ob15kD.js → angular-ts-CJKAKY9F.js} +1 -1
- package/ui/dist/assets/{apl-BzQg5wKE.js → apl-CCcfDDqZ.js} +1 -1
- package/ui/dist/assets/{astro-DQ6PiTON.js → astro-DKCnAnVR.js} +1 -1
- package/ui/dist/assets/{blade-DdGMUzbN.js → blade-DQ_JdT2N.js} +1 -1
- package/ui/dist/assets/{c-DhrAH2di.js → c-D3TwszVj.js} +1 -1
- package/ui/dist/assets/{chapel-BIJH6n90.js → chapel-C-2sPcCX.js} +1 -1
- package/ui/dist/assets/{cobol-CR-dFJ1T.js → cobol-BPmo_4f_.js} +1 -1
- package/ui/dist/assets/{coffee-DWT27RO5.js → coffee-DMTsNDVt.js} +1 -1
- package/ui/dist/assets/{cpp-CuONTEvg.js → cpp-BJr6_Y8R.js} +1 -1
- package/ui/dist/assets/{crystal-Bvc0l-OM.js → crystal-pIWESUkD.js} +1 -1
- package/ui/dist/assets/{css-BYejQHVV.js → css-DRd9Q4Li.js} +1 -1
- package/ui/dist/assets/{dist-COogZ9jg.js → dist-BFQBMwmz.js} +1 -1
- package/ui/dist/assets/{dist-BgZhLp84.js → dist-BGfsV7n2.js} +1 -1
- package/ui/dist/assets/{dist-Cuerw0_4.js → dist-BJ7SLVNC.js} +1 -1
- package/ui/dist/assets/{dist-D-lJ6tIg.js → dist-BNr0c85A.js} +1 -1
- package/ui/dist/assets/{dist-C-FIXuZc.js → dist-C6t67BjE.js} +1 -1
- package/ui/dist/assets/{dist-Bn_ExXCc.js → dist-CD25BkNS.js} +1 -1
- package/ui/dist/assets/{dist-BbAsmjOe.js → dist-CU5aMlNo.js} +1 -1
- package/ui/dist/assets/{dist-CCPLuvXB.js → dist-DoJTqy3z.js} +1 -1
- package/ui/dist/assets/{dist-RKZBj-sl.js → dist-RKKeH1zs.js} +1 -1
- package/ui/dist/assets/{dist-DV7t-p4N.js → dist-aKlRxpD_.js} +1 -1
- package/ui/dist/assets/{edge-B_-8Eolr.js → edge-DeNk4F2G.js} +1 -1
- package/ui/dist/assets/{elixir-CAhwbI74.js → elixir-DHc-DOO8.js} +1 -1
- package/ui/dist/assets/{elm-yg-VSwEl.js → elm-CJRqEcvz.js} +1 -1
- package/ui/dist/assets/{erb-CIMOpWGO.js → erb-BEWGTHDA.js} +1 -1
- package/ui/dist/assets/{git-rebase-DI22snkc.js → git-rebase-DzlcWhg9.js} +1 -1
- package/ui/dist/assets/{glimmer-js-CZHZNAs-.js → glimmer-js-B-aWCRIZ.js} +1 -1
- package/ui/dist/assets/{glimmer-ts-C2rq-JGq.js → glimmer-ts-DcjmPyQn.js} +1 -1
- package/ui/dist/assets/{glsl-ByXN6hIN.js → glsl-C9FOT-Uv.js} +1 -1
- package/ui/dist/assets/{graphql-DiiJwLqR.js → graphql-BgKSoRp0.js} +1 -1
- package/ui/dist/assets/{hack-hQSSm1D1.js → hack-BOedpUUE.js} +1 -1
- package/ui/dist/assets/{haml-cf9hcOgw.js → haml-Cpngw1vd.js} +1 -1
- package/ui/dist/assets/{handlebars-B3tg892p.js → handlebars-CpW1IyBg.js} +1 -1
- package/ui/dist/assets/{html-CkIr2qlC.js → html-CV142D8s.js} +1 -1
- package/ui/dist/assets/{html-derivative-U1EOyk5J.js → html-derivative-Df7h1Nuw.js} +1 -1
- package/ui/dist/assets/{http-ek0-hDT8.js → http-Cneddv9N.js} +1 -1
- package/ui/dist/assets/{hurl-DbN52Bpk.js → hurl-D6TS55N6.js} +1 -1
- package/ui/dist/assets/{index-uBaTjG1U.js → index-Ci9yCNtV.js} +248 -214
- package/ui/dist/assets/index-t6i4na1R.css +1 -0
- package/ui/dist/assets/{java-DXEu0FGs.js → java-BicD3zQH.js} +1 -1
- package/ui/dist/assets/{javascript-Bi95ibpN.js → javascript-l6vKHWBV.js} +1 -1
- package/ui/dist/assets/{jinja-BcKnkqjY.js → jinja-BJF2iot2.js} +1 -1
- package/ui/dist/assets/{jison-CFaHQ730.js → jison-DiXPFJAT.js} +1 -1
- package/ui/dist/assets/{json--ulVurkL.js → json-BDRuI4UV.js} +1 -1
- package/ui/dist/assets/{jsx-ZonywBBD.js → jsx-BvfdkIOy.js} +1 -1
- package/ui/dist/assets/{julia-Bnt3bGqT.js → julia-bXAhreI5.js} +1 -1
- package/ui/dist/assets/{just-CBpil8Tz.js → just-BOxCfXDg.js} +1 -1
- package/ui/dist/assets/{latex-D-qwKUqq.js → latex-B9OiTBLF.js} +1 -1
- package/ui/dist/assets/{liquid-Bu8cI_b1.js → liquid-DuLKDeWE.js} +1 -1
- package/ui/dist/assets/{lua-DO_5Mymc.js → lua-Dbh9FQm_.js} +1 -1
- package/ui/dist/assets/{marko-DJfs6dcc.js → marko--tjEBgDM.js} +1 -1
- package/ui/dist/assets/{mdc-CnkU1WJT.js → mdc-ByNJpl3m.js} +1 -1
- package/ui/dist/assets/{nginx-B5LxXEA3.js → nginx-D4wsmFK9.js} +1 -1
- package/ui/dist/assets/{nim-DLTorTuD.js → nim-Cy8RxisP.js} +1 -1
- package/ui/dist/assets/{org-DnNvsXCU.js → org-CAm7o61d.js} +1 -1
- package/ui/dist/assets/{perl-Q5Q2rA31.js → perl-BAvaFGKe.js} +1 -1
- package/ui/dist/assets/{php-S65hjGBt.js → php-DpAf3w7u.js} +1 -1
- package/ui/dist/assets/{pug-k-MB8PEo.js → pug-Dm7otyJU.js} +1 -1
- package/ui/dist/assets/{qml-Bf9cLRPv.js → qml-D4rWQQKC.js} +1 -1
- package/ui/dist/assets/{r-Cr3hDQkZ.js → r-DMKUX8qf.js} +1 -1
- package/ui/dist/assets/{razor-BO5-wpmp.js → razor-BIUJkuf_.js} +1 -1
- package/ui/dist/assets/{regexp-ffQ9gsih.js → regexp-D8W_pVHj.js} +1 -1
- package/ui/dist/assets/{rst-B4AQC5xj.js → rst-aFr0DP5H.js} +1 -1
- package/ui/dist/assets/{ruby-Dr8kfvLX.js → ruby-BzH6kUKr.js} +1 -1
- package/ui/dist/assets/{sas-Kuo2pLeL.js → sas-CDk9DyCV.js} +1 -1
- package/ui/dist/assets/{scss-Ci63BQ8y.js → scss-DABbvU5W.js} +1 -1
- package/ui/dist/assets/{shellscript-DBh4gnVx.js → shellscript-B0ObP3K-.js} +1 -1
- package/ui/dist/assets/{shellsession-L5KFvHI3.js → shellsession-2sv3Jq0j.js} +1 -1
- package/ui/dist/assets/{soy-D6F5ilyd.js → soy-irSRV4oq.js} +1 -1
- package/ui/dist/assets/{sql-xwRblxjL.js → sql-DJCHoKvP.js} +1 -1
- package/ui/dist/assets/{stata-l8sBIGPT.js → stata-a8YnzbIc.js} +1 -1
- package/ui/dist/assets/{surrealql-DL2sOSzB.js → surrealql-DdXPOg8s.js} +1 -1
- package/ui/dist/assets/{svelte-Dp5d2Ja3.js → svelte-ClAKUEZR.js} +1 -1
- package/ui/dist/assets/{templ-B3LfgeSM.js → templ-NUCvvUBA.js} +1 -1
- package/ui/dist/assets/{tex-CfLQjgEV.js → tex-DwZeSAfC.js} +1 -1
- package/ui/dist/assets/{ts-tags-BCfi6Eh_.js → ts-tags-CFJEHYY_.js} +1 -1
- package/ui/dist/assets/{tsx-BXhT7ita.js → tsx-B934yX2T.js} +1 -1
- package/ui/dist/assets/{twig-t6tdMpRg.js → twig-NFkkOj3w.js} +1 -1
- package/ui/dist/assets/{typescript-CFIlDlxn.js → typescript-7bmMC_xU.js} +1 -1
- package/ui/dist/assets/{typst-CVQ9ief2.js → typst-BQEjp9tn.js} +1 -1
- package/ui/dist/assets/{vue-D92U7PL6.js → vue-JqD9ieOF.js} +1 -1
- package/ui/dist/assets/{vue-html-B_-4d7cG.js → vue-html-CT4XQRX1.js} +1 -1
- package/ui/dist/assets/{vue-vine-LaG8znAe.js → vue-vine-DIR27Dmn.js} +1 -1
- package/ui/dist/assets/{xml-DgAE0jfV.js → xml-DhsMbw-1.js} +1 -1
- package/ui/dist/assets/{xsl-DkD8aIzS.js → xsl-BvqsVUg9.js} +1 -1
- package/ui/dist/assets/{yaml-CEG92fN8.js → yaml-C8eWjK_Q.js} +1 -1
- package/ui/dist/index.html +2 -2
- package/commands/debug.md +0 -138
- package/commands/discovery.md +0 -35
- package/opencode/commands/debug.md +0 -59
- package/opencode/commands/discovery.md +0 -31
- package/ui/dist/assets/index-CAl4rX7_.css +0 -1
package/README.md
CHANGED
|
@@ -35,6 +35,8 @@ After installing:
|
|
|
35
35
|
1. **Restart the agent.** OpenCode installs the plugin package on its next start.
|
|
36
36
|
2. **Run `/caret:demo`.** It presents a short demo plan that points at files in the repo
|
|
37
37
|
you run it from, so you can exercise the whole flow before a real one arrives.
|
|
38
|
+
3. **Run `/caret:plan <what you want done>`** to plan a real change and review it in
|
|
39
|
+
`caret` before the agent implements it.
|
|
38
40
|
|
|
39
41
|
### Running `caret` yourself
|
|
40
42
|
|
|
@@ -58,8 +60,12 @@ bunx --no-cache @macintacos/caret@latest install --refresh # update
|
|
|
58
60
|
bunx --no-cache @macintacos/caret@latest install --uninstall # remove
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
`--refresh` restarts the caret service on the newest caret installed, never older than the
|
|
64
|
+
one you ran; caret keeps a copy of it under `~/.local/state/caret/roots/`.
|
|
65
|
+
|
|
61
66
|
`caret`'s UI is designed to check for updates, at most once a day. When a newer `caret` is
|
|
62
|
-
out, the review UI says so once: a toast on load
|
|
67
|
+
out, the review UI says so once: a toast on load that opens **What's new** (the skipped
|
|
68
|
+
release notes or trunk commits), a mark on the settings button, and a
|
|
63
69
|
**Settings → Updates** pane naming the version and the exact command to take it.
|
|
64
70
|
|
|
65
71
|
Turn the check off from that same pane, or by hand in
|
|
@@ -87,10 +93,12 @@ your browser instead of the terminal prompt. There you:
|
|
|
87
93
|
and re-present.
|
|
88
94
|
|
|
89
95
|
> [!TIP]
|
|
90
|
-
> You don't have to wait to be intercepted:
|
|
91
|
-
>
|
|
92
|
-
> `
|
|
93
|
-
> same
|
|
96
|
+
> You don't have to wait to be intercepted: `/caret:plan <what you want done>` asks the
|
|
97
|
+
> agent for a plan and routes it straight to the review UI. In OpenCode it runs on the
|
|
98
|
+
> `plan` agent, so switch to `build` to implement once you approve. A skill of your own
|
|
99
|
+
> can do the same with the plan-review tool `caret` gives both agents — `review_plan` in
|
|
100
|
+
> Claude Code (from the plugin's MCP server) and `caret_review_plan` in OpenCode. The tool
|
|
101
|
+
> is for plans only; see
|
|
94
102
|
> [`doc/ARCHITECTURE.md`](doc/ARCHITECTURE.md#calling-the-review-tool-from-your-own-skill).
|
|
95
103
|
|
|
96
104
|
## Configuration
|
|
@@ -114,10 +122,12 @@ Two more live at the repo root:
|
|
|
114
122
|
|
|
115
123
|
## Diagnostics
|
|
116
124
|
|
|
117
|
-
- `/caret:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
- `/caret:doctor` runs `caret doctor`: a one-shot, read-only check of your install —
|
|
126
|
+
what's wrong, how to fix it, and the state it read that from. Always redacted, and it
|
|
127
|
+
never contains plan, prompt, or feedback bodies, nor any log message, error or stack
|
|
128
|
+
text.
|
|
129
|
+
- `caret doctor --bundle` archives the raw logs and review records when a maintainer needs
|
|
130
|
+
them. That archive is **not** redacted, so caret asks before writing it.
|
|
121
131
|
|
|
122
132
|
## License
|
|
123
133
|
|
package/bin/caret
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# caret plugin entrypoint shim (EXC-643). This is the ONE file Claude Code's
|
|
4
4
|
# hooks invoke — `${CLAUDE_PLUGIN_ROOT}/bin/caret <subcommand>` (see
|
|
5
|
-
# hooks/hooks.json and commands/
|
|
5
|
+
# hooks/hooks.json and commands/doctor.md). It is committed and shipped in
|
|
6
6
|
# every install; it resolves the actual caret runtime from whatever the install
|
|
7
7
|
# method placed beside it:
|
|
8
8
|
#
|
package/bin/caret-launcher
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
# service the launchd label WITHOUT `.plist` on macOS; the full unit name
|
|
20
20
|
# WITH `.service` on Linux. Only macOS appends a suffix below.
|
|
21
21
|
#
|
|
22
|
+
# $XDG_STATE_HOME/caret/roots/<version>/ holds caret install's own copies, scanned
|
|
23
|
+
# beside the agents' caches.
|
|
24
|
+
#
|
|
22
25
|
# Each record must end with a newline: `read -r` reports EOF on a file without
|
|
23
26
|
# one, and the read is what decides the record exists.
|
|
24
27
|
|
|
@@ -28,6 +31,8 @@ state="${XDG_STATE_HOME:-$HOME/.local/state}/caret"
|
|
|
28
31
|
records="$state/launcher"
|
|
29
32
|
claude_roots="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/plugins/cache/caret/caret"
|
|
30
33
|
opencode_roots="${XDG_CACHE_HOME:-$HOME/.cache}/opencode/packages/@macintacos"
|
|
34
|
+
# Keep in sync with ownedRootsDir() (src/config/paths.ts).
|
|
35
|
+
owned_roots="$state/roots"
|
|
31
36
|
|
|
32
37
|
# The units name no log destination, so nothing opens this before caret does:
|
|
33
38
|
# openDaemonStderr's 0700/0600 guarantee (src/daemon/lifecycle.ts) happens here for a
|
|
@@ -60,50 +65,70 @@ read_record() {
|
|
|
60
65
|
[ -f "$1" ] && read -r value <"$1" && [ -n "$value" ] && printf '%s' "$value"
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
# Highest of the `<version><TAB><
|
|
64
|
-
# rather than `sort -V`, which BSD sort does not carry everywhere.
|
|
65
|
-
# equal numeric prefix the whole
|
|
66
|
-
#
|
|
68
|
+
# Highest of the `<version><TAB><rest>` lines on stdin, printing <rest>. A numeric
|
|
69
|
+
# field sort rather than `sort -V`, which BSD sort does not carry everywhere. On an
|
|
70
|
+
# equal numeric prefix sort compares the whole line, and two things ride on that:
|
|
71
|
+
# candidates() puts its rank right after the version, so an agent's root (1) beats an
|
|
72
|
+
# owned copy (0) — keep the sort unstable (no -s); and a prerelease sorts ABOVE its
|
|
73
|
+
# release (0.15.0-rc.1 beats 0.15.0), a ceiling since caret publishes no prereleases.
|
|
74
|
+
# Keep in sync with pickLauncherRoot (src/commands/install/launcher.ts).
|
|
67
75
|
highest() { sort -t. -k1,1n -k2,2n -k3,3n | tail -1 | cut -f2-; }
|
|
68
76
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
77
|
+
# bin/caret-native embeds its UI at compile time; the bundle serves ui/dist, so a
|
|
78
|
+
# half-installed one would answer every UI request with ENOENT. A non-executable
|
|
79
|
+
# bin/caret would exec-fail at 126 and the supervisor would respawn into it forever.
|
|
80
|
+
# Keep in sync with isRunnableRoot (src/daemon/lifecycle.ts).
|
|
81
|
+
root_runnable() {
|
|
82
|
+
[ -x "$1/bin/caret" ] || return 1
|
|
83
|
+
[ -x "$1/bin/caret-native" ] || [ -f "$1/ui/dist/index.html" ]
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
# Version comes from package.json, never the directory name: OpenCode names its
|
|
73
87
|
# dir after the verbatim `plugin` specifier. The key is anchored so a minified
|
|
74
88
|
# manifest yields nothing rather than a later key's value — a miss falls into the
|
|
75
89
|
# branch that names the directory, a wrong version picks the wrong caret.
|
|
76
90
|
candidate_version() {
|
|
77
|
-
|
|
91
|
+
root_runnable "$1" || return 1
|
|
78
92
|
sed -n 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$1/package.json" 2>/dev/null | head -1
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
# Every dir either cache
|
|
82
|
-
# a broken install (terminal) and an empty cache
|
|
83
|
-
# is a two-line npm project, not a caret root; the
|
|
95
|
+
# Every dir either agent's cache or caret's own copies offer, runnable caret or
|
|
96
|
+
# not — the difference between a broken install (terminal) and an empty cache
|
|
97
|
+
# (eviction). The OpenCode entry is a two-line npm project, not a caret root; the
|
|
98
|
+
# root is one level down. `*` skips dot-prefixed dirs, so a half-copied owned root
|
|
99
|
+
# is never a candidate. Keep in sync with launcherCandidateDirs
|
|
100
|
+
# (src/commands/install/launcher.ts).
|
|
84
101
|
candidate_dirs() {
|
|
85
102
|
local dir
|
|
86
|
-
for dir in "$claude_roots"/* "$opencode_roots"/caret*/node_modules/@macintacos/caret
|
|
103
|
+
for dir in "$claude_roots"/* "$opencode_roots"/caret*/node_modules/@macintacos/caret \
|
|
104
|
+
"$owned_roots"/*; do
|
|
87
105
|
if [ -d "$dir" ]; then printf '%s\n' "$dir"; fi
|
|
88
106
|
done
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
# `<version><TAB><rank><TAB><dir>`: rank 0 for caret's own copy, 1 for an agent's.
|
|
91
110
|
candidates() {
|
|
92
|
-
local dir version
|
|
111
|
+
local dir version rank
|
|
93
112
|
candidate_dirs | while read -r dir; do
|
|
94
113
|
version="$(candidate_version "$dir" || true)"
|
|
95
|
-
|
|
114
|
+
rank=1
|
|
115
|
+
case "$dir" in "$owned_roots"/*) rank=0 ;; esac
|
|
116
|
+
if [ -n "$version" ]; then printf '%s\t%s\t%s\n' "$version" "$rank" "$dir"; fi
|
|
96
117
|
done
|
|
97
118
|
}
|
|
98
119
|
|
|
99
120
|
resolve_root() {
|
|
100
121
|
local pin
|
|
101
122
|
pin="$(read_record "$records/pinned-root" || true)"
|
|
102
|
-
if [ -n "$pin" ] &&
|
|
123
|
+
if [ -n "$pin" ] && root_runnable "$pin"; then
|
|
103
124
|
printf '%s' "$pin"
|
|
104
125
|
return 0
|
|
105
126
|
fi
|
|
106
|
-
|
|
127
|
+
# The rank sorts an agent's root above an owned copy of the same version: a hook
|
|
128
|
+
# reuses a supervised daemon only when its build matches. highest() leaves
|
|
129
|
+
# candidates()' rank in front of the dir. Keep in sync with pickLauncherRoot
|
|
130
|
+
# (src/commands/install/launcher.ts).
|
|
131
|
+
candidates | highest | cut -f2-
|
|
107
132
|
}
|
|
108
133
|
|
|
109
134
|
# The newest mise-managed bun, for a machine whose mise shims are not on the
|
|
@@ -153,8 +178,8 @@ stop_agent() {
|
|
|
153
178
|
# tears its service down and deletes itself. Returns non-zero without touching
|
|
154
179
|
# anything when there is no `service` record — nothing here installed a
|
|
155
180
|
# supervisor, so this launcher cannot know what still names it, and deleting
|
|
156
|
-
# itself would leave whatever does respawning on ENOENT. Deleting
|
|
157
|
-
#
|
|
181
|
+
# itself would leave whatever does respawning on ENOENT. Deleting bin/, the
|
|
182
|
+
# records and caret's own roots leaves review state for a reinstall.
|
|
158
183
|
evict() {
|
|
159
184
|
local service unit
|
|
160
185
|
service="$(read_record "$records/service" || true)"
|
|
@@ -166,17 +191,17 @@ evict() {
|
|
|
166
191
|
systemctl --user disable --now "$service" || true
|
|
167
192
|
unit="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/$service"
|
|
168
193
|
fi
|
|
169
|
-
printf 'caret-launcher: no caret installed anywhere; removing %s and %s.\n' \
|
|
170
|
-
"$unit" "$state
|
|
194
|
+
printf 'caret-launcher: no caret installed anywhere; removing %s and %s/{bin,launcher,roots}.\n' \
|
|
195
|
+
"$unit" "$state" >&2
|
|
171
196
|
rm -f "$unit"
|
|
172
|
-
rm -rf "${state:?}/bin" "${state:?}/launcher"
|
|
197
|
+
rm -rf "${state:?}/bin" "${state:?}/launcher" "${state:?}/roots"
|
|
173
198
|
exit 0
|
|
174
199
|
}
|
|
175
200
|
|
|
176
201
|
# Only under a supervisor: a hand-run launcher keeps its diagnostics on the terminal.
|
|
177
202
|
if [ "${CARET_SUPERVISED:-}" = 1 ]; then open_daemon_log; fi
|
|
178
203
|
|
|
179
|
-
# Only a total miss re-probes. A populated version dir with no runnable
|
|
204
|
+
# Only a total miss re-probes. A populated version dir with no runnable caret
|
|
180
205
|
# is far more likely a bad install than an extraction caught mid-flight, and that
|
|
181
206
|
# path is recoverable with `caret install`; an empty cache is the one whose branch
|
|
182
207
|
# deletes things, so it is the one worth waiting on.
|
|
@@ -190,7 +215,7 @@ done
|
|
|
190
215
|
if [ -z "$root" ]; then
|
|
191
216
|
found="$(candidate_dirs)"
|
|
192
217
|
if [ -z "$found" ]; then evict || true; fi
|
|
193
|
-
printf 'caret-launcher: no runnable bin/caret under any of:\n%s\n' \
|
|
218
|
+
printf 'caret-launcher: no runnable caret (an executable bin/caret and a UI build) under any of:\n%s\n' \
|
|
194
219
|
"${found:-(no install directories at all)}" >&2
|
|
195
220
|
printf 'caret-launcher: run: caret install — to restore the service.\n' >&2
|
|
196
221
|
stop_agent
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Diagnose this caret install — run caret doctor, relay each failing check's remedy, and present the report ready to share
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
caret's `doctor` subcommand renders a verdict over a one-shot, read-only snapshot of the
|
|
6
|
+
local install: a `checks:` block naming what is wrong and how to fix it, then the state it
|
|
7
|
+
read that from — running caret processes, daemon identity, lock/port state, effective
|
|
8
|
+
settings, review counts, the agent adapter's install-state probe (plugin version, enabled
|
|
9
|
+
state, and whether a manual hook sits in the agent's user settings), log sizes and error
|
|
10
|
+
counts, recent failures grouped by review with their codes, install/runtime info, and
|
|
11
|
+
system basics. The report is **always redacted** (home paths become `~`, usernames in
|
|
12
|
+
foreign home paths are censored) and never contains plan, prompt, or feedback bodies, nor
|
|
13
|
+
any log message, error or stack text — it exists to be shared.
|
|
14
|
+
|
|
15
|
+
## 1. Run it
|
|
16
|
+
|
|
17
|
+
First ask the user (via `AskUserQuestion`) which format they want:
|
|
18
|
+
|
|
19
|
+
- **JSON** — for feeding to tooling.
|
|
20
|
+
- **Human-readable** — for scanning in the conversation.
|
|
21
|
+
|
|
22
|
+
Then run the matching invocation:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
"${CLAUDE_PLUGIN_ROOT}/bin/caret" doctor --json # JSON
|
|
26
|
+
"${CLAUDE_PLUGIN_ROOT}/bin/caret" doctor # human-readable
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The exit code is the verdict: `0` every check passed, `1` at least one failed, `2` no
|
|
30
|
+
report — doctor could not produce one, or a `--bundle` run had no terminal to ask consent
|
|
31
|
+
at and stopped before collecting anything. On `2`, present the exit code and stderr
|
|
32
|
+
instead — and if the binary itself is missing (a source checkout that has never been
|
|
33
|
+
built), say so and point at `mise run build` (or `mise run build --install`).
|
|
34
|
+
|
|
35
|
+
## 2. Relay each failing check
|
|
36
|
+
|
|
37
|
+
A failing check already carries the remedy that closes it; relay it rather than inventing
|
|
38
|
+
one. What each id means:
|
|
39
|
+
|
|
40
|
+
| id | what failed | relay |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `daemon-reachable` | a caret service is recorded but its supervisor is not keeping it alive (not loaded, turned off, or parked) or the daemon did not answer; or the effective port answers as something other than caret | the check's remedy verbatim, then offer to read any log it names |
|
|
43
|
+
| `daemon-version` | the running daemon is older than npm's latest | the check's remedy verbatim |
|
|
44
|
+
| `daemon-lock` | the lock names a dead pid, or a port caret is not configured to bind | the check's remedy verbatim |
|
|
45
|
+
| `agent-install` | the agent the check names does not have caret enabled | `caret install` |
|
|
46
|
+
| `log-errors` | a live log wrote an NDJSON error record in the last 24h; the detail names which log and when it last erred | step 4 |
|
|
47
|
+
| `opencode-caret-version` | OpenCode would load a caret behind the published one | the check's remedy verbatim |
|
|
48
|
+
|
|
49
|
+
An `unknown` check is not a failure: it names its own `reason` — no network, an unreadable
|
|
50
|
+
config, or a report section that never collected, which also shows as `<name> error: …`
|
|
51
|
+
(`"error": "…"` under `--json`) and leaves the check with no detail. A doctor run offline
|
|
52
|
+
reaching one is the normal case; present it as-is rather than treating it as a failure of
|
|
53
|
+
this command. A daemon that is simply not running is not one of these — it reports
|
|
54
|
+
`reachable false` and *passes*, because an on-demand daemon idle-exits by design.
|
|
55
|
+
|
|
56
|
+
## 3. Present the report
|
|
57
|
+
|
|
58
|
+
End your output with the command's stdout **verbatim** in a fenced code block —
|
|
59
|
+
` ```json ` for the JSON format, plain ` ``` ` for the human-readable one — so the user
|
|
60
|
+
can copy it out in one piece. Add nothing inside the block and do not summarize away
|
|
61
|
+
sections; the report is already redacted and complete.
|
|
62
|
+
|
|
63
|
+
## 4. Debug a failing `log-errors`
|
|
64
|
+
|
|
65
|
+
When `log-errors` failed and the `/systematic-debugging` skill is available, invoke it to
|
|
66
|
+
drive root-cause investigation from the logs the check named. Otherwise read the last
|
|
67
|
+
error records yourself and reason from the failing step, code, msg, cause, and stack.
|
|
68
|
+
|
|
69
|
+
Read the report's `failures` first. Each group is one review's error records from both
|
|
70
|
+
logs with their codes (a session's, when the failure came before a review id existed), so
|
|
71
|
+
a hook-side record sits beside any daemon-side record from that review's own requests. A
|
|
72
|
+
daemon-wide failure (a crash, a bind failure, a failed update check) carries no id and
|
|
73
|
+
lands in `ungrouped`: read it alongside the group and correlate the two by `time`. Pull a
|
|
74
|
+
group's full records from both logs (`.sessionId` for a session group):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
dir="${XDG_STATE_HOME:-$HOME/.local/state}/caret"
|
|
78
|
+
grep -h '^{' "$dir/logs/caret.log" "$dir/logs/daemon.log" | jq -c 'select(.reviewId == "<id>")'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For records outside any group or the report's window, fall back to the last error records
|
|
82
|
+
of each log:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
grep '^{' "$dir/logs/caret.log" | jq -s '[.[] | select(.level >= 50)] | .[-5:]'
|
|
86
|
+
grep '^{' "$dir/logs/daemon.log" | jq -s '[.[] | select(.level >= 50)] | .[-5:]'
|
|
87
|
+
tail -n 40 "$dir/logs/daemon-stderr.log"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`log-errors` counts only NDJSON error records, so a crash that reached `daemon-stderr.log`
|
|
91
|
+
alone leaves it passing — tail that file even when the check is green. It fails only on
|
|
92
|
+
records from the last 24h, so a log that has settled passes while still reporting when it
|
|
93
|
+
last erred; work from that time rather than from the count. If a failure predates the
|
|
94
|
+
current live file, `gunzip -c` the segment you need from `logs/archive/` and rerun the
|
|
95
|
+
recipe against the result.
|
|
96
|
+
|
|
97
|
+
## 5. Review this session's plans
|
|
98
|
+
|
|
99
|
+
The report counts reviews in aggregate; it does not say what happened in *this* session.
|
|
100
|
+
When the user wants that timeline, reconstruct it from the records — identify the session
|
|
101
|
+
by the most recently updated review whose `cwd` matches, then list every review in it,
|
|
102
|
+
oldest first:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
dir="${XDG_STATE_HOME:-$HOME/.local/state}/caret"
|
|
106
|
+
sid=$(jq -rs --arg cwd "$PWD" '[.[] | select(.cwd == $cwd)] | sort_by(.updatedAt) | last | .sessionId // empty' "$dir"/reviews/*.json)
|
|
107
|
+
jq -s --arg sid "$sid" '[.[] | select(.sessionId == $sid)] | sort_by(.createdAt) | .[] | {id, title, status, versions: (.versions | length), decidedAt: .decision.decidedAt, feedback: .decision.feedback}' "$dir"/reviews/*.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
That field whitelist is deliberate: `versions[].plan` and `generalCommentDraft` hold full
|
|
111
|
+
plan and draft bodies — never select or echo them.
|
|
112
|
+
|
|
113
|
+
If `sid` comes back empty, no review matches the working directory. Say so, then fall back
|
|
114
|
+
to the most recently updated session across all reviews and re-run the listing with it:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
sid=$(jq -rs 'sort_by(.updatedAt) | last | .sessionId // empty' "$dir"/reviews/*.json)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
If `$dir/reviews` is missing or empty the glob won't match and these commands error (shell
|
|
121
|
+
or jq, depending on the shell) — that is "no reviews recorded", not a failure.
|
|
122
|
+
|
|
123
|
+
Present the result grouped by status, each review with its id, title, and version count
|
|
124
|
+
(the number of plan revisions):
|
|
125
|
+
|
|
126
|
+
- **pending** — awaiting a decision in the browser.
|
|
127
|
+
- **rejected** — changes requested; awaiting a revised plan. Include a short excerpt of
|
|
128
|
+
the decision `feedback`.
|
|
129
|
+
- **approved** — plan accepted; terminal success.
|
|
130
|
+
- **expired** — abandoned by its hook (timeout) or superseded by a resubmitted plan;
|
|
131
|
+
terminal, never reviewed.
|
|
132
|
+
|
|
133
|
+
## 6. Sharing the raw logs
|
|
134
|
+
|
|
135
|
+
If the report is not enough and the user wants to hand the raw material to a maintainer,
|
|
136
|
+
`caret doctor --bundle` writes a zip of the live logs and review records to caret's state
|
|
137
|
+
dir. Tell them plainly before suggesting it: that archive is **not** redacted — it holds
|
|
138
|
+
their logs and full plan bodies — so it is written only after they confirm, and it should
|
|
139
|
+
move over a channel they trust. caret asks at a terminal and your run has none, so carry
|
|
140
|
+
their answer with `--yes` — never before they have given it.
|
package/commands/plan.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Plan a change and route the plan through caret's review UI before implementing it
|
|
3
|
+
argument-hint: [what you want done]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Plan this, then implement it once caret approves the plan:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
If that is empty, plan whatever the user asked for earlier in this session.
|
|
11
|
+
|
|
12
|
+
Call `EnterPlanMode` first: `ExitPlanMode` is refused outside plan mode. Research what the
|
|
13
|
+
change needs — read the relevant code, trace the real flow — then write the plan to the
|
|
14
|
+
file your plan-mode instructions name. Call `ExitPlanMode` with that same text as its
|
|
15
|
+
`plan` argument — caret reviews the argument, so a call without it reaches the review UI
|
|
16
|
+
empty.
|
|
17
|
+
|
|
18
|
+
This triggers caret's `PermissionRequest`/`ExitPlanMode` hook, which opens the plan in a
|
|
19
|
+
local browser UI for inline review. When the user approves or requests changes there, the
|
|
20
|
+
decision flows back to you:
|
|
21
|
+
|
|
22
|
+
- **Approved** → implement the plan.
|
|
23
|
+
- **Changes requested** → the feedback arrives as the denial reason. Re-read the plan file
|
|
24
|
+
(caret rewrites it in its canonical shape, so the feedback's line numbers match it),
|
|
25
|
+
revise it, and call `ExitPlanMode` again with the revised text as its `plan` argument.
|