Documentation
¶
Index ¶
- Constants
- Variables
- func CheckPathContainment(resolved, base string) error
- func DetectProjectType(dir string) string
- func EnsureProjectRegistered(rootPath string) bool
- func EnvBytesOr(key string, fallback int64) int64
- func EnvDurationOr(key string, fallback time.Duration) time.Duration
- func EnvIntOr(key string, fallback int) int
- func EnvOr(key, fallback string) string
- func LoadProjectRegistry() (map[string]string, error)
- func MacheHomeDir() (string, error)
- func RegisterAllEditors(w io.Writer, binaryPath string, claudeRegister func(string) bool)
- func RegisterClaudeCodeCLI(binaryPath string) bool
- func RegisterProject(absPath string) (string, error)
- func ResolveDataSource(sourcePath, configDir string) (string, error)
- func ResolveProjectToken(token string) (string, bool)
- func ResolveSchema(schemaRef, configDir string) (*api.Topology, error)
- func WriteClaudeMCPConfig(projectDir, projectToken string) error
- func WriteClaudeMD(projectDir, schemaPreset string) error
- type ProjectConfig
- type SourceConfig
Constants ¶
const ConfigFileName = ".mache.json"
ConfigFileName is the convention file looked up by `mache serve`.
Variables ¶
var ( MacheHTTPListen = EnvOr("MACHE_DAEMON_LISTEN", "localhost:7532") MacheHTTPURL = "http://" + MacheHTTPListen + "/mcp" )
Canonical MCP transport endpoint. mache serves Streamable HTTP on this address, and onboarding registers clients against it. Re-homed here from cmd/daemon_agent.go (R2, mache-96c378): editor/MCP-config generation needs the endpoint, and reaching upward into cmd for it was the other would-be import cycle. The daemon lifecycle code in cmd reads these same values, so there is exactly one answer to "where does mache listen".
Overridable via environment so a second, isolated daemon can coexist with the canonical one (the hermetic launchd E2E, or an operator's canary).
Functions ¶
func CheckPathContainment ¶
CheckPathContainment verifies that resolved is within or equal to base. Prevents path traversal attacks from untrusted .mache.json files.
Resolves symlinks via filepath.EvalSymlinks before comparison so a target pointing to a symlink that escapes the project directory is caught even if the symlink itself sits inside the project. If the target does not exist yet (EvalSymlinks errors), falls back to filepath.Abs since nonexistent paths cannot point anywhere malicious until they are created.
func DetectProjectType ¶
DetectProjectType scans a directory and returns the best-fit schema preset name. Checks sentinel files first (go.mod, pyproject.toml, etc.), then falls back to counting file extensions in the top-level directory. Returns empty string if no preset matches (caller should omit schema).
func EnsureProjectRegistered ¶
EnsureProjectRegistered records rootPath in the local registry if it is not already there, and reports whether a write happened.
This is what makes registration a BYPRODUCT of serving rather than a separate setup step. Before it, RegisterProject had exactly one caller — `mache init` — so a daemon could serve a project a hundred times and ~/.mache/projects.json would still not exist. That is not hypothetical: a long-running shared daemon was found serving this repo with an empty registry, so every ?project= lookup necessarily missed (mache-6ec106's resolution path was in place but had nothing to resolve against).
Now any session that resolves a workspace root by ANY means — client roots, an explicit --path — registers it, so the token exists for the next client that cannot answer roots/list. `mache init` keeps its distinct job: writing the ?project= token into the client's config, which a server cannot do.
Deliberately non-fatal and quiet on failure. A read-only HOME or a corrupt registry must not take down a session that is otherwise resolving fine — serving the graph is the job; registration is an optimization for later. The existence check keeps the steady state read-only: re-registering on every tool call would rewrite the file constantly for no gain.
func EnvBytesOr ¶
EnvBytesOr parses key as a non-negative byte count, or returns fallback. Separate from EnvIntOr because a size budget is int64 and may legitimately be zero, which EnvIntOr's positive-only rule would reject.
func EnvDurationOr ¶
EnvDurationOr parses key as a Go duration, or returns fallback when unset, unparseable, or non-positive. Lives here beside EnvOr because the daemon lifecycle's tunables (settle, drain, breaker window) are read from three different packages, and three copies of this parser is exactly what the duplicate-code gate exists to prevent.
func LoadProjectRegistry ¶
LoadProjectRegistry reads the token -> path map, returning an empty map (not an error) when the registry has not been created yet.
func MacheHomeDir ¶
MacheHomeDir returns ~/.mache, creating it if necessary. Exported so other packages that keep per-machine daemon state (internal/daemonguard) resolve it through the SAME seam — which means they inherit the test-hermeticity guard below for free, rather than each re-deriving a path that tests can pollute (mache-3e78d2).
func RegisterAllEditors ¶
RegisterAllEditors registers mache with all detected editors. claudeRegister performs the Claude Code CLI registration — production callers pass RegisterClaudeCodeCLI; tests pass a stub.
func RegisterClaudeCodeCLI ¶
RegisterClaudeCodeCLI registers mache via `claude mcp add` if the CLI is available. Callers pass it (or a stub) to RegisterAllEditors — a parameter, not a package var, so tests in different packages can never race on one shared global (B14/B19 in the decomposition review).
func RegisterProject ¶
RegisterProject records absPath in the local registry under its derived token and returns the token. Idempotent: re-registering the same path (e.g. re-running `mache init`) reproduces the same token and simply overwrites its own entry — every other project's entry is preserved. That preservation is also the migration policy for tokens minted before path canonicalization: adding the canonical token does not delete the old spelling, so already-written client URLs keep resolving during the grace period. New registrations converge on the canonical token.
func ResolveDataSource ¶
ResolveDataSource resolves a data source path relative to configDir. Absolute paths are returned as-is. Relative paths are checked for containment within configDir to prevent path traversal.
func ResolveProjectToken ¶
ResolveProjectToken looks up token in the local registry, returning the absolute path it was registered against. ok is false when the token is unknown — whether guessed, stale, or from a registry that was wiped after the client's config was written.
func ResolveSchema ¶
ResolveSchema resolves a schema reference to a Topology. It handles three forms:
- Preset name: "go", "python", etc.
- Relative path: "./custom-schema.json" (resolved against configDir)
- Absolute path: "/path/to/schema.json"
Returns nil if schemaRef is empty (caller should use inference or default).
func WriteClaudeMCPConfig ¶
WriteClaudeMCPConfig writes or merges a mache entry into .claude/mcp.json. Uses map[string]any as root type to preserve unknown top-level keys. projectToken is forwarded to httpServerEntry — see its doc comment.
func WriteClaudeMD ¶
WriteClaudeMD writes a .claude/CLAUDE.md that describes the mache MCP tools so Claude Code automatically knows how to use them.
Types ¶
type ProjectConfig ¶
type ProjectConfig struct {
Sources []SourceConfig `json:"sources"`
// SmellRulesDir points at a directory of external SmellRule JSON
// files (see LoadExternalSmellRules). A relative value is resolved
// against the .mache.json's own directory for portability; see
// resolveSmellRulesDir. Optional — omit to use built-in rules only.
SmellRulesDir string `json:"smellRulesDir,omitempty"`
}
ProjectConfig represents the .mache.json project configuration.
func LoadProjectConfig ¶
func LoadProjectConfig(dir string) (*ProjectConfig, error)
LoadProjectConfig reads .mache.json from the given directory.
type SourceConfig ¶
SourceConfig describes a single data source within a project.