Documentation
¶
Overview ¶
Package idearelocate implements cross-repo relocation of Idea and sidekick-seed artifacts per spec/features/cli/idea/relocate/README.md. This file houses the slug-and-target resolution primitives used by the verb's input-validation phase (Task 1 of the implementation plan). Mutation logic — file copy, in-file rewrite, cross-repo link cleanup, commits — lives in later tasks.
Index ¶
- func DirtyTreeError(dirty []PreflightSubject) error
- func ExecuteCommitPhase(changes []RepoChange, mode CommitMode) ([]RepoChange, *CommitFailure, error)
- func ExecutePreCommitPhase(sourceRepoRoot string, source SourceArtifact, target TargetRepo, ...) (MutationResult, []LinkUpdateResult, error)
- func FindReferences(repoRoot, slug string) ([]string, error)
- func FormatStdout(changes []RepoChange, mode CommitMode) string
- func IsPathClean(repoRoot, relPath string) (bool, error)
- func RewriteBody(content, targetRepo string) string
- type ArtifactKind
- type CommitFailure
- type CommitMode
- type LinkUpdateResult
- type MutationResult
- type PreflightSubject
- type RepoAction
- type RepoChange
- type SourceArtifact
- type TargetRepo
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DirtyTreeError ¶
func DirtyTreeError(dirty []PreflightSubject) error
DirtyTreeError formats a stderr-suitable error message naming every dirty subject, then returns an *exitcode.Error with code 7.
func ExecuteCommitPhase ¶
func ExecuteCommitPhase(changes []RepoChange, mode CommitMode) ([]RepoChange, *CommitFailure, error)
ExecuteCommitPhase stages each change's paths and, in CommitAuto mode, commits them. On a commit failure mid-flight it returns a CommitFailure with the committed-so-far state, the failing change, and the unprocessed remainder.
Non-git repos are vacuously processed: no staging, no commit, no SHA — the verb tolerates non-git project roots in the same way preflight does, so a mid-experiment workspace not yet under VCS can still relocate artifacts on disk.
func ExecutePreCommitPhase ¶
func ExecutePreCommitPhase( sourceRepoRoot string, source SourceArtifact, target TargetRepo, scanRepos []TargetRepo, slug string, ) (MutationResult, []LinkUpdateResult, error)
ExecutePreCommitPhase runs Task 3 (file copy + in-file rewrite + source delete) followed by Task 4 (cross-repo link cleanup), tracking rollback state. On any failure during these phases (other than destination collision), it restores on-disk state to its pre- invocation form and returns an *exitcode.Error with code 10 naming the failed step and the rollback actions performed.
Returns the MutationResult and per-repo link-update results when the whole pre-commit phase succeeds. Callers then proceed to Task 5's commit phase using those results.
func FindReferences ¶
FindReferences walks repoRoot/spec/**/*.md and returns the repo-root- relative paths of files that reference <slug> per the patterns from cli/idea/relocate#req:preflight-clean-tree-siblings-with-references:
- Bold-prefixed metadata lines naming the slug in any of: **Source Ideas:**, **Related Ideas:**, **Supersedes:**, **Promotes To:**. The slug must appear as a comma-separated value, not as a substring of a different value.
- Markdown links whose target ends in /<slug>.md (the slug is the last path component before .md, optionally preceded by /).
Files outside spec/ are ignored. The returned slice is sorted.
func FormatStdout ¶
func FormatStdout(changes []RepoChange, mode CommitMode) string
FormatStdout produces the canonical per-affected-repo lines and the summary line per cli/idea/relocate#req:stdout-format. When mode=CommitNo, the [<sha7>] bracket is omitted.
func IsPathClean ¶
IsPathClean reports whether <relPath> inside <repoRoot> has any uncommitted modifications (staged, unstaged, or untracked).
A non-git repoRoot (no .git directory anywhere up the tree) is treated as vacuously clean — there is no working tree to check. This matches the verb's "best-effort" pre-flight discipline: invocations against a non-git project don't fail pre-flight on the absence of git itself; the commit phase later will fail loudly if commits are requested in such a project.
func RewriteBody ¶
RewriteBody applies the two cross-repo-relocate substitution rules to content. targetRepo is the value of project.repo from the target's specscore.yaml — it replaces every word-bounded "this repo" occurrence in body prose.
Types ¶
type ArtifactKind ¶
type ArtifactKind string
ArtifactKind distinguishes an Idea document from a sidekick-seed.
const ( // KindIdea marks artifacts at spec/ideas/<slug>.md. KindIdea ArtifactKind = "idea" // KindSeed marks artifacts at spec/ideas/seeds/<slug>.md. KindSeed ArtifactKind = "seed" )
type CommitFailure ¶
type CommitFailure struct {
Failed RepoChange
FailedStderr string
Committed []RepoChange
Unprocessed []RepoChange
}
CommitFailure carries the post-mortem details for a stop-on-first- failure event: which change failed, the captured stderr from git-commit, the changes already committed (with SHAs), and the changes that were never attempted.
func (*CommitFailure) AsExitError ¶
func (f *CommitFailure) AsExitError() *exitcode.Error
AsExitError formats the CommitFailure into a stderr-suitable message and wraps it in an *exitcode.Error with code 10 per cli/idea/relocate#req:stop-on-first-commit-failure.
type CommitMode ¶
type CommitMode int
CommitMode toggles the post-mutation commit behavior.
const ( // CommitAuto stages and commits every affected repo. CommitAuto CommitMode = iota // CommitNo stages only — no commits. CommitNo )
type LinkUpdateResult ¶
LinkUpdateResult records which files were updated in a given repository.
func UpdateCrossRepoLinks ¶
func UpdateCrossRepoLinks(allRepos []TargetRepo, targetRepo TargetRepo, slug string, targetRelPath string) ([]LinkUpdateResult, error)
UpdateCrossRepoLinks updates all markdown link references to the relocated slug across all SpecScore repositories.
In-repo links are rewritten to relative paths. Cross-repo links are rewritten to full GitHub URL form. Bold-prefixed metadata lines are preserved.
type MutationResult ¶
type MutationResult struct {
// DestinationPath is the absolute path the artifact was written to
// inside the target repo.
DestinationPath string
}
MutationResult records the on-disk effects of ApplyMutation. Returned to the CLI verb for use in the per-affected-repo stdout summary.
func ApplyMutation ¶
func ApplyMutation(sourceRepoRoot string, source SourceArtifact, target TargetRepo) (MutationResult, error)
ApplyMutation performs Task 3 of cli/idea/relocate:
- Computes the destination path by mirroring source's repo-relative path inside target.Path.
- Rejects a pre-existing destination file with exit 1 (Conflict). No mutations are performed in this case.
- Reads the source artifact, applies RewriteBody (org rename + "this repo" → target.RepoName), writes the result to the destination (creating parent dirs as needed).
- Deletes the source artifact.
Task 6's pre-commit rollback wrapper is responsible for restoring state on any mid-sequence I/O failure (file copy or in-file rewrite failure). ApplyMutation itself does NOT roll back — it surfaces the underlying error so the wrapper can decide. The exception is the destination-collision case, which is treated as a pre-check and guarantees zero mutations by design.
type PreflightSubject ¶
type PreflightSubject struct {
RepoRoot string // absolute path to the repo
RelPath string // path relative to RepoRoot
}
PreflightSubject identifies one (repo, file) pair that pre-flight must verify is clean.
func CheckPreflight ¶
func CheckPreflight(subjects []PreflightSubject) ([]PreflightSubject, error)
CheckPreflight runs IsPathClean against every subject and returns the list of dirty (repo, path) subjects, in input order. The error result is non-nil only if a git invocation itself fails; a dirty path is signaled via the returned slice, not via the error.
func PreflightSubjectsForRelocate ¶
func PreflightSubjectsForRelocate( sourceRepoRoot string, sourceRelPath string, targetRepoRoot string, targetRelPath string, siblings []TargetRepo, slug string, ) ([]PreflightSubject, error)
PreflightSubjectsForRelocate composes the full list of preflight subjects for one relocate invocation:
- Source repo: the artifact file + spec/ideas/README.md.
- Target repo: the destination path + spec/ideas/README.md.
- Each sibling SpecScore-managed repo (other than source and target) that contains a reference to the slug: every matched file path.
Caller supplies the resolved source and target. siblings is the list of additional sibling repos discovered via DiscoverSiblings; the caller is responsible for excluding source and target from siblings before passing in.
type RepoAction ¶
type RepoAction string
RepoAction labels a repo's role in the relocate. Used both in stdout per-repo lines and to derive commit-subject formatting.
const ( // ActionMoved is the source repo's role: the artifact was deleted // from it. ActionMoved RepoAction = "moved" // ActionReceived is the target repo's role: the artifact landed in // it. ActionReceived RepoAction = "received" // ActionUpdatedLinks is a sibling repo's role: only link rewrites. ActionUpdatedLinks RepoAction = "updated-links" )
type RepoChange ¶
type RepoChange struct {
Repo TargetRepo // includes Path, RepoName, Org
Action RepoAction
Kind ArtifactKind // idea or seed
Slug string
Paths []string // repo-relative paths to `git add`
Subject string // commit subject (computed)
SHA string // populated after a successful commit; empty in CommitNo
}
RepoChange is one affected repo's slice of work — paths to stage and, in auto-commit mode, a subject line for `git commit`.
func AssembleRepoChanges ¶
func AssembleRepoChanges( source TargetRepo, sourceKind ArtifactKind, sourceRelPath string, target TargetRepo, targetRelPath string, siblings []TargetRepo, linkUpdates map[string][]string, slug string, ) []RepoChange
AssembleRepoChanges builds the ordered slice of affected-repo work. Order:
- Source (action=moved)
- Target (action=received)
- Siblings (action=updated-links), alphabetically by RepoName, ONLY when their Updated[] is non-empty.
linkUpdates maps each affected repo's path to its updated repo-rel paths. The source and target repos additionally carry the artifact- move path: sourceRelPath in source (a `git add` of a deleted file stages the deletion), and targetRelPath in target.
type SourceArtifact ¶
type SourceArtifact struct {
Path string // absolute path to the artifact file
Kind ArtifactKind // idea or seed
}
SourceArtifact is the resolved on-disk location of a relocate-target artifact within the source project.
func ResolveSourceArtifact ¶
func ResolveSourceArtifact(specRoot, slug string) (SourceArtifact, error)
ResolveSourceArtifact resolves <slug> per cli/idea/relocate#req:slug-resolves-idea-or-seed: Idea path is checked first at spec/ideas/<slug>.md; on miss, the seed path spec/ideas/seeds/<slug>.md is checked. Exactly one match returns that path + kind. Both existing returns an *exitcode.Error with code 5 (AmbiguousSlug). Neither existing returns code 3 (NotFound).
type TargetRepo ¶
type TargetRepo struct {
Path string // absolute path to the repo root (the dir containing specscore.yaml)
RepoName string // value of project.repo from the target's specscore.yaml (may be empty)
Org string // value of project.org from the target's specscore.yaml (may be empty)
}
TargetRepo is the resolved destination repository.
func DiscoverSiblings ¶
func DiscoverSiblings(specRoot string) ([]TargetRepo, error)
DiscoverSiblings returns the same data as discoverSiblings (the unexported helper used by ResolveTargetRepo). Exported here so the CLI verb can fetch the list once and pass it into both resolution and pre-flight, without scanning the workspace twice.
func ResolveTargetRepo ¶
func ResolveTargetRepo(specRoot, toRepo string) (TargetRepo, error)
ResolveTargetRepo resolves the --to-repo flag value per cli/idea/relocate#req:target-repo-resolution.
- Value containing no "/" is a repo slug: scan sibling directories of specRoot's parent for specscore.yaml files and match project.repo. Single match returns it. Zero matches → exit 3 (NotFound). Multiple matches → exit 2 (InvalidArgs).
- Value containing "/" is a path: relative to specRoot, or absolute if starting with "/". The path MUST be a directory containing a specscore.yaml; missing yaml → exit 6 (TargetNotSpecScore); missing directory → exit 3 (NotFound).