Documentation
¶
Overview ¶
Package lifecycle hosts the project-lifecycle command cluster extracted from the root commands package as part of the root-command-decomposition plan (SHAPE.md §1, §4a).
Lifecycle commands mutate or inspect a managed project's on-disk state: add, doctor, import, import_plugins, init, install, refresh, remove, status, and the platform-tagged linkcount helpers. They share the project-mutation helpers in internal/links, internal/platform, internal/projectsync, and internal/scaffold/{home,hooks}, which is why they cluster into a single subpackage rather than per-command subpackages (contrast with commands/agents/, commands/skills/, etc., which are single-resource browsers).
This file is part of the t02 skeleton: only doc.go and deps.go land here. Individual command moves arrive in tasks t03 through t09, and the root re-export shims are deleted in t13 once commands/root.go switches to importing the lifecycle.New*Cmd constructors directly.
Index ¶
- Constants
- Variables
- func BackupExistingConfigsList(files []string, projectPath, agentsHome, project, timestamp string, ...) (int, error)
- func CloneGitSource(gitBin, url, ref, cacheDir string, deps InstallDeps) (string, error)
- func CountClaudeRules(path string) (int, int)
- func DetectAndEnableNewPlatforms(cfg *config.Config) []string
- func EnsureGlobalKGMCPConfigs(agentsHome string) error
- func FindProjectByPath(projectPath string, deps InstallDeps) string
- func InitNoArgs(hints ...string) cobra.PositionalArgs
- func IsBackupArtifact(name string) bool
- func IsCanonicalResourceBackupRel(relPath string) bool
- func IsManagedCursorRuleRel(project, rel string) bool
- func IsManagedProjectOutput(project, projectPath, filePath, agentsHome string) bool
- func IsManagedSymlink(path, agentsHome string) bool
- func KGConfigPath() string
- func LinkResourceFromSources(resourceType, name, project string, sources []string, deps InstallDeps) error
- func MapResourceRelToDest(project, relPath string) string
- func MirrorBackup(project, projectPath, srcFile, timestamp string)
- func MirrorBackupChecked(project, projectPath, srcFile, timestamp string, deps AddDeps) error
- func NewDoctorCmd(deps Deps) *cobra.Command
- func NewInitCmd(deps Deps) *cobra.Command
- func NewInstallCmd(deps Deps) *cobra.Command
- func NewRefreshCmd(deps Deps) *cobra.Command
- func NewStatusCmd(deps Deps, jsonOutput func() bool) *cobra.Command
- func PrintAudit(name, path, agentsHome, agentFilter string, cfg *config.Config)
- func PrintSymlinkDirAudit(dir, emptyLabel, nameFormat string) (int, int)
- func RegisterInstallProject(projectName, projectPath string, deps InstallDeps) error
- func RestoreFromResourcesCountedWithDeps(project, projectPath string, deps AddDeps) (int, error)
- func RestoreLegacyResourceFile(project, relPath, agentsHome, path string, deps AddDeps) (int, error)
- func RunDoctor(cmd *cobra.Command, args []string, deps DoctorConfigLoader) error
- func RunInit(cmd *cobra.Command, args []string) error
- func RunInitForTest(cmd *cobra.Command, args []string, deps InitDirMakerForTest) error
- func RunInstall(strict bool, deps InstallDeps) error
- func RunInstallGenerate(deps InstallDeps) error
- func RunStatus(audit bool, agentFilter string, deps StatusConfigLoader, jsonOut bool) error
- func RunStatusDefault(audit bool, agentFilter string, jsonOut bool) error
- func ScaffoldWorkflowAssetsForTest(agentsHome string, deps InitDirMakerForTest) error
- func SetInitFlags(force, dryRun, yes func() bool)
- func ShouldUseCachedGitSource(cacheDir, url string) bool
- func WriteKGMCPConfigFile(path string, server map[string]any, deps AddDeps) error
- func WriteKGMCPConfigs(scopeDir string, deps AddDeps) error
- type AddDeps
- type Deps
- type DoctorConfigLoader
- type GlobalFlags
- type ImportCandidate
- type ImportOutput
- type InitDirMakerForTest
- type InstallDeps
- type StatusConfigLoader
- type StdAddDeps
- func (StdAddDeps) CopyFile(src, dst string) error
- func (StdAddDeps) Executable() (string, error)
- func (StdAddDeps) LoadConfig() (*config.Config, error)
- func (StdAddDeps) MkdirAll(path string, perm os.FileMode) error
- func (StdAddDeps) Remove(name string) error
- func (StdAddDeps) WriteFile(name string, data []byte, perm os.FileMode) error
- type StdDoctorConfigLoader
- type StdInstallDeps
Constants ¶
const ( InitCmdUse = "init" InitCmdShort = "Initialize ~/.agents/ directory structure" InitCmdLong = `` /* 263-byte string literal not displayed */ InitCmdNoArgsHint = "`da init` bootstraps the shared store and does not take a project path." )
Command-text constants for the init cobra command. The shim in commands/init.go constructs the *cobra.Command literal using these so the RunE closure (and thus the runtime symbol globalflagcov resolves) lives in package commands, not lifecycle. This keeps the t05 move transparent to the existing globalflagcov tool — folded into a single constructor when t13 deletes the shim and globalflagcov gains lifecycle in its package list.
const ( RelClaudeSettingsJSON = ".claude/settings.json" RelCursorSettingsJSON = ".cursor/settings.json" RelCursorMCPJSON = ".cursor/mcp.json" RelCursorHooksJSON = ".cursor/hooks.json" RelCursorIgnore = ".cursorignore" RelCursorIndexingIgnore = ".cursorindexingignore" RelClaudeSettingsLocal = ".claude/settings.local.json" RelMCPJSON = ".mcp.json" RelVSCodeMCPJSON = ".vscode/mcp.json" RelOpenCodeJSON = "opencode.json" RelAgentsMD = "AGENTS.md" RelCodexInstructionsMD = ".codex/instructions.md" RelCodexRulesMD = ".codex/rules.md" RelCodexConfigTOML = ".codex/config.toml" RelCodexHooksJSON = ".codex/hooks.json" RelCopilotInstructionsMD = ".github/copilot-instructions.md" RelCursorCommandsDir = ".cursor/commands/" RelClaudeCommandsDir = ".claude/commands/" RelOpenCodeCommandsDir = ".opencode/commands/" RelClaudeOutputStylesDir = ".claude/output-styles/" RelOpenCodeModesDir = ".opencode/modes/" RelOpenCodeThemesDir = ".opencode/themes/" RelGitHubPromptsDir = ".github/prompts/" RelCursorRulesDir = ".cursor/rules/" RelAgentsSkillsDir = ".agents/skills/" RelClaudeSkillsDir = ".claude/skills/" RelGitHubAgentsDir = ".github/agents/" RelCodexAgentsDir = ".codex/agents/" RelOpenCodeAgentsDir = ".opencode/agent/" RelGitHubHooksDir = ".github/hooks/" RelAgentMarkdownSuffix = ".agent.md" RelJSONSuffix = ".json" AgentsHooksPrefix = "hooks/" )
Repo-relative path constants used by MapResourceRelToDest and referenced cross-package by commands/import.go (via aliasing const declarations) and commands/add.go. Lifted from commands/import.go in plan root-command-decomposition t02b so the resource-relative ↔ canonical-dest mapping can live alongside its consumers in the lifecycle subpackage.
Variables ¶
var ( Version = "dev" Commit = "" Describe = "" )
Version/Commit/Describe mirror the build-info vars defined in commands/refresh.go. Populated by the same two paths as Flags above: the transitional install.go shim writes them via syncLifecycleGlobals, and the NewInstallCmd / NewDoctorCmd / NewInitCmd RunE wrappers (t13a) write them from Deps via applyDepsToGlobals.
var ( InitForceFn = func() bool { return initForce } InitDryRunFn = func() bool { return initDryRun } InitYesFn = func() bool { return initYes } )
Package-level seam vars for init (preserved per t01 SHAPE.md "PRESERVE current package-var seams during the moves"). Each is a getter so the shim in commands/init.go can repoint them at commands.Flags lazily — NewInitCmd runs at root-construction time (before flags parse), and the getters are evaluated at RunE time. Tests in this package set the underlying bool fields directly via the convenience setters below. Kept file-scoped (not promoted to deps.go) because t05's bundle write_scope intentionally limits this task to the init files only — Force/DryRun/Yes are init-specific seams today and consolidate into a shared lifecycle.Flags struct alongside the other lifecycle command moves (t03/t04/t06+).
var CanonicalImportOutputs = func(c ImportCandidate) ([]ImportOutput, bool, error) { return nil, false, nil }
CanonicalImportOutputs is wired at init time by commands/import.go. Returns ([]ImportOutput, handled, err); handled=false means the candidate is not a canonical resource (not a hook bundle / settings file the canonical importer recognizes) and the caller should fall back to legacy per-file restore. Stub default returns "not handled" so the lifecycle package builds standalone and runs without the commands package's init() (e.g. in commands/lifecycle/... tests that do not exercise canonical-import).
var ErrorWithHintsFn = defaultErrorWithHints
ErrorWithHintsFn is a package-var seam onto commands.ErrorWithHints. Lifecycle cannot import commands (cycle); the t03 shim in commands/install.go assigns this to commands.ErrorWithHints at init time. A default value (plain fmt.Errorf-style formatting) keeps tests that exercise lifecycle entry points directly green even when the shim is absent. The NewXxxCmd constructors' RunE wrappers also write this from Deps.ErrorWithHints when non-nil (t13a) so a t13b call site needs no extra sync step.
var HasMultipleHardLinks = platform.HasMultipleHardLinks
HasMultipleHardLinks is the platform-tagged hard-link counter seam. Backed by platform.HasMultipleHardLinks (the canonical implementation lives in internal/platform/claude_linkcount_{unix,windows}.go after the P3 fold-back absorbed the relocation deferred by P1 — see .agents/active/fold-back/p1-hasmultiplehardlinks-move-deferred-to-p3.md). Declared as a package-level func variable so backup_test.go can override the link-count behavior without invoking real syscalls.
Exported because backup.go (this file) and status.go's claude-rules counter both read it across the same package, and seams_test.go in the root commands package still overrides it for fault injection. Once the last cross-file consumer disappears the seam can lowercase back to package-private.
var InitCmdExample = strings.Join([]string{
" da init",
" da init --dry-run",
" da init --force",
" da init --from git@github.com:you/agents-config.git",
}, "\n")
InitCmdExample is the rendered Example string for the init command.
var InitEnsureGlobalKGMCPConfigsFn = EnsureGlobalKGMCPConfigs
InitEnsureGlobalKGMCPConfigsFn is the in-package seam for the KG MCP config scaffolder. Defaults to lifecycle.EnsureGlobalKGMCPConfigs (lifted into this package by t02b), eliminating the prior cross-package indirection through the commands shim. Kept as a var (not a direct call) so tests can fault-inject failure scenarios without monkey-patching the underlying helper.
var InitUsageErrorFn = func(msg string, hints ...string) error { if len(hints) == 0 { return fmt.Errorf("%s", msg) } return fmt.Errorf("%s\n %s", msg, strings.Join(hints, "\n ")) }
InitUsageErrorFn is the UsageError seam (defaults to a plain error so the lifecycle subpackage does not import commands, avoiding the commands→lifecycle→commands cycle). The shim in commands/init.go overrides this with commands.UsageError to preserve the formatted-hint UX users see today.
var RestoreCanonicalResourceFileFn = func(project, resourcesDir, agentsHome, path string, deps AddDeps) (int, bool, error) { return 0, false, nil }
RestoreCanonicalResourceFileFn is the canonical-import branch wired at init time by commands/import.go (canonicalImportOutputs and its hook bundle helpers stay in import.go until t06 moves the whole import command). Returns (count, handled, err); handled=false means the file is not a canonical resource and the caller should fall back to RestoreLegacyResourceFile.
Functions ¶
func BackupExistingConfigsList ¶
func BackupExistingConfigsList(files []string, projectPath, agentsHome, project, timestamp string, deps AddDeps) (int, error)
BackupExistingConfigsList backs up the given files into ~/.agents/resources/<project>/... and removes the originals from the project tree. No *.dot-agents-backup files are left in the project. Returns count of files processed and a non-nil error if any required backup copy failed. On backup failure the original is NOT removed (the user's only copy is preserved) and the error aborts runAdd before any destructive removal. Lifted from commands/add.go in root-command-decomposition t02b.
func CloneGitSource ¶
func CloneGitSource(gitBin, url, ref, cacheDir string, deps InstallDeps) (string, error)
CloneGitSource is exported so the t03 shim in commands/install.go can forward to it. seams_test.go's TestCloneGitSource_MkdirAllError reaches it through the lower-case wrapper in the shim.
func CountClaudeRules ¶
CountClaudeRules is the exported entry point used by commands/seams_test.go (still in root before t11) and the legacy status_exports_test.go suite. Now delegates to the claude platform's CountLinks-style helper exposed for the test-only seam (claude.CountLinks covers the same rules-dir branch plus the managed-file extras). Returns the (ok, warn) tally for the .claude/rules directory only — matching the historical countClaudeRules signature so test fixtures keep the same assertion shape.
func DetectAndEnableNewPlatforms ¶
DetectAndEnableNewPlatforms re-probes every known platform and reconciles cfg with what is actually installed on this machine:
- A platform installed but currently disabled in cfg is flipped to enabled and its live version recorded. This is the refresh-driven fix for a platform installed AFTER `da init`: init writes enabled:false for tools that were not on PATH at init time, and a plain refresh would otherwise iterate only already-enabled platforms forever (the "Nothing to refresh" dead-end).
- A platform already enabled and still installed has its recorded version refreshed from the live probe (stale version strings are updated).
The probe is the same one init uses: platform.Platform.IsInstalled() (CLI on PATH) and Version() for the recorded version string. Detection is conservative — it never DISABLES anything. A platform that is enabled but no longer installed is left enabled (callers simply skip projecting/refreshing what is absent); refresh does not auto-disable.
The caller is responsible for persisting cfg (cfg.Save()) after this returns. Returns the display names of the platforms that were newly enabled, in All() order, so the caller can announce each one.
func EnsureGlobalKGMCPConfigs ¶
EnsureGlobalKGMCPConfigs writes the KG MCP config files into the global mcp scope when a KG_HOME/self/config.yaml exists. No-op when the KG self config is absent. Lifted from commands/add.go in root-command-decomposition t02b.
func FindProjectByPath ¶
func FindProjectByPath(projectPath string, deps InstallDeps) string
FindProjectByPath looks up the registered project name for a given path. Exported because the t03 shim in commands/install.go forwards to it and seams_test.go reaches it through the shim.
func InitNoArgs ¶
func InitNoArgs(hints ...string) cobra.PositionalArgs
InitNoArgs is the exported Args validator for the init command, delegating to the file-local initNoArgs implementation. Exported so the shim can wire it into the cobra command literal.
func IsBackupArtifact ¶
IsBackupArtifact reports whether name is a dot-agents backup artifact. Lifted from commands/add.go in root-command-decomposition t02b.
func IsCanonicalResourceBackupRel ¶
IsCanonicalResourceBackupRel reports whether relPath sits under one of the canonical resource roots (rules/, settings/, mcp/, skills/, agents/, hooks/) and should therefore be ignored when the restore loop walks the resources directory (those subtrees are produced by canonical-import, not legacy backups). Exported (not in the named 15) because add_test.go covers it directly.
func IsManagedCursorRuleRel ¶
IsManagedCursorRuleRel reports whether rel points at a managed Cursor rule under .cursor/rules/ with the project's or global namespace prefix. Internal helper used by IsManagedProjectOutput; not part of the t02b 15 but lifted alongside its sole caller to keep the backup cluster self-contained.
func IsManagedProjectOutput ¶
IsManagedProjectOutput reports whether filePath is a managed dot-agents projection at projectPath under agentsHome for project. Lifted from commands/add.go in root-command-decomposition t02b.
func IsManagedSymlink ¶
IsManagedSymlink reports whether path is a resolvable managed link (POSIX symlink / Windows junction) whose resolved target lies under agentsHome. A Windows hard-linked managed *file* has no reparse point to test against a prefix and is reported false here, matching the prior symlink-only behavior on POSIX. Lifted from commands/import.go in root-command-decomposition t02b.
func KGConfigPath ¶
func KGConfigPath() string
KGConfigPath returns the path to KG_HOME/self/config.yaml without importing the kg subpackage. Used by EnsureGlobalKGMCPConfigs and re-aliased into commands/add.go for the legacy kgConfigPath test site until commands/init.go and commands/seams_test.go move.
func LinkResourceFromSources ¶
func LinkResourceFromSources(resourceType, name, project string, sources []string, deps InstallDeps) error
LinkResourceFromSources symlinks a resource from the first matching source into ~/.agents/{resourceType}/{project}/{name}/. Exported because the t03 shim in commands/install.go forwards to it and seams_test.go reaches it through the shim.
func MapResourceRelToDest ¶
MapResourceRelToDest maps a project-relative input path (e.g. ".cursor/rules/global--foo.mdc") to the canonical ~/.agents-relative destination (e.g. "rules/global/foo.mdc"). Returns "" when no mapping applies. Lifted from commands/refresh.go in root-command-decomposition t02b; behavior unchanged from the original.
Implementation note: the logic is split into focused helpers (mapExactRel, mapBucketDirRel, mapCursorRulesRel, mapSkillsRel, mapAgentsRel, mapHooksRel, mapPassThroughRel) so each branch has a single concern and the top-level function stays inside Sonar's cognitive-complexity limit (S3776).
func MirrorBackup ¶
func MirrorBackup(project, projectPath, srcFile, timestamp string)
MirrorBackup copies srcFile (original path, before deletion) into the ~/.agents/resources/<project>/ tree using the file's original relative path. No *.dot-agents-backup suffix is added anywhere.
This is the errorless wrapper retained for import.go callers, whose own failure handling keys off the subsequent CopyFile into the destination (a mirror-backup failure there does not destroy the user's only copy because import.go never removes the source after MirrorBackup). Callers that delete the original after backing it up (BackupExistingConfigsList) MUST use MirrorBackupChecked so a failed backup aborts before the destructive removal.
func MirrorBackupChecked ¶
MirrorBackupChecked performs the same copy as MirrorBackup but propagates the CopyFile errors. BackupExistingConfigsList relies on this: it removes the user's only copy of an unmanaged config after backing it up, so a silent backup failure (unwritable ~/.agents/resources, disk full, unreadable source through a symlink) would destroy that config while reporting a successful backup. Exported (not in the named 15) because add_test.go exercises it directly through its var alias in commands/add.go.
func NewDoctorCmd ¶
NewDoctorCmd builds the `da doctor` cobra command. Mirrors the NewStatusCmd / NewInstallCmd Deps-injection pattern: lifecycle does not import the parent commands/ package (cycle), so Args/Example helpers and the UsageError formatter come in via Deps.
The RunE wrapper calls applyDepsToGlobals(deps) before delegating so the moved doctor body (which reads lifecycle.Flags / .ErrorWithHintsFn package vars directly) sees live state from the caller. After t13a this absorbs what the parent commands/doctor.go shim's syncLifecycleGlobals wrap used to do — a t13b call site of the form `lifecycle.NewDoctorCmd(buildLifecycleDeps())` works end-to-end without a separate sync step. The existing parent shim's wrap remains compatible because both syncs write the same values (idempotent).
func NewInitCmd ¶
NewInitCmd builds the `da init` cobra command. Mirrors the NewInstallCmd / NewDoctorCmd Deps-injection pattern so the t13b worker can call `lifecycle.NewInitCmd(buildLifecycleDeps())` without any further wiring. Added in t13a — before this, the parent commands/init.go shim owned the cobra literal and used SetInitFlags + InitUsageErrorFn package-var seam writes to repoint the per-init getters at commands.Flags / commands.UsageError.
Construction-time wiring:
SetInitFlags is called with closures over deps so InitForceFn / InitDryRunFn / InitYesFn read live state at RunE time (cobra parses flags AFTER constructor return; a value snapshot taken at construction would be stale). FlagsFn takes precedence over the Deps.Flags value, mirroring applyDepsToGlobals.
InitUsageErrorFn is repointed at deps.UsageError when non-nil so init's positional-arg rejection renders through commands.UsageError's formatted-hint UX. When deps.UsageError is nil (lifecycle-only unit tests that don't construct a hint formatter) the in-package default InitUsageErrorFn is preserved.
The RunE wrapper additionally calls applyDepsToGlobals(deps) so downstream helpers that read lifecycle.Flags / .ErrorWithHintsFn / .Version / .Commit / .Describe (none for init today, but symmetric with NewInstallCmd / NewDoctorCmd) observe the same Deps-derived state.
Compatible with the existing parent commands/init.go shim, which sets the same seams via SetInitFlags + direct InitUsageErrorFn assignment before constructing its own cobra literal — t13b's shim deletion will switch root.go to call NewInitCmd(buildLifecycleDeps()) directly and drop the parent shim's manual wiring.
func NewInstallCmd ¶
NewInstallCmd builds the `da install` cobra command. The Deps argument carries UX helpers and the global-flags snapshot from the commands package; the RunE wrapper calls applyDepsToGlobals(deps) before each invocation so the helper functions in this file (which read lifecycle.Flags / .Version / .Commit / .Describe / .ErrorWithHintsFn package vars directly per the t01 SHAPE.md "PRESERVE current package-var seams during the moves" decision) stay in sync with the parent process's live state.
After t13a this absorbs what the parent commands/install.go shim's syncLifecycleGlobals helper used to do — a t13b call site of the form `lifecycle.NewInstallCmd(buildLifecycleDeps())` works end-to-end without a separate sync step. The existing parent shim's wrap remains compatible: the shim's syncLifecycleGlobals runs before the inner RunE, then this wrapper re-applies the same values from deps. Both writes are idempotent.
func NewRefreshCmd ¶
NewRefreshCmd builds the `da refresh` cobra command. The cobra metadata (Use/Short/Long/Example/Args) and the `--import` / `--inexact` flags live here in lifecycle; the RunE delegates to deps.RunRefresh which still holds the legacy runRefresh body in commands/ until t04 (add) and t06 (import) merge. See SHAPE.md §4a (refresh row) and the fold-back at .agents/active/fold-back/t07-refresh-body-deferred.md for the rationale.
func NewStatusCmd ¶
NewStatusCmd builds the `da status` cobra command. The jsonOutput closure reports whether to emit the JSON variant — the parent shim in commands/status.go passes `func() bool { return commands.Flags.JSON }` so the package-var seam stays at the root while lifecycle stays import-cycle free. Tests can pass their own closure to exercise either path.
── t13a constructor-shape decision ────────────────────────────────────────
NewStatusCmd retains the second jsonOutput func() bool argument (option (c) in the t13a fold-back observation at .agents/active/fold-back/ t13a-respawn-lifecycle-shims-not-passthroughs.yaml) rather than folding it into Deps. Rationale: the parent commands/status.go shim's RunE-override reads Flags.JSON through the jsonFlag closure inside the commands package so the globalflagcov static analyzer (which loads ./commands but not ./commands/lifecycle) sees the Flags.JSON load it requires for handler coverage. Threading jsonOutput through Deps would move that read into lifecycle and silently drop the coverage. T13b's worker can pass `lifecycle.NewStatusCmd(buildStatusDeps(), func() bool { return Flags.JSON })` without further wrapping, or widen globalflagcov's load set to include ./commands/lifecycle (preferred long-term — see t13 PR description). The jsonOutput argument also lets tests exercise both JSON and text paths without mutating any package var.
NewStatusCmd intentionally does NOT call applyDepsToGlobals because the moved status helpers do not read the lifecycle.Flags / .Version / .Commit / .Describe / .ErrorWithHintsFn package vars — status only consumes Deps directly (UsageError via statusNoArgsHint) plus the jsonOutput closure. Install/doctor/init all sync because their moved RunE bodies read the package vars; status does not.
func PrintAudit ¶
PrintAudit is the exported entry point used by commands/doctor.go (still in root before t09) to render the per-platform audit block. After t09 lands doctor in this package, the only caller is intra-package and PrintAudit can be lowercased back to printAudit (SHAPE.md OD-2 reversal pattern).
func PrintSymlinkDirAudit ¶
PrintSymlinkDirAudit is the exported entry point used by commands/seams_test.go (still in root before t11). Reversed when t11 splits the test file per cluster.
func RegisterInstallProject ¶
func RegisterInstallProject(projectName, projectPath string, deps InstallDeps) error
RegisterInstallProject upserts the project into ~/.agents/config.json, honoring --dry-run. Exported because the t03 shim in commands/install.go forwards to it and seams_test.go reaches it through the shim.
func RestoreFromResourcesCountedWithDeps ¶
RestoreFromResourcesCountedWithDeps restores files from ~/.agents/resources/<project>/ and returns the number of files restored plus a non-nil error if any directory walk, mkdir, write, or copy failed. Callers that stamp success (e.g. refresh metadata) MUST observe this error: a partially-applied restore that is reported as success makes retries and doctor/refresh recovery ambiguous. Lifted from commands/add.go in root-command-decomposition t02b.
func RestoreLegacyResourceFile ¶
func RestoreLegacyResourceFile(project, relPath, agentsHome, path string, deps AddDeps) (int, error)
RestoreLegacyResourceFile restores one non-canonical resource file by mapping its repo-relative path to its canonical ~/.agents location (via MapResourceRelToDest) and copying via deps.CopyFile. Returns 0 when the rel path has no known mapping. Lifted from commands/add.go in root-command-decomposition t02b.
func RunDoctor ¶
func RunDoctor(cmd *cobra.Command, args []string, deps DoctorConfigLoader) error
RunDoctor is the exported entry point used by commands/doctor.go's t09→t11 root shim so seams_test.go can drive the doctor pipeline with a fault- injected DoctorConfigLoader. After t11 the shim is deleted and the lowercase runDoctor satisfies the only remaining (intra-package) callers.
func RunInit ¶
RunInit is the exported entry the shim's RunE closure calls. It fronts the unexported runInit (which keeps the initDirMaker seam file-scoped to lifecycle).
func RunInitForTest ¶
func RunInitForTest(cmd *cobra.Command, args []string, deps InitDirMakerForTest) error
RunInitForTest is the export-for-test bridge for runInit, used by the parent package's seams_test.go fault-injection cases until t11 relocates them. Production callers go through NewInitCmd.
func RunInstall ¶
func RunInstall(strict bool, deps InstallDeps) error
func RunInstallGenerate ¶
func RunInstallGenerate(deps InstallDeps) error
func RunStatus ¶
func RunStatus(audit bool, agentFilter string, deps StatusConfigLoader, jsonOut bool) error
RunStatus is the exported entry point used by the root commands/status.go shim during the t08→t11 window. After t11 splits seams_test.go into commands/lifecycle/seams_test.go, the only remaining caller is the shim itself; at that point RunStatus can be lowercased back to runStatus per SHAPE.md OD-2's reversal pattern.
func RunStatusDefault ¶
RunStatusDefault is the convenience entry point the root status shim uses from inside the cobra RunE closure it owns. Keeping the RunE closure in the commands package (instead of inheriting lifecycle.NewStatusCmd's closure) lets the globalflagcov static analyzer trace the Flags.JSON read without expanding its package load set to ./commands/lifecycle.
func ScaffoldWorkflowAssetsForTest ¶
func ScaffoldWorkflowAssetsForTest(agentsHome string, deps InitDirMakerForTest) error
ScaffoldWorkflowAssetsForTest mirrors RunInitForTest for the scaffoldWorkflowAssets MkdirAll fault-injection test in seams_test.go.
func SetInitFlags ¶
func SetInitFlags(force, dryRun, yes func() bool)
SetInitFlags is the shim hook for repointing the init force/dry-run/ yes getters at the parent commands.Flags. Callers can pass nil for a getter to leave the in-package default in place (used by tests that only need to flip a subset of the seams).
func ShouldUseCachedGitSource ¶
ShouldUseCachedGitSource is exported so the t03 shim in commands/install.go can forward to it. seams_test.go's verbose-info branch test reaches it through the lower-case wrapper in the shim.
func WriteKGMCPConfigFile ¶
WriteKGMCPConfigFile merges the dot-agents-kg server entry into the JSON file at path, preserving any existing top-level keys and any other server entries. Lifted from commands/add.go in root-command-decomposition t02b.
func WriteKGMCPConfigs ¶
WriteKGMCPConfigs writes the dot-agents-kg MCP server entry into the three canonical platform config files (claude.json, cursor.json, mcp.json) under scopeDir. Lifted from commands/add.go in root-command-decomposition t02b.
Types ¶
type AddDeps ¶
type AddDeps interface {
MkdirAll(path string, perm os.FileMode) error
WriteFile(name string, data []byte, perm os.FileMode) error
Remove(name string) error
Executable() (string, error)
CopyFile(src, dst string) error
LoadConfig() (*config.Config, error)
}
AddDeps is the multi-method collaborator the lifecycle backup / restore / KG-MCP-config helpers need (interface-DI per docs/TEST_SEAMS.md). Lifted from commands/add.go in plan root-command-decomposition t02b so the helpers can live outside the root commands package without dragging in the rest of add.go.
The six operations are the add pipeline's fault-injectable touch points: filesystem materialization of resource trees and MCP config parents (MkdirAll), the MCP config payload itself (WriteFile), the destructive removal of an unmanaged config after a successful backup (Remove), the dot-agents binary path used to build the KG MCP server command (Executable), the resource copy used to back up and restore unmanaged configs (CopyFile), and config.json load for project registration lookups (LoadConfig).
type Deps ¶
type Deps struct {
Flags GlobalFlags
ErrorWithHints func(message string, hints ...string) error
UsageError func(message string, hints ...string) error
MaximumNArgsWithHints func(n int, hints ...string) cobra.PositionalArgs
RangeArgsWithHints func(min, max int, hints ...string) cobra.PositionalArgs
ExactArgsWithHints func(n int, hints ...string) cobra.PositionalArgs
NoArgsWithHints func(hints ...string) cobra.PositionalArgs
// ExampleBlock formats a multi-line cobra Example block. Mirrors the
// commands.ExampleBlock helper. Lifecycle subcommands use it for the
// cobra Example field at construction time.
ExampleBlock func(lines ...string) string
// RunRefresh is the back-edge into commands.runRefresh used by
// NewRefreshCmd's RunE. importAlso/inexact carry the parsed --import and
// --inexact flags through to the legacy body. The actual run body,
// package-var seams (stdRefreshConfigLoader, stdImportDeps, stdAddDeps),
// and the helper fan-out (mapResourceRelToDest, restoreFromResources)
// remain in commands/ until t04 (add) and t06 (import) merge and the
// cross-cluster constants / interfaces (addDeps, importDeps,
// importScope*, rel*Dir) can be re-homed into lifecycle. See
// .agents/active/fold-back/t07-refresh-body-deferred.md.
RunRefresh func(projectFilter string, importAlso, inexact bool) error
// FlagsFn is an optional closure over the caller's live flag state.
// When non-nil it is invoked by NewInstallCmd / NewDoctorCmd /
// NewInitCmd's RunE wrapper to populate the lifecycle.Flags package
// var at RunE time (cobra has parsed flags by then; a Deps.Flags
// value snapshot taken at constructor time would be stale). When
// nil, Deps.Flags is copied as-is.
//
// T13b's worker passes a closure of the form
// func() GlobalFlags { return GlobalFlags{DryRun: commands.Flags.DryRun, ...} }
// so the constructor reads live state on each invocation. Test code
// that exercises the constructor with a deterministic snapshot leaves
// FlagsFn nil and sets Deps.Flags directly.
FlagsFn func() GlobalFlags
// Version/Commit/Describe carry the build-info values the install
// pipeline's finalizeInstall helper writes into .agentsrc.json. When
// non-empty they are copied to the lifecycle.Version / .Commit /
// .Describe package vars by NewInstallCmd / NewDoctorCmd / NewInitCmd's
// RunE wrapper (the moved helpers read those vars directly). The
// zero-value "" defaults to leaving the package var untouched, so the
// build-info defaults set on the package vars themselves ("dev"/"" for
// Version/Commit/Describe) survive when a caller omits them.
Version string
Commit string
Describe string
}
Deps carries UX helpers and runtime state from the commands package into lifecycle without an import cycle. Mirrors agents.Deps and skills.Deps so the three extracted subpackages share the same shape. Only fields actually consumed by lifecycle subcommands are present.
Per SHAPE.md OD-7, there is intentionally no NewDeps() factory: the composition root in commands/root.go (and the transitional shims in commands/<verb>.go during t03–t12) construct the struct inline, the same way commands.NewAgentsCmd and commands.NewSkillsCmd do today.
── Constructor contract after t13a ────────────────────────────────────────
All four lifecycle command constructors accept Deps as the first (and only) argument:
NewInstallCmd(deps Deps) *cobra.Command NewDoctorCmd(deps Deps) *cobra.Command NewInitCmd(deps Deps) *cobra.Command NewStatusCmd(deps Deps, jsonOutput func() bool) *cobra.Command
NewStatusCmd retains the second jsonOutput argument (option (c) in the t13a fold-back observation) rather than absorbing it into Deps.JSONFlag. Rationale: the parent commands/status.go shim already passes the jsonFlag closure as a second positional arg so the globalflagcov static analyzer (which loads ./commands but not ./commands/lifecycle) can see the Flags.JSON read for handler coverage. Folding the closure into Deps would move the read site into lifecycle and break that coverage. T13b will either widen globalflagcov's load set (preferred) or keep the second-arg shape — the t13a contract leaves both doors open. T13b's worker can pass `lifecycle.NewStatusCmd(buildStatusDeps(), func() bool { return Flags.JSON })` without further wrapping.
── Constructor-side global sync (t13a) ───────────────────────────────────
NewInstallCmd / NewDoctorCmd / NewInitCmd's RunE wrappers internally call applyDepsToGlobals(deps) before delegating to the moved RunE body. That absorbs the parent shim's syncLifecycleGlobals dance: t13b's worker just constructs Deps with live commands.Flags / Version / Commit / Describe / ErrorWithHints in FlagsFn / Version / Commit / Describe / ErrorWithHints and calls the constructor. The constructor handles the rest. FlagsFn is a closure (not a value) so it captures live state at each RunE invocation — critical because cobra parses flags AFTER constructor calls. NewStatusCmd does NOT call applyDepsToGlobals because its moved helpers do not read the commands-package globals (status uses Flags only for jsonOutput, which the caller-supplied closure carries directly).
type DoctorConfigLoader ¶
DoctorConfigLoader is the narrow collaborator doctor.go's fault-injectable LoadConfig operation needs (interface-DI per docs/TEST_SEAMS.md). Single-method, file-prefixed -er form; file-scoped — do not share with other commands files.
Exported during the t09→t11 window so commands/seams_test.go (still in root until t11 splits it per cluster) can construct test doubles via the root shim's RunDoctor entry point. After t11 the root shim drops the type alias and this can lowercase back to doctorConfigLoader.
type GlobalFlags ¶
GlobalFlags mirrors the subset of commands.Flags consumed by lifecycle subcommands. Kept as a parallel type to commands.GlobalFlags so the lifecycle subpackage has no import on the parent commands/ package, matching the agents.GlobalFlags / skills.GlobalFlags pattern.
Extended in t03 with DryRun/Force/Verbose because the install command reads all four flags from a package-level Flags var (preserving the existing commands.Flags package-var seam per t01 SHAPE.md decision — "PRESERVE current package-var seams during the moves").
var Flags GlobalFlags
Flags is the package-level mirror of commands.Flags consumed by lifecycle subcommands' moved helper bodies (which still read the package var directly per the t01 SHAPE.md "PRESERVE current package-var seams during the moves" decision). Three populating paths converge here:
- The transitional commands/install.go shim's syncLifecycleGlobals helper (active until t13b deletes the shim) writes commands.Flags into this package var before invoking any lifecycle entry point.
- The NewInstallCmd / NewDoctorCmd / NewInitCmd constructors' RunE wrappers (added by t13a) call applyDepsToGlobals(deps) before delegating to the moved RunE body, so a t13b call site of the form `lifecycle.NewInstallCmd(buildLifecycleDeps())` works end-to-end without a separate sync step.
- Tests that exercise lifecycle entry points directly write the var via `Flags = lifecycle.GlobalFlags{...}` and restore it on cleanup.
type ImportCandidate ¶
ImportCandidate is the exported, lifecycle-facing shape of an import candidate: the project name plus the source root + source file path from which a canonical import is produced. Lifted from commands/import.go in plan root-command-decomposition t02b.
The shape is intentionally identical to the unexported importCandidate struct in commands/import.go; the function-var seam below (CanonicalImportOutputs) converts between the two when bridging into the canonicalImportOutputs machinery that still lives in import.go until t06 moves the whole import command. Once t06 lands, the commands/import.go duplicate can be deleted and the lifecycle shape becomes the only one.
type ImportOutput ¶
ImportOutput is the exported, lifecycle-facing shape of a canonical import emission: the destination-relative path and content bytes, plus the optional origin platform id used by RFC §6 alternate naming. Lifted from commands/import.go in t02b alongside ImportCandidate.
type InitDirMakerForTest ¶
type InitDirMakerForTest = initDirMaker
InitDirMakerForTest re-exports the file-private initDirMaker interface so the commands package's seams_test.go can pass its own fault-injection double to RunInitForTest / ScaffoldWorkflowAssetsForTest without lifecycle exposing its full internal type. Folded out in t11 when seams_test.go itself relocates here.
type InstallDeps ¶
type InstallDeps interface {
Getwd() (string, error)
MkdirAll(path string, perm os.FileMode) error
Symlink(oldname, newname string) error
LoadConfig() (*config.Config, error)
}
InstallDeps is the multi-method collaborator RunInstall, RunInstallGenerate, and their helpers need (interface-DI per docs/TEST_SEAMS.md). File-scoped — do not share with other commands files. The four operations are the install pipeline's fault-injectable touch points: working-directory resolution (Getwd), filesystem materialization of resource link parents and git cache roots (MkdirAll), the resource symlink itself (Symlink), and config.json load (LoadConfig) for project registration and lookup.
type StatusConfigLoader ¶
StatusConfigLoader is the narrow collaborator status.go's fault-injectable LoadConfig operation needs (interface-DI per docs/TEST_SEAMS.md). Single-method, file-prefixed -er form; file-scoped — do not share with other commands files.
Exported during the t08→t11 window so commands/seams_test.go (still in root until t11 splits it per cluster) can construct test doubles via the root shim's RunStatus entry point.
type StdAddDeps ¶
type StdAddDeps struct{}
StdAddDeps is the production AddDeps backed by direct os / projectsync / config calls. Production callers construct StdAddDeps{} inline; tests substitute a fake AddDeps implementation.
func (StdAddDeps) CopyFile ¶
func (StdAddDeps) CopyFile(src, dst string) error
func (StdAddDeps) Executable ¶
func (StdAddDeps) Executable() (string, error)
func (StdAddDeps) LoadConfig ¶
func (StdAddDeps) LoadConfig() (*config.Config, error)
func (StdAddDeps) Remove ¶
func (StdAddDeps) Remove(name string) error
type StdDoctorConfigLoader ¶
type StdDoctorConfigLoader struct{}
StdDoctorConfigLoader is the production DoctorConfigLoader backed by internal/config.Load. Exported alongside DoctorConfigLoader for the t09→t11 window so the root shim's NewDoctorCmd RunE can construct one while keeping lifecycle import-cycle free.
func (StdDoctorConfigLoader) LoadConfig ¶
func (StdDoctorConfigLoader) LoadConfig() (*config.Config, error)
LoadConfig delegates to internal/config.Load.
type StdInstallDeps ¶
type StdInstallDeps struct{}
StdInstallDeps is the production InstallDeps backed by the os package and config.Load.
func (StdInstallDeps) Getwd ¶
func (StdInstallDeps) Getwd() (string, error)
func (StdInstallDeps) LoadConfig ¶
func (StdInstallDeps) LoadConfig() (*config.Config, error)
func (StdInstallDeps) MkdirAll ¶
func (StdInstallDeps) MkdirAll(path string, perm os.FileMode) error
func (StdInstallDeps) Symlink ¶
func (StdInstallDeps) Symlink(oldname, newname string) error