surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
| 1 | import type { |
| 2 | EngineInterface, |
| 3 | FsAncestor, |
| 4 | InstructionFile, |
| 5 | On, |
| 6 | PluginOptions, |
| 7 | } from 'claude-code' |
| 8 | |
| 9 | import Files from './files' |
| 10 | import Frames from './frames' |
| 11 | import Modes from './modes' |
| 12 | import Names from './names' |
| 13 | import Switches from './switches' |
| 14 | import Telemetry from './telemetry' |
| 15 | |
| 16 | /** |
| 17 | * No files: what a walk not taken, or one that failed, found. |
| 18 | */ |
| 19 | const NONE: readonly FsAncestor[] = [] |
| 20 | |
| 21 | /** |
| 22 | * Registers the plugin's hooks for the mode `instructionFiles` names |
| 23 | * (`claude-md-or-agents-md` when unset; the manifest lists the four, nothing |
| 24 | * else arrives). |
| 25 | * |
| 26 | * `claude-md`: nothing beyond the usage row, the engine's CLAUDE.md walk |
| 27 | * standing alone. `managed-only`: the project's and the person's instruction |
| 28 | * files dropped, the organization's kept. `claude-md-or-agents-md` (a project |
| 29 | * with none of its own) and `claude-md-and-agents-md`: AGENTS.md files joined |
| 30 | * to the engine's instruction files, nested ones on a Read except in a run |
| 31 | * where the engine attaches nothing to a turn (--bare, which sets |
| 32 | * CLAUDE_CODE_SIMPLE, or CLAUDE_CODE_DISABLE_ATTACHMENTS). Every mode sends |
| 33 | * its usage rows through `$.telemetry` where that noun is seated and drops |
| 34 | * them where it is not. |
| 35 | * |
| 36 | * @param on the engine's registrar |
| 37 | * @param options the plugin's options; `instructionFiles` is `claude-md`, |
| 38 | * `claude-md-or-agents-md`, `claude-md-and-agents-md` or `managed-only` |
| 39 | */ |
| 40 | export function register(on: On, options: PluginOptions): void { |
| 41 | const named = Modes.modeOf(options.instructionFiles) |
| 42 | // COMPAT_BREAK(agents-md-project-instructions): drop the projectInstructions |
| 43 | // mapping once stored settings have migrated. The host fills in the default, |
| 44 | // so an instructionFiles set to it cannot be told from one left unset. |
| 45 | const legacy = Modes.legacyModeOf(options.projectInstructions) |
| 46 | const isLegacyRead = legacy !== undefined && named === Modes.DEFAULT_MODE |
| 47 | const mode = isLegacyRead ? legacy : named |
| 48 | let isRenameTold = legacy === undefined |
| 49 | |
| 50 | on('session.start', ($, e, next) => { |
| 51 | Telemetry.quietly(() => |
| 52 | $.telemetry.log(Telemetry.modeRowOf(mode, e.isInteractive)), |
| 53 | ) |
| 54 | |
| 55 | if (!isRenameTold) { |
| 56 | isRenameTold = true |
| 57 | $.ui.log( |
| 58 | isLegacyRead |
| 59 | ? 'option projectInstructions in settings is honoured for now, ' + |
| 60 | `read as instructionFiles ${mode}; set instructionFiles to ` + |
| 61 | `${mode} and remove projectInstructions` |
| 62 | : `option projectInstructions in settings is not read: ` + |
| 63 | `instructionFiles ${mode} is set; remove projectInstructions`, |
| 64 | ) |
| 65 | } |
| 66 | |
| 67 | return next(e) |
| 68 | }) |
| 69 | |
| 70 | if (mode === 'claude-md') { |
| 71 | return |
| 72 | } |
| 73 | |
| 74 | if (mode === 'managed-only') { |
| 75 | on( |
| 76 | 'prompt.context', |
| 77 | { instructionFiles: { kind: Files.DROPPED_KINDS } }, |
| 78 | ($, e, next) => |
| 79 | next({ |
| 80 | ...e, |
| 81 | instructionFiles: e.instructionFiles?.filter( |
| 82 | Files.isKeptWithoutInstructions, |
| 83 | ), |
| 84 | }), |
| 85 | ) |
| 86 | |
| 87 | return |
| 88 | } |
| 89 | |
| 90 | const isFallback = mode === 'claude-md-or-agents-md' |
| 91 | const given = new Map<string, Set<string>>() |
| 92 | let inContext: readonly InstructionFile[] = [] |
| 93 | let home: string | undefined |
| 94 | let isClaudeProject: boolean | undefined |
| 95 | let rootSeen: string | undefined |
| 96 | let rootLogged: string | undefined |
| 97 | let isCounted = false |
| 98 | |
| 99 | on('prompt.context', async ($, e, next) => { |
| 100 | const handed = e.instructionFiles |
| 101 | |
| 102 | if (handed === undefined) { |
| 103 | return next(e).finally(() => { |
| 104 | given.clear() |
| 105 | }) |
| 106 | } |
| 107 | |
| 108 | const root = isFallback ? await $.session.root() : undefined |
| 109 | rootSeen = root ?? rootSeen |
| 110 | let isWalkFailed = false |
| 111 | // The walk, not only what was handed: the engine can withhold a project |
| 112 | // CLAUDE.md it loaded, and the project still has one. |
| 113 | isClaudeProject = |
| 114 | root !== undefined && |
| 115 | (handed.some(file => Files.isClaudeFileOnWalk(file, root)) || |
| 116 | (await $.fs.ancestors({ names: Names.CLAUDE_NAMES }).then( |
| 117 | files => files.length > 0, |
| 118 | () => { |
| 119 | isWalkFailed = true |
| 120 | |
| 121 | return true |
| 122 | }, |
| 123 | ))) |
| 124 | const found = isClaudeProject |
| 125 | ? NONE |
| 126 | : await $.fs.ancestors({ names: Names.AGENTS_NAMES }).catch(() => { |
| 127 | isWalkFailed = true |
| 128 | |
| 129 | return NONE |
| 130 | }) |
| 131 | const added = Files.unseenFiles(Files.filesOf(found), handed) |
| 132 | |
| 133 | if (!isCounted) { |
| 134 | isCounted = true |
| 135 | const counts = Telemetry.loadCountsOf( |
| 136 | added, |
| 137 | isClaudeProject && !isWalkFailed, |
| 138 | isWalkFailed, |
| 139 | ) |
| 140 | Telemetry.quietly(() => |
| 141 | $.telemetry.log(Telemetry.loadRowOf(mode, counts)), |
| 142 | ) |
| 143 | Telemetry.quietly(() => $.telemetry.mark(Telemetry.loadMarkOf(counts))) |
| 144 | } |
| 145 | |
| 146 | const isFirstLoad = |
| 147 | root !== undefined && root !== rootLogged && added.length > 0 |
| 148 | |
| 149 | if (isFirstLoad) { |
| 150 | rootLogged = root |
| 151 | $.ui.log( |
| 152 | 'no CLAUDE.md found; AGENTS.md loaded: ' + |
| 153 | added |
| 154 | .filter(file => file.parent === undefined) |
| 155 | .map(file => file.path) |
| 156 | .join(', '), |
| 157 | ) |
| 158 | } |
| 159 | |
| 160 | const instructionFiles = Files.withProjectFiles(handed, added) |
| 161 | |
| 162 | return next({ ...e, instructionFiles }).finally(() => { |
| 163 | given.clear() |
| 164 | inContext = instructionFiles |
| 165 | }) |
| 166 | }) |
| 167 | |
| 168 | on('agent.spawn', { fork: true }, async ($, e, next) => { |
| 169 | const result = await next(e) |
| 170 | |
| 171 | if (result.agentId !== undefined) { |
| 172 | const parent = given.get(e.parentAgentId ?? Names.MAIN_LOOP) |
| 173 | given.set(result.agentId, new Set(parent)) |
| 174 | } |
| 175 | |
| 176 | return result |
| 177 | }) |
| 178 | |
| 179 | on('tool.call', { tool: 'Read' }, async ($, e, next) => { |
| 180 | const result = await next(e) |
| 181 | const isSettledElsewhere = |
| 182 | e.tool !== 'Read' || result.deny !== undefined || result.isError |
| 183 | |
| 184 | if (isSettledElsewhere) { |
| 185 | return result |
| 186 | } |
| 187 | |
| 188 | if (!(await attachesOnRead($))) { |
| 189 | return result |
| 190 | } |
| 191 | |
| 192 | const [root, cwd] = await Promise.all([$.session.root(), $.session.cwd()]) |
| 193 | home ??= await homeOf($, cwd) |
| 194 | const read = Frames.absoluteOf(e.file_path, cwd, home) |
| 195 | |
| 196 | if (root !== rootSeen) { |
| 197 | rootSeen = root |
| 198 | isClaudeProject = undefined |
| 199 | given.clear() |
| 200 | } |
| 201 | |
| 202 | isClaudeProject ??= |
| 203 | isFallback && |
| 204 | (await $.fs.ancestors({ names: Names.CLAUDE_NAMES })).length > 0 |
| 205 | const isOutOfReach = isClaudeProject || !Frames.isBelow(read, root) |
| 206 | |
| 207 | if (isOutOfReach) { |
| 208 | return result |
| 209 | } |
| 210 | |
| 211 | const [stack, claude] = await Promise.all([ |
| 212 | $.fs.ancestors({ names: Names.AGENTS_NAMES, of: read, below: root }), |
| 213 | $.fs.ancestors({ names: Names.CLAUDE_NAMES, of: read, below: root }), |
| 214 | ]).catch((): [typeof NONE, typeof NONE] => [NONE, NONE]) |
| 215 | const loop = e.agentId ?? Names.MAIN_LOOP |
| 216 | const sent = given.get(loop) ?? new Set<string>() |
| 217 | given.set(loop, sent) |
| 218 | const nested = ( |
| 219 | isFallback ? Frames.outsideClaudeDirs(stack, claude) : stack |
| 220 | ).filter(file => Frames.isBelow(file.dir, root)) |
| 221 | const fresh = Files.unseenFiles(Files.filesOf(nested), [ |
| 222 | ...inContext, |
| 223 | ...Files.filesOf(claude), |
| 224 | ]).filter(file => !sent.has(file.path)) |
| 225 | const attached = fresh.filter(file => !Frames.isFileAt(file, read)) |
| 226 | const isWholeRead = e.offset === undefined && e.limit === undefined |
| 227 | |
| 228 | for (const file of fresh) { |
| 229 | const isSent = |
| 230 | attached.includes(file) || (Frames.isFileAt(file, read) && isWholeRead) |
| 231 | |
| 232 | if (isSent) { |
| 233 | sent.add(file.path) |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | const hasAttached = attached.length > 0 |
| 238 | |
| 239 | if (hasAttached) { |
| 240 | Telemetry.quietly(() => |
| 241 | $.telemetry.log(Telemetry.nestedRowOf(mode, attached.length)), |
| 242 | ) |
| 243 | } |
| 244 | |
| 245 | return hasAttached |
| 246 | ? { |
| 247 | ...result, |
| 248 | context: [ |
| 249 | ...(result.context ?? []), |
| 250 | ...attached.map(Frames.nestedFrame), |
| 251 | ], |
| 252 | } |
| 253 | : result |
| 254 | }) |
| 255 | } |
| 256 | |
| 257 | /** |
| 258 | * Whether a Read attaches nested AGENTS.md files in this run: not where the |
| 259 | * engine attaches nothing to a turn, a nested CLAUDE.md included, which is a |
| 260 | * --bare run (it sets CLAUDE_CODE_SIMPLE) or one with |
| 261 | * CLAUDE_CODE_DISABLE_ATTACHMENTS on. |
| 262 | * |
| 263 | * Read on every Read, as the engine reads them on every turn: a settings |
| 264 | * `env` block or a managed delivery can flip either mid-session. The files of |
| 265 | * the walk itself need no such check: where the engine loads no instruction |
| 266 | * files `$.fs.ancestors` finds none. |
| 267 | * |
| 268 | * @param $ the engine, as the `tool.call` hook holds it |
| 269 | * @returns whether nested files ride a Read's result here |
| 270 | */ |
| 271 | async function attachesOnRead($: EngineInterface): Promise<boolean> { |
| 272 | const [simple, attachmentsOff] = await Promise.all([ |
| 273 | $.env.get('CLAUDE_CODE_SIMPLE'), |
| 274 | $.env.get('CLAUDE_CODE_DISABLE_ATTACHMENTS'), |
| 275 | ]) |
| 276 | |
| 277 | return ( |
| 278 | !Switches.isSwitchedOn(simple) && !Switches.isSwitchedOn(attachmentsOff) |
| 279 | ) |
| 280 | } |
| 281 | |
| 282 | /** |
| 283 | * The home directory a `~` in a Read's path stands for, read the way the |
| 284 | * Read tool reads it. |
| 285 | * |
| 286 | * The profile directory on a Windows spelling of the working directory, |
| 287 | * else `HOME`, each falling back to the other. |
| 288 | * |
| 289 | * @param $ the engine, as the `tool.call` hook holds it |
| 290 | * @param cwd the session's working directory, whose spelling names the platform |
| 291 | * @returns the home directory, or undefined when the environment names none |
| 292 | */ |
| 293 | async function homeOf( |
| 294 | $: EngineInterface, |
| 295 | cwd: string, |
| 296 | ): Promise<string | undefined> { |
| 297 | const [home, profile] = await Promise.all([ |
| 298 | $.env.get('HOME'), |
| 299 | $.env.get('USERPROFILE'), |
| 300 | ]) |
| 301 | const isWindowsSpelling = cwd.includes('\\') |
| 302 | |
| 303 | return isWindowsSpelling ? (profile ?? home) : (home ?? profile) |
| 304 | } |