projectsync

package
v0.3.4 Latest Latest
Warning

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

Go to latest
Published: Jun 3, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package projectsync provides shared helpers for creating project directory structures, restoring resource files, writing refresh markers, and managing gitignore entries. These were extracted from commands/add.go, commands/refresh.go, and commands/init.go so that multiple command implementations can share them without duplication.

Index

Constants

View Source
const (
	PromoteStatePrepared        = "prepared"
	PromoteStateCanonicalCopied = "canonical-copied"
	PromoteStateSourceRemoved   = "source-removed"
	PromoteStateSymlinked       = "symlinked"
	PromoteStateRCSaved         = "rc-saved"
	PromoteStateRolledBack      = "rolled-back"
)

Promote journal states. The pipeline writes one entry, then advances through these states as each destructive step succeeds.

View Source
const PromoteJournalDir = ".promote-journal"

PromoteJournalDir is the relative directory (under ~/.agents/) holding promote-journal entries. Each in-flight promotion writes a single JSON file here; the file is removed once the promotion completes (or rollback finishes).

Variables

This section is empty.

Functions

func AdvancePromoteJournal

func AdvancePromoteJournal(path, newState string) error

AdvancePromoteJournal updates the State and UpdatedAt of an existing journal entry. The file is rewritten in place. A missing journal file is reported as an error so the caller can distinguish "lost-journal" failures from normal progression.

func BeginPromoteJournal

func BeginPromoteJournal(agentsHome string, entry PromoteJournalEntry) (string, error)

BeginPromoteJournal writes a fresh journal entry under ~/.agents/.promote-journal/ with state="prepared". The returned path is the absolute journal file location; the caller passes it to AdvancePromoteJournal and RemovePromoteJournal as the promotion progresses.

The caller must populate Singular, Bucket, Name, SourcePath, and CanonicalPath on the entry. ID/State/StartedAt/UpdatedAt are overwritten.

func CopyFile

func CopyFile(src, dst string) error

CopyFile copies src to dst, creating parent directories as needed.

func CopyTree

func CopyTree(src, dst string) error

CopyTree recursively copies the directory tree at src to dst, preserving file modes. Symlinks in the source tree are skipped — the canonical store holds only real files. On Windows a directory junction also carries os.ModeSymlink (Go 1.23+) and is skipped by the same check; a file hard link carries no reparse point and is indistinguishable from a real file by the OS, but production promote sources are real skill/agent trees with no internal hard links, so the symlink mode check is the complete and correct production contract on every OS.

func CreateProjectDirs

func CreateProjectDirs(project string) error

CreateProjectDirs creates the standard per-project bucket directories inside AgentsHome. It is safe to call repeatedly; MkdirAll is idempotent.

func EnsureGitignoreEntry

func EnsureGitignoreEntry(repoPath, entry string)

EnsureGitignoreEntry appends entry to <repoPath>/.gitignore if it is not already present. Silent no-op if the file cannot be opened or read.

func ListBucket

func ListBucket(scope string, spec BucketSpec) error

ListBucket prints resources under ~/.agents/<bucket>/<scope>/ following the shared layout: each resource is a directory containing the spec's manifest. Description is parsed from the manifest's YAML frontmatter when present.

func PromoteResource

func PromoteResource(name, projectPath string, spec PromoteSpec) error

PromoteResource promotes a repo-local resource (.agents/<bucket>/<name>/) into the shared agents store at ~/.agents/<bucket>/<project>/<name>/. The canonical location becomes the real directory and the repo-local path is replaced with a managed symlink pointing at it. Idempotent when the repo-local path is already a managed symlink to the canonical path.

func ReadFrontmatterDescription

func ReadFrontmatterDescription(mdPath string) string

ReadFrontmatterDescription parses the YAML frontmatter of a markdown file and returns the value of the "description:" field. Returns "" if the file cannot be opened, has no frontmatter, or has no description field.

func RecoverPendingPromote

func RecoverPendingPromote(agentsHome string, e PromoteJournalEntry) (string, error)

RecoverPendingPromote inspects a journal entry and either rolls it forward (when the next step is safe) or surfaces a manual-cleanup notice for states past the recoverable line. Returns a short human-readable description of what was done; the journal file is removed only when recovery completes cleanly.

