Skip to content

feat(skills): trail init look-back, hook wiring with skills on by default, and learnings without repeats - #658

Merged
shhdwi merged 69 commits into
mainfrom
split/5-init-look-back
Oct 23, 2026
Merged

shhdwi merged 69 commits into
mainfrom
split/5-init-look-back

Conversation

@shhdwi

@shhdwi shhdwi commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Parts 5 to 7 of the learned-skills stack, landing together. Parts 6 (#659) and 7 (#665) were merged into this branch so CI runs once on the whole thing against main. Their descriptions explain those parts.


Part 5 of 6 of the learned-skills stack. Base: part 4.

What it adds:

  • The look-back (init/sessions.ts, transcript.ts, look-back*.ts, miners.ts, candidates.ts, howtos.ts). It reads the person's own typed messages from past Claude Code sessions about the repo, with no model, and finds what came up on two or more days: preferences, guardrails and repeated jobs.
  • The review screen and its keys (init/review-tty.ts), keep and handover (init/keep.ts, handover.ts), and replays of a past run to build a skill from it (init/replay-runs.ts).
  • The skills part of each hook: session start, prompt, Stop, PostToolUse on Bash, and walls (claude/start-skills.ts, prompt-skills.ts, stop-skills.ts, post-bash.ts, walls-hook.ts).

Nothing calls any of it yet. trail init and the hooks switch to it in the last part.

What to look at:

  • init/look-back-mine.ts: what is read, and what is skipped (headless runs, scratch folders, old sessions).
  • init/keep.ts: nothing is kept without the person's pick.
  • The hook handlers: each fails open, so a part that throws adds nothing.

🤖 Generated with Claude Code

@trailhq-graft

trailhq-graft Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

🌱 graft blast radius

5 areas changed → 15 areas can be affected. 105 dependent symbols, depth 2.
Tests: 5 areas updated their tests.
Tag: @anirudhkumar-nanonets — 16 of 20 areas · @Frankie-Xu — Context Building, Bash Pre-Processing · Marcelo Caporale — Telemetry

flowchart TB
  A0(("Note Repository<br/>25 symbols"))
  A1(("Graph Building<br/>17 symbols"))
  A2(("Installation Records<br/>12 symbols"))
  A3(("Host Initialization<br/>9 symbols"))
  A4(("Skill Lessons<br/>6 symbols"))
  AX(("10 smaller areas<br/>36 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
Note Repository 25 src/notes/branches.ts:L1-L357 branches.ts — imports, depth 1 Initialization Discovery, writeJsonAtomic
Graph Building 17 src/graph/extract-cache.ts:L217-L227 writeExtractCache — calls, depth 1 writeJsonAtomic
Installation Records 12 src/init/install-record.ts:L50-L74 fileSha — calls, depth 1 Initialization Discovery
Host Initialization 9 src/hosts/init.ts:L53-L119 runHostsInit — calls, depth 1 Claude Integration, writeJsonAtomic
Skill Lessons 6 src/skills/value-line.ts:L203-L218 ledgerTail — calls, depth 1 Initialization Discovery, writeJsonAtomic
MCP Server 6 src/mcp/server.ts:L1-L156 server.ts — imports, depth 1 writeJsonAtomic, Telemetry Tracking
9 smaller areas 30 Telemetry, Context Building, Bash Pre-Processing, … see below
Who knows this code — 3 people across 20 areas
Area Who knows it
writeJsonAtomic · changed @anirudhkumar-nanonets — 67 commits, last today
Claude Integration · changed @anirudhkumar-nanonets — 34 commits, last today
Telemetry Tracking · changed @anirudhkumar-nanonets — 15 commits, last today
Skills Compilation · changed @anirudhkumar-nanonets — 1 commit, last 9d ago
Note Repository · affected @anirudhkumar-nanonets — 4 commits, last today
Graph Building · affected @anirudhkumar-nanonets — 30 commits, last 9d ago
Host Initialization · affected @anirudhkumar-nanonets — 6 commits, last 7d ago
MCP Server · affected @anirudhkumar-nanonets — 13 commits, last 2d ago
…12 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 105 dependent symbols, grouped by area

Note Repository — 25 symbols in 12 files

  • src/notes/branches.ts:L1-L357 — branches.ts (imports, depth 1)
    2: * Teammates' pushed branches: which files each one changes, and the learnings
  • src/notes/branches.ts:L297-L309 — maybeRefreshBranches (calls, depth 1)
    297: export function maybeRefreshBranches(repo: string, now = Date.now(), maxAgeMs = REFRESH_MS): boolean {
  • src/notes/branches.ts:L202-L241 — refreshBranches (calls, depth 1)
    203: repo: string,
  • src/notes/home.ts:L1-L523 — home.ts (imports, depth 1)
    2: * Where a repo's personal learnings and takeaways live: on this machine, under
  • src/notes/home.ts:L326-L341 — ensureRepoHome (calls, depth 1)
    326: export function ensureRepoHome(start: string, env: NodeJS.ProcessEnv = process.env): { place: RepoPlace; created: boolean } {
  • src/notes/home.ts:L292-L301 — findRepoPlace (calls, depth 1)
    292: export function findRepoPlace(start: string, env: NodeJS.ProcessEnv = process.env): RepoPlace {
  • src/notes/home.ts:L509-L522 — rememberRepo (calls, depth 1)
    509: export function rememberRepo(dir: string, env: NodeJS.ProcessEnv = process.env, now = Date.now()): void {
  • src/notes/memory-notes.ts:L161-L169 — memoryBody (calls, depth 1)
    161: export function memoryBody(note: Pick<MemoryNote, "body">): string {
  • src/notes/memory-notes.ts:L62-L64 — memoryDirs (calls, depth 1)
    62: export function memoryDirs(repo: string, home: string = homedir()): string[] {
  • src/notes/memory-notes.ts:L1-L280 — memory-notes.ts (imports, depth 1)
    2: * Learnings from the notes Claude Code keeps for this repo on this machine
  • src/notes/memory-notes.ts:L252-L279 — finishMemoryRead (calls, depth 1)
    253: repo: string,
  • src/notes/ask-notes.ts:L15-L22 — attachNotes (calls, depth 2)
    15: export function attachNotes(r: AskResult, repo: string): AskResult {
  • src/notes/ask-notes.ts:L1-L23 — ask-notes.ts (imports, depth 2)
    2: * Puts past sessions' notes next to a query's results, for every surface that
  • src/notes/credit.ts:L1-L92 — credit.ts (imports, depth 2)
    2: * How trail credits a learning in what an agent says: a token count short
  • src/notes/every-repo.ts:L1-L89 — every-repo.ts (imports, depth 2)
    2: * Every-repo personal learnings (design 1.0, P21, R6): learnings that hold
  • src/notes/find-notes.ts:L1-L62 — find-notes.ts (imports, depth 2)
    2: * Which learnings to show next to a query's results.
  • src/notes/lesson-index.ts:L1-L132 — lesson-index.ts (imports, depth 2)
    2: * D117 (4): the lesson learnings in a folder, through an index keyed by each
  • src/notes/memory-notes.ts:L190-L212 — memoryCandidates (calls, depth 2)
    190: export function memoryCandidates(repo: string, opts: { home?: string; offered?: Set<string> } = {}): MemoryCandidate[] {
  • src/notes/memory-notes.ts:L112-L127 — memoryNotes (calls, depth 2)
    112: export function memoryNotes(repo: string, home: string = homedir()): MemoryNote[] {
  • src/notes/memory-notes.ts:L220-L222 — offeredMark (calls, depth 2)
    220: function offeredMark(repo: string): string {
  • src/notes/note-gate.ts:L1-L215 — note-gate.ts (imports, depth 2)
    2: * What `trail note` checks before it saves a learning for the team: that a
  • src/notes/replace.ts:L1-L41 — replace.ts (imports, depth 2)
    2: * `trail note --replaces <id>`: which learning a new one replaces, and what
  • src/notes/targets.ts:L1-L105 — targets.ts (imports, depth 2)
    2: * Where a session's learning goes: the repos it changed, with the files it
  • src/notes/team-check.ts:L99-L111 — knownText (calls, depth 2)
    99: export function knownText(repo: string): KnownText {
  • src/notes/team-check.ts:L1-L134 — team-check.ts (imports, depth 2)
    2: * Whether something `trail init` found is fit to offer as a team learning:

Graph Building — 17 symbols in 11 files

  • src/graph/extract-cache.ts:L217-L227 — writeExtractCache (calls, depth 1)
    221: writeJsonAtomic(path, cache, true);
  • src/graph/fingerprint.ts:L87-L103 — writeFingerprint (calls, depth 1)
    92: const files: Record<string, Print> = {};
  • src/graph/workspace.ts:L564-L604 — federateGrep (calls, depth 1)
    569: ): { result: GrepResult; coverage: string } {
  • src/graph/build.ts:L1-L473 — build.ts (imports, depth 2)
    2: * `graph` — build `.context/graph.json` from a code repository.
  • src/graph/build.ts:L204-L472 — buildGraph (calls, depth 2)
    210: // Enumerate once: source extraction, scope discovery, and Go module
  • src/graph/cards.ts:L1-L322 — cards.ts (imports, depth 2)
    2: * Tier-2 "wiring cards" — the passive-channel surface of the wiring graph.
  • src/graph/check.ts:L1-L216 — check.ts (imports, depth 2)
    2: * `checkGraph` — is the committed `graph.json` still in sync with the code?
  • src/graph/extract-cache.ts:L1-L228 — extract-cache.ts (imports, depth 2)
    2: * Per-file memo for Tier-1 extraction — `<outDir>/.cache/extract.json`.
  • src/graph/fingerprint.ts:L1-L197 — fingerprint.ts (imports, depth 2)
    2: * The cheap "has the working tree moved?" probe that gates the pre-query rebuild.
  • src/graph/refresh.ts:L1-L275 — refresh.ts (imports, depth 2)
    2: * `ensureFreshGraph` — the pre-query gate that keeps retrieval honest.
  • src/graph/root.ts:L1-L46 — root.ts (imports, depth 2)
    2: * Where "here" is when a query names no directory. An agent session — or a plain
  • src/graph/seed.ts:L1-L229 — seed.ts (imports, depth 2)
    2: * `seedGraph` — give a linked git worktree the graph its parent checkout already has.
  • src/graph/traverse-cli.ts:L1-L244 — traverse-cli.ts (imports, depth 2)
    2: * CLI wiring for `graft callers` and its `--direction` / `--depth` flags.
  • src/graph/workspace-cli.ts:L104-L113 — runWorkspaceAsk (calls, depth 2)
    108: opts: { limit?: number; source?: boolean; full?: boolean; in?: string; json?: boolean },
  • src/graph/workspace-cli.ts:L1-L161 — workspace-cli.ts (imports, depth 2)
    2: * CLI print/exit wrappers + per-child build orchestration for workspace
  • src/graph/workspace-cli.ts:L115-L132 — runWorkspaceGrep (calls, depth 2)
    119: opts: { ignoreCase?: boolean; fixed?: boolean; json?: boolean },
  • src/graph/workspace.ts:L1-L726 — workspace.ts (imports, depth 2)
    2: * Workspace federation — a "workspace" is a parent directory that holds two or

Installation Records — 12 symbols in 4 files

  • src/init/install-record.ts:L50-L74 — fileSha (calls, depth 1)
    58: fd = openPlain(path);
  • src/init/keep-list.ts:L35-L70 — linesFrom (calls, depth 1)
    35: export function linesFrom(path: string, opts: { from?: number; bytes?: number }): Array<{ line: string; offset: number }> | null {
  • src/init/init-record.ts:L379-L398 — walk (calls, depth 2)
    379: const walk = (dir: string, parts: string[]) => {
  • src/init/init-record.ts:L307-L317 — readInitRecord (calls, depth 2)
    307: export function readInitRecord(repoKey: string, env?: NodeJS.ProcessEnv): InitRecord {
  • src/init/install-record.ts:L96-L99 — initLeftIt (calls, depth 2)
    96: export function initLeftIt(files: InstallFiles | null, checkout: string, rel: string): boolean {
  • src/init/install-record.ts:L129-L143 — recordInstall (calls, depth 2)
    129: export function recordInstall(snap: InstallSnap, env: NodeJS.ProcessEnv = process.env): string[] {
  • src/init/install-record.ts:L120-L126 — snapInstall (calls, depth 2)
    120: export function snapInstall(checkout: string, rels: string[], env: NodeJS.ProcessEnv = process.env): InstallSnap {
  • src/init/install-record.ts:L77-L93 — readInstallRecord (calls, depth 2)
    77: export function readInstallRecord(checkout: string, env?: NodeJS.ProcessEnv): InstallFiles | null {
  • src/init/keep-list.ts:L161-L203 — afterList (calls, depth 2)
    161: export function afterList(path: string, list: { offset: number; via: 'init-call' | 'handover' }, env: NodeJS.ProcessEnv = process.env): AfterList | null {
  • src/init/keep-list.ts:L108-L144 — findList (calls, depth 2)
    109: { list: ListAt | null; cut: number | null } | null {
  • src/init/keep-list.ts:L210-L224 — typedLine (calls, depth 2)
    210: export function typedLine(ref: { t: string; o: number; s: string }, env: NodeJS.ProcessEnv = process.env): string | null {
  • src/init/prefs.ts:L244-L277 — repoWordsOf (calls, depth 2)
    244: export function repoWordsOf(checkout: string, programs: Iterable<string> = []): Set<string> {

Host Initialization — 9 symbols in 5 files

  • src/hosts/init.ts:L53-L119 — runHostsInit (calls, depth 1)
    54: repo: string,
  • src/hosts/plan.ts:L69-L95 — planInit (calls, depth 1)
    69: export function planInit(repo: string, opts: { home?: string; ids?: string[] } = {}): HostPlan[] {
  • src/hosts/retract.ts:L370-L520 — targets (calls, depth 1)
    370: function targets(repo: string, opts: RetractOpts): Target[] {
  • src/hosts/antigravity.ts:L43-L61 — installAntigravitySkill (calls, depth 2)
    43: export function installAntigravitySkill(home: string, name: Brand = 'graft'): ConfigWrite[] {
  • src/hosts/plan.ts:L1-L101 — plan.ts (imports, depth 2)
    2: * What `graft init` *would* write, computed before anything is written.
  • src/hosts/registry.ts:L1-L186 — registry.ts (references, depth 2)
    2: * Registry of AI coding hosts Graft can write instructions for.

…44 further symbols not listed.

Test signal per changed area — 5 ✓

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.

  • ✓ writeJsonAtomic — 37 of 91 reached · 34 test files changed here: test/claude-hook-timeouts.test.ts, test/claude-hooks-fail-open-all.test.ts, test/claude-init.test.ts, test/claude-pre-bash-fail-open.test.ts, test/claude-pre-bash-wiring.test.ts, test/claude-settings-merge.test.ts, test/claude-state.test.ts, test/cli-meta.test.ts, test/cli-picker.test.ts, test/cli-status-codex.test.ts, test/graft-to-trail.test.ts, test/hosts-codex-hooks-2.test.ts, test/hosts-codex-hooks.test.ts, test/hosts-codex-trust.test.ts, test/hosts-cursor-hooks.test.ts, test/search-user-regexp.test.ts, test/trace-gate-file.test.ts, test/trace-typed.test.ts, test/trail-check-gate.test.ts, test/trail-check-learning-edits.test.ts, test/trail-check-local.test.ts, test/trail-cloud.test.ts, test/trail-cold-start.test.ts, test/trail-edit-overlap.test.ts, test/trail-init-layout.test.ts, test/trail-learning-schema.test.ts, test/trail-name.test.ts, test/trail-nudge.test.ts, test/trail-prompt-activity.test.ts, test/trail-text-safety.test.ts, test/trail-upload-names.test.ts, test/trail-upload-safety.test.ts, test/trail-words.test.ts, test/util-state-atomic.test.ts
    • not reached: warnUnsupportedExtensions, queryRoot, parseTabs, afterAsk, runCheckCommand, runNoteCommand, runInitCommand, wireTarget, …46 more
  • ✓ Initialization Discovery — 44 of 177 reached · 37 test files changed here: test/agent-trust-merge-1b.test.ts, test/claude-hooks-1b-review-trust.test.ts, test/claude-hooks-1b.test.ts, test/init-b6-review.test.ts, test/init-bounds-review.test.ts, test/init-candidates.test.ts, test/init-claude-call.test.ts, test/init-days.test.ts, test/init-disk-review.test.ts, test/init-fold-lessons.test.ts, test/init-howtos.test.ts, test/init-keep-cli-review.test.ts, test/init-keep-cli.test.ts, test/init-keep-gate-1b.test.ts, test/init-keep-shell-noise.test.ts, test/init-keep.test.ts, test/init-lab-sdk.test.ts, test/init-look-back-home.test.ts, test/init-lookback-hook-taint.test.ts, test/init-lookback.test.ts, test/init-miners.test.ts, test/init-name-call.test.ts, test/init-name-review.test.ts, test/init-prefs-near-repeat.test.ts, test/init-refusal-reasons.test.ts, test/init-relay-review.test.ts, test/init-relay-words.test.ts, test/init-replay.test.ts, test/init-run.test.ts, test/init-screen-review.test.ts, test/init-screen.test.ts, test/init-subrange-review.test.ts, test/init-team-rows.test.ts, test/init-trust-review.test.ts, test/init-uncompiled-record.test.ts, test/learnings-link-1b.test.ts, test/trail-cold-start.test.ts
    • not reached: dateOf, guardrailItems, of, preferenceItems, skillItems, v1Hash, askClaude, finish, …125 more
  • ✓ Claude Integration — 32 of 136 reached · 57 test files changed here: test/agent-trust-merge-1b.test.ts, test/claude-codex-post-bash.test.ts, test/claude-copy-dashes.test.ts, test/claude-format.test.ts, test/claude-hook-layers.test.ts, test/claude-hook-timeouts.test.ts, test/claude-hooks-1b.test.ts, test/claude-hooks.test.ts, test/claude-host-standdown.test.ts, test/claude-init.test.ts, test/claude-post-bash-walls.test.ts, test/claude-pre-bash-fail-open.test.ts, test/claude-pre-bash-skills.test.ts, test/claude-pre-bash-wiring.test.ts, test/claude-pre-bash.test.ts, test/claude-prompt-private.test.ts, test/claude-settings-merge.test.ts, test/claude-shim-resolve.test.ts, test/claude-start-skills-1b.test.ts, test/claude-stop-moments.test.ts, test/claude-stop.test.ts, test/claude-tally.test.ts, test/d117-words-lessons-1b.test.ts, test/graft-to-trail.test.ts, test/hosts-trail.test.ts, test/regex-linear-time.test.ts, test/session-metrics.test.ts, test/skills-agent-trust.test.ts, test/skills-anchors-wide-journeys.test.ts, test/skills-anchors-wide-review.test.ts, test/skills-bound-values.test.ts, test/skills-check-readonly.test.ts, test/skills-credit-1b-review.test.ts, test/skills-gated-retry.test.ts, test/skills-held-notice.test.ts, test/skills-lesson-fixtures.ts, test/skills-moments-review.test.ts, test/skills-prompt-attacks.test.ts, test/skills-prompt.test.ts, test/skills-save-gate-asked.test.ts, test/skills-save-parent-folder.test.ts, test/skills-session-start.test.ts, test/skills-ship-credit.test.ts, test/skills-words-1b.test.ts, test/start-kept-notice-race.test.ts, test/start-kept-race-child.ts, test/trace-multiturn.test.ts, test/trail-init-layout.test.ts, test/trail-init-upgrade.test.ts, test/trail-learning-on-touch.test.ts, test/trail-learnings-committed.test.ts, test/trail-note-ask.test.ts, test/trail-notes.test.ts, test/trail-prompt-notes.test.ts, test/trail-start-learnings-place.test.ts, test/trail-text-safety.test.ts, test/trail-words.test.ts
    • not reached: learnings, learningsHere, total, graftJson, readStdin, checkStaleCount, handleToolUse, learningsOnTouch, …96 more
  • ✓ Telemetry Tracking — 7 of 9 reached · 8 test files changed here: test/claude-hooks-1b.test.ts, test/claude-hooks-skills.test.ts, test/claude-post-bash-walls.test.ts, test/claude-start-skills-1b.test.ts, test/telemetry-contract.test.ts, test/telemetry-gate.test.ts, test/telemetry-skills-2.test.ts, test/trail-upload-safety.test.ts
    • not reached: trim, track
  • ✓ Skills Compilation — 31 of 141 reached · 20 test files changed here: test/claude-journeys-3.test.ts, test/cli-skills-2.test.ts, test/cli-skills-report-1b-review.test.ts, test/cli-skills.test.ts, test/init-fold-lessons.test.ts, test/learnings-link-1b-review.test.ts, test/skills-agent-trust.test.ts, test/skills-anchors-wide-journeys.test.ts, test/skills-anchors-wide.test.ts, test/skills-fixtures.test.ts, test/skills-follow-learnings.test.ts, test/skills-follow-words.test.ts, test/skills-gated-retry.test.ts, test/skills-learning-steps.test.ts, test/skills-own.test.ts, test/skills-person-codex.test.ts, test/skills-save-gate.test.ts, test/skills-settings.test.ts, test/skills-validate.test.ts, test/trail-skills.test.ts
    • not reached: binsNear, cliCall, forgetSkillLesson, initDraftCommand, learningFiles, lessonCommand, one, readLearning, …102 more
76 test suites also reference this code

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

  • test/app-review-process.test.ts
  • test/ask-fusion.test.ts
  • test/ask-index.test.ts
  • test/ask.test.ts
  • test/blast-render.test.ts
  • test/blast-viz.test.ts
  • test/claude-guard-review.test.ts
  • test/claude-shim-template.test.ts
  • test/claude-statusline.test.ts
  • test/context.test.ts
  • test/front-matter.test.ts
  • test/generic-extract.test.ts
  • test/graph-go.test.ts
  • test/graph-incremental.test.ts
  • test/graph-invariants.test.ts
  • test/graph-refresh.test.ts
  • test/graph-scopes.test.ts
  • test/graph-workspace-include-dir.test.ts
  • test/graph-write.test.ts
  • test/hosts-antigravity.test.ts
  • …56 more

⚠️ 4 changed files not in the graph (README.md, TELEMETRY.md, docs/github-app.md, test/fixtures/skills/trail/SKILL.md) — no parser claims the extension, or the index predates the file.

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

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

@shhdwi
shhdwi force-pushed the split/4-offers-gates branch from 6af78df to 8241d18 Compare October 22, 2026 03:47
@shhdwi
shhdwi force-pushed the split/5-init-look-back branch from 641bb51 to a631e01 Compare October 22, 2026 03:48
Shrish Dwivedi added 21 commits October 22, 2026 09:20
- anchors and the timeout guard split PATH on ';' and look for git.exe,
  npm.cmd and the like (PATHEXT); Windows has no exec bit to check
- a scratchpad folder is found with either slash, and Windows paths are
  compared without case (scratch folders, tamper's protected places)
- tamper's cd from an unknown folder stays unknown on Windows, where
  resolve() puts a drive before the '/\0' it starts from
- POSIX file modes are asserted only off Windows; the FIFO and /dev/zero
  cases run only where those exist
- expected paths are built with resolve()/join(), and the fold fixture
  compares checkouts resolved
- capped children import the source by file URL, and say why they failed
- os.homedir() follows USERPROFILE on Windows, so the cd test sets it too
A Windows stack frame joins its folders with '\', so the hook-errors log
kept the whole path, user name included, with the slashes stripped.
…backtracking pattern still misses by seconds
A small matcher with no model reads tool and command preferences from a
learning ("use pnpm, not npm", "never run X", "always run Y before Z",
"run X instead of editing F") and matches them against each step's program
and arguments. It is conservative: when unsure it says nothing.

- At a save or offer, steps follow the person's live learnings: a step one
  goes against is rewritten when that is safe, else left out, and the offer
  names it in one plain line.
- At recall, a learning that only repeats a step is said once, in the run
  block, and left out of the prompt's learnings lines. One that goes against
  a step wins; the block marks the step, and the update offer proposes the
  change with its usual typed yes.
- At trail init, a preference that only repeats a step of a skill kept on
  the same screen goes into that skill as a lesson.

Retired, replaced and held learnings are ignored.
command -v split PATH on the platform's delimiter but looked only for the
bare name, so on Windows every program read as not installed. It now uses
pathDirs and programFiles from guard-fast, as anchors and the timeout guard do.
- POSIX file modes are asserted only off Windows
- the FIFO cases and the socket, /tmp and ':' PATH probe test run only off Windows
- a non-executable timeout on PATH counts on Windows, which has no exec bit
- children import the source by file URL
On Windows HOME is often unset. The update gate then showed a path in
full instead of as ~/..., and a saved skill's repairs never turned the
home folder into $HOME. Both now fall back to homedir().
- the FIFO half of the value-line link test runs only off Windows
- apply_patch's relative file and tamper's hit are compared resolved
- the wall fixes' program check splits PATH on ';' and looks for git.exe,
  npm.cmd and the like (PATHEXT); Windows has no exec bit to check
- look-back finds a scratchpad folder with either slash and ignores case
  on Windows, so it no longer mines scratch sessions there
- the FIFO cases and the file name with a newline run only off Windows
- capped children import the source by file URL
- the naming calls' shebang stand-ins run only off Windows, as in
  init-claude-call
# Conflicts:
#	test/hosts-codex-trust.test.ts
… only USERPROFILE is set

As in the save gate: on Windows HOME is often unset, so fall back to homedir().
The file-open text said "it's a note from your team". It now says what the
prompt hook says: a learning from past sessions, this repo's or the user's own.
- the FIFO cases, the unwritable Trail home and the cache's file mode are
  tried only off Windows; the corrupt-file cases run everywhere
- PATHs are joined with the platform's delimiter
- the stand-down test sets USERPROFILE with HOME, which os.homedir() reads
- the naming call's shebang stand-in runs only off Windows
Shrish Dwivedi added 10 commits October 22, 2026 20:48
…p, and run again when one finds the lock busy

On a loaded Windows runner the two processes took longer than the fixed 1.5 s
to start, and the first held the ledger lock past the 200 ms the second waits,
so the second gave up and wrote nothing (by design). The lock itself works
there: the real-processes test in the same file passed with no lock file left.
Each hook now warms up, says it is ready, and starts at an instant sent after
both are up. A round where one found the lock busy proves nothing and runs
again, up to 5 times. Reading the ledger outside the lock still fails it 5/5.
…s part 4 adds and the ones that failed on Windows

timeBar(ms, floor) in machine-speed.ts: the bar where it was set, times
slowness(), never under a floor of 3 to 5 times it; exactly the bar under
TRAIL_PERF=1. The 2 MB walls probe times CPU, not the wall clock. The busy
value.json lock wait is measured over the same call with the lock free.
The 20,000-line clustering bar and the time caps' slack (the 1.5 s replay
cap, the prompt's 400 ms deadline) use timeBar. The caps themselves stay as
they are. The stand-in timeouts that are posix-only and already bounded by
what a broken cap would take are left alone.
Absolute bars use timeBar (3 to 5 times the bar as the floor). Budget tests
keep the budget and scale only the slack around it, held under whatever a
broken budget would take (the 4 s default, the stub's 3 s parse). The
pre-bash host runs kill the child 3.5 s past the scaled bar, as before.
TRAIL_PERF-only tests keep their bars.
@shhdwi
shhdwi changed the base branch from split/4-offers-gates to main October 22, 2026 19:10
github-actions Bot added a commit that referenced this pull request Oct 22, 2026
Comment thread test/init-screen.test.ts Fixed
Comment thread test/init-name-call.test.ts Fixed
Comment thread test/init-name-call.test.ts Fixed
Comment thread test/init-name-review.test.ts Fixed
Comment thread test/init-name-review.test.ts Fixed
Comment thread test/init-name-review.test.ts Fixed
feat(skills): learnings and skills never repeat or contradict each other
feat(skills): wire skills into the hooks and the CLI, on by default
@shhdwi shhdwi changed the title feat(init): trail init's look-back and review screen, and the skills hook handlers feat(skills): trail init look-back, hook wiring with skills on by default, and learnings without repeats Oct 22, 2026
github-actions Bot added a commit that referenced this pull request Oct 22, 2026
Shrish Dwivedi added 3 commits October 23, 2026 01:35
…chors

- a shell edit's file keeps its drive and backslashes, so the Stop ask names it
- trail check queues a learning's file name, not its whole Windows path
- a plain-file read refuses a link on Windows, where open has no O_NOFOLLOW
- a bin target and a node <file> word compare as dist/cli.js on every platform
- the cp line for a hand-edited skill uses forward slashes, which Git Bash and PowerShell both take
- the journey box aliases CWD's own key (local/d-home-dev-shop on Windows), not a hand-written one
- the fail-open hook commands run in Git Bash on Windows, the shell Claude Code runs hooks in
- the J16 deny tests skip where Trail still finds a timeout: Windows ships System32\timeout.exe
- the Codex sandbox tests skip where chmod refuses nothing (Windows, root)
- CRLF in TELEMETRY.md, native separators in a skills path, a Claude projects folder name
- the prompt-activity ask child from source gets the installed 60 s, not the hook's 4 s
…fuse a nested repeat

A grep takes a regex, so the pattern's syntax is kept: every literal character
goes through the metacharacter escape and only fixed syntax is written as
itself, so RegExp gets the typed pattern rebuilt from known parts (CodeQL
js/regex-injection, alert 23). A repeat of a group that repeats inside, like
(a+)+, is refused as an invalid pattern is: on one long line it backtracks for
minutes and holds the MCP server. Every other pattern compiles exactly as before.
github-actions Bot added a commit that referenced this pull request Oct 22, 2026
Comment thread test/skills-probes-init-paths-1b.test.ts Fixed
Comment thread test/trail-note-replaces.test.ts Fixed
Comment thread test/trail-upload-names.test.ts Fixed
Comment thread test/trail-upload-names.test.ts Fixed
Comment thread test/init-run.test.ts Fixed
github-actions Bot added a commit that referenced this pull request Oct 22, 2026
@shhdwi
shhdwi merged commit d205ac3 into main Oct 23, 2026
8 checks passed
github-actions Bot added a commit that referenced this pull request Oct 23, 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