framework

package
v0.0.40 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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.

View Source
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

View Source
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".

View Source
var ErrGitMissing = errors.New("git executable not found on PATH")

ErrGitMissing signals the git binary is absent from PATH. Returned from EnsureGitAvailable.

View Source
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.

View Source
var GitCmd = "git"

GitCmd is the git executable name. Indirection lets tests substitute a stub binary when verifying error paths.

Functions

func AtomicWriteFile

func AtomicWriteFile(path string, data []byte, mode os.FileMode) error

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

func CopyFile(src, dst string) error

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

func CopyTree(src, dst string) (filesCopied int, err error)

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

func CurrentBranch(ctx context.Context, repoDir string) (string, error)

CurrentBranch returns the abbreviated branch name (e.g., "main"), or "HEAD" when the repo is in a detached-HEAD state.

func DefaultProjectName

func DefaultProjectName(projectRoot string) string

DefaultProjectName returns the best-effort default for the project_name field during config bootstrap, in priority order:

  1. The last path segment of the module path declared in <projectRoot>/go.mod, if present and parseable.
  2. The base name of projectRoot.
  3. "my-project" — the framework template default.

func EnsureGitAvailable

func EnsureGitAvailable() error

EnsureGitAvailable returns ErrGitMissing if `git` cannot be found.

func ExactTag

func ExactTag(ctx context.Context, repoDir string) (string, error)

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

func FetchAndFastForward(ctx context.Context, repoDir, branch string) error

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 HeadSHA

func HeadSHA(ctx context.Context, repoDir string) (string, error)

HeadSHA returns the full 40-char SHA at HEAD.

func IsClean

func IsClean(ctx context.Context, repoDir string) (bool, error)

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

func IsFrameworkSkill(name string) bool

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 IsGitRepo

func IsGitRepo(ctx context.Context, dir string) bool

IsGitRepo reports whether dir is the working tree of a git repo.

func IsKnownExtension

func IsKnownExtension(id string) bool

IsKnownExtension reports whether id is one of the canonical extensions. Used to validate --extensions flag values.

func ListInstalledSkills added in v0.0.18

func ListInstalledSkills(dir string) ([]string, error)

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

func MetadataPath(projectRoot string) string

MetadataPath returns the absolute path of framework.yaml inside a project root.

func ProjectSkillsPath added in v0.0.18

func ProjectSkillsPath(projectRoot string) string

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

func RemoteOrigin(ctx context.Context, repoDir string) (string, error)

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

func WriteMetadata(projectRoot string, m *Metadata) error

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

type Extension struct {
	ID          string
	Description string
}

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

func ReadMetadata(projectRoot string) (*Metadata, error)

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

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

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

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.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL