Skip to content

trail: teammate install offer and init learnings picker - #611

Merged
anirudhkumar-nanonets merged 12 commits into
mainfrom
feat/offer-trail-to-teammates
Oct 15, 2026
Merged

anirudhkumar-nanonets merged 12 commits into
mainfrom
feat/offer-trail-to-teammates

Conversation

@anirudhkumar-nanonets

@anirudhkumar-nanonets anirudhkumar-nanonets commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Moves: learnings_committed, the number of clones whose committed learnings went up in the last 7 days. The PostHog query is in daily/project.md. I didn't check its value today: this session has no Metabase access. A direct signal for the init half is cold_start events with outcome = no_claude, which should drop.

All the trail CLI work for 0.22.1 in one PR. #612 and #614 were folded in here.

Teammates who clone without trail

Once a team commits .trail/, a teammate who hasn't installed trail should be offered it rather than left with silent hooks.

  • A teammate without trail sees what a learning saved them, then is offered trail. This follows the order in rushabh's 13 Oct onboarding plan. In a repo whose trail hooks are committed but where trail isn't installed, the hooks shim no longer does nothing at session start. It lists the newest 12 learnings in .trail/learnings/ with their authors and tells Claude to read the ones that bear on the task. When one shapes a reply, Claude ends it with a credit line, e.g. "📝 Used Rushabh's learning on the auth refactor: skipped the session-token approach he already ruled out." After that first credit, and only then, it adds once: "Install trail to get this on every task, plus search and checks. Want me to set it up?" Only on a yes does it run npm install -g @trailhq/trail-code and trail init --no-agents. When no learning applies, it says nothing about trail. The shim is plain Node, so this works with nothing installed. The skill says the same for agents that read the skill instead.
  • trail check flags edits to a teammate's learning. A branch that changes someone else's file in .trail/learnings/ gets a finding. The check reads that learning as it was before the branch.
  • Credit lines show tokens from 80k, and "3x faster to a PR" below that. This covers every place trail credits a learning: the closing lines after one is saved, the line when an agent starts from one, ask's savings line, and trail note's output. The cutoff is TOKEN_CREDIT_FLOOR in src/notes/notes.ts. Lines with no token count, such as the end of trail init and the daily uncommitted reminder, say 3x. Both 80k and "3x" are picks: nothing measures them yet.

trail init

trail init reads the repo's past Claude sessions by running Claude Code headless, and it only looked for claude on PATH. Someone who uses Claude Code only through the VS Code or Cursor extension has no claude on PATH, so init skipped their sessions without a word and saved no learnings. Someone on the PA team hit this on airflow-dags.

claudeBin() in src/notes/cold-start.ts now looks in this order:

  1. claude on PATH
  2. ~/.local/bin/claude, then ~/.claude/local/claude
  3. the newest anthropic.claude-code-*/resources/native-binary/claude under ~/.vscode, ~/.cursor, ~/.vscode-insiders or ~/.windsurf

The read runs whichever it finds. When there's none, init stays quiet as before.

A failed read never marked the repo as read, so anyone who hit this gets their learnings by running trail init again once this ships.

Each learning says how many sessions it came from. From rushabh's 13 Oct suggestion for init's opening ("you've corrected claude 6 times about using pnpm, not npm"):

✓ learned 2 things you told Claude more than once, from your last 12 sessions here
    for your team · told Claude in 6 sessions: Use pnpm, not npm
    just for you  · told Claude in 4 sessions: Say what you understood before building

You tick which learnings to keep. The other half of the same suggestion ("you can tick the ones to keep. claude will follow them from the first prompt"). On a terminal, once the read finishes, init shows what it found, all ticked, in the same picker as the agent choice (↑↓ move, space toggle, a all, enter confirm, esc cancel):

trail init — you told Claude these more than once. Keep the ones it should follow:

› [x] Use pnpm, not npm                 for your team · told Claude in 6 sessions
  [x] Say what you understood first     just for you · told Claude in 4 sessions

Only the ticked ones are saved, and init reports "kept 1 of the 2 things…". Esc saves none and leaves the repo unread, so the next trail init asks again. A run without a terminal, or with --yes, keeps them all as before, so an agent running init isn't blocked. cold_start already had offered_bucket and picked_bucket; picked_bucket now counts what the person kept, and outcome gains cancelled (in TELEMETRY.md).

