Documentation
¶
Overview ¶
Package framework implements the project-side install machinery for apex_process_framework: copying skills + pipelines from a checked-out framework repo into a project root, seeding _apex/config.yaml on first run, and writing _apex/framework.yaml metadata.
Index ¶
- Constants
- Variables
- func AtomicWriteFile(path string, data []byte, mode os.FileMode) error
- func CopyFile(src, dst string) error
- func CopyTree(src, dst string) (filesCopied int, err error)
- func CurrentBranch(ctx context.Context, repoDir string) (string, error)
- func DefaultProjectName(projectRoot string) string
- func EnsureGitAvailable() error
- func ExactTag(ctx context.Context, repoDir string) (string, error)
- func ExtensionIDs() []string
- func FetchAndFastForward(ctx context.Context, repoDir, branch string) error
- func HeadSHA(ctx context.Context, repoDir string) (string, error)
- func IsClean(ctx context.Context, repoDir string) (bool, error)
- func IsFrameworkSkill(name string) bool
- func IsGitRepo(ctx context.Context, dir string) bool
- func IsKnownExtension(id string) bool
- func ListInstalledSkills(dir string) ([]string, error)
- func MetadataPath(projectRoot string) string
- func ProjectSkillsPath(projectRoot string) string
- func RemoteOrigin(ctx context.Context, repoDir string) (string, error)
- func UserSkillsPath() string
- func WriteMetadata(projectRoot string, m *Metadata) error
- type AlreadyInstalledError
- type ApeInfo
- type BootstrapValues
- type Bootstrapper
- type ConfigLocalExampleSource
- type ConfigSource
- type Drift
- type Extension
- type Metadata
- type NoopBootstrapper
- type NotInstalledError
- type PipelinesSource
- type PorcelainEntry
- type ProjectSkillsModifiedError
- type RepoInfo
- type SkillScope
- type SkillsSource
- type Sources
- type StaticBootstrapper
- type StatusOptions
- type StatusResult
- type TUIBootstrapper
- type UpdateOptions
- type UpdateResult
- type UpdateSummary
- type ValidationError
Constants ¶
const ( SubtreeSkills = ".claude/skills" SubtreePipelines = "_apex/pipelines" SubtreeConfig = "_apex/config.yaml" SubtreeConfigLocalExample = "_apex/config.local.example.yaml" )
Subtree paths inside a checked-out apex_process_framework repo. These target the *released* framework layout, where .claude/ and _apex/ sit at the repo root (identical to the shape a project consumes). The earlier build layout nested these under a framework/ subfolder (framework/_claude, framework/_apex); that layout is no longer supported. Hard-coded for now; if the layout changes again we'll lift these to a manifest the framework itself ships.
const ( ProjectSkillsDir = ".claude/skills" ProjectPipelinesDir = "_apex/pipelines" ProjectConfig = "_apex/config.yaml" ProjectConfigLocalExample = "_apex/config.local.example.yaml" ProjectMetadata = "_apex/framework.yaml" )
Project-side paths, relative to the project root the user is installing into.
const MetadataSchemaVersion = "1"
MetadataSchemaVersion identifies the framework.yaml schema. Bump when the on-disk shape changes in a non-additive way; readers can then refuse to parse incompatible versions.
const SkillPrefix = "apex-"
SkillPrefix is the filename prefix that identifies framework-managed skills. Anything else under .claude/skills/ is left alone by `ape framework update`.
Variables ¶
var ErrBootstrapCancelled = errors.New("config bootstrap cancelled by user")
ErrBootstrapCancelled is returned by a Bootstrapper when the user chose not to seed config.yaml. Update treats it as "skip the seed, proceed with the install".
var ErrGitMissing = errors.New("git executable not found on PATH")
ErrGitMissing signals the git binary is absent from PATH. Returned from EnsureGitAvailable.
var Extensions = []Extension{
{ID: "ext-adrs", Description: "Architecture Decision Records — track significant architectural choices."},
{ID: "ext-patterns", Description: "Reusable patterns catalog — codify and enforce engineering patterns."},
{ID: "ext-capabilities", Description: "Capability inventory — track product capabilities and their delivery state."},
{ID: "ext-features", Description: "Feature inventory — track features, their states, and links to stories."},
}
Extensions is the canonical list of extensions offered to the user during `ape framework update` config bootstrap. Order is preserved in the TUI.
var GitCmd = "git"
GitCmd is the git executable name. Indirection lets tests substitute a stub binary when verifying error paths.
Functions ¶
func AtomicWriteFile ¶
AtomicWriteFile writes data to path via a sibling tempfile + rename, so a partial write cannot leave a half-written file in place. Permissions on the resulting file follow mode.
func CopyFile ¶
CopyFile copies a single file from src to dst, preserving the source file's permission bits. Parent directory of dst must already exist. Truncates dst if it exists.
func CopyTree ¶
CopyTree recursively copies the directory tree at src into dst, creating dst (and any missing intermediate directories) as needed. File modes are preserved; dst-side files are truncated/replaced if they already exist. Returns a tally of the regular files copied.
func CurrentBranch ¶
CurrentBranch returns the abbreviated branch name (e.g., "main"), or "HEAD" when the repo is in a detached-HEAD state.
func DefaultProjectName ¶
DefaultProjectName returns the best-effort default for the project_name field during config bootstrap, in priority order:
- The last path segment of the module path declared in <projectRoot>/go.mod, if present and parseable.
- The base name of projectRoot.
- "my-project" — the framework template default.
func EnsureGitAvailable ¶
func EnsureGitAvailable() error
EnsureGitAvailable returns ErrGitMissing if `git` cannot be found.
func ExactTag ¶
ExactTag returns the annotated/lightweight tag at HEAD, or empty string + nil when HEAD is not on a tagged commit. Distinguishes the "no tag" case from real errors.
func ExtensionIDs ¶
func ExtensionIDs() []string
ExtensionIDs returns the bare identifiers of the canonical extensions.
func FetchAndFastForward ¶
FetchAndFastForward runs `git fetch origin <branch>` then `git merge --ff-only origin/<branch>`. Any non-fast-forward situation returns an error naming the divergence.
func IsClean ¶
IsClean reports whether the working tree has no modifications. Untracked files are included in the porcelain output by default.
func IsFrameworkSkill ¶ added in v0.0.18
IsFrameworkSkill reports whether a skill name is one the framework manages — currently every name starting with the `apex-` prefix. Non-framework skills installed by the user under the same tree are left alone by `ape framework update` and are tagged "custom" in doctor output.
func IsKnownExtension ¶
IsKnownExtension reports whether id is one of the canonical extensions. Used to validate --extensions flag values.
func ListInstalledSkills ¶ added in v0.0.18
ListInstalledSkills returns the names of every skill installed under `<dir>/<name>/SKILL.md`, sorted. Returns an empty slice when dir doesn't exist or has no skills. Errors only on unexpected I/O issues (a missing dir is not an error).
func MetadataPath ¶
MetadataPath returns the absolute path of framework.yaml inside a project root.
func ProjectSkillsPath ¶ added in v0.0.18
ProjectSkillsPath returns the absolute path of the project's .claude/skills directory. Convenience for callers that don't want to reach for filepath.Join + the layout constant.
func RemoteOrigin ¶
RemoteOrigin returns the URL for the "origin" remote, or an error if no such remote is configured.
func UserSkillsPath ¶ added in v0.0.18
func UserSkillsPath() string
UserSkillsPath returns the absolute path of the user-scoped ~/.claude/skills directory, or empty string when the home directory is not resolvable.
func WriteMetadata ¶
WriteMetadata serializes m to <projectRoot>/_apex/framework.yaml via an atomic write (tempfile + rename). The file gets a one-line generated-by header above the YAML.
Types ¶
type AlreadyInstalledError ¶ added in v0.0.7
type AlreadyInstalledError struct {
Path string
}
AlreadyInstalledError signals that <projectRoot>/_apex/framework.yaml already exists when `ape framework setup` was invoked. Pass --force to re-bootstrap (which resets project_name + extensions).
func (*AlreadyInstalledError) Error ¶ added in v0.0.7
func (e *AlreadyInstalledError) Error() string
type ApeInfo ¶
type ApeInfo struct {
Version string `json:"version" yaml:"version"`
}
ApeInfo records which ape binary performed the install.
type BootstrapValues ¶
type BootstrapValues struct {
ProjectName string
Extensions []string // canonical IDs; e.g., {"ext-adrs", "ext-features"}
}
BootstrapValues are the user-supplied (or flag-supplied) inputs that drive _apex/config.yaml seeding on first install.
type Bootstrapper ¶
type Bootstrapper interface {
Bootstrap(ctx context.Context, defaultProjectName string, extensions []Extension) (BootstrapValues, error)
}
Bootstrapper resolves BootstrapValues for a fresh project. The production implementation is a Bubble Tea TUI; tests pass static stubs. NoopBootstrapper is used when --no-bootstrap is set.
type ConfigLocalExampleSource ¶
type ConfigLocalExampleSource struct {
Seeded bool `json:"seeded" yaml:"seeded"`
}
ConfigLocalExampleSource records whether the local-example file was seeded (it carries no values worth reflecting back).
type ConfigSource ¶
type ConfigSource struct {
Seeded bool `json:"seeded" yaml:"seeded"`
ProjectName string `json:"projectName,omitempty" yaml:"project_name,omitempty"`
Extensions []string `json:"extensions,omitempty" yaml:"extensions,omitempty"`
}
ConfigSource records whether _apex/config.yaml was seeded and what the bootstrap chose for project_name + extensions. Cross-checked by `framework status` against the live config.yaml to surface drift.
type Drift ¶
type Drift struct {
HashDrift bool `json:"hashDrift" yaml:"hashDrift"`
TagDrift bool `json:"tagDrift" yaml:"tagDrift"`
Notes []string `json:"notes,omitempty" yaml:"notes,omitempty"`
}
Drift summarizes how the project's installed framework state compares to the framework repo's current HEAD. Only populated when FrameworkRepo was provided.
type Extension ¶
Extension is a configurable APEX framework extension that the user chooses among during first-run config bootstrap. The set is hardcoded today; a future framework version may ship a manifest (_apex/extensions.yaml) that this package would parse instead.
type Metadata ¶
type Metadata struct {
ConfigSchemaVersion string `json:"configSchemaVersion" yaml:"config_schema_version"`
InstalledAt time.Time `json:"installedAt" yaml:"installed_at"`
Framework RepoInfo `json:"framework" yaml:"framework"`
Ape ApeInfo `json:"ape" yaml:"ape"`
Sources Sources `json:"sources" yaml:"sources"`
}
Metadata is the on-disk shape of <project>/_apex/framework.yaml. Written by `ape framework update`, read by `ape framework status` and downstream tools that need to know which framework version a project is pinned to.
func ReadMetadata ¶
ReadMetadata loads <projectRoot>/_apex/framework.yaml. Returns a *NotInstalledError when the file is absent.
type NoopBootstrapper ¶
type NoopBootstrapper struct{}
NoopBootstrapper always cancels — used for --no-bootstrap.
func (NoopBootstrapper) Bootstrap ¶
func (NoopBootstrapper) Bootstrap(_ context.Context, _ string, _ []Extension) (BootstrapValues, error)
Bootstrap implements Bootstrapper.
type NotInstalledError ¶ added in v0.0.7
type NotInstalledError struct {
Path string
}
NotInstalledError signals that <projectRoot>/_apex/framework.yaml is absent — the canonical "project never had `ape framework setup` run" state. It satisfies errors.Is(err, fs.ErrNotExist) so programmatic callers can still pattern-match on the underlying cause, but the rendered message hides Go's syscall-level "open …: no such file or directory" trailer in favor of the actionable hint.
func (*NotInstalledError) Error ¶ added in v0.0.7
func (e *NotInstalledError) Error() string
func (*NotInstalledError) Unwrap ¶ added in v0.0.7
func (e *NotInstalledError) Unwrap() error
type PipelinesSource ¶
type PipelinesSource struct {
Count int `json:"count" yaml:"count"`
Paths []string `json:"paths" yaml:"paths"`
}
PipelinesSource records the pipelines installed on this run.
type PorcelainEntry ¶
type PorcelainEntry struct {
Status string // 2-char status code, e.g. " M", "M ", "??", "A "
Path string
}
PorcelainEntry models one line of `git status --porcelain` output: the two-char status code plus the affected path. This package treats the prefix `??` (untracked) specially during the project-side skill-deletion safety check.
func ParsePorcelain ¶
func ParsePorcelain(out string) []PorcelainEntry
ParsePorcelain parses the output of `git status --porcelain`. Returns nil for empty input.
func SkillsPorcelain ¶
func SkillsPorcelain(ctx context.Context, repoDir string) ([]PorcelainEntry, error)
SkillsPorcelain returns the porcelain entries restricted to paths under <repoDir>/.claude/skills/apex-*. Used for the project-side skill-deletion safety check.
func (PorcelainEntry) IsUntracked ¶
func (p PorcelainEntry) IsUntracked() bool
IsUntracked reports whether the entry is an "untracked file" line.
type ProjectSkillsModifiedError ¶
type ProjectSkillsModifiedError struct {
Paths []string
}
ProjectSkillsModifiedError signals the project has uncommitted edits to .claude/skills/apex-* — refusing to clobber them without --force.
func (*ProjectSkillsModifiedError) Error ¶
func (e *ProjectSkillsModifiedError) Error() string
Error implements error.
type RepoInfo ¶
type RepoInfo struct {
RepoOrigin string `json:"repoOrigin" yaml:"repo_origin"`
VersionTag string `json:"versionTag" yaml:"version_tag"` // empty when HEAD has no exact tag
GitHash string `json:"gitHash" yaml:"git_hash"`
GitBranch string `json:"gitBranch" yaml:"git_branch"`
}
RepoInfo records the framework repo state at install time.
type SkillScope ¶ added in v0.0.18
type SkillScope string
SkillScope tags whether a skill was resolved under the project's .claude/skills tree or the user-scoped ~/.claude/skills tree. Matches the lookup order claude itself uses (project wins over user).
const ( // ScopeProject — skill resolved under <projectRoot>/.claude/skills/. ScopeProject SkillScope = "project" // ScopeUser — skill resolved under <home>/.claude/skills/. ScopeUser SkillScope = "user" // ScopeNone — skill did not resolve at either location. ScopeNone SkillScope = "" )
func ResolveSkill ¶ added in v0.0.18
func ResolveSkill(name, projectRoot string) (path string, scope SkillScope, found bool)
ResolveSkill reports whether a skill name resolves to an on-disk SKILL.md, mirroring claude's lookup order: project-scoped `<projectRoot>/.claude/skills/<name>/SKILL.md` first, then user-scoped `~/.claude/skills/<name>/SKILL.md`. An empty projectRoot disables the project-scope check. ResolveSkill returns the absolute path, the scope it was found in, and a found flag.
type SkillsSource ¶
type SkillsSource struct {
Count int `json:"count" yaml:"count"`
Paths []string `json:"paths" yaml:"paths"`
}
SkillsSource records the skills installed on this run.
type Sources ¶
type Sources struct {
Skills SkillsSource `json:"skills" yaml:"skills"`
Pipelines PipelinesSource `json:"pipelines" yaml:"pipelines"`
Config ConfigSource `json:"config" yaml:"config"`
ConfigLocalExample ConfigLocalExampleSource `json:"configLocalExample" yaml:"config_local_example"`
}
Sources records what was installed into the project tree.
type StaticBootstrapper ¶
type StaticBootstrapper struct {
Values BootstrapValues
}
StaticBootstrapper returns predetermined values without prompting. Used when --project-name / --extensions flags are provided.
func (StaticBootstrapper) Bootstrap ¶
func (s StaticBootstrapper) Bootstrap(_ context.Context, _ string, _ []Extension) (BootstrapValues, error)
Bootstrap implements Bootstrapper.
type StatusOptions ¶
type StatusOptions struct {
ProjectRoot string
FrameworkRepo string // optional; when set, drift fields are populated
NoFetch bool // skip fetch when reading framework HEAD
}
StatusOptions parameterize a Status call.
type StatusResult ¶
type StatusResult struct {
Installed Metadata `json:"installed" yaml:"installed"`
Current *RepoInfo `json:"current,omitempty" yaml:"current,omitempty"`
Drift *Drift `json:"drift,omitempty" yaml:"drift,omitempty"`
}
StatusResult is the payload `ape framework status` returns.
func Status ¶
func Status(ctx context.Context, opts StatusOptions) (*StatusResult, error)
Status reads a project's framework.yaml, optionally compares it against the framework repo's current HEAD, and returns a structured drift report.
type TUIBootstrapper ¶
type TUIBootstrapper struct{}
TUIBootstrapper drives a two-phase Bubble Tea form to collect project_name + extensions on first install. Returns ErrBootstrapCancelled if the user presses Esc or Ctrl+C.
func (TUIBootstrapper) Bootstrap ¶
func (TUIBootstrapper) Bootstrap(ctx context.Context, defaultProjectName string, extensions []Extension) (BootstrapValues, error)
Bootstrap implements Bootstrapper.
type UpdateOptions ¶
type UpdateOptions struct {
// FrameworkRepo is the absolute path to a checked-out
// apex_process_framework repo.
FrameworkRepo string
// ProjectRoot is the absolute path to the project the install
// targets.
ProjectRoot string
// NoFetch skips the `git fetch && merge --ff-only` step.
NoFetch bool
// Force bypasses the framework-clean / project-skills-modified
// safety checks. Use sparingly.
Force bool
// ApeVersion is the version string of the binary performing the
// install. Recorded into framework.yaml's `ape.version`.
ApeVersion string
// Bootstrapper resolves config-bootstrap values when
// _apex/config.yaml is absent. Required.
Bootstrapper Bootstrapper
// Now is injectable for deterministic tests; defaults to
// time.Now().UTC().
Now func() time.Time
}
UpdateOptions parameterize an Update call.
type UpdateResult ¶
type UpdateResult struct {
Metadata Metadata
Summary UpdateSummary
}
UpdateResult is the structured payload an Update call returns.
func Setup ¶ added in v0.0.7
func Setup(ctx context.Context, opts *UpdateOptions) (*UpdateResult, error)
Setup runs the initial-install flow: bootstrap config.yaml (if absent), copy skills + pipelines, write framework.yaml. Refuses to run if framework.yaml already exists unless opts.Force is set.
Per PLAN-1 / I3, this is the command users run on a fresh project. Subsequent refreshes use Update, which deliberately omits the bootstrap step so config.yaml stays untouched.
func Update ¶
func Update(ctx context.Context, opts *UpdateOptions) (*UpdateResult, error)
Update runs the refresh flow: re-copy skills + pipelines, refresh framework.yaml. Does NOT touch config.yaml — that's a one-time bootstrap recorded by Setup. Refuses to run if framework.yaml is absent (no install to refresh).
Per PLAN-1 / I3, this is the command users run on every framework version bump after the initial Setup. The Bootstrapper field of opts is ignored.
type UpdateSummary ¶
type UpdateSummary struct {
SkillsInstalled int `json:"skillsInstalled" yaml:"skillsInstalled"`
SkillsRemoved int `json:"skillsRemoved" yaml:"skillsRemoved"`
SkillsRemovedPaths []string `json:"skillsRemovedPaths,omitempty" yaml:"skillsRemovedPaths,omitempty"`
PipelinesInstalled int `json:"pipelinesInstalled" yaml:"pipelinesInstalled"`
ConfigSeeded bool `json:"configSeeded" yaml:"configSeeded"`
ConfigLocalSeeded bool `json:"configLocalSeeded" yaml:"configLocalSeeded"`
}
UpdateSummary is a tally of what changed in the project tree.
type ValidationError ¶
type ValidationError struct {
Code string // e.g. "framework_dirty", "framework_not_main"
Detail string
}
ValidationError signals the framework repo is in a state that blocks the install (dirty, wrong branch, missing subtree, etc.). Cobra layer maps this to a structured error envelope.