@trailkeep/cli 0.3.1 → 0.4.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/.claude-plugin/plugin.json +1 -1
- package/README.md +13 -9
- package/bin/trailkeep +91 -8
- package/package.json +1 -1
- package/skills/effort/SKILL.md +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trailkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Injects your private repo notes and the current branch's effort file at session start, plus the effort skill.",
|
|
5
5
|
"author": { "name": "Mario Širić" },
|
|
6
6
|
"repository": "https://github.com/msiric/trailkeep",
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Context for coding agents that works the same in **Claude Code**, **Codex CLI** and **GitHub Copilot CLI**. It keeps three kinds of knowledge apart: what's always true about you, what's always true about a repo, and what matters only for the branch you're on. It also keeps an append-only log of why things were decided.
|
|
4
4
|
|
|
5
|
-
It's a single POSIX shell script that needs only `git`. It never changes your repos' tracked files. In a repo with no notes, effort or log, it adds
|
|
5
|
+
It's a single POSIX shell script that needs only `git`. It never changes your repos' tracked files. In a repo with no notes, effort or log, it adds one short capture rule to a session (about 110 tokens), and nothing at all with `TRAILKEEP_HINT=0`.
|
|
6
6
|
|
|
7
7
|
## Install (one command, once per machine)
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ It's a single POSIX shell script that needs only `git`. It never changes your re
|
|
|
10
10
|
npm install -g @trailkeep/cli && trailkeep install
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Or without npm:
|
|
13
|
+
Or without npm, or if your npm goes through a company mirror that doesn't carry the package (a 404 from a feed proxy):
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
16
|
git clone https://github.com/msiric/trailkeep ~/.trailkeep && ~/.trailkeep/bin/trailkeep install
|
|
@@ -30,8 +30,9 @@ Later, `trailkeep update` gets the latest version the same way you installed it
|
|
|
30
30
|
|---|---|---|---|
|
|
31
31
|
| **Personal**: you, in every repo | `~/.agents/AGENTS.md` | you | every session, by all three agents |
|
|
32
32
|
| **Repo**: true for every task here | the repo's own `AGENTS.md` (committed) | you and your team | natively by all three agents |
|
|
33
|
-
| **
|
|
34
|
-
| **
|
|
33
|
+
| **Personal notes**: preferences the agent picked up | `~/.agents/NOTES.md` | the agent (`trailkeep note --all`) | at session start in every repo, by a hook |
|
|
34
|
+
| **Private repo notes**: things you can't commit | `~/.agents/repos/<repo>/NOTES.md` | you, or the agent (`trailkeep note --repo`) | at session start, by a hook |
|
|
35
|
+
| **Effort**: this branch's goal, plan, progress, decisions | `.trailkeep/<branch>.md` in the worktree, ignored by git | the agent (`trailkeep note` adds a decision, creating the file if needed) | at session start and after compaction, by a hook |
|
|
35
36
|
| **Decision log**: why things happened, append-only | `~/.agents/repos/<repo>/log/YYYY-MM.tsv` | the session-end hook | never loaded; agents search it |
|
|
36
37
|
|
|
37
38
|
`<repo>` comes from the origin URL, so every clone and worktree of a repo shares the same notes and log.
|
|
@@ -41,7 +42,7 @@ Later, `trailkeep update` gets the latest version the same way you installed it
|
|
|
41
42
|
| When | What happens automatically |
|
|
42
43
|
|---|---|
|
|
43
44
|
| Any session starts in any repo | Notes and the branch's effort file are injected. A one-line pointer mentions the decision log if it has entries. Leftovers from a session that didn't exit cleanly are logged first. |
|
|
44
|
-
|
|
|
45
|
+
| Any session starts in a repo | A short capture rule tells the agent to record decisions, user rules and dead ends the moment they come up, with `trailkeep note`. It doesn't wait for anyone to decide the task "spans sessions". Turn it off with `TRAILKEEP_HINT=0`. |
|
|
45
46
|
| The agent works | It keeps the effort file current: plan ticks, a progress line per session, decisions and dead ends. That's the effort skill's job. |
|
|
46
47
|
| Context gets compacted | The effort file is re-injected fresh. |
|
|
47
48
|
| A session ends | New Decisions and Progress lines from every effort in the worktree go into the decision log, stamped with the commit, the branch's changed files, branch and session id. It takes under 0.1 s, well inside Codex's 3-second limit for this hook. Anything a killed session missed is picked up at the next start, in the background. |
|
|
@@ -76,6 +77,7 @@ The format is one event per line, tab-separated, one file per month: `id time ty
|
|
|
76
77
|
|
|
77
78
|
```sh
|
|
78
79
|
trailkeep new # start an effort for this branch (or ask the agent to)
|
|
80
|
+
trailkeep note "..." # record a decision or dead end for this branch (--repo: whole repo, --all: every repo)
|
|
79
81
|
trailkeep show # exactly what agents get here at session start
|
|
80
82
|
trailkeep list # active efforts across all repos, with their last progress line
|
|
81
83
|
trailkeep close # archive the effort; lists decisions worth promoting
|
|
@@ -103,7 +105,7 @@ Files it edits are backed up to `~/.agents/backups/` first.
|
|
|
103
105
|
- `~/.claude/CLAUDE.md` gets a 3-line block importing `~/.agents/AGENTS.md`; your own lines stay. HTML comments are stripped by Claude, so the markers cost nothing.
|
|
104
106
|
- `~/.codex/AGENTS.md` and `~/.copilot/copilot-instructions.md` become symlinks to `~/.agents/AGENTS.md`. Existing files with content are left alone, with a warning.
|
|
105
107
|
- Hooks go in through a Claude Code plugin (installed from the clone), `~/.codex/hooks.json` and `~/.copilot/hooks/trailkeep.json`. Hook files it didn't create are never edited; it prints what to add instead.
|
|
106
|
-
- The `effort` skill is linked into `~/.agents/skills/`, which Codex and Copilot read. Claude Code gets it from the plugin
|
|
108
|
+
- The `effort` skill is linked into `~/.agents/skills/`, which Codex and Copilot read. Claude Code gets it from the plugin; its description costs about 75 tokens per session (measured, see `bench/results/stage0-injection.md`).
|
|
107
109
|
- A copy of the tool goes to `~/.agents/trailkeep/`, and `trailkeep` is linked into `~/.local/bin`.
|
|
108
110
|
|
|
109
111
|
`trailkeep uninstall` removes all of that. It leaves each agent a plain copy of your personal instructions, and keeps `~/.agents` and any `.trailkeep/` folders.
|
|
@@ -131,7 +133,7 @@ Live-session behavior can't be tested offline: hooks running, the Claude import
|
|
|
131
133
|
|
|
132
134
|
- `TRAILKEEP_HOME` (default `~/.agents`)
|
|
133
135
|
- `TRAILKEEP_MAX_CHARS` (default 9000, max 9500)
|
|
134
|
-
- `TRAILKEEP_HINT=0` (no
|
|
136
|
+
- `TRAILKEEP_HINT=0` (no capture rule at session start)
|
|
135
137
|
- `TRAILKEEP_EFFORT` (use a named effort instead of the branch)
|
|
136
138
|
|
|
137
139
|
It also honours `CLAUDE_CONFIG_DIR`, `CODEX_HOME` and `COPILOT_HOME`. It works on Linux and macOS; on Windows, use WSL.
|
|
@@ -148,6 +150,8 @@ The workflow bumps the version in all three places, runs the tests, and only the
|
|
|
148
150
|
|
|
149
151
|
From a clone, `sh scripts/release patch` (or `X.Y.Z`) does the bump, tests, commit and tag locally and pushes; the tag starts the same workflow, which checks the versions, tests again and publishes. Either way, no machine but GitHub's talks to npm.
|
|
150
152
|
|
|
151
|
-
npm authentication
|
|
153
|
+
npm authentication:
|
|
152
154
|
- **Trusted publishing (preferred):** on npmjs.com, open the package's Settings, then Trusted Publisher, then GitHub Actions. Enter owner `msiric`, repository `trailkeep`, workflow `release.yml`. No secret is needed. npm only offers this for packages that already exist.
|
|
153
|
-
- **An `NPM_TOKEN` repository secret
|
|
155
|
+
- **An `NPM_TOKEN` repository secret** was used for the very first publish only. The workflow no longer reads it; trusted publishing is set up for `@trailkeep/cli`.
|
|
156
|
+
|
|
157
|
+
If a publish fails after the release commit and tag were pushed, run the workflow again with that exact version (`-f version=X.Y.Z`); it publishes the existing tag without bumping.
|
package/bin/trailkeep
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
set -u
|
|
7
7
|
|
|
8
|
-
VERSION="0.
|
|
8
|
+
VERSION="0.4.0"
|
|
9
9
|
PKG="@trailkeep/cli"
|
|
10
10
|
MARKER="managed by trailkeep"
|
|
11
11
|
|
|
@@ -28,6 +28,7 @@ ROOT=$(cd "$(dirname "$SELF")/.." && pwd -P)
|
|
|
28
28
|
|
|
29
29
|
TRAILKEEP_HOME=${TRAILKEEP_HOME:-$HOME/.agents}
|
|
30
30
|
PERSONAL=$TRAILKEEP_HOME/AGENTS.md
|
|
31
|
+
GLOBAL_NOTES=$TRAILKEEP_HOME/NOTES.md # `note --all`: agent-written, injected everywhere
|
|
31
32
|
REPOS=$TRAILKEEP_HOME/repos
|
|
32
33
|
SKILLS_DIR=$HOME/.agents/skills # read by Codex and Copilot
|
|
33
34
|
BIN_DIR=${TRAILKEEP_BIN_DIR:-$HOME/.local/bin}
|
|
@@ -226,6 +227,12 @@ default_branch() {
|
|
|
226
227
|
return 1
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
# One standing instruction, so capture doesn't depend on the agent deciding a
|
|
231
|
+
# task "spans sessions" (it can't know). Measured cost: about 110 tokens.
|
|
232
|
+
capture_rule() {
|
|
233
|
+
printf '%s\n' 'trailkeep: when something comes up that a later session must not lose (a decision and its reason, a rule or preference the user states, an approach that failed and why), record it right away: `trailkeep note "<what and why>"`. It is kept for this branch and shown at the start of the next session. Use `trailkeep note --repo` for a fact about the whole repo, or `--all` for a personal preference that applies in every repo.'
|
|
234
|
+
}
|
|
235
|
+
|
|
229
236
|
build_context() {
|
|
230
237
|
in_repo || return 0
|
|
231
238
|
KEY=$(repo_key) || return 0
|
|
@@ -238,18 +245,28 @@ build_context() {
|
|
|
238
245
|
[ -n "$effort" ] && [ -s "$effort" ] && has_effort=1
|
|
239
246
|
|
|
240
247
|
budget=$MAX_CHARS
|
|
248
|
+
if [ -s "$GLOBAL_NOTES" ]; then
|
|
249
|
+
piece "trailkeep: your notes for every repo" "$GLOBAL_NOTES" "$((budget / 4))"
|
|
250
|
+
budget=$((budget - $(chars "$GLOBAL_NOTES")))
|
|
251
|
+
[ "$budget" -lt $((MAX_CHARS / 2)) ] && budget=$((MAX_CHARS / 2))
|
|
252
|
+
printf '\n'
|
|
253
|
+
fi
|
|
241
254
|
if [ "$has_notes" -eq 1 ]; then
|
|
242
255
|
piece "trailkeep: private notes for this repository" "$notes" "$((budget / 3))"
|
|
243
256
|
budget=$((budget - $(chars "$notes")))
|
|
244
257
|
[ "$budget" -lt $((MAX_CHARS / 2)) ] && budget=$((MAX_CHARS / 2))
|
|
245
258
|
printf '\n'
|
|
246
259
|
fi
|
|
247
|
-
if [ "$has_effort" -eq 1 ]; then
|
|
248
|
-
|
|
260
|
+
if [ "$has_effort" -eq 1 ] && ! grep -q '^## Plan' "$effort"; then
|
|
261
|
+
piece "trailkeep: notes for branch $b" "$effort" "$budget"
|
|
262
|
+
printf '\n'
|
|
263
|
+
elif [ "$has_effort" -eq 1 ]; then
|
|
264
|
+
printf 'trailkeep: this branch has an effort file. It is the source of truth for the current task. Keep it current as you work: tick Plan items and add one dated line under Progress before you stop.\n'
|
|
249
265
|
piece "trailkeep: effort file for branch $b" "$effort" "$budget"
|
|
250
266
|
printf '\n'
|
|
251
|
-
|
|
252
|
-
|
|
267
|
+
fi
|
|
268
|
+
if [ "${TRAILKEEP_HINT:-1}" != 0 ] && [ -n "$effort" ]; then
|
|
269
|
+
capture_rule
|
|
253
270
|
fi
|
|
254
271
|
n=0
|
|
255
272
|
for f in "$(log_dir "$KEY")"/*.tsv; do [ -f "$f" ] && n=$((n + $(lines "$f"))); done
|
|
@@ -391,7 +408,8 @@ harvest_file() { # FILE BRANCH SESSION TRANSCRIPT
|
|
|
391
408
|
l = $0; sub(/^[[:space:]]*[-*][[:space:]]*/, "", l); gsub(/[\t\r]/, " ", l); sub(/[[:space:]]+$/, "", l)
|
|
392
409
|
if (l == "" || l ~ /^\[[ xX]\]/ || (l in seen)) next
|
|
393
410
|
seen[l] = 1; t = sec
|
|
394
|
-
|
|
411
|
+
d = l; sub(/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]:?[[:space:]]*/, "", d)
|
|
412
|
+
if (d ~ /^([Dd]ead[ -]end|[Tt]ried |[Rr]ejected|[Rr]everted)/) t = "dead-end"
|
|
395
413
|
print t tab l
|
|
396
414
|
}' "$1")
|
|
397
415
|
[ -n "$new" ] || return 0
|
|
@@ -558,18 +576,79 @@ EOF
|
|
|
558
576
|
cmd_new() {
|
|
559
577
|
need_repo; need_branch
|
|
560
578
|
f=$(effort_path "$KEY" "$BRANCH")
|
|
561
|
-
if [ -e "$f" ]; then
|
|
579
|
+
if [ -e "$f" ] && grep -q '^## Plan' "$f"; then
|
|
562
580
|
say "Effort already exists: $f"
|
|
563
581
|
return 0
|
|
564
582
|
fi
|
|
565
583
|
kind=branch; [ -n "${TRAILKEEP_EFFORT:-}" ] && kind=named
|
|
566
584
|
ensure_effort_dir
|
|
567
|
-
|
|
585
|
+
if [ -e "$f" ]; then
|
|
586
|
+
# a notes-only file from `trailkeep note`: add the task sections, keep the notes
|
|
587
|
+
kept=$(section "$f" "Decisions and dead ends" | awk 'NF')
|
|
588
|
+
effort_template "$BRANCH" "$KEY" "$kind" > "$f" || die "could not write $f"
|
|
589
|
+
[ -n "$kept" ] && printf '%s\n' "$kept" | while IFS= read -r l; do append_to_section "$f" "Decisions and dead ends" "$l"; done
|
|
590
|
+
else
|
|
591
|
+
effort_template "$BRANCH" "$KEY" "$kind" > "$f" || die "could not write $f"
|
|
592
|
+
fi
|
|
568
593
|
remember_repo
|
|
569
594
|
say "Created $f"
|
|
570
595
|
say "Fill in Goal, Done when and Plan (or ask your agent to, with the effort skill)."
|
|
571
596
|
}
|
|
572
597
|
|
|
598
|
+
# Append LINE at the end of a "## HEADING" section (adds the section if missing).
|
|
599
|
+
append_to_section() { # FILE HEADING LINE
|
|
600
|
+
tmp=$(mktemp) || return 1
|
|
601
|
+
awk -v h="## $2" -v add="$3" '
|
|
602
|
+
function flush( i) { for (i = 1; i <= n; i++) print buf[i]; print add; print ""; on = 0; done = 1; n = 0 }
|
|
603
|
+
$0 == h { print; print ""; on = 1; n = 0; blank = 0; next }
|
|
604
|
+
on && /^## / { flush() }
|
|
605
|
+
on { if (NF) { if (n) for (i = 0; i < blank; i++) buf[++n] = ""; blank = 0; buf[++n] = $0 } else blank++; next }
|
|
606
|
+
{ print }
|
|
607
|
+
END { if (on) flush(); else if (!done) { print ""; print h; print ""; print add } }' "$1" > "$tmp" &&
|
|
608
|
+
cat "$tmp" > "$1"
|
|
609
|
+
st=$?; rm -f "$tmp"; return $st
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
cmd_note() {
|
|
613
|
+
scope=branch
|
|
614
|
+
case ${1:-} in
|
|
615
|
+
--repo) scope=repo; shift ;;
|
|
616
|
+
--all) scope=all; shift ;;
|
|
617
|
+
-h|--help|help|"") die 'usage: trailkeep note [--repo|--all] "what and why"' ;;
|
|
618
|
+
esac
|
|
619
|
+
[ $# -ge 1 ] || die 'usage: trailkeep note [--repo|--all] "what and why"'
|
|
620
|
+
text=$(printf '%s' "$*" | one_line)
|
|
621
|
+
[ -n "$text" ] || die "note: empty text"
|
|
622
|
+
case $scope in
|
|
623
|
+
branch)
|
|
624
|
+
need_repo; need_branch
|
|
625
|
+
f=$(effort_path "$KEY" "$BRANCH")
|
|
626
|
+
if [ ! -e "$f" ]; then
|
|
627
|
+
# Notes only: no empty Goal/Plan to inject; `trailkeep new` adds them.
|
|
628
|
+
kind=branch; [ -n "${TRAILKEEP_EFFORT:-}" ] && kind=named
|
|
629
|
+
ensure_effort_dir
|
|
630
|
+
printf '# %s\n\nBranch: %s · Repo: %s · Kind: %s · Created: %s\n\n## Decisions and dead ends\n\n' \
|
|
631
|
+
"$BRANCH" "$BRANCH" "$KEY" "$kind" "$(today)" > "$f" || die "could not write $f"
|
|
632
|
+
remember_repo
|
|
633
|
+
fi
|
|
634
|
+
append_to_section "$f" "Decisions and dead ends" "- $(today): $text" || die "could not write $f"
|
|
635
|
+
say "Noted for branch $BRANCH ($f)." ;;
|
|
636
|
+
repo)
|
|
637
|
+
need_repo
|
|
638
|
+
f=$(notes_path "$KEY")
|
|
639
|
+
{ mkdir -p "$(dirname "$f")" && { [ -e "$f" ] || printf '# Private notes: %s\n\n' "$KEY" > "$f"; } &&
|
|
640
|
+
printf -- '- %s: %s\n' "$(today)" "$text" >> "$f"; } 2>/dev/null ||
|
|
641
|
+
die "could not write $f (a sandbox may block it; use a branch note instead)"
|
|
642
|
+
remember_repo
|
|
643
|
+
say "Noted for this repo ($(tilde "$f"))." ;;
|
|
644
|
+
all)
|
|
645
|
+
# Not ~/.agents/AGENTS.md: that file is yours, and agents only propose changes to it.
|
|
646
|
+
{ mkdir -p "$(dirname "$GLOBAL_NOTES")" && printf -- '- %s: %s\n' "$(today)" "$text" >> "$GLOBAL_NOTES"; } 2>/dev/null ||
|
|
647
|
+
die "could not write $GLOBAL_NOTES (a sandbox may block it; ask the user to add it)"
|
|
648
|
+
say "Noted for every repo ($(tilde "$GLOBAL_NOTES"))." ;;
|
|
649
|
+
esac
|
|
650
|
+
}
|
|
651
|
+
|
|
573
652
|
cmd_path() {
|
|
574
653
|
need_repo
|
|
575
654
|
case ${1:-} in
|
|
@@ -1412,6 +1491,9 @@ Setup
|
|
|
1412
1491
|
|
|
1413
1492
|
Per repo / branch (run inside a git repo)
|
|
1414
1493
|
new create the effort file for the current branch
|
|
1494
|
+
note [--repo|--all] TEXT
|
|
1495
|
+
record a decision, rule or dead end: for this branch (default),
|
|
1496
|
+
the whole repo, or every repo
|
|
1415
1497
|
show print what agents get at session start here
|
|
1416
1498
|
edit open the effort file in \$EDITOR
|
|
1417
1499
|
path [--notes|--repo|--dir]
|
|
@@ -1439,6 +1521,7 @@ case $cmd in
|
|
|
1439
1521
|
doctor) cmd_doctor "$@" ;;
|
|
1440
1522
|
verify) cmd_verify "$@" ;;
|
|
1441
1523
|
new) cmd_new "$@" ;;
|
|
1524
|
+
note) cmd_note "$@" ;;
|
|
1442
1525
|
show) cmd_show "$@" ;;
|
|
1443
1526
|
edit) cmd_edit "$@" ;;
|
|
1444
1527
|
path) cmd_path "$@" ;;
|
package/package.json
CHANGED
package/skills/effort/SKILL.md
CHANGED
|
@@ -25,7 +25,8 @@ directly with your file tools.
|
|
|
25
25
|
- Add one line under **Progress** per session: `YYYY-MM-DD: what changed, what's next`.
|
|
26
26
|
- Record decisions with their reason under **Decisions and dead ends**, one
|
|
27
27
|
line each. Start a line with `Dead end:` for an approach that failed, and say
|
|
28
|
-
why, so no later session retries it.
|
|
28
|
+
why, so no later session retries it. `trailkeep note "<what and why>"` does
|
|
29
|
+
this in one step, and works before an effort file exists.
|
|
29
30
|
- Keep the file under about 150 lines: fold old Progress lines into one summary line.
|
|
30
31
|
|
|
31
32
|
Every new Decisions and Progress line is copied to the repo's append-only
|