trail init also reads PR review comments

From rushabh's 13 Oct ask for init's opening, e.g. "PR reviews keep flagging missing error handling in /api". (Was #614.)

  • Reading. One gh api graphql call fetches review summaries and inline comments on the repo's last 50 merged PRs. It runs only when the repo has a GitHub remote and gh auth token succeeds; otherwise it's skipped quietly, the same way init stays quiet when it can't find claude.
  • Cleaning. Bot comments, the PR author's own replies, quoted lines and HTML comments are dropped. scrub() runs on every comment before it reaches Claude, and no logins are sent.
  • Asking Claude. A second headless Claude call runs alongside the session read. A finding is kept only when its quotes really appear on 2 or more PRs. The quote matcher is shared with verify() as placeQuotes.
  • Picking. One picker lists session and review findings together. Review rows say "flagged in review on N PRs". Cancelling saves none and leaves both reads unmarked.
  • Saving. Kept findings become team learnings in .trail/learnings/, ending with "Flagged in review on N PRs here (fix: unique node ids, UTF-16 read coherence, clearer ambiguous-callers messaging #41, Added Dart & Kotlin support #38), most recently on Oct 10."
  • Once only. It runs once per repo per machine, and is skipped when .trail/learnings/ already holds review learnings from a teammate, so the same reviews don't create duplicates. It also runs for repos that move from graft to trail at session start.
  • Telemetry. cold_start gains source (sessions/reviews) and reviews_bucket, plus the outcomes no_reviews and no_gh. TELEMETRY.md and the README's "What stays on your machine" section are updated.
  • A real run on assign, with nothing saved: 50 merged PRs, 13 with reviewer comments, 1 finding verified across 2 PRs, in 14 s for $0.04.

Tests

  • New: trail-check-learning-edits, the offer cases in claude-shim-resolve, and the 80k cutoff in trail-cold-start and trail-prompt-notes.
  • Updated: note, prompt-notes and learnings-committed tests for the wording.
  • New claudeBin cases in trail-cold-start: it uses claude on PATH, picks the newest extension copy across VS Code and Cursor (2.1.296 over 2.1.289 over 2.1.99), prefers a standalone install, and skips an extension folder without the binary.
  • trail init end to end: the list shows told Claude in 2 sessions: before each title.
  • Picking: the picker starts all ticked and keeps the ticked ones in order; finishPastRead saves only what was kept and marks the repo read; cancelling saves nothing and leaves it unread.
  • By hand: the VS Code extension's copy on my Mac answers --version (2.1.289).
  • New trail-pr-reviews: comment filtering and scrubbing, keeping only findings on 2+ PRs, end-to-end trail init with a fake gh and claude, gh missing or signed out, no GitHub remote, and keeping or cancelling review findings.
  • Full suite: 1,423 pass, 0 fail, 1 skipped.

🤖 Generated with Claude Code

@trailhq-graft

trailhq-graft Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

🌱 graft blast radius

5 areas changed → 8 areas can be affected. 24 dependent symbols, depth 2.
Tests: 4 areas updated their tests.
Tag: @shhdwi — 9 of 13 areas · Buseong Kim — Ask Formatting · @afeddersen — Telemetry Tracking

flowchart TB
  A0(("Claude Session State<br/>9 symbols"))
  A1(("Host Integration<br/>6 symbols"))
  A2(("MCP Tool Invocation<br/>3 symbols"))
  A3(("Telemetry Tracking<br/>2 symbols"))
  A4(("Ask Formatting<br/>1 symbol"))
  AX(("3 smaller areas<br/>3 symbols"))
  classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
  class A0,A1,A2,A3,A4 reached;
  classDef tail fill:#EEF2F3,stroke:#9AA4A9,stroke-width:1px,color:#3A4247;
  class AX tail;
Loading
Can be affected Symbols Nearest hop Reached from
Claude Session State 9 src/claude/hooks.ts:L574-L661 main — calls, depth 1 Prompt Formatting, Notes Processing +1
Host Integration 6 src/hosts/claude-global.ts:L93-L134 installClaudeGlobal — calls, depth 1 Prompt Formatting
MCP Tool Invocation 3 src/mcp/tools.ts:L248-L330 callSingleTool — calls, depth 2 Notes Processing, Telemetry Contract
Telemetry Tracking 2 src/telemetry/sessions.ts:L1-L113 sessions.ts — imports, depth 1 Telemetry Contract
Ask Formatting 1 src/ask/ask.ts:L1476-L1531 formatAsk — calls, depth 1 Notes Processing
Cloud Nudge 1 src/cloud/nudge.ts:L1-L330 nudge.ts — imports, depth 1 Telemetry Contract
2 smaller areas 2 Wiring Rewrite, Workspace Ask see below
Who knows this code — 4 people across 13 areas
Area Who knows it
Prompt Formatting · changed @shhdwi — 15 commits, last yesterday
learningsAtBase · changed @shhdwi — 25 commits, last yesterday
Claude Session State · affected @shhdwi — 20 commits, last yesterday
Host Integration · affected @shhdwi — 11 commits, last yesterday
MCP Tool Invocation · affected @shhdwi — 15 commits, last yesterday
Telemetry Tracking · affected @afeddersen — 1 commit, last 2mo ago
Ask Formatting · affected @shhdwi — 16 commits, last 3mo ago · Buseong Kim — 3 commits, last 2mo ago
Cloud Nudge · affected @shhdwi — 1 commit, last yesterday
…5 further areas

Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no @ has no GitHub handle in its commit email — tag them by hand, or add a .mailmap entry. A suggestion from history, not a CODEOWNERS rule.

All 24 dependent symbols, grouped by area

Claude Session State — 9 symbols in 7 files

  • src/claude/hooks.ts:L574-L661 — main (calls, depth 1)
    586: // background:false — the registry check stays off the network here; the CLI
  • src/claude/hooks.ts:L529-L547 — uncommittedDirective (calls, depth 1)
    532: if (!place.git) return '';
  • src/claude/hooks.ts:L1-L662 — hooks.ts (imports, depth 1)
    6: import { formatBlastRadius, relevantRetrieval, formatOrientation, notesForPrompt, overlapsForPrompt, standingDirective } from './format.js';
  • src/claude/init.ts:L80-L136 — runInit (calls, depth 1)
    101: writeFileSync(hk, hooksShim()); chmodSync(hk, 0o755);
  • src/claude/session-metrics.ts:L1-L215 — session-metrics.ts (imports, depth 1)
    74: return GRAFT_MCP_TOOL_NAMES.has(bare);
  • src/claude/state.ts:L1-L112 — state.ts (imports, depth 1)
    35: * a hit whose pointer was shown once is never re-injected). Optional so
  • src/claude/stats.ts:L1-L27 — stats.ts (imports, depth 2)
  • src/claude/statusline.ts:L1-L39 — statusline.ts (imports, depth 2)
    2: import { renderStatusline, renderSubagent } from './format.js';
  • src/claude/sync-run.ts:L1-L42 — sync-run.ts (imports, depth 2)