State machine:

  • "prepared": no destructive change yet — delete the journal.
  • "canonical-copied": canonical exists but source intact; remove canonical.
  • "source-removed": source gone, canonical present, no symlink yet — recreate symlink (preferred) so the on-disk layout matches the steady state of a successful promote.
  • "symlinked": canonical + symlink ok; only rc.Save was pending — surface a manual rc update notice (we cannot guess the rc.Project field from here).
  • "rc-saved": success — just delete the journal.
  • "rolled-back": rollback already attempted — just delete.

func RefreshMarkerContent

func RefreshMarkerContent(version, commit, describe string) []byte

RefreshMarkerContent returns the byte content for a .agents-refresh marker file, suitable for os.WriteFile.

func RemovePromoteJournal

func RemovePromoteJournal(path string) error

RemovePromoteJournal deletes the journal file. Safe to call on a missing path — a not-exist error is treated as success.

func WriteRefreshToAgentsRC

func WriteRefreshToAgentsRC(projectName, projectPath, version, commit, describe string) error

WriteRefreshToAgentsRC updates or creates .agentsrc.json with refresh metadata (version, commit, describe, refreshedAt) and removes a legacy .agents-refresh file if present.

Types

type BucketSpec

type BucketSpec struct {
	// Bucket is the directory name under ~/.agents/, e.g. "agents" or "skills".
	Bucket string
	// ManifestName is the filename that must exist inside each resource directory
	// to count as installed, e.g. "AGENT.md" or "SKILL.md".
	ManifestName string
	// Singular is the lowercase noun used in count output, e.g. "agent" or "skill".
	Singular string
	// Plural is the capitalized heading noun, e.g. "Agents" or "Skills".
	Plural string
}

BucketSpec describes a per-resource-type bucket under ~/.agents/<bucket>/ so list/promote helpers can be shared between agents and skills (and any future resource types that follow the same shape).

type PromoteJournalEntry

type PromoteJournalEntry struct {
	ID            string    `json:"id"`
	Singular      string    `json:"singular"`
	Bucket        string    `json:"bucket"`
	Name          string    `json:"name"`
	SourcePath    string    `json:"source_path"`
	CanonicalPath string    `json:"canonical_path"`
	State         string    `json:"state"`
	StartedAt     time.Time `json:"started_at"`
	UpdatedAt     time.Time `json:"updated_at"`
}

PromoteJournalEntry is the JSON shape persisted under ~/.agents/.promote-journal/. One entry exists per in-flight PromoteResource call; entries are removed on success or on completed rollback.

func ListPendingPromoteJournals

func ListPendingPromoteJournals(agentsHome string) ([]PromoteJournalEntry, error)

ListPendingPromoteJournals returns all journal entries under ~/.agents/.promote-journal/ whose state is not terminal (rc-saved or rolled-back). The returned slice is sorted by StartedAt ascending so callers can process oldest-first during recovery.

type PromoteSpec

type PromoteSpec struct {
	BucketSpec
	// Force overwrites an existing real canonical directory at
	// ~/.agents/<bucket>/<project>/<name>/. When false, an existing real dir
	// is fatal and the returned error appends ExistingRealDirHint as a
	// recovery hint.
	Force bool
	// ExistingRealDirHint is the trailing recovery hint shown when canonical
	// is a real directory and Force is false. Pass "; use --force to overwrite"
	// for buckets that expose a force flag, or "; cannot promote" otherwise.
	ExistingRealDirHint string
	// RegisterInRC appends name to the bucket-specific slice in
	// .agentsrc.json (e.g. rc.Agents or rc.Skills) and returns the new slice
	// length, used in the SuccessBox count line.
	RegisterInRC func(rc *config.AgentsRC, name string) int
	// MirrorRefresh refreshes platform-level mirrors for the bucket. Errors
	// must be surfaced via ui.Bullet by the callback itself; PromoteResource
	// ignores the returned error so a failed mirror refresh does not roll
	// back the manifest update (matching pre-extraction behavior).
	MirrorRefresh func(projectName, projectPath string) error
}

PromoteSpec parameterizes PromoteResource for the agents/skills buckets. Add a new bucket by populating BucketSpec, wiring RegisterInRC and MirrorRefresh, and choosing whether the CLI exposes a force flag.

Jump to

Keyboard shortcuts

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