Documentation
¶
Index ¶
- Constants
- Variables
- func AbsPath(ctx context.Context, relPath string) (string, error)
- func AgentTranscriptFileName(agentID string) string
- func CaseInsensitiveFS() bool
- func ClearWorktreeRootCache()
- func Equal(a, b string) bool
- func ExtractSessionIDFromTranscriptPath(transcriptPath string) string
- func GetLastTimestampFromBytes(data []byte) time.Time
- func GetLastTimestampFromFile(path string) time.Time
- func GetWorktreeID(worktreePath string) (string, error)
- func IsInfrastructurePath(path string) bool
- func IsProtectedSubpath(parent, child string) bool
- func IsRelativeTraversal(rel string) bool
- func IsSubpath(parent, child string) bool
- func ParseTimestampFromJSONL(line string) time.Time
- func RequireEntireDir(ctx context.Context) error
- func SessionMetadataDirFromSessionID(sessionID string) string
- func SubagentsDir(transcriptDir, sessionID string) string
- func SymlinkedEntryError(path string) error
- func ToRelativePath(absPath, cwd string) string
- func ValidateEntireDirAt(worktreeRoot string) error
- func WorktreeRoot(ctx context.Context) (string, error)
Constants ¶
const ( EntireDir = ".entire" EntireTmpDir = ".entire/tmp" EntireMetadataDir = ".entire/metadata" )
Directory constants
const ( PromptFileName = "prompt.txt" TranscriptFileName = "full.jsonl" TranscriptFileNameLegacy = "full.log" // CompactTranscriptFileName is the compact transcript stored alongside // full.jsonl. It holds the full compacted session; this checkpoint's slice // begins at the session metadata's compact_transcript_start. CompactTranscriptFileName = "transcript.jsonl" MetadataFileName = "metadata.json" CheckpointFileName = "checkpoint.json" ContentHashFileName = "content_hash.txt" SettingsFileName = "settings.json" // AssetsDir is the per-session subfolder holding externalized transcript // assets (e.g. images); AssetsManifestFile indexes them. AssetsDirName is the // bare tree-entry name (no trailing slash) used when walking git trees. AssetsDirName = "assets" AssetsDir = "assets/" AssetsManifestFile = "assets/manifest.json" )
Metadata file names
const MetadataBranchName = "entire/checkpoints/v1"
MetadataBranchName is the orphan branch used by manual-commit strategy to store metadata
const TrailsBranchName = "entire/trails/v1"
TrailsBranchName is the orphan branch used to store trail metadata. Trails are branch-centric work tracking abstractions that link to checkpoints by branch name.
Variables ¶
var ( // ErrEntireDirNotDirectory reports that `.entire` exists and is not a real // directory. The remedy is to inspect and replace the path. ErrEntireDirNotDirectory = errors.New("not a directory") // ErrEntireDirUnsupportedEntry reports that an entry directly under // `.entire` is neither a regular file nor a directory. The remedy is to // inspect that entry and replace it, which is the same shape as // ErrEntireDirNotDirectory's but not the same sentence: // `.entire/settings.json` is not required to be a directory, so telling // someone it is not one names the wrong problem. ErrEntireDirUnsupportedEntry = errors.New("not a regular file or directory") // ErrEntireDirUnreadable reports that `.entire` could not be inspected at // all — a permission failure, an I/O error, a dead mount. Nothing is known // about what is at the path. The remedy is ownership, permissions, or the // filesystem itself. ErrEntireDirUnreadable = errors.New("cannot be inspected") // ErrRepositoryUnresolved reports that the worktree root could not be // determined for a reason other than there being no repository, so there is // no `.entire` path to inspect yet. The remedy is git. ErrRepositoryUnresolved = errors.New("cannot determine which repository this directory belongs to") )
The four ways validating `.entire` can fail. Each is identified positively and carries a different remedy, which is why they are separate sentinels rather than one error plus an else branch: callers print the fix, and telling someone to reinstall git because their filesystem returned EACCES sends them after the wrong thing. A caller matching none of these must offer no remedy rather than guess at one.
var ErrNotARepository = errors.New("not a git repository")
ErrNotARepository reports that git positively identified the working directory as being outside any repository. It is the ONLY worktree-root failure that means "there is nothing here to look at" — every other one (git missing from PATH, a cancelled context, a permission failure, dubious ownership, malformed output) means "we could not find out", which is a different thing and must not be treated as the benign case.
Functions ¶
func AbsPath ¶
AbsPath returns the absolute path for a relative path within the repository. If the path is already absolute, it is returned as-is. Uses WorktreeRoot() to resolve paths relative to the worktree root.
func AgentTranscriptFileName ¶ added in v0.10.0
AgentTranscriptFileName returns the file name an agent writes a subagent's transcript under: agent-<agentID>.jsonl.
func CaseInsensitiveFS ¶ added in v0.9.0
func CaseInsensitiveFS() bool
CaseInsensitiveFS reports whether path comparisons should be case-insensitive on the host OS. This is OS-based, not volume-based: Windows and macOS default to case-insensitive filesystems, Linux to case-sensitive. Keying on GOOS keeps the result deterministic. It must only influence EXCLUSION decisions (see IsProtectedSubpath / Equal): on an atypical volume (e.g. a case-sensitive macOS APFS volume) it treats a differently-cased path as matching, which is safe only when the effect is to exclude more, never to widen an allow gate.
func ClearWorktreeRootCache ¶ added in v0.4.8
func ClearWorktreeRootCache()
ClearWorktreeRootCache clears the cached worktree root. This is primarily useful for testing when changing directories.
func Equal ¶ added in v0.9.0
Equal reports whether two paths refer to the same location, honoring the host OS's case sensitivity (see CaseInsensitiveFS). Both inputs are cleaned and slash-normalized before comparison. Like IsProtectedSubpath, this is intended for EXCLUSION matching (e.g. protected files), not fail-closed containment.
func ExtractSessionIDFromTranscriptPath ¶
ExtractSessionIDFromTranscriptPath attempts to extract a session ID from a transcript path. Claude transcripts are stored at ~/.claude/projects/<project>/sessions/<id>.jsonl If the path doesn't match expected format, returns empty string.
func GetLastTimestampFromBytes ¶
GetLastTimestampFromBytes extracts the timestamp from the last non-empty line of JSONL content. Returns zero time if not found.
func GetLastTimestampFromFile ¶
GetLastTimestampFromFile reads the last non-empty line from a JSONL file and extracts the timestamp field. Returns zero time if file doesn't exist or no valid timestamp is found.
func GetWorktreeID ¶
GetWorktreeID returns the internal git worktree identifier for the given path. For the main worktree (where .git is a directory), returns empty string. For linked worktrees (where .git is a file), extracts the name from .git/worktrees/<name>/ path. This name is stable across `git worktree move`.
func IsInfrastructurePath ¶
IsInfrastructurePath returns true if the path is part of CLI infrastructure (i.e., inside the .entire directory). It is used only to EXCLUDE infra paths from checkpoints/tracking, so it matches case-insensitively on case-insensitive filesystems via IsProtectedSubpath. Do not use it as a containment/allow gate.
func IsProtectedSubpath ¶ added in v0.9.0
IsProtectedSubpath reports whether child is under parent for the purpose of EXCLUDING protected/infrastructure content from checkpoints and tracking. Unlike IsSubpath it honors OS case-insensitivity (see CaseInsensitiveFS), so a case variant of a protected dir (".Claude" vs ".claude") is still excluded on Windows/macOS.
SECURITY: never use this for allow/containment decisions. Case-folding widens what counts as "inside" parent, which is safe only when the effect is to exclude more. On a case-sensitive volume under a case-insensitive GOOS it over-matches; for a fail-closed gate that would fail open. Use IsSubpath there.
func IsRelativeTraversal ¶ added in v0.7.7
IsRelativeTraversal reports whether rel escapes its base directory. It accepts both OS-native paths and Git-style slash-normalized paths.
func IsSubpath ¶ added in v0.5.1
IsSubpath reports whether child is lexically under parent (or equal to it). It uses filepath.Rel, which cleans both inputs and is traversal-resistant: a crafted child like "/a/b/../../../etc/passwd" that escapes parent will produce a relative path starting with ".." and be rejected.
Matching is case-SENSITIVE. This is the correct primitive for fail-closed containment/allow checks (e.g. validating an attacker-influenced path stays under an Entire-owned dir): on a case-sensitive volume a differently-cased path names a different directory, so folding it in would fail open. For EXCLUSION decisions that must also catch case variants on Windows/macOS, use IsProtectedSubpath instead.
func ParseTimestampFromJSONL ¶
ParseTimestampFromJSONL extracts the timestamp from a JSONL line. Returns zero time if the line is empty or doesn't contain a valid timestamp.
func RequireEntireDir ¶ added in v0.10.4
RequireEntireDir validates the current worktree's `.entire`.
Outside a git repository there is no worktree root and so nothing to validate, which is not an error: commands that need a repository report its absence themselves, with a message about the repository rather than about `.entire`. That skip requires git's positive ErrNotARepository verdict.
Every other discovery failure — git missing from PATH, a cancelled context, a permission failure, dubious ownership, malformed output — fails closed. Those mean "we could not find out", and the consequence of guessing "no repository" is not merely a skipped check: settings resolution falls back to a path relative to the current directory when the root will not resolve (settingsAbsPaths in the settings package), so a guess would read ./.entire/settings.json — through the very symlink this exists to reject. Refusing to run on a machine whose git is broken is the cheaper mistake.
Deliberately not memoized. The Lstat and the one-level listing are free next to the `git rev-parse` that WorktreeRoot runs — measured 8.2µs against a ~millisecond subprocess — and a cached "it was fine" is a stale answer in a long-lived process such as `entire mcp`.
func SessionMetadataDirFromSessionID ¶
SessionMetadataDirFromSessionID returns the path to a session's metadata directory for the given Entire session ID. The sessionID must be the full, already date-prefixed Entire session identifier as stored on disk, not an agent-specific or raw Claude ID.
func SubagentsDir ¶ added in v0.10.0
SubagentsDir returns the directory an agent stores a session's subagent transcripts in: <transcriptDir>/<sessionID>/subagents.
This layout lives here, in the leaf paths package, because it is needed on both sides of the import graph — the lifecycle dispatcher and the strategy, review, and agentimport packages all resolve it, and those cannot import each other. Before it was named it existed as five copies of the same filepath.Join, which is how the SubagentEnd path came to disagree with the turn-end path about where subagent transcripts live.
sessionID is the *agent's* session ID (the transcript file's own basename), not the date-prefixed Entire session ID.
func SymlinkedEntryError ¶ added in v0.10.4
SymlinkedEntryError reports that path is a symbolic link, naming the target when it can be read. A Readlink that fails leaves the entry named, which is still enough to act on.
Exported because this sentence has two producers — the `.entire` entry scan here and the settings reader, which refuses a symlinked settings file at the read itself (see readConfined in the settings package). One function so the two cannot drift into describing the same condition differently.
func ToRelativePath ¶
ToRelativePath converts an absolute path to relative. Returns empty string if the path is outside the working directory.
func ValidateEntireDirAt ¶ added in v0.10.4
ValidateEntireDirAt reports whether worktreeRoot's `.entire` is safe to read and write through. It is safe when the path is absent (Entire is not enabled here yet, or `enable` is about to create it), or is a real directory whose own entries are real files and directories. Anything else is a broken repo and the caller must not touch the path.
The stat is Lstat, not Stat, so a symlink is rejected even when it points at a perfectly good directory. `.entire` holds session metadata, transcripts, and the settings that decide what gets redacted before it is committed, so a path someone else controls the far end of is not a path we write through.
The same reasoning covers one level down, so the entries directly inside are checked too, and there the rule is an allowlist: Entire only ever creates regular files and directories under `.entire`, so anything else arrived some other way. A symlinked `.entire/metadata` redirects transcripts, and a symlinked `.entire/settings.local.json` redirects the file that names the command Entire executes at pre-push. See validateEntireDirEntries for why the scan stops there, and unsupportedEntryType for the one type it tolerates.
The settings package refuses a symlinked settings file at the read itself as well (readConfined). Neither check subsumes the other: this one stops a command before it does anything, and covers the subdirectories no settings read touches, while that one covers the many callers that reach settings.Load without passing through a command's pre-run.
A stat error other than "not exist" is also a failure. It is not evidence that the invariant is violated, but neither is it evidence that it holds, and the caller's next move is to write there.
func WorktreeRoot ¶ added in v0.4.8
WorktreeRoot returns the git worktree root directory — in a linked worktree that worktree's root, not the main repository's. The result is cached per working directory. Callers that need to distinguish "outside a repository" from "could not find out" match ErrNotARepository; nothing else may be read as the benign case.
Everything Entire stores is located from this path — .entire via the entiredir package, and the git common dir via gitdir — so a failure here used to be papered over by callers falling back to a relative path, which then resolved against wherever the process happened to be. From a subdirectory that silently meant a second .entire beside the agent instead of the repository's one.
The subprocess fails for more reasons than "no repository" — `git` off $PATH is the common one — so those two outcomes are separated rather than merged: only ErrNotARepository means "there is nothing here", and it is the only failure any caller is allowed to answer with a directory of its own.
Types ¶
This section is empty.