Host Integration — 6 symbols in 6 files

  • src/hosts/claude-global.ts:L93-L134 — installClaudeGlobal (calls, depth 1)
    94: const [shim, settings, mcp] = claudeGlobalTargets(home, brand);
  • src/hosts/codex-hooks.ts:L60-L89 — installCodexHooks (calls, depth 1)
    65: const shimWrite = writeOwned('codex-hook-shim', shimPath, hooksShim(), 0o755);
  • src/hosts/cursor-hooks.ts:L86-L114 — installCursorHooks (calls, depth 1)
    88: const shimWrite = writeOwned('cursor-hook-shim', shimPath, hooksShim(), 0o755);
  • src/hosts/antigravity.ts:L43-L61 — installAntigravitySkill (calls, depth 2)
  • src/hosts/init.ts:L53-L119 — runHostsInit (calls, depth 2)
    76: unknown = opts.agents.filter((id) => !byId.has(id));
  • src/hosts/registry.ts:L1-L186 — registry.ts (references, depth 2)
    147: /** Cursor's hooks shim is repo-local, so it carries the repo's name too. */

MCP Tool Invocation — 3 symbols in 1 file

  • src/mcp/tools.ts:L248-L330 — callSingleTool (calls, depth 2)
    272: const r = engine.check(root);
  • src/mcp/tools.ts:L154-L200 — callWorkspaceTool (calls, depth 2)
    172: const { text, found } = federateCallers(root, dirOverride, symbol, {
  • src/mcp/tools.ts:L1-L331 — tools.ts (imports, depth 2)
    7: import { formatCheckReport } from '../context/check.js';

Telemetry Tracking — 2 symbols in 2 files

  • src/telemetry/sessions.ts:L1-L113 — sessions.ts (imports, depth 1)
    13: * because Claude Code has no reliable end-of-session hook — `Stop` fires per
  • src/telemetry/track.ts:L1-L161 — track.ts (imports, depth 1)
    39: /** Repo root, when the call site has one — supplies `repo_id`. */

Ask Formatting — 1 symbol in 1 file

  • src/ask/ask.ts:L1476-L1531 — formatAsk (calls, depth 1)
    1479: // above every hit — so a loud structural-fallthrough note (or the

Cloud Nudge — 1 symbol in 1 file

  • src/cloud/nudge.ts:L1-L330 — nudge.ts (imports, depth 1)
    4: * Nobody is asked to type `trail login`. Once a person has a learning worth

Wiring Rewrite — 1 symbol in 1 file

  • src/upkeep-run.ts:L46-L65 — rewriteWiring (calls, depth 2)
    52: if (place.git) ensureRepoTrail(place.checkout);

Workspace Ask — 1 symbol in 1 file

  • src/graph/workspace-cli.ts:L102-L111 — runWorkspaceAsk (calls, depth 2)
Test signal per changed area — 4 ✓ · 1 –

Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.

  • ✓ Notes Processing — 9 of 28 reached · 2 test files changed here: test/trail-cold-start.test.ts, test/trail-pr-reviews.test.ts
    • not reached: claudeOnPath, extensionVersion, hasClaude, askClaude, norm, has, findLearnings, beginPastRead, …11 more
  • – Telemetry Contract — 1 test file reaches it, none changed here
  • ✓ Prompt Formatting — 2 of 5 reached · 3 test files changed here: test/claude-shim-resolve.test.ts, test/claude-shim-template.test.ts, test/trail-prompt-notes.test.ts
    • not reached: teamNotesDirective, shim, trailSkill
  • ✓ learningsAtBase — 5 of 15 reached · 2 test files changed here: test/cli-picker.test.ts, test/trail-check-learning-edits.test.ts
    • not reached: drivePicker, runPicker, draw, finish, pickLearnings, runNoteCommand, runInitCommand, keepFrom, …2 more
  • ✓ Branch Diff — 1 of 1 reached · 1 test file changed here: test/trail-check-learning-edits.test.ts
29 test suites also reference this code

30 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.

  • test/ask-fusion.test.ts
  • test/ask.test.ts
  • test/claude-format.test.ts
  • test/claude-hook-layers.test.ts
  • test/claude-hooks.test.ts
  • test/claude-init.test.ts
  • test/claude-skill-template.test.ts
  • test/claude-state.test.ts
  • test/claude-statusline.test.ts
  • test/claude-tally.test.ts
  • test/graft-to-trail.test.ts
  • test/hosts-claude-global.test.ts
  • test/hosts-codex-hooks.test.ts
  • test/hosts-cursor-hooks.test.ts
  • test/hosts-retract.test.ts
  • test/hosts-trail.test.ts
  • test/regression-single-repo.test.ts
  • test/session-metrics.test.ts
  • test/telemetry-contract.test.ts
  • test/telemetry-gate.test.ts
  • …9 more

⚠️ 3 changed files not in the graph (.github/workflows/codeql.yml, README.md, TELEMETRY.md) — no parser claims the extension, or the index predates the file.

graft blast · origin/main...HEAD · depth 2 · 23 changed files

Open the interactive graph → — click an area to see its dependent symbols at file:line.

github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
@anirudhkumar-nanonets anirudhkumar-nanonets changed the title Offer trail to teammates who clone without it trail: teammate install offer and init learnings picker Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
Comment thread src/notes/pr-reviews.ts Fixed
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
@anirudhkumar-nanonets
anirudhkumar-nanonets merged commit 813d361 into main Oct 15, 2026
8 checks passed
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
@shhdwi shhdwi mentioned this pull request Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants