Documentation
¶
Overview ¶
Package project discovers projects within a workspace via magusfile presence and exposes spell-based source/output/dep inference over the types in magus/types.
Index ¶
- Variables
- func Affected(ctx context.Context, w *types.Workspace, base string) (*types.AffectedResult, error)
- func AffectedFromPaths(ctx context.Context, w *types.Workspace, paths []string) (*types.AffectedResult, error)
- func Affinity(scan []ScannedCommit) []types.CoChange
- func Complexity(path string) int
- func Discover(_ context.Context, root string) (*types.Workspace, error)
- func ExtraArgs(ctx context.Context) []string
- func FileHotspots(scan []ScannedCommit, complexity func(rel string) int) []types.FileHotspot
- func IsIgnoreDir(name string) bool
- func Midpoint(scan []ScannedCommit) time.Time
- func Ownership(scan []ScannedCommit, staleBefore time.Time) []types.OwnershipEntry
- func ProjectStats(scan []ScannedCommit) map[string]ProjectStat
- func Trend(scan []ScannedCommit) []types.TrendEntry
- func Where(w *types.Workspace, dir string) (*types.Project, bool)
- func WithExtraArgs(ctx context.Context, args []string) context.Context
- type ProjectStat
- type ScannedCommit
- type ShadowConflict
- type SpellRegistry
- func (r *SpellRegistry) All() []*spells.Spell
- func (r *SpellRegistry) Lookup(name string) (*spells.Spell, bool)
- func (r *SpellRegistry) RegisterIfAbsent(s *spells.Spell) *spells.Spell
- func (r *SpellRegistry) RegisterSpell(s *spells.Spell)
- func (r *SpellRegistry) SetEnsureHook(fn func())
- func (r *SpellRegistry) UnregisterSpell(name string)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var IgnoreDirs = []string{
"vendor",
"node_modules",
"target",
"gen",
}
IgnoreDirs lists non-dot directory names skipped by discovery and watch (matched at any depth). Dot-directories are skipped separately by IsIgnoreDir without being listed here - see there.
SCOPE: this is the PRE-resolution default. Discovery and watch run before any spell is known, so they cannot ask a project's spells which dirs are noise; this list is the standing answer for the big build/dependency trees that must be pruned fast on every walk. Language spells ALSO declare their ecosystem's dirs via mgs_listIgnoreDirs (go->vendor, ts->node_modules, ...), which the POST-resolution input-hashing walk unions on top - so vendor/node_modules/target appear in both places on purpose. Do NOT remove a dependency dir here on the theory that "the spell owns it now": the spell only reaches the hash walk, and dropping it here would make discovery descend into that tree for every project the spell does not resolve. The two sets are not nested (this one carries gen, which no spell declares; the python spell declares __pycache__, deliberately left out here - missing it costs only a little discovery-walk time, and adding it would change hash keys for broad- glob projects that sit near a __pycache__). These names also prune the cache-key expansion walk (internal/cache.expandSources), which is the sharper edge: a target CAN legitimately declare a file inside one of these trees - a generated descriptor another project produces is a real input - and a walk that skips the directory would drop it from the key while `describe target` went on listing it under sources. So an EXACT, wildcard-free declaration is resolved by stat instead and reaches the key normally. A PATTERN gets no such exemption, because that is the whole point of pruning (a bare **/*.js must not hash node_modules); MGS1029 reports one aimed inside a pruned tree rather than letting it match nothing in silence. Confirm what actually keyed with `describe target --cache --inputs`.
Functions ¶
func Affected ¶
Affected returns the set of projects impacted by VCS changes. Errors wrap types.ErrAffectedFallback when VCS is disabled or fails so callers fall back to all projects.
func AffectedFromPaths ¶
func AffectedFromPaths(ctx context.Context, w *types.Workspace, paths []string) (*types.AffectedResult, error)
AffectedFromPaths computes the affected set from explicit paths without VCS. Absolute paths outside the workspace root are silently skipped.
func Affinity ¶
func Affinity(scan []ScannedCommit) []types.CoChange
Affinity returns every pair of projects that changed together, hottest pair first. The Hidden flag is left for the caller to set from the dependency graph.
func Complexity ¶
Complexity returns a whitespace-complexity proxy for the file at path: one point per non-blank line plus one per indentation level, so size and nesting both count. Unreadable files (e.g. deleted within the window) score 0.
func Discover ¶
Discover walks root and returns a *types.Workspace. Only directories with a magusfile are registered; explicit spell registration in the magusfile is required (auto-detection via spell markers has been retired).
Example ¶
ExampleDiscover shows how to discover projects in a workspace root using the project package directly. Callers that want the full orchestrator (cache, telemetry, VCS integration) should use magus.Inspect instead.
// Create a minimal workspace with two projects for illustration.
root, err := os.MkdirTemp("", "magus-project-example-*")
if err != nil {
fmt.Println("setup error:", err)
return
}
defer os.RemoveAll(root)
for _, name := range []string{"api", "web"} {
dir := filepath.Join(root, name)
if err := os.MkdirAll(dir, 0o755); err != nil {
fmt.Println("setup error:", err)
return
}
if err := os.WriteFile(filepath.Join(dir, "magusfile.tl"), []byte(""), 0o644); err != nil {
fmt.Println("setup error:", err)
return
}
}
ws, err := Discover(context.Background(), root)
if err != nil {
fmt.Println("inspect error:", err)
return
}
for _, p := range ws.All() {
fmt.Println(p.Path)
}
Output: api web
func FileHotspots ¶
func FileHotspots(scan []ScannedCommit, complexity func(rel string) int) []types.FileHotspot
FileHotspots ranks files by churn × complexity (the canonical hotspot score). complexity maps a workspace-relative path to its complexity proxy.
Churn is attributed along LINEAGE, not by path string: every name a file went by in the window folds onto the name it ends under, so a file renamed three times ranks once with its whole history rather than four times with a quarter each. That is what makes the ranking answer "what keeps getting rewritten" - the thing, not the path - and it is why a file's move count is worth reporting beside its edits.
A file whose last event was a delete is left OUT. The ranking exists to point at what to fix first, and a deleted file is not a refactoring target; including it would seat a long tail of unfixable zero-complexity rows above real ones.
func IsIgnoreDir ¶
IsIgnoreDir reports whether a directory named name is skipped during discovery and watch. Any dot-directory is skipped: VCS, editor, and tool metadata (.git, .hg, .magus, .idea, .vscode, .claude and its worktree checkouts, ...) never holds a discoverable magus project or spell. Other names must be a well-known build or dependency directory. There is deliberately no opt-in for a dot-dir project today; add a config allowlist if a real workspace ever needs one.
func Midpoint ¶
func Midpoint(scan []ScannedCommit) time.Time
Midpoint returns the time halfway between the oldest and newest commit in the scan, or the zero time when the scan carries no dated commits.
func Ownership ¶
func Ownership(scan []ScannedCommit, staleBefore time.Time) []types.OwnershipEntry
Ownership reports per-project author concentration, most-concentrated first. staleBefore flags projects whose most recent commit predates it (abandonment risk); pass the zero time to disable the flag.
func ProjectStats ¶
func ProjectStats(scan []ScannedCommit) map[string]ProjectStat
ProjectStats counts commits, distinct authors, and the most recent commit per project.
func Trend ¶
func Trend(scan []ScannedCommit) []types.TrendEntry
Trend splits the window at its midpoint and ranks projects by the change in activity between the two halves (rising first).
Types ¶
type ProjectStat ¶
ProjectStat is one project's churn over the window.
type ScannedCommit ¶
type ScannedCommit struct {
Author string
Date time.Time
Files []string // workspace-relative paths that fell inside the workspace
Projects []string // distinct projects the commit touched, sorted
// Renames carries {before, after} for every path this commit moved, already
// workspace-relative. It is the lineage edge: FileHotspots walks these to fold a
// file's churn onto the name it ends the window under, instead of ranking each
// name it has been through as a separate, quieter file. A pair is dropped unless
// BOTH sides fall inside the workspace, since a half-resolved chain would
// silently reattribute churn to a path the caller cannot open.
Renames [][2]string
// Deleted is the subset of Files this commit removed. Kept separate rather than
// inferred from the filesystem: a path missing from disk today says nothing about
// WHEN it went, and a file deleted and later restored must not read as deleted.
Deleted []string
}
ScannedCommit is one commit reduced to workspace-relative, project-attributed form — the shared input every insight lens aggregates from.
func Scan ¶
func Scan(ctx context.Context, w *types.Workspace, dir string, commits int, since string) ([]ScannedCommit, error)
Scan reads recent history (scoped to dir) and attributes each commit's files to projects. since bounds the window by commit date. It returns a wrapped ErrVCSUnsupported when VCS is disabled or the backend can't report per-commit files.
type ShadowConflict ¶ added in v0.2.0
type ShadowConflict struct {
Import string // the import path an author writes, e.g. "spells/hello"
Winner string // absolute path to the canonical (root-most) spell source
Shadowed string // absolute path to the dead (deeper) spell source
}
ShadowConflict is one spell import defined at two levels in ancestor-descendant relation, so root-wins resolution (see the spell import walk) makes the deeper definition dead. It is the footgun the shadow ward guards: an author placed a spell next to a nested project expecting it to be used, but a spell of the same import path higher up silently wins.
func SpellShadows ¶ added in v0.2.0
func SpellShadows(root string) ([]ShadowConflict, error)
SpellShadows scans the workspace rooted at root and returns every spell-import shadow: a "spells/<name>" defined at both an ancestor directory and a descendant directory. Sibling subtrees that reuse a name (web/spells/x and api/spells/x) are NOT shadows, since no project's root-to-leaf path sees both; only an ancestor-descendant pair is. Results are sorted by (Import, Shadowed) for a stable diagnostic. The scan skips the usual ignore dirs (.git, node_modules, ...).
type SpellRegistry ¶
type SpellRegistry struct {
// contains filtered or unexported fields
}
SpellRegistry is the metadata repository for registered spells.
func DefaultSpellRegistry ¶
func DefaultSpellRegistry() *SpellRegistry
DefaultSpellRegistry returns the process-level spell registry singleton.
func NewSpellRegistry ¶
func NewSpellRegistry() *SpellRegistry
NewSpellRegistry returns an empty SpellRegistry.
func (*SpellRegistry) All ¶
func (r *SpellRegistry) All() []*spells.Spell
All returns a snapshot of every registered spell.
func (*SpellRegistry) Lookup ¶
func (r *SpellRegistry) Lookup(name string) (*spells.Spell, bool)
Lookup returns the named spell, or (nil, false) if not found.
func (*SpellRegistry) RegisterIfAbsent ¶
func (r *SpellRegistry) RegisterIfAbsent(s *spells.Spell) *spells.Spell
RegisterIfAbsent registers s and returns it, or — if a spell of the same name is already registered — returns the existing one without adding a duplicate. The ensure-read, name check, and insert happen as one critical section, so concurrent callers that load the same spell (parallel magusfile evaluation, remote-cache backend resolution and an `import` racing for one spell) settle on a single registration instead of racing into RegisterSpell's duplicate panic.
func (*SpellRegistry) RegisterSpell ¶
func (r *SpellRegistry) RegisterSpell(s *spells.Spell)
RegisterSpell adds s to the registry; panics on nil or duplicate name.
func (*SpellRegistry) SetEnsureHook ¶
func (r *SpellRegistry) SetEnsureHook(fn func())
SetEnsureHook installs an idempotent hook called before each registry read; nil clears it.
func (*SpellRegistry) UnregisterSpell ¶
func (r *SpellRegistry) UnregisterSpell(name string)
UnregisterSpell removes the named spell; no-ops if not found.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package impact computes the forensic blast radius of a changeset: the changed files, the projects that directly contain them (seeds), and the transitive set of projects and targets a change ripples out to via the dependency-graph reverse closure.
|
Package impact computes the forensic blast radius of a changeset: the changed files, the projects that directly contain them (seeds), and the transitive set of projects and targets a change ripples out to via the dependency-graph reverse closure. |