Documentation
¶
Overview ¶
codemeld-owned ignore filtering: a single-format `codemeldignore` matcher plugged into the walker's traversal seam, honored by glob/grep/list/tree by default in place of native .gitignore/.hgignore parsing.
Built-in structured delete tool: irreversible removal of a single existing in-project file, symlink, or empty directory, with pre-authorization target resolution and an fd-relative unlink that can neither recurse nor land outside the authorized parent. Unlike edit/create it is never auto-approved by any mode and never folds under the write capability.
Built-in structured edit tool: exact single text replacement on an existing in-project file, with pre-authorization target resolution and atomic apply.
Built-in structured file_count tool: project-confined, recursive counting of the entries under an optional `path` (default project root), over the shared walker (directory symlinks not followed, hidden/ignore filtering). The walked entries are filtered by `kinds` (which entry types to count) and by `include_globs`/`exclude_globs`, then either totalled flat or aggregated by `group_by` (file extension, top-level directory, parent directory, depth, or logical stem) into a deterministic, sorted breakdown. A `limit` caps how many groups are shown; the remainder is folded into a trailing `other:` line so the visible group counts plus `other` always equal the reported total. It is read-only, mirroring read/glob/grep/tree's target-scoped resolution and external-read posture.
Built-in structured glob tool: project-root-confined filename globbing with `**` recursive matching via the pure-Go doublestar matcher, run over the shared hidden/ignore/symlink-safe walker, returning project-relative paths in ascending order bounded by a per-tool limit.
Built-in structured grep tool: project-root-confined RE2 content search over the shared hidden/ignore/symlink-safe walker, with a basename include glob, case-insensitivity, merged context windows, binary-file skip, files-only mode, and a match-count limit, returning deterministic path:line:match/context output bounded by the truncation indicator.
Shared output-bounding policy for the read-only navigation tools (read, grep, glob, list, tree): product-default/maximum result limits, numeric-argument validation, and a byte-cap-safe truncation indicator. The indicator is encoded as a single, prefixed JSON line at the start of stderr — out of read's verbatim stdout and inside the prefix-preserving byte-cap path — so it survives any schema-valid sessions.max_tool_output_bytes trim regardless of per-tool limits.
Built-in structured list tool: project-confined, non-recursive listing of a directory's immediate entries (default project root) — each entry's name and type (file / dir / symlink) in deterministic ascending name order, bounded by a per-tool limit with the truncation signal, over the shared walker in single-level mode so descendants are never traversed or counted.
Built-in structured mkdir tool: create a directory and any missing parents (mkdir -p semantics) with pre-authorization target resolution and a held-descriptor, fd-relative create. It is registered as a built-in mutating tool in the production registry: Build advertises it, Plan filters it out, and each directory create needs an explicit mkdir grant or approval rather than folding under the write capability.
Built-in structured move tool: no-overwrite rename of an existing in-project entry to a new path, with independent pre-authorization resolution of both endpoints and an atomic no-overwrite publish through the fsutil rename helper. Unregistered in this slice — production wiring, the multi-scope dispatch seam, mode posture, and advertising land in later slices; this file owns only the tool, its two-endpoint resolved handoff, and its standalone guard.
Built-in structured read tool: verbatim, line-paginated read of a regular in-project file with no injected line numbers, hidden-path exempt, returning truncation/continuation metadata out-of-band
Built-in read-only image tool.
Shared read-target path resolution and symlink-policy predicates for the five read-only navigation tools (read, grep, glob, list, tree): project-root-relative resolution, required vs optional `path` modes, fail-closed project-root containment, and the per-caller symlink rules.
Shared file-target resolution and argument helpers for the structured file tools: parent resolution via the permission path-scope rules, basename anchoring, and project-root containment. The edit and create tools parameterize it by their target precondition; the read-only navigation tools share the same parent-resolution/containment step through ResolveReadTarget in readresolve.go.
Built-in structured tree tool: project-confined, recursive listing of the directory structure under an optional `path` (default project root) to an optional maximum descendant `depth`, in deterministic ascending project-relative (pre-order) path order, bounded by a total-entry `limit` with the truncation signal, over the shared walker (directory symlinks not followed, hidden/ignore filtering). Display is selected by `format`: `ai` (the default) emits one project-relative, slash-normalized path per line; `human` emits the indented basename tree. The two layouts differ only in the per-line body — resolution, walk order, depth, limit, filtering, symlink handling, and truncation are shared.
Shared directory walker for the recursive read-only navigation tools (grep, glob, list, tree): ordered, hidden-filtered, symlink-no-follow traversal with a pluggable ignore filter seam that the codemeldignore matcher registers into.
Index ¶
- Constants
- func BoundedResult(stdout []byte, limitTruncated bool, nextOffset int, cap int) tools.Result
- func GlobalCodemeldignorePath(home string) string
- func IsDirectorySymlink(path string) (bool, error)
- func ProjectCodemeldignorePath(projectRoot string) string
- func ResolveDeclaredScopes(home, launchDir string, writeDecls, deleteDecls []string) (write, remove map[string]bool, err error)
- func ResolveReadFile(target string, projectRoot string) (real string, ok bool, reason string)
- func ResolveReadTarget(home string, processCWD string, projectRoot string, args map[string]any, ...) (target string, scope string, err error)
- func ValidatePhase1ReadArgs(name string, args map[string]any) error
- func ValidateReadImageArgs(args map[string]any) error
- func WalkContext(ctx context.Context, opts WalkOptions, visit WalkVisitor) error
- func WithWriteJournal(ctx context.Context, journal WriteJournal) context.Context
- type CodemeldignoreMatcher
- type CreateTool
- func (t *CreateTool) Advertise() tools.Metadata
- func (t *CreateTool) Invoke(ctx context.Context, args map[string]any) (tools.Result, error)
- func (t *CreateTool) InvokeWithAuthorizedScope(ctx context.Context, args map[string]any, resolvedTarget string, scope string, ...) (tools.Result, error)
- func (t *CreateTool) InvokeWithResolvedTarget(ctx context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *CreateTool) Name() string
- func (t *CreateTool) OutOfRootScope(scope string) bool
- func (t *CreateTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *CreateTool) SubjectKind() string
- type DeleteTool
- func (t *DeleteTool) Advertise() tools.Metadata
- func (t *DeleteTool) Invoke(ctx context.Context, args map[string]any) (tools.Result, error)
- func (t *DeleteTool) InvokeWithAuthorizedScope(ctx context.Context, args map[string]any, resolvedTarget string, scope string, ...) (tools.Result, error)
- func (t *DeleteTool) InvokeWithResolvedTarget(ctx context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *DeleteTool) Name() string
- func (t *DeleteTool) OutOfRootScope(scope string) bool
- func (t *DeleteTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *DeleteTool) SubjectKind() string
- type EditTool
- func (t *EditTool) Advertise() tools.Metadata
- func (t *EditTool) Invoke(ctx context.Context, args map[string]any) (tools.Result, error)
- func (t *EditTool) InvokeWithAuthorizedScope(ctx context.Context, args map[string]any, resolvedTarget string, scope string, ...) (tools.Result, error)
- func (t *EditTool) InvokeWithResolvedTarget(ctx context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *EditTool) Name() string
- func (t *EditTool) OutOfRootScope(scope string) bool
- func (*EditTool) Preview(target string, args map[string]any) (string, *tools.Result)
- func (t *EditTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *EditTool) SubjectKind() string
- type EntryKind
- type FileCountTool
- func (t *FileCountTool) Advertise() tools.Metadata
- func (t *FileCountTool) ExternalReadScope(scope string) bool
- func (t *FileCountTool) Invoke(_ context.Context, args map[string]any) (tools.Result, error)
- func (t *FileCountTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *FileCountTool) Name() string
- func (t *FileCountTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *FileCountTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
- func (t *FileCountTool) SubjectKind() string
- type GlobTool
- func (t *GlobTool) Advertise() tools.Metadata
- func (t *GlobTool) ExternalReadScope(scope string) bool
- func (t *GlobTool) Invoke(_ context.Context, args map[string]any) (tools.Result, error)
- func (t *GlobTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *GlobTool) Name() string
- func (t *GlobTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *GlobTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
- func (t *GlobTool) SubjectKind() string
- type GrepTool
- func (t *GrepTool) Advertise() tools.Metadata
- func (t *GrepTool) ExternalReadScope(scope string) bool
- func (t *GrepTool) Invoke(_ context.Context, args map[string]any) (tools.Result, error)
- func (t *GrepTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *GrepTool) Name() string
- func (t *GrepTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *GrepTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
- func (t *GrepTool) SubjectKind() string
- type IgnoreMatcher
- type JournalState
- type ListTool
- func (t *ListTool) Advertise() tools.Metadata
- func (t *ListTool) ExternalReadScope(scope string) bool
- func (t *ListTool) Invoke(_ context.Context, args map[string]any) (tools.Result, error)
- func (t *ListTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *ListTool) Name() string
- func (t *ListTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *ListTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
- func (t *ListTool) SubjectKind() string
- type MkdirTool
- func (t *MkdirTool) Advertise() tools.Metadata
- func (t *MkdirTool) Invoke(_ context.Context, args map[string]any) (tools.Result, error)
- func (t *MkdirTool) InvokeWithAuthorizedScope(_ context.Context, args map[string]any, resolvedTarget string, scope string, ...) (tools.Result, error)
- func (t *MkdirTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *MkdirTool) Name() string
- func (t *MkdirTool) OutOfRootScope(scope string) bool
- func (t *MkdirTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *MkdirTool) SubjectKind() string
- type MoveTool
- func (t *MoveTool) Advertise() tools.Metadata
- func (t *MoveTool) Invoke(ctx context.Context, args map[string]any) (tools.Result, error)
- func (t *MoveTool) InvokeWithResolvedMove(ctx context.Context, resolved ResolvedMove) (tools.Result, error)
- func (t *MoveTool) Name() string
- func (t *MoveTool) OutOfRootScope(scope string) bool
- func (t *MoveTool) ResolveEndpoints(args map[string]any) (ResolvedMove, error)
- func (t *MoveTool) SubjectKind() string
- type NumericArgs
- type OutputMeta
- type ReadImageTool
- func (t *ReadImageTool) Advertise() tools.Metadata
- func (t *ReadImageTool) ExternalReadScope(scope string) bool
- func (t *ReadImageTool) Invoke(_ context.Context, args map[string]any) (tools.Result, error)
- func (t *ReadImageTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *ReadImageTool) Name() string
- func (t *ReadImageTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *ReadImageTool) SubjectKind() string
- type ReadTargetOptions
- type ReadTool
- func (t *ReadTool) Advertise() tools.Metadata
- func (t *ReadTool) ExternalReadScope(scope string) bool
- func (t *ReadTool) Invoke(_ context.Context, args map[string]any) (tools.Result, error)
- func (t *ReadTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *ReadTool) Name() string
- func (t *ReadTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *ReadTool) SubjectKind() string
- type ResolvedMove
- type ScopeIdentity
- type TargetError
- type TreeTool
- func (t *TreeTool) Advertise() tools.Metadata
- func (t *TreeTool) ExternalReadScope(scope string) bool
- func (t *TreeTool) Invoke(_ context.Context, args map[string]any) (tools.Result, error)
- func (t *TreeTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
- func (t *TreeTool) Name() string
- func (t *TreeTool) ResolveTarget(args map[string]any) (target string, scope string, err error)
- func (t *TreeTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
- func (t *TreeTool) SubjectKind() string
- type WalkEntry
- type WalkOptions
- type WalkVisitor
- type WriteJournal
Constants ¶
const ( FileCountDefaultLimit = 250 FileCountMaxLimit = 5000 )
FileCountDefaultLimit and FileCountMaxLimit bound the `limit` argument, which caps how many groups the grouped output shows (the remainder is folded into `other`). They mirror the grep/glob/tree entry-limit constants; the whole subtree is always walked and counted regardless of `limit` — only the number of visible groups is bounded.
const ( ReadDefaultLimit = 2000 ReadMaxLimit = 10000 GrepDefaultLimit = 250 GrepMaxLimit = 5000 GlobDefaultLimit = 250 GlobMaxLimit = 5000 ListDefaultLimit = 250 ListMaxLimit = 5000 TreeDefaultLimit = 250 TreeMaxLimit = 5000 )
Per-tool product-default and product-maximum result limits, defined as named constants rather than configuration. The default applies when the model omits `limit`; Max is the largest `limit` the model may request per call — a larger value is rejected in-band naming the maximum (see ValidateNumericArgs). read counts lines; grep/glob/list/tree count entries (matches/paths/names).
const ( TruncationReasonByteCap = "byte_cap" TruncationReasonLimit = "limit" )
Truncation-reason values carried in OutputMeta.TruncationReason. ByteCap means the global sessions.max_tool_output_bytes cap trimmed the output; Limit means only a per-tool line/entry limit withheld more output. ByteCap takes precedence when both apply — the byte cap is the harder backstop.
const CreateToolName = "create"
CreateToolName is the exact dispatch name the structured create tool registers under. Like `edit` and `bash`, it is a fixed built-in name carrying no template namespace.
const DeleteToolName = "delete"
DeleteToolName is the exact dispatch name the structured delete tool registers under. Like `edit` and `create` it is a fixed built-in name carrying no template namespace. It is deliberately absent from permissions.capabilityMembers, so a `write` capability grant (which folds create/edit) never authorizes a delete — every delete needs its own explicit grant or approval.
const EditToolName = "edit"
EditToolName is the exact dispatch name the structured edit tool registers under. Like `bash`, it is a fixed built-in name; unlike vcs_<action> it carries no template namespace.
const FileCountToolName = "file_count"
FileCountToolName is the exact dispatch name the structured file_count tool registers under. Like `read`/`glob`/`grep`/`tree` it is a fixed built-in name carrying no template namespace.
const GlobToolName = "glob"
GlobToolName is the exact dispatch name the structured glob tool registers under. Like `read`/`edit`/`create`/`bash` it is a fixed built-in name carrying no template namespace.
const GrepToolName = "grep"
GrepToolName is the exact dispatch name the structured grep tool registers under. Like `read`/`glob`/`edit`/`create`/`bash` it is a fixed built-in name carrying no template namespace.
const ListToolName = "list"
ListToolName is the exact dispatch name the structured list tool registers under. Like `read`/`glob`/`grep`/`edit`/`create`/`bash` it is a fixed built-in name carrying no template namespace.
const MetadataPrefix = "codemeld-tool-meta: "
MetadataPrefix marks the single success-metadata line BoundedResult writes at the start of stderr. A stable, compact, ASCII prefix lets the model (and tests) pick the truncation indicator out unambiguously, and keeps the line small enough to survive a small byte cap.
const MkdirToolName = "mkdir"
MkdirToolName is the exact dispatch name the structured mkdir tool registers under. Like `edit`, `create`, and `bash`, it is a fixed built-in name carrying no template namespace.
const MoveToolName = "move"
MoveToolName is the exact dispatch name the structured move tool will register under once a later slice wires it into production. Like `edit`, `create`, and `bash` it is a fixed built-in name carrying no template namespace.
const ReadImageToolName = "read_image"
const ReadToolName = "read"
ReadToolName is the exact dispatch name the structured read tool registers under. Like `edit`/`create`/`bash` it is a fixed built-in name carrying no template namespace.
const TreeToolName = "tree"
TreeToolName is the exact dispatch name the structured tree tool registers under. Like `read`/`glob`/`grep`/`list`/`edit`/`create`/`bash` it is a fixed built-in name carrying no template namespace.
Variables ¶
This section is empty.
Functions ¶
func BoundedResult ¶
BoundedResult assembles the success tools.Result for a read-only tool from its already line/entry-limited stdout, encoding the truncation indicator as a single MetadataPrefix JSON line at the START of stderr. Three properties make the indicator byte-cap-safe and non-corrupting:
It lives in stderr, never in stdout, so it cannot corrupt read's verbatim file content.
- It is the first (and only) line the helper writes to stderr, and the global byte cap (events.TruncateToolOutput / events.BuildModelInputBytes) preserves a *prefix* of each stream, so the indicator survives even when stdout is trimmed by any schema-valid cap.
- It single-sources truncation through that existing byte-cap path rather than trimming stdout itself: stdout is passed through untrimmed and the helper only records whether the cap will trim
it, so there is exactly one byte-truncation mechanism in the system.
limitTruncated reports whether the per-tool logical limit withheld output; nextOffset is read's resume line (0 for the entry-listing tools and when no more lines remain). cap is the Cache.MaxToolOutputBytes() value, passed as an int so this package stays independent of config; when the production byte cap would trim stdout the reason is byte_cap, which takes precedence over a logical-limit truncation.
func GlobalCodemeldignorePath ¶
GlobalCodemeldignorePath returns ~/.codemeld/global/codemeldignore, the global ignore file.
func IsDirectorySymlink ¶
IsDirectorySymlink reports whether path is itself a symlink whose target is a directory. The recursive read tools (grep, tree, and glob's recursive matching) use it to refuse to descend through a directory symlink, so a symlink under the tree cannot redirect a recursive walk outside it. A real directory, a non-symlink, a dangling symlink, or a symlink to a non-directory all return false; a stat/lstat error other than non-existence is returned to the caller.
func ProjectCodemeldignorePath ¶
ProjectCodemeldignorePath returns <project-root>/.codemeld/codemeldignore, the per-project ignore file.
func ResolveDeclaredScopes ¶
func ResolveDeclaredScopes(home, launchDir string, writeDecls, deleteDecls []string) (write, remove map[string]bool, err error)
ResolveDeclaredScopes canonicalizes one run's caller-declared write and delete paths into the exact target forms the create/edit and delete tools resolve their own targets to, so the runtime membership test is a path comparison and never a comparison of the raw argument. A write declaration follows a final symlink when the path exists and otherwise anchors the missing segments under the deepest existing ancestor; a delete declaration resolves the parent and keeps the literal basename, so declaring a symlink authorizes removing the link rather than its target. Either list failing leaves both unresolved.
func ResolveReadFile ¶
ResolveReadFile applies the read tool's symlink policy to a candidate target: it follows a symlink only when the link resolves to a regular file that is still inside projectRoot. It returns the resolved real file path when the target — after following any symlink — is a regular file in-root; otherwise ok is false with a reason naming why (a directory, a special file, a symlink that escapes the root, or a missing target), so read never follows a symlink to a directory or out of the project. The target argument is normally the path ResolveReadTarget returned. The project root is normalized to its symlink-resolved form before the containment check so a launch through a symlinked project directory does not reject valid in-root files (matching ResolveReadTarget).
func ResolveReadTarget ¶
func ResolveReadTarget(home string, processCWD string, projectRoot string, args map[string]any, opts ReadTargetOptions) (target string, scope string, err error)
ResolveReadTarget resolves a read-only tool's path argument to a safe, in-project absolute target before any content read. It is the shared security boundary for read, grep, glob list, and tree, and differs from resolveFileTarget in three ways: (1) a relative path resolves against projectRoot, not the process cwd, so resolution is stable however codemeld was launched (2) it imposes no existence-shape precondition, so the target may be a regular file or a directory; and (3) an omitted path defaults to projectRoot for the directory-oriented tools (grep/glob/list/tree).
It expands a leading ~ (rejecting ~user), resolves and contains the parent directory, anchors the basename, then follows a final-component symlink and re-checks that the real target — including one reached through a symlink — stays inside projectRoot, failing closed with a *TargetError before any read. When opts.RejectDirectorySymlink is set (the recursive grep/glob/tree tools), a final-component symlink that resolves to a directory is refused fail-closed before it is collapsed to the real directory, so a recursive walk cannot be redirected through a directory symlink. It returns the resolved target and the path scope to authorize: the target itself when it is a directory, or the target's enclosing directory when it is a file. A non-existent target is returned as the anchored (already-contained) path so the calling tool reports a normal not-found.
By default the target must be in-project: an out-of-root parent is a fail-closed rejection. When opts.AllowExternal is set (the five dispatch read tools) the one exception is an EXPLICIT absolute or ~ path whose parent resolves outside the root: it is resolved to a concrete out-of-root target/scope (via resolveExternalReadTarget) rather than rejected, so DispatchTool can route it to a permission prompt. Every other escape stays fail-closed even with AllowExternal — a relative ".." traversal, a ~user form, an in-root path whose final symlink escapes the root, a directory-symlink root for the recursive tools, and an unresolvable parent — so the only widening is an explicitly named external path the user can be prompted to approve.
processCWD is accepted for signature symmetry with resolveFileTarget but is deliberately not the base for relative resolution; read targets resolve against projectRoot.
func ValidateReadImageArgs ¶
ValidateReadImageArgs is the complete non-executing argument check for a read_image call: it reads nothing and resolves no path, so an authorization group can reject a malformed call before preparing it.
func WalkContext ¶
func WalkContext(ctx context.Context, opts WalkOptions, visit WalkVisitor) error
func WithWriteJournal ¶
func WithWriteJournal(ctx context.Context, journal WriteJournal) context.Context
WithWriteJournal binds journal to ctx for one child agent's dispatch. It travels as a call parameter rather than a package variable because nested delegates hold overlapping declared scopes, so a process-wide binding could route a write into the wrong child's journal.
Types ¶
type CodemeldignoreMatcher ¶
type CodemeldignoreMatcher struct {
// contains filtered or unexported fields
}
CodemeldignoreMatcher is the IgnoreMatcher over the union of patterns loaded from the project and global codemeldignore files. Each pattern is a single doublestar glob matched literally against an entry's project-root-relative, slash-separated path: a slash-less pattern is root-only, "match a name at any depth" needs an explicit leading `**/`, and there is no negation, so the two files' patterns are an order-independent union. A matched directory prunes its whole subtree because the shared walker consults the matcher before it descends.
func LoadCodemeldignoreMatcher ¶
func LoadCodemeldignoreMatcher(projectRoot, home string) (*CodemeldignoreMatcher, error)
LoadCodemeldignoreMatcher reads the project and global codemeldignore files (each optional — a missing file is not an error), unions their patterns, and returns a matcher. It returns a nil matcher when neither file contributes a pattern, so a caller can skip registering an empty filter. An invalid pattern in either file is a hard error (the file path and 1-based line number are named), surfaced in-band by the consuming tool rather than silently dropped. Order is irrelevant: there is no negation, so the union is the same set regardless of which file is read first.
func (*CodemeldignoreMatcher) ShouldSkip ¶
func (m *CodemeldignoreMatcher) ShouldSkip(relPath string, _ EntryKind) bool
ShouldSkip reports whether relPath matches any loaded pattern. Patterns are pre-validated at load via doublestar.ValidatePattern, so doublestar.Match cannot return a pattern error here; a defensive error is treated as no-match so a single odd pattern can never wrongly exclude an entry. The entry kind is irrelevant to matching — directory-subtree pruning is the walker's job once a directory is skipped.
type CreateTool ¶
type CreateTool struct {
// contains filtered or unexported fields
}
func NewCreateTool ¶
func NewCreateTool(home, processCWD, projectRoot string) *CreateTool
NewCreateTool builds the create tool bound to the run's ~ anchor (home), the default base directory for a relative target path (processCWD), and the project root the target must stay within. The project root is symlink-resolved once here so the containment check compares it against the symlink-resolved parent on the same footing — mirroring NewEditTool — so dispatch's pre-authorization containment check and Auto edits' later in-scope check agree.
func (*CreateTool) Advertise ¶
func (t *CreateTool) Advertise() tools.Metadata
Advertise returns the create tool's advertising metadata and mutating classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly. ReadOnly is false: create mutates the filesystem, so Plan denies it and Auto edits may auto-approve it.
func (*CreateTool) Invoke ¶
Invoke resolves the target and creates the file in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithAuthorizedScope; this method keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry.
Standalone Invoke has no authorizer, so it must enforce the out-of-root boundary itself: an out-of-root resolved target (OutOfRootScope) is reachable only by explicit per-write approval, which only the dispatch path can obtain. This method therefore refuses an out-of-root target in-band rather than writing it — without this guard a direct caller could create outside the project root with no prompt or grant check. The in-root path is unchanged.
func (*CreateTool) InvokeWithAuthorizedScope ¶
func (t *CreateTool) InvokeWithAuthorizedScope(ctx context.Context, args map[string]any, resolvedTarget string, scope string, scopeIdentity ScopeIdentity) (tools.Result, error)
InvokeWithAuthorizedScope is the seam production dispatch invokes the create tool through. scope is the ancestor that was authorized, not the deepest ancestor existing now, so an intermediate directory legitimately created while the prompt was open does not read as a replacement. The confirmed scope descriptor is held for the whole create and every directory the create opens is reached through it.
func (*CreateTool) InvokeWithResolvedTarget ¶
func (t *CreateTool) InvokeWithResolvedTarget(ctx context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget creates the file at the target dispatch already resolved before authorization, so authorization and the write cannot drift if a symlink changes in between — the same resolved-before-authorize / reuse-at-invoke contract the edit tool follows. Production dispatch routes the create tool through InvokeWithAuthorizedScope instead, which carries the authorized directory's identity as well as the target.
func (*CreateTool) Name ¶
func (t *CreateTool) Name() string
func (*CreateTool) OutOfRootScope ¶
func (t *CreateTool) OutOfRootScope(scope string) bool
OutOfRootScope reports whether scope (the resolved path scope ResolveTarget returned) lies outside this tool's project root — the out-of-root signal dispatch uses to re-enable the folder grant option for an out-of-root create. Containment uses the same descendant rule as folder-grant path scopes; the scope is the resolved real parent directory, so the verdict matches the folder the file would land in.
func (*CreateTool) ResolveTarget ¶
func (*CreateTool) SubjectKind ¶
func (t *CreateTool) SubjectKind() string
type DeleteTool ¶
type DeleteTool struct {
// contains filtered or unexported fields
}
DeleteTool is the built-in mutating delete tool. It removes a single existing target — a regular file, a final-component symlink (the link entry, never the file it points at), or an empty directory — and refuses a non-empty directory in-band, with no recursive fallback. Deletion is irreversible, so it is held to stronger guardrails than edit/create: it is never auto-approved by any mode (it is not in the Auto edits in-scope class) and never folds under the write capability. The run-scoped home, process cwd, and project root are bound at construction so the same expansion/cleaning/symlink/containment rules used for permission path scopes resolve the target, exactly as the create tool does.
func NewDeleteTool ¶
func NewDeleteTool(home, processCWD, projectRoot string) *DeleteTool
NewDeleteTool builds the delete tool bound to the run's ~ anchor (home), the default base directory for a relative target path (processCWD), and the project root the target must stay within. The project root is symlink-resolved once here so the containment check compares it against the symlink-resolved parent on the same footing — mirroring NewCreateTool — so dispatch's pre-authorization containment check and the production authorizer's boundary agree.
func (*DeleteTool) Advertise ¶
func (t *DeleteTool) Advertise() tools.Metadata
Advertise returns the delete tool's advertising metadata and mutating classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly. ReadOnly is false: delete mutates the filesystem. Unlike create, delete is never auto-approved — Plan denies it and Auto edits prompts for it — because it is not in the Auto edits in-scope class (it is neither edit nor create); an explicit grant or approval is always required.
func (*DeleteTool) Invoke ¶
Invoke resolves the target and deletes it in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithAuthorizedScope; this method keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry.
Standalone Invoke has no authorizer, so it must enforce the out-of-root boundary itself: an out-of-root resolved target (OutOfRootScope) is reachable only by explicit per-delete approval, which only the dispatch path can obtain. This method therefore refuses an out-of-root target in-band rather than deleting it — without this guard a direct caller could delete outside the project root with no prompt or grant check. The in-root path is unchanged.
func (*DeleteTool) InvokeWithAuthorizedScope ¶
func (t *DeleteTool) InvokeWithAuthorizedScope(ctx context.Context, args map[string]any, resolvedTarget string, scope string, scopeIdentity ScopeIdentity) (tools.Result, error)
InvokeWithAuthorizedScope is the seam production dispatch invokes the delete tool through: it deletes only once the directory at scope is still the one whose identity was captured when the target resolved, closing the window the permission prompt holds open. The confirmed scope descriptor is held for the whole delete and the parent the unlink runs through is reached through it.
func (*DeleteTool) InvokeWithResolvedTarget ¶
func (t *DeleteTool) InvokeWithResolvedTarget(ctx context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget deletes the target dispatch already resolved before authorization, so authorization and the removal cannot drift if a symlink changes in between — the same resolved-before-authorize / reuse-at-invoke contract the create tool follows. Production dispatch routes the delete tool through InvokeWithAuthorizedScope instead, which carries the authorized directory's identity as well as the target.
func (*DeleteTool) Name ¶
func (t *DeleteTool) Name() string
func (*DeleteTool) OutOfRootScope ¶
func (t *DeleteTool) OutOfRootScope(scope string) bool
OutOfRootScope reports whether scope (the resolved path scope ResolveTarget returned) lies outside this tool's project root — the out-of-root signal dispatch uses to re-enable the folder grant option for an out-of-root delete. Containment uses the same descendant rule as folder-grant path scopes; the scope is the resolved real parent directory, so the verdict matches the folder the entry lives in. The final component is never resolved, so deleting a final symlink that points outside the root is judged on its in-root parent, never the link's target.
func (*DeleteTool) ResolveTarget ¶
ResolveTarget resolves the delete target from args before authorization. It rejects unknown arguments, then runs the shared parent resolution via resolveFileTarget with the delete precondition (must-exist file/symlink/directory, the final component never followed). It returns the resolved target path (placed in the mode-auth context) and the path scope to authorize (the resolved real parent directory the entry lives in). A missing target, a basename escaping the resolved parent, an unsupported type, or an invalid argument is rejected with a *TargetError before any prompt. An out-of-root resolved target is not rejected: it is classified out-of-root (OutOfRootScope) and routed to the approval prompt, anchored on its real out-of-root parent folder.
func (*DeleteTool) SubjectKind ¶
func (t *DeleteTool) SubjectKind() string
type EditTool ¶
type EditTool struct {
// contains filtered or unexported fields
}
EditTool is the built-in mutating edit tool. It applies exactly one occurrence of an old-text snippet being replaced by new text on an existing regular file, failing closed when the precondition is stale or ambiguous so it never silently overwrites unrelated content. The run-scoped home, process cwd, and project root are bound at construction — mirroring vcs.ActionTool binding its repo root — so the same expansion/cleaning/symlink/containment rules used for permission path scopes resolve the target.
func NewEditTool ¶
NewEditTool builds the edit tool bound to the run's ~ anchor (home), the default base directory for a relative target path (processCWD, codemeld's launch cwd), and the project root the target must stay within. The project root is symlink-resolved once here so the containment check compares it against the symlink-resolved target on the same footing — the bootstrap project root is absolute and cleaned but not symlink-resolved, so a root path containing a symlink would otherwise reject in-project edits. The resolved root mirrors the boundary the production authorizer overlays onto the mode-authorization context, so dispatch's pre-authorization containment check and Auto edits' later in-scope check agree.
func (*EditTool) Advertise ¶
Advertise returns the edit tool's advertising metadata and mutating classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly. ReadOnly is false: the edit tool mutates, so Plan denies it and Auto edits may auto-approve it.
func (*EditTool) Invoke ¶
Invoke resolves the target and applies the edit in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithAuthorizedScope; this method keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry.
Standalone Invoke has no authorizer, so it must enforce the out-of-root boundary itself: an out-of-root resolved target (OutOfRootScope) — including an in-root lexical path that symlink-resolves outside the root — is reachable only by explicit per-write approval, which only the dispatch path can obtain. This method therefore refuses an out-of-root target in-band rather than editing it; without this guard a direct caller could modify a file outside the project root with no prompt or grant check. The in-root path is unchanged.
func (*EditTool) InvokeWithAuthorizedScope ¶
func (t *EditTool) InvokeWithAuthorizedScope(ctx context.Context, args map[string]any, resolvedTarget string, scope string, scopeIdentity ScopeIdentity) (tools.Result, error)
InvokeWithAuthorizedScope is the seam production dispatch invokes the edit tool through: it applies the edit only once the directory at scope is still the one whose identity was captured when the target resolved, closing the window the permission prompt holds open. The confirmed scope descriptor is held for the whole edit and the parent the edit reads and publishes through is reached through it.
func (*EditTool) InvokeWithResolvedTarget ¶
func (t *EditTool) InvokeWithResolvedTarget(ctx context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget applies the edit to the target dispatch already resolved before authorization, so authorization and the write cannot drift if a symlink changes in between — the same resolved-before-authorize / reuse-at-invoke contract vcs.ActionTool follows for its cwd. Production dispatch routes the edit tool through InvokeWithAuthorizedScope instead, which carries the authorized directory's identity as well as the target.
func (*EditTool) OutOfRootScope ¶
OutOfRootScope reports whether scope (the resolved path scope ResolveTarget returned) lies outside this tool's project root — the out-of-root signal dispatch uses to re-enable the folder grant option for an out-of-root edit. Containment uses the same descendant rule as folder-grant path scopes; the scope is the resolved real parent folder, so a symlink-escaped target is judged on its real location.
func (*EditTool) ResolveTarget ¶
ResolveTarget resolves the edit target from args before authorization and returns the resolved target file path (placed in the mode-authorization context for Auto edits' in-scope check) and the path scope to authorize (the resolved parent directory the write lands in). It uses the same expansion, cleaning, and symlink-resolution rules as permission path scopes (permissions.CleanAbsResolve), so a target naming a directory, a non-regular/missing file, or lacking a safe parent directory is rejected with a *TargetError before any prompt. Symlinks are resolved on both the parent directory and the final component, and the path scope is always the resolved *real* file's parent — so a target whose real path lands outside the project root (including an in-root lexical path that symlink-resolves outside) is not rejected but classified out-of-root (OutOfRootScope) and routed to the approval prompt, anchored on that real out-of-root folder, never the lexical one.
func (*EditTool) SubjectKind ¶
type EntryKind ¶
type EntryKind string
EntryKind is the type the list tool displays for a directory entry, classified without following symlinks.
const ( // EntryFile is a regular file. EntryFile EntryKind = "file" // EntryDir is a directory. EntryDir EntryKind = "dir" // EntrySymlink is a symbolic link, classified by its own type rather than its target's. EntrySymlink EntryKind = "symlink" // EntryOther is any other node (socket, device, named pipe). EntryOther EntryKind = "other" )
func ClassifyEntry ¶
ClassifyEntry returns path's type for the list tool using a non-following lstat, so a symlink entry is reported as EntrySymlink rather than as whatever it points to: list classifies entries by their own type, and following here would let a symlink masquerade as a regular file or directory in the listing. A regular file is EntryFile, a directory EntryDir, and anything else EntryOther.
type FileCountTool ¶
type FileCountTool struct {
// contains filtered or unexported fields
}
FileCountTool is the built-in read-only file_count tool. It walks the directory structure under an optional `path` (default project root) through the shared walker (hidden-skip by default, the ignore seam, directory-symlink no-follow), keeps the entries whose kind is selected by `kinds` and whose path passes the `include_globs`/`exclude_globs` filters, and reports either a flat total or — when `group_by` is set — a deterministic breakdown sorted by descending count then ascending key, bounded by `limit` with an `other:` remainder. It is read-only (Advertise().ReadOnly == true), so it auto-allows without prompting in every mode. The run-scoped home, process cwd, project root, and the configured tool-output byte cap are bound at construction so target resolution matches the permission path-scope rules and the truncation indicator survives the same byte cap as every other tool output.
func NewFileCountTool ¶
func NewFileCountTool(home, processCWD, projectRoot string, outputCap int) *FileCountTool
NewFileCountTool builds the file_count tool bound to the run's ~ anchor (home), the process cwd accepted for signature symmetry with the other file tools, the project root the count is confined to (via ResolveReadTarget), and the tool-output byte cap (cache.MaxToolOutputBytes()) BoundedResult uses to predict byte-cap truncation. The project root is symlink-resolved once here — mirroring NewTreeTool/NewGlobTool — so a launch through a symlinked project directory does not reject valid in-project paths; ResolveReadTarget normalizes again, so the double normalization is idempotent.
func (*FileCountTool) Advertise ¶
func (t *FileCountTool) Advertise() tools.Metadata
Advertise returns the file_count tool's advertising metadata and read-only classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly: every argument is optional. The public hidden-file flag is `include_dotfiles` (not `include_hidden`). ReadOnly is true: file_count never mutates, so it is advertised in Plan and auto-allowed in every mode.
func (*FileCountTool) ExternalReadScope ¶
func (t *FileCountTool) ExternalReadScope(scope string) bool
ExternalReadScope reports whether scope (the directory ResolveTarget returned) is an explicit out-of-project read target — the promptable case DispatchTool routes to a permission prompt while bypassing the read-only mode auto-allow. False for an in-project directory, keeping in-project counts on their existing no-prompt auto-allow.
func (*FileCountTool) Invoke ¶
Invoke resolves the directory and counts it in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithResolvedTarget; this keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry. Standalone Invoke has no authorizer, so it refuses an explicit out-of-project directory in-band: only the dispatch path can obtain the required approval. The in-project path is unchanged.
func (*FileCountTool) InvokeWithResolvedTarget ¶
func (t *FileCountTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget counts the directory dispatch already resolved before authorization, so the authorized directory and the directory actually walked cannot diverge if a symlink changes in between — the same reuse-at-invoke contract grep/glob/tree follow. It is the production invocation path; the registered tool implements it so DispatchTool reuses the authorized target.
func (*FileCountTool) Name ¶
func (t *FileCountTool) Name() string
func (*FileCountTool) ResolveTarget ¶
ResolveTarget resolves the directory to walk from args before authorization and returns it as both the resolved target and the path scope to authorize, mirroring grep/glob/tree's target-scoped contract so authorization and the directory actually walked cannot drift. It rejects unknown arguments, then delegates to ResolveReadTarget in optional-path mode (an omitted/blank `path` defaults to the project root) with RejectDirectorySymlink set so a directory-symlink `path` argument is refused fail-closed before a recursive walk could be redirected through it. Remaining argument validation is deferred to invocation.
func (*FileCountTool) SetBlockedPaths ¶
func (t *FileCountTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
func (*FileCountTool) SubjectKind ¶
func (t *FileCountTool) SubjectKind() string
type GlobTool ¶
type GlobTool struct {
// contains filtered or unexported fields
}
GlobTool is the built-in read-only glob tool. It returns the project-relative paths of filesystem entries whose path matches a glob `pattern` — including `**` recursive segments — under an optional `path` (default project root), in ascending order, bounded by a per-tool `limit` with the truncation indicator. It is read-only (Advertise().ReadOnly == true), so it auto-allows without prompting in every mode. Matching uses doublestar.Match over entries produced by the shared walker (hidden-skip by default, the ignore seam, directory-symlink no-follow); it never invokes doublestar's own filesystem traversal, so the walker stays the single source of traversal policy. The run-scoped home, process cwd, project root, and the configured tool-output byte cap are bound at construction so target resolution matches the permission path-scope rules and the truncation indicator survives the same byte cap as every other tool output.
func NewGlobTool ¶
NewGlobTool builds the glob tool bound to the run's ~ anchor (home), the process cwd accepted for signature symmetry with the other file tools, the project root the search is confined to (via ResolveReadTarget), and the tool-output byte cap (cache.MaxToolOutputBytes()) BoundedResult uses to predict byte-cap truncation. The project root is symlink-resolved once here — mirroring NewReadTool — so a launch through a symlinked project directory does not reject valid in-project paths; ResolveReadTarget normalizes again, so the double normalization is idempotent.
func (*GlobTool) Advertise ¶
Advertise returns the glob tool's advertising metadata and read-only classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly: `pattern` is the only required argument; `path`, `limit`, `include_ignored`, and `include_hidden` are optional. ReadOnly is true: glob never mutates, so it is advertised in Plan and auto-allowed in every mode.
func (*GlobTool) ExternalReadScope ¶
ExternalReadScope reports whether scope (the search root ResolveTarget returned) is an explicit out-of-project read target — the promptable case DispatchTool routes to a permission prompt while bypassing the read-only mode auto-allow. False for an in-project search root, keeping in-project globs on their existing no-prompt auto-allow.
func (*GlobTool) Invoke ¶
Invoke resolves the search root and globs it in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithResolvedTarget; this keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry. Standalone Invoke has no authorizer, so it refuses an explicit out-of-project search root in-band: only the dispatch path can obtain the required approval. The in-project path is unchanged.
func (*GlobTool) InvokeWithResolvedTarget ¶
func (t *GlobTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget runs glob over the search root dispatch already resolved before authorization, so the authorized directory and the directory actually walked cannot diverge if a symlink changes in between — the same reuse-at-invoke contract read/edit/create follow. It is the production invocation path; the registered tool implements it so DispatchTool reuses the authorized target.
func (*GlobTool) ResolveTarget ¶
ResolveTarget resolves the search root from args before authorization and returns it as both the resolved target and the path scope to authorize, mirroring read's target-scoped contract so authorization and the directory actually walked cannot drift. It rejects unknown arguments, then delegates to ResolveReadTarget in optional-path mode (an omitted/blank `path` defaults to the project root) with RejectDirectorySymlink set so a directory-symlink `path` argument is refused fail-closed before a recursive walk could be redirected through it. Pattern and numeric/boolean validation are deferred to invocation, as read defers its offset/limit checks.
func (*GlobTool) SetBlockedPaths ¶
func (t *GlobTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
func (*GlobTool) SubjectKind ¶
type GrepTool ¶
type GrepTool struct {
// contains filtered or unexported fields
}
GrepTool is the built-in read-only grep tool. It searches the contents of regular files under an optional `path` (default project root) for an RE2 regular expression `pattern`, emitting matches deterministically by ascending project-relative path then ascending line number. It is read-only (Advertise.ReadOnly == true), so it auto-allows without prompting in every mode. Traversal runs through the shared walker (hidden-skip by default, the ignore seam, directory-symlink no-follow); the walker is run unbounded so every eligible file is searched and only the *match* count is bounded by `limit` — searching a capped set of files could miss matches in later files. The run-scoped home, process cwd, project root, and the configured tool-output byte cap are bound at construction so target resolution matches the permission path-scope rules and the truncation indicator survives the same byte cap as every other tool output.
func NewGrepTool ¶
NewGrepTool builds the grep tool bound to the run's ~ anchor (home), the process cwd accepted for signature symmetry with the other file tools, the project root the search is confined to (via ResolveReadTarget), and the tool-output byte cap (cache.MaxToolOutputBytes()) BoundedResult uses to predict byte-cap truncation. The project root is symlink-resolved once here — mirroring NewGlobTool/NewReadTool — so a launch through a symlinked project directory does not reject valid in-project paths; ResolveReadTarget normalizes again, so the double normalization is idempotent.
func (*GrepTool) Advertise ¶
Advertise returns the grep tool's advertising metadata and read-only classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly: `pattern` is the only required argument. ReadOnly is true: grep never mutates, so it is advertised in Plan and auto-allowed in every mode.
func (*GrepTool) ExternalReadScope ¶
ExternalReadScope reports whether scope (the search root ResolveTarget returned) is an explicit out-of-project read target — the promptable case DispatchTool routes to a permission prompt while bypassing the read-only mode auto-allow. False for an in-project search root, keeping in-project greps on their existing no-prompt auto-allow.
func (*GrepTool) Invoke ¶
Invoke resolves the search root and greps it in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithResolvedTarget; this keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry. Standalone Invoke has no authorizer, so it refuses an explicit out-of-project search root in-band: only the dispatch path can obtain the required approval. The in-project path is unchanged.
func (*GrepTool) InvokeWithResolvedTarget ¶
func (t *GrepTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget runs grep over the search root dispatch already resolved before authorization, so the authorized directory and the directory actually walked cannot diverge if a symlink changes in between — the same reuse-at-invoke contract read/glob/edit/create follow. It is the production invocation path; the registered tool implements it so DispatchTool reuses the authorized target.
func (*GrepTool) ResolveTarget ¶
ResolveTarget resolves the search root from args before authorization and returns it as both the resolved target and the path scope to authorize, mirroring glob's target-scoped contract so authorization and the directory actually walked cannot drift. It rejects unknown arguments, then delegates to ResolveReadTarget in optional-path mode (an omitted/blank `path` defaults to the project root) with RejectDirectorySymlink set so a directory-symlink `path` argument is refused fail-closed before a recursive walk could be redirected through it. Pattern, include, and numeric/boolean validation are deferred to invocation, as glob defers its pattern/limit checks.
func (*GrepTool) SetBlockedPaths ¶
func (t *GrepTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
func (*GrepTool) SubjectKind ¶
type IgnoreMatcher ¶
type IgnoreMatcher interface {
// ShouldSkip reports whether the entry at relPath (project-root-relative, slash-separated) of the
// given kind must be excluded from the walk. Returning true for a directory both drops it from the
// results and prunes its whole subtree, because the walker consults the matcher before it descends —
// so a pruned directory's children are never enumerated or passed to any matcher.
ShouldSkip(relPath string, kind EntryKind) bool
}
IgnoreMatcher is one pluggable ignore filter the walker consults before an entry is emitted or a directory is descended into. The codemeldignore matcher registers into a Walk call's ordered WalkOptions.Ignore set; the set is empty by default, so include_ignored is a no-op when no matcher is supplied. The walker owns only this seam — it implements no ignore syntax itself.
type JournalState ¶
JournalState is the exact visible state of one declared path; absence is the zero value.
type ListTool ¶
type ListTool struct {
// contains filtered or unexported fields
}
ListTool is the built-in read-only list tool. It returns the immediate (non-recursive) entries of a directory under an optional `path` (default project root): each entry's name and type (file / dir / symlink), in deterministic ascending name order, bounded by a per-tool `limit` with the truncation indicator. It is read-only (Advertise().ReadOnly == true), so it auto-allows without prompting in every mode. It drives the shared walker in single-level mode (MaxDepth 1), so descendants are never traversed, filtered, or counted against the limit — keeping list's output, hidden/ignore filtering, and limit accounting strictly to the directory's own entries. The run-scoped home, process cwd, project root, and the configured tool-output byte cap are bound at construction so target resolution matches the permission path-scope rules and the truncation indicator survives the same byte cap as every other tool output.
func NewListTool ¶
NewListTool builds the list tool bound to the run's ~ anchor (home), the process cwd accepted for signature symmetry with the other file tools, the project root the listing is confined to (via ResolveReadTarget), and the tool-output byte cap (cache.MaxToolOutputBytes()) BoundedResult uses to predict byte-cap truncation. The project root is symlink-resolved once here — mirroring NewReadTool/NewGlobTool — so a launch through a symlinked project directory does not reject valid in-project paths; ResolveReadTarget normalizes again, so the double normalization is idempotent.
func (*ListTool) Advertise ¶
Advertise returns the list tool's advertising metadata and read-only classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly: every argument is optional — `path`, `limit`, `include_ignored`, and `include_hidden`. ReadOnly is true: list never mutates, so it is advertised in Plan and auto-allowed in every mode.
func (*ListTool) ExternalReadScope ¶
ExternalReadScope reports whether scope (the directory ResolveTarget returned) is an explicit out-of-project read target — the promptable case DispatchTool routes to a permission prompt while bypassing the read-only mode auto-allow. False for an in-project directory, keeping in-project lists on their existing no-prompt auto-allow.
func (*ListTool) Invoke ¶
Invoke resolves the directory and lists it in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithResolvedTarget; this keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry. Standalone Invoke has no authorizer, so it refuses an explicit out-of-project directory in-band: only the dispatch path can obtain the required approval. The in-project path is unchanged.
func (*ListTool) InvokeWithResolvedTarget ¶
func (t *ListTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget lists the directory dispatch already resolved before authorization, so the authorized directory and the directory actually listed cannot diverge if a symlink changes in between — the same reuse-at-invoke contract read/glob/edit/create follow. It is the production invocation path; the registered tool implements it so DispatchTool reuses the authorized target.
func (*ListTool) ResolveTarget ¶
ResolveTarget resolves the directory to list from args before authorization and returns it as both the resolved target and the path scope to authorize, mirroring read/glob's target-scoped contract so authorization and the directory actually listed cannot drift. It rejects unknown arguments, then delegates to ResolveReadTarget in optional-path mode (an omitted/blank `path` defaults to the project root). RejectDirectorySymlink is left false: list is non-recursive, so a symlinked directory `path` is resolved to its real in-root target and listed (the resolver still rejects a symlink that escapes the root), while a symlink *entry inside* the listed directory is reported as type symlink and never descended. Numeric/boolean validation is deferred to invocation, as read/glob defer theirs.
func (*ListTool) SetBlockedPaths ¶
func (t *ListTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
func (*ListTool) SubjectKind ¶
type MkdirTool ¶
type MkdirTool struct {
// contains filtered or unexported fields
}
MkdirTool is the built-in mutating mkdir tool. It creates a directory and any missing parent directories (mkdir -p semantics) at a target inside the project root, authorizing the deepest already-existing ancestor directory as the path scope and creating the missing suffix fd-relative to a held descriptor on that ancestor (so a symlink or rename swapped in after authorization cannot redirect the create out of the authorized tree). It mirrors CreateTool's resolve-once / reuse-at-invoke contract. The run-scoped home, process cwd, and project root are bound at construction so the same expansion/cleaning/symlink/ containment rules used for permission path scopes resolve the target.
func NewMkdirTool ¶
NewMkdirTool builds the mkdir tool bound to the run's ~ anchor (home), the default base directory for a relative target path (processCWD), and the project root the authorized scope is classified against. The project root is symlink-resolved once here so the containment check compares it against the symlink-resolved ancestor on the same footing — mirroring NewCreateTool.
func (*MkdirTool) Advertise ¶
Advertise returns the mkdir tool's advertising metadata and mutating classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly. ReadOnly is false: mkdir mutates the filesystem.
func (*MkdirTool) Invoke ¶
Invoke resolves the target and creates the directory tree in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithAuthorizedScope; this method keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render.
Standalone Invoke has no authorizer, so it must enforce the out-of-root boundary itself: an out-of-root resolved scope (OutOfRootScope) is reachable only by explicit per-write approval, which only the dispatch path can obtain. This method therefore refuses an out-of-root target in-band rather than creating it. The in-root path is unchanged.
func (*MkdirTool) InvokeWithAuthorizedScope ¶
func (t *MkdirTool) InvokeWithAuthorizedScope(_ context.Context, args map[string]any, resolvedTarget string, scope string, scopeIdentity ScopeIdentity) (tools.Result, error)
InvokeWithAuthorizedScope is the seam production dispatch invokes the mkdir tool through. scope is the ancestor that was authorized, not the deepest ancestor existing now, so an intermediate directory legitimately created while the prompt was open does not read as a replacement. The confirmed scope descriptor is held for the whole mkdir and the ancestor the create runs under is reached through it.
func (*MkdirTool) InvokeWithResolvedTarget ¶
func (t *MkdirTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget creates the directory tree at the target dispatch already resolved before authorization, so authorization and the create cannot drift if a symlink changes in between — the same resolved-before-authorize / reuse-at-invoke contract the create tool follows. Production dispatch routes the mkdir tool through InvokeWithAuthorizedScope instead, which carries the authorized directory's identity as well as the target.
func (*MkdirTool) OutOfRootScope ¶
OutOfRootScope reports whether scope (the resolved path scope ResolveTarget returned) lies outside this tool's project root — the out-of-root signal dispatch uses to re-enable the folder grant option for an out-of-root mkdir. Containment uses the same descendant rule as folder-grant path scopes; the scope is the resolved real deepest-existing ancestor, so the verdict matches the folder the new directories would land in.
func (*MkdirTool) ResolveTarget ¶
ResolveTarget resolves the mkdir target from args before authorization. It rejects unknown arguments and a missing/non-string/empty path, expands and anchors the path, then walks to the deepest existing ancestor directory and symlink-resolves it as the authorized path scope (the directory the new subdirectories are created under). The resolved target is the real directory path that will exist after the create; the scope is the resolved deepest-existing ancestor. A malformed path, an unsupported ~user form, or an existing non-directory component is rejected with a *TargetError before any prompt. An out-of-root resolved scope is not rejected: it is classified out-of-root (OutOfRootScope) and routed to the approval prompt, anchored on its real out-of-root ancestor folder.
func (*MkdirTool) SubjectKind ¶
type MoveTool ¶
type MoveTool struct {
// contains filtered or unexported fields
}
MoveTool is the built-in mutating move tool. It renames an existing entry (a regular file or a directory) to a destination that must not already exist in any form, never overwriting and never following a final symlink on either endpoint. Unlike the single-target file tools, a move carries two independent endpoints — a source to read-and-relocate and a destination to create — each with its own resolved real path and authorization scope, so it resolves and classifies them separately rather than through the single-target seam. The run-scoped home, process cwd, and project root are bound at construction so the same expansion/cleaning/symlink/containment rules used for permission path scopes resolve both endpoints, exactly as the edit and create tools resolve their single target.
func NewMoveTool ¶
NewMoveTool builds the move tool bound to the run's ~ anchor (home), the default base directory for a relative endpoint path (processCWD), and the project root both endpoints are classified against. The project root is symlink-resolved once here so the containment check compares it against the symlink-resolved parent on the same footing — mirroring NewEditTool and NewCreateTool.
func (*MoveTool) Advertise ¶
Advertise returns the move tool's advertising metadata and mutating classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false and a closed {source, destination} object so the advertised contract matches the decoder exactly. ReadOnly is false: move mutates the filesystem, so Plan denies it and Auto edits may auto-approve it.
func (*MoveTool) Invoke ¶
Invoke resolves both endpoints and moves in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveEndpoints then InvokeWithResolvedMove; this method keeps the tool a complete unit for direct callers and tests. An endpoint-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry.
Standalone Invoke has no authorizer, so it must enforce the out-of-root boundary itself: an out-of-root source or destination (OutOfRootScope) is reachable only by explicit per-write approval, which only the dispatch path can obtain. This method therefore refuses either out-of-root endpoint in-band rather than moving — without this guard a direct caller could read from or write to outside the project root with no prompt or grant check. The fully in-root path is unchanged.
func (*MoveTool) InvokeWithResolvedMove ¶
func (t *MoveTool) InvokeWithResolvedMove(ctx context.Context, resolved ResolvedMove) (tools.Result, error)
InvokeWithResolvedMove performs the rename for the endpoint pair dispatch already resolved and authorized, so authorization and the rename cannot drift if a symlink changes in between — the resolved-before-authorize / reuse-at-invoke contract the edit and create tools follow, extended to two endpoints. It is the pre-resolved invocation path the multi-scope dispatch seam will call after authorizing both scopes; it does not re-resolve the lexical arguments.
func (*MoveTool) OutOfRootScope ¶
OutOfRootScope reports whether scope (a resolved endpoint scope ResolveEndpoints returned) lies outside this tool's project root — the out-of-root signal dispatch uses to re-enable the folder grant option for an out-of-root move endpoint. Containment uses the same descendant rule as folder-grant path scopes; the scope is the resolved real parent directory, so the verdict matches the folder the read/write touches.
func (*MoveTool) ResolveEndpoints ¶
func (t *MoveTool) ResolveEndpoints(args map[string]any) (ResolvedMove, error)
ResolveEndpoints resolves both move endpoints from args before authorization and classifies each against the project root. It rejects unknown arguments, then anchors each endpoint's parent through the shared symlink-resolving parent resolver, refuses a source and destination that resolve to the same path, applies the source (must-exist, non-symlink) and destination (must-not-exist) preconditions, and returns the resolved real paths and authorization scopes for both endpoints together with their out-of-root verdicts. A missing source, an existing destination, a missing destination parent, a final source symlink, an existing destination node of any kind, a basename escaping its resolved parent, an unsupported ~user form, or an invalid argument is rejected with a *TargetError before any prompt. An out-of-root endpoint is not rejected here: it is classified out-of-root and routed to the approval prompt, anchored on its real out-of-root parent folder.
func (*MoveTool) SubjectKind ¶
type NumericArgs ¶
NumericArgs carries the already-extracted numeric arguments a read-only tool wants validated together. A nil field means that argument is absent for this tool/call and is skipped; Max is the tool's product-maximum limit, consulted only when Limit is non-nil.
type OutputMeta ¶
type OutputMeta struct {
// Truncated reports whether any output was withheld, by either a per-tool limit or the byte cap.
Truncated bool `json:"truncated"`
// TruncationReason is byte_cap or limit when Truncated, empty otherwise.
TruncationReason string `json:"truncation_reason,omitempty"`
// RetryHint is a short, model-facing suggestion for narrowing or paginating, set only when Truncated.
RetryHint string `json:"retry_hint,omitempty"`
// NextOffset is read's 1-based line to resume from when more lines remain; 0/omitted for the
// entry-listing tools and when no more lines remain.
NextOffset int `json:"next_offset,omitempty"`
}
OutputMeta is the per-result truncation/continuation metadata every successful read-only tool result carries. BoundedResult serializes it as compact JSON on a single MetadataPrefix line at the start of stderr; see that helper for why it lives there rather than in stdout. Truncated is always emitted; the remaining fields are present only when meaningful (omitempty).
type ReadImageTool ¶
type ReadImageTool struct {
// contains filtered or unexported fields
}
func NewReadImageTool ¶
func NewReadImageTool(home, processCWD, projectRoot string, outputCap int) *ReadImageTool
func (*ReadImageTool) Advertise ¶
func (t *ReadImageTool) Advertise() tools.Metadata
func (*ReadImageTool) ExternalReadScope ¶
func (t *ReadImageTool) ExternalReadScope(scope string) bool
func (*ReadImageTool) InvokeWithResolvedTarget ¶
func (*ReadImageTool) Name ¶
func (t *ReadImageTool) Name() string
func (*ReadImageTool) ResolveTarget ¶
func (*ReadImageTool) SubjectKind ¶
func (t *ReadImageTool) SubjectKind() string
type ReadTargetOptions ¶
type ReadTargetOptions struct {
// RequirePath selects the argument mode: read sets it true, so a missing or empty `path` is
// an in-band rejection; grep, glob, list, and tree leave it false, so an omitted (or empty) `path`
// defaults to the project root.
RequirePath bool
// Kind names the calling tool ("read", "grep", "glob", "list", "tree") for the *TargetError message;
// an empty Kind falls back to the generic "file" label in TargetError.Error.
Kind string
// RejectDirectorySymlink makes a final-component symlink that resolves to a directory an in-band
// rejection: the recursive tools (grep, glob, tree) set it true so a directory-symlink path
// argument is refused fail-closed *before* the resolver collapses it to the real directory, instead of
// being handed back as an ordinary directory target they would then walk. read and list leave it false:
// read targets regular files (a directory target is rejected downstream), and list reports a symlink as
// type symlink without descending. Symlinks encountered *within* a recursive walk are handled separately
// by IsDirectorySymlink; this option governs only the top-level path argument.
RejectDirectorySymlink bool
// AllowExternal opts the caller into promptable out-of-project read resolution: when set, an
// EXPLICIT absolute or ~ path whose parent resolves outside the project root is resolved to a concrete
// out-of-root target and scope instead of being rejected, so dispatch can route it to a permission
// prompt. It is the five dispatch read tools' mode; every other structural rejection (a relative ".."
// escape, a ~user form, an in-root path whose final symlink escapes the root, a directory-symlink root
// for the recursive tools, an unresolvable parent) stays fail-closed regardless. Left false by callers
// that must keep the unconditional root-confinement — notably the image-attachment resolver, which has
// no permission prompt to gate an out-of-root read — so their behavior is unchanged.
AllowExternal bool
}
ReadTargetOptions configures ResolveReadTarget's argument handling for a specific read-only tool.
type ReadTool ¶
type ReadTool struct {
// contains filtered or unexported fields
}
ReadTool is the built-in read-only read tool. It returns the verbatim contents of a regular in-project file, paginated by line with no injected line numbers (the exact-text contract shared with edit/create); pagination/truncation metadata is carried in the prefixed-stderr channel, never appended to the content. It is read-only (Advertise().ReadOnly == true), so it auto-allows without prompting in every mode. Unlike the recursive navigation tools it is exempt from hidden filtering — an explicit path is honored even if hidden — because it never routes through the hidden-filtering walker. The run-scoped home, process cwd, project root, and the configured tool-output byte cap are bound at construction so target resolution matches the permission path-scope rules and the truncation indicator survives the same byte cap as every other tool output.
func NewReadTool ¶
NewReadTool builds the read tool bound to the run's ~ anchor (home), the process cwd accepted for signature symmetry with the other file tools, the project root the target must stay within (via ResolveReadTarget), and the tool-output byte cap (cache.MaxToolOutputBytes()) BoundedResult uses to predict byte-cap truncation. The project root is symlink-resolved once here — mirroring NewEditTool/NewCreateTool — so a launch through a symlinked project directory does not reject valid in-project reads; ResolveReadTarget normalizes again, so the double normalization is idempotent.
func (*ReadTool) Advertise ¶
Advertise returns the read tool's advertising metadata and read-only classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly: `path` is the only required argument; `offset` and `limit` are optional integers. ReadOnly is true: read never mutates, so it is advertised in Plan and auto-allowed in every mode.
func (*ReadTool) ExternalReadScope ¶
ExternalReadScope reports whether scope (the path scope ResolveTarget returned) is an explicit out-of-project read target — the promptable case DispatchTool routes to a permission prompt while bypassing the read-only mode auto-allow. It mirrors the mutating tools' OutOfRootScope but drives the read-only external posture, never a write grant. An in-project scope returns false, keeping in-project reads on their existing no-prompt auto-allow.
func (*ReadTool) Invoke ¶
Invoke resolves the target and reads it in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithResolvedTarget; this keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry. Standalone Invoke has no authorizer, so it must enforce the out-of-project boundary itself: an explicit out-of-project read target (ExternalReadScope) is reachable only by per-read approval, which only the dispatch path can obtain. This method refuses it in-band rather than reading; the in-project path is unchanged. InvokeWithResolvedTarget carries no such guard because dispatch already authorized the read.
func (*ReadTool) InvokeWithResolvedTarget ¶
func (t *ReadTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget reads the target dispatch already resolved before authorization, so the authorized path and the path actually read cannot diverge if a symlink changes in between — the same reuse-at-invoke contract edit/create follow. It is the production invocation path; the registered tool implements it so DispatchTool reuses the authorized target.
func (*ReadTool) ResolveTarget ¶
ResolveTarget resolves the read target from args before authorization and returns the resolved target path (placed in the mode-auth context) and the path scope to authorize, mirroring the edit/create target-scoped contract so authorization and the path actually read cannot drift. It rejects unknown arguments, then delegates to ResolveReadTarget in required-path mode (a missing/empty `path` is an in-band rejection) without rejecting directory symlinks (read targets a regular file, which is enforced at invocation by ResolveReadFile, not here). The directory/non-regular/missing rejection is deferred to InvokeWithResolvedTarget, where the read-tool symlink policy is applied to the resolved target immediately before the read.
func (*ReadTool) SubjectKind ¶
type ResolvedMove ¶
type ResolvedMove struct {
Source string
SourceScope string
SourceOutOfRoot bool
Destination string
DestinationScope string
DestinationOutOfRoot bool
}
ResolvedMove is the move-specific two-endpoint handoff the multi-scope dispatch seam consumes. It carries each endpoint's resolved real path, the authorization scope to grant (the resolved real parent directory the read/write touches), and whether that scope lies outside the project root (the promptable out-of-root signal). The two scopes are kept distinct so dispatch can authorize each separately — a move can have an in-root source and an out-of-root destination, or vice versa — and so a grant on one endpoint can never silently widen to the other. ResolveEndpoints produces it; the pre-resolved InvokeWithResolvedMove consumes the same value so authorization and the rename cannot drift if a symlink changes in between.
type ScopeIdentity ¶
type ScopeIdentity struct {
// contains filtered or unexported fields
}
ScopeIdentity is the directory an authorization was granted for, recorded as an inode rather than a path so a different folder standing at that path by the time the approval arrives is still distinguishable. Its zero value is never a captured directory, so it can be refused rather than compared.
func CaptureScopeIdentity ¶
func CaptureScopeIdentity(scope string) (ScopeIdentity, error)
CaptureScopeIdentity requires scope to be already symlink-resolved: the open does not follow a final symlink, so a link standing at that path is a capture failure rather than a capture of its target.
type TargetError ¶
TargetError reports that a file tool's target path could not be resolved to a safe target. It is the fail-closed result of the structural conditions — no safe parent directory, the unsupported ~user form — plus each tool's own precondition (edit: missing/directory/non-regular target; create: an already-existing target or a basename that escapes its resolved parent; the read-only tools: an out-of-root path, a symlink escape, or an unresolvable type). For the mutating edit/create tools an out-of-root resolved *real* target is no longer a TargetError: it is a promptable case routed to the approval prompt, not a pre-authorization rejection, so only the structural failures above abort before authorization — and such a structural call is never authorized or executed (so never auto-approved). The read-only tools keep their unconditional root-confinement, so an out-of-root read target is still a TargetError before any content read (invariant unchanged). The production dispatcher surfaces it to the model in-band so the model can correct the path rather than aborting the turn. Kind names the rejecting tool ("edit", "create", "read", "grep", "glob", "list", "tree") so the model-facing message points at the right tool.
func ValidateNumericArgs ¶
func ValidateNumericArgs(kind string, a NumericArgs) *TargetError
ValidateNumericArgs enforces the shared numeric contract: offset >= 1, limit in [1, Max] context >= 0, and depth >= 0. It returns the first violation as a *TargetError tagged with kind (the calling tool name) so dispatch surfaces it in-band and the model can correct it; an over-maximum limit names the maximum. No value is ever silently clamped, and a nil field is skipped. It returns nil when every present argument is in range.
func (*TargetError) Error ¶
func (e *TargetError) Error() string
type TreeTool ¶
type TreeTool struct {
// contains filtered or unexported fields
}
TreeTool is the built-in read-only tree tool. It returns a recursive listing of the directory structure under an optional `path` (default project root), in deterministic ascending project-relative (pre-order) path order, to an optional maximum descendant `depth`, bounded by a total-entry `limit` with the truncation indicator. Display is selected by `format`: `ai` (the default) emits one project-relative, slash-normalized path per line; `human` emits the indented basename tree. It is read-only (Advertise().ReadOnly == true), so it auto-allows without prompting in every mode. Traversal runs through the shared walker (hidden-skip by default, the ignore seam directory-symlink no-follow), consuming the walker's pre-order entries directly so the human-format indentation matches the hierarchy; `depth` and the total-entry `limit` are both applied by the walker, so deeper-than-depth descendants never consume the visible entry budget and exactly `limit` entries are emitted before truncation is signalled. The run-scoped home, process cwd, project root, and the configured tool-output byte cap are bound at construction so target resolution matches the permission path-scope rules and the truncation indicator survives the same byte cap as every other tool output.
func NewTreeTool ¶
NewTreeTool builds the tree tool bound to the run's ~ anchor (home), the process cwd accepted for signature symmetry with the other file tools, the project root the listing is confined to (via ResolveReadTarget), and the tool-output byte cap (cache.MaxToolOutputBytes()) BoundedResult uses to predict byte-cap truncation. The project root is symlink-resolved once here — mirroring NewListTool/NewGrepTool — so a launch through a symlinked project directory does not reject valid in-project paths; ResolveReadTarget normalizes again, so the double normalization is idempotent.
func (*TreeTool) Advertise ¶
Advertise returns the tree tool's advertising metadata and read-only classification. The parameter JSON-Schema is the OpenAI function-tool shape with additionalProperties:false so the advertised contract matches the decoder exactly: every argument is optional — `path`, `depth`, `limit`, `format`, `include_ignored`, and `include_hidden`. `format` is a closed enum (`ai`, `human`) defaulting to `ai`. ReadOnly is true: tree never mutates, so it is advertised in Plan and auto-allowed in every mode.
func (*TreeTool) ExternalReadScope ¶
ExternalReadScope reports whether scope (the directory ResolveTarget returned) is an explicit out-of-project read target — the promptable case DispatchTool routes to a permission prompt while bypassing the read-only mode auto-allow. False for an in-project directory, keeping in-project trees on their existing no-prompt auto-allow.
func (*TreeTool) Invoke ¶
Invoke resolves the directory and walks it in one call (the standalone, non-dispatch path). Production dispatch instead calls ResolveTarget then InvokeWithResolvedTarget; this keeps the tool a complete unit for direct callers and tests. A target-resolution failure surfaces as the same in-band error result the dispatcher would render, so the observable outcome matches either entry. Standalone Invoke has no authorizer, so it refuses an explicit out-of-project directory in-band: only the dispatch path can obtain the required approval. The in-project path is unchanged.
func (*TreeTool) InvokeWithResolvedTarget ¶
func (t *TreeTool) InvokeWithResolvedTarget(_ context.Context, args map[string]any, resolvedTarget string) (tools.Result, error)
InvokeWithResolvedTarget walks the directory dispatch already resolved before authorization, so the authorized directory and the directory actually walked cannot diverge if a symlink changes in between — the same reuse-at-invoke contract grep/list/read/glob/edit/create follow. It is the production invocation path; the registered tool implements it so DispatchTool reuses the authorized target.
func (*TreeTool) ResolveTarget ¶
ResolveTarget resolves the directory to walk from args before authorization and returns it as both the resolved target and the path scope to authorize, mirroring grep/list's target-scoped contract so authorization and the directory actually walked cannot drift. It rejects unknown arguments, then delegates to ResolveReadTarget in optional-path mode (an omitted/blank `path` defaults to the project root) with RejectDirectorySymlink set so a directory-symlink `path` argument is refused fail-closed before a recursive walk could be redirected through it. Numeric/boolean validation is deferred to invocation, as grep/list defer theirs.
func (*TreeTool) SetBlockedPaths ¶
func (t *TreeTool) SetBlockedPaths(blocked *permissions.BlockedPaths)
func (*TreeTool) SubjectKind ¶
type WalkEntry ¶
type WalkEntry struct {
// RelPath is the entry's project-root-relative, slash-separated path. It is the deterministic
// ordering key and the stable identifier consumer tools format and matchers filter on.
RelPath string
// AbsPath is the entry's absolute filesystem path.
AbsPath string
// Kind is the entry's own type (file / dir / symlink / other), classified by an lstat-equivalent that
// never resolves a symlink to its target, mirroring ClassifyEntry.
Kind EntryKind
// Depth is the entry's depth below Root; Root's immediate children are depth 1.
Depth int
}
WalkEntry is one traversed filesystem entry, classified without following symlinks.
func Walk ¶
func Walk(opts WalkOptions) (entries []WalkEntry, truncated bool, err error)
Walk traverses opts.Root depth-first and returns its entries in deterministic order — each directory's children ascending by name, every directory emitted before its descendants — classified without following symlinks. It skips dot-prefixed (hidden) entries, and does not descend hidden directories, unless opts.IncludeHidden. Unless opts.IncludeIgnored, it consults the ordered opts.Ignore matchers before emitting or descending into each entry, so a matcher can both drop an entry and prune a directory subtree; with no matchers registered this is a no-op. A symlink is always reported as an EntrySymlink entry and never descended into, so a directory symlink cannot redirect a recursive walk.
Output is bounded by opts.Limit: when more entries exist than the limit admits, Walk emits exactly Limit of them in order and returns truncated=true, so the caller can surface the truncation signal — Walk reports only this logical-limit fact and does not format any metadata itself (that is the BoundedResult helper's job; the global byte cap is applied separately downstream). A directory that cannot be read (including a non-existent or non-directory Root) aborts the walk and is returned as the error value, so a consumer surfaces it in-band and model-correctable rather than aborting the turn.
type WalkOptions ¶
type WalkOptions struct {
// Root is the absolute, already-resolved directory to traverse. It is not emitted; its entries are.
Root string
// ProjectRoot is the absolute project root each emitted entry's RelPath is computed against.
ProjectRoot string
// Limit caps the number of entries emitted; a value <= 0 means unbounded. When more entries exist
// than the limit admits, Walk emits exactly Limit of them and reports truncated=true.
Limit int
// MaxDepth caps how deep below Root the walk descends: 0 means unlimited, 1 means immediate children
// only (the non-recursive list tool), N means down to depth N. A directory at the depth cap is still
// emitted as an entry but not descended into.
MaxDepth int
// IncludeHidden, when true, disables the default dot-entry filter so dot-prefixed files and
// directories are emitted and descended into.
IncludeHidden bool
// IncludeIgnored, when true, disables the ignore filters by not consulting Ignore at all — a
// no-op while Ignore is empty, the disable switch once the codemeldignore matcher is registered.
IncludeIgnored bool
// Ignore is the ordered set of ignore matchers, empty by default. The codemeldignore matcher populates
// it; matchers run in order and the first to skip an entry wins.
Ignore []IgnoreMatcher
Blocked *permissions.BlockedPaths
OnBlocked func(absPath string)
}
WalkOptions configures a single Walk traversal. Root and ProjectRoot are absolute: Root is the already-resolved subtree to traverse (normally ResolveReadTarget's result) and is not itself emitted, while ProjectRoot anchors the project-relative paths used for ordering, matcher input, and output. The two differ when a tool is invoked on a subdirectory of the project (RelPath stays project-root-relative, so the codemeldignore matcher sees repo-root-relative paths regardless of the traversal root).
type WalkVisitor ¶
type WriteJournal ¶
type WriteJournal interface {
Covers(target string) bool
MaxStateBytes() int64
BeginMutation(target string, observed, next JournalState) error
BeginDelete(target string, parentDir *os.File) error
ResolveMutation(target string, parentDir *os.File, published JournalState)
AbandonMutation(target string, parentDir *os.File)
RemovePublished(target string, parentDir *os.File, published JournalState)
DirLifecycle(target, ancestor string) fsutil.DirLifecycle
MoveLifecycle(source, destination string) fsutil.MoveLifecycle
}
WriteJournal is the durable rollback record a delegated child agent's structured writes report to. A mutating tool consults it only for a path it covers, and the call order is fixed:
BeginMutation records the state publication is about to make visible and refuses the mutation when observed does not equal the durable last-known child state, so an external change made after the snapshot is never absorbed as the child's own; its error prevents the publish. ResolveMutation runs once the publish and any post-publication compensation have finished, through the same held parent descriptor the publish ran through, and is told the identity it published so a byte-identical file another writer put at the name in that window is not read as the child's own. AbandonMutation resolves the record instead whenever the write never published (the helpers report that as a nil published identity): the pending bytes then belong to nobody, so a file that happens to hold them was put there by someone else and must not be taken as the child's own work. RemovePublished is the only route by which a caller may take back a create it published, because it removes the inode named by published rather than whatever answers to the name.
BeginDelete reads the entry the held parent descriptor shows, refuses when it is not the durable last-known child state, and records expected absence before the caller unlinks; ResolveMutation then settles a completed unlink and AbandonMutation one that failed. An entry replaced after that comparison and before the unlink is removed without being detected.
MoveLifecycle brackets one rename: it refuses a source and destination the journal does not declare as one pair, or endpoints that are not their durable last-known child states, and settles the move once the rename has run.
func WriteJournalFor ¶
func WriteJournalFor(ctx context.Context, target string) WriteJournal
WriteJournalFor returns the journal bound to ctx when it covers target, and nil otherwise, so an undeclared path and an unjournalled call take the same unrecorded route.
func WriteJournalFromContext ¶
func WriteJournalFromContext(ctx context.Context) WriteJournal
WriteJournalFromContext returns the journal bound to ctx, or nil when the call is not running under a child agent.