initcfg

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Jun 25, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package initcfg discovers project structure and renders a starter .archfit.yaml. It is an adapter (uses toolrun.Runner for go list) and may import os for filesystem inspection (DiscoverTS, DiscoverPy).

Package initcfg — source patcher for .archfit.yaml files. ApplyEdits locates nodes via goccy AST, then mutates the original bytes via line splices so comments, formatting, and untargeted sections are preserved.

Index

Constants

View Source
const (
	SubdomainStatusDraft    = "draft"
	SubdomainStatusApproved = "approved"
)

Draft status values for SubdomainDraft.Status.

View Source
const (
	DraftStatusDraft    = "draft"
	DraftStatusApproved = "approved"
)

Draft status values shared by the single-scalar draft files (owner, volatility).

Variables

This section is empty.

Functions

func ApplyEdits added in v0.2.0

func ApplyEdits(src []byte, edits []Edit) ([]byte, error)

ApplyEdits applies a list of edits to src in a single bottom-up pass. The original bytes outside targeted line ranges are preserved exactly, keeping comments, key order, rules, and exceptions intact.

func DiscoverRust added in v0.6.0

func DiscoverRust(ctx context.Context, root string, runner toolrun.Runner) ([]ModuleDef, []ModuleEdge, error)

DiscoverRust enumerates first-party crates from `cargo metadata` and returns one ModuleDef per workspace member. Each module's Paths glob is the crate name itself — Rust graph nodes are "package:<crate>" (path = crate name) and the change-locality file mapping also keys on the crate name, so a crate-name glob matches both the dependency node and the file-derived module key.

Returns (nil, nil, nil) when cargo is absent (a Cargo.toml can exist without a toolchain installed); a present-but-failing cargo or unparseable output is a real error, mirroring discoverGo's loud-on-failure behaviour.

func PinModuleValues added in v0.5.0

func PinModuleValues(src []byte, field ModuleField, current map[string]string, pins []ValuePin) ([]byte, int, error)

PinModuleValues applies approved pins for one module field to the .archfit.yaml source bytes. field selects which scalar to write (FieldOwner or FieldVolatility). current maps module name → current value for that field (empty string if unset). Only modules whose field is absent are updated — a field already set to a different value is left alone (never overwrite human edits); a field already set to the same value is an idempotent no-op. Returns (patched bytes, count of modules actually patched, error).

func PinSubdomains added in v0.3.0

func PinSubdomains(src []byte, currentSubdomains map[string]string, pins []SubdomainPin) ([]byte, int, error)

PinSubdomains applies approved subdomain pins to the .archfit.yaml source bytes. currentSubdomains maps module name → current subdomain value (empty string if unset). Only modules whose subdomain field is absent (or whose current value already matches the pin — idempotent) are updated. Returns (patched bytes, count of modules actually patched, error).

func Render

func Render(cfg DiscoveredConfig, ann map[string]ModuleAnnotation, apply bool) string

Render converts a DiscoveredConfig into a YAML string suitable for saving as .archfit.yaml. The output includes a TODO comment and uses only known config fields so it round-trips through config.Load.

ann maps module names to LLM annotations. When ann is nil the output is byte-identical to the pre-annotation Render. apply controls plan vs live mode: false = comment-only suggestions; true = write live fields.

func RenderUpdateReport added in v0.2.0

func RenderUpdateReport(r UpdateReport, ann map[string]ModuleAnnotation, allowedLayers []string) string

RenderUpdateReport produces a human-readable plan-mode drift report. Sections are omitted when their slice is empty.

  • ADDED: paste-ready YAML stanzas via writeModuleStanza (apply=true so fields are visible to copy; out-of-set layers still render as comments per writeModuleStanza rules).
  • REMOVED: each removed module noted as "not found in discovery — verify or remove".
  • PATH DRIFT: config vs discovered paths, with an explicit note that --apply replaces module paths with the discovered paths and writes a backup.
  • UNCLASSIFIED: modules missing classification; shows LLM suggestion from ann when present, otherwise suggests running with --llm.
  • When r.StructuralInSync is true, emits a "structurally in sync" line.

Output is deterministic. Must not import internal/config or internal/llm.

func WriteSubdomainDrafts added in v0.3.0

func WriteSubdomainDrafts(path string, f SubdomainDraftFile) error

WriteSubdomainDrafts writes the draft file atomically (temp + rename).

func WriteValueDrafts added in v0.5.0

func WriteValueDrafts(path string, f ValueDraftFile) error

WriteValueDrafts writes the draft file atomically (temp + rename).

Types

type AddModuleEdit added in v0.2.0

type AddModuleEdit struct {
	Def ModuleDef
	Ann *ModuleAnnotation // may be nil
}

AddModuleEdit inserts a new module stanza. NO-OP if the module key already exists live.

type ClassifyTarget added in v0.2.0

type ClassifyTarget struct {
	Name  string
	Paths []string
	Files []string
}

ClassifyTarget bundles the information sent to the LLM classifier for one module. Paths mirrors ModuleDef.Paths verbatim. Files holds up to 20 first-level file basenames resolved from filesystem-glob paths (Go/TS style, e.g. "internal/x/**") or from a Python dotted path converted to a directory (e.g. "ccgram.handlers" → <root>/ccgram/handlers). Files is empty when the directory does not exist or when paths are purely dotted with no corresponding directory.

func BuildClassifyTargets added in v0.2.0

func BuildClassifyTargets(root string, mods []ModuleDef) []ClassifyTarget

BuildClassifyTargets produces one ClassifyTarget per ModuleDef, in the same order as mods. For each module it attempts to collect up to 20 first-level regular-file basenames:

  • Filesystem-glob paths (contain "/" — Go "internal/x/**" or TS "src/x/**"): strip a trailing "/**" to get the directory, join under root, read first-level regular files, skip dot- and underscore-prefixed names.
  • Python dotted paths (contain ".", no "/"): convert dots to slashes and look for the directory under root. If absent, Files is left empty (explicit degrade).

Files are de-duplicated, sorted, and capped at 20. Output is deterministic: target order matches mods; Files within each target are sorted.

type CommentModuleEdit added in v0.2.0

type CommentModuleEdit struct {
	Module string
	Note   string
}

CommentModuleEdit prefixes the module's full source range (incl. head comment) with a removal marker. NO-OP if the marker already exists.

type DiscoveredConfig

type DiscoveredConfig struct {
	// ModulePath is the Go module path (e.g. "github.com/alexei-led/archfit").
	ModulePath string
	// Modules are the discovered candidate modules.
	Modules []ModuleDef
	// Layers are the inferred layers in order (outermost to innermost).
	Layers []string
	// Edges are the directed module-level dependency edges, populated when a
	// dependency graph was available at discovery time (go list Imports, cargo
	// metadata resolve). Empty when no graph data was available.
	Edges []ModuleEdge
	// PyPackage is the primary Python top-level package name (e.g. "ccgram").
	PyPackage string
	// HasGo is true when a go.mod was found at root.
	HasGo bool
	// HasPython is true when Python packages were discovered.
	HasPython bool
	// HasTS is true when TypeScript packages were discovered.
	HasTS bool
	// HasRust is true when a Cargo.toml was found at root.
	HasRust bool
}

DiscoveredConfig holds all discovered modules from Go, TypeScript, and Python.

func Discover

func Discover(ctx context.Context, root string, runner toolrun.Runner) (DiscoveredConfig, error)

Discover detects Go, Python, and TypeScript modules at root. Go discovery is skipped when no go.mod exists. Python and TypeScript discovery run unconditionally (they guard on their own marker files).

Name uniqueness is guaranteed by a two-pass disambiguation applied after all language discoverers have run:

  1. First pass — only colliders are touched. For each module name shared by more than one module, every module with that name is renamed to a slug derived from its first Paths glob: strip a trailing "/**", then replace every "/" and "." with "_". Modules whose names are already unique are left completely unchanged.

  2. Second pass — if any slug still collides with another entry (slug or an original name that was never touched), a deterministic numeric suffix is appended ("_2", "_3", …). Suffixes are assigned in ascending order over the slice position so the result is stable across runs.

type Edit added in v0.2.0

type Edit interface {
	// contains filtered or unexported methods
}

Edit is a sealed mutation to apply to an .archfit.yaml source. Only the types defined in this package satisfy Edit.

type ExistingModule added in v0.2.0

type ExistingModule struct {
	Name          string
	Paths         []string
	HasSubdomain  bool
	HasVolatility bool
	HasLayer      bool
}

ExistingModule represents a module already present in the config file.

type ModuleAnnotation added in v0.2.0

type ModuleAnnotation struct {
	Subdomain     string
	Volatility    string
	Owner         string
	Layer         string
	Role          string
	SuggestedName string
}

ModuleAnnotation carries optional LLM-suggested metadata for a module. Layer holds the raw LLM layer suggestion; whether it is written live vs as a comment is decided in writeModuleStanza based on allowedLayers.

type ModuleDef

type ModuleDef struct {
	// Name is a short human-readable identifier (e.g. "extract", "model").
	Name string
	// Paths contains the glob patterns that own this module's files.
	Paths []string
	// Public contains globs for exported/public surface files.
	Public []string
	// Internal contains globs for package-private files.
	Internal []string
	// Layer is the inferred architectural layer (e.g. "adapter", "core", "cmd").
	Layer string
}

ModuleDef is a candidate module discovered from the project structure.

func DiscoverPy

func DiscoverPy(root string) ([]ModuleDef, error)

DiscoverPy reads Python packages from root and root/src (src-layout). A Python project is detected by pyproject.toml or setup.py at root. For each top-level package found, sub-packages are returned as individual modules. If a top-level package has no sub-packages it is returned itself.

func DiscoverTS

func DiscoverTS(root string) ([]ModuleDef, error)

DiscoverTS reads src/ or lib/ subdirectories if package.json is present at root. Returns discovered module definitions, one per subdirectory.

type ModuleEdge added in v0.6.0

type ModuleEdge struct {
	From string // module Name of the dependent
	To   string // module Name of the dependency
}

ModuleEdge is a directed dependency edge between two discovered modules. From depends on (imports) To.

type ModuleField added in v0.2.0

type ModuleField int

ModuleField is the set of module scalar fields that SetModuleFieldsEdit can insert.

const (
	FieldSubdomain  ModuleField = iota // subdomain: ...
	FieldVolatility                    // volatility: ...
	FieldOwner                         // owner: ...
	FieldLayer                         // layer: ...
	FieldReviewedAt                    // reviewed_at: 2026-06-18T...
	FieldReviewedBy                    // reviewed_by: ...
)

ModuleField enum values in canonical order.

type PathDelta added in v0.2.0

type PathDelta struct {
	Name            string
	ConfigPaths     []string
	DiscoveredPaths []string
}

PathDelta records a module present in both config and discovery whose paths differ.

type SetModuleFieldsEdit added in v0.2.0

type SetModuleFieldsEdit struct {
	Module string
	Fields map[ModuleField]string
}

SetModuleFieldsEdit inserts absent scalar fields (subdomain/volatility/layer) into an existing module. All absent fields are coalesced into ONE block insertion, in canonical order subdomain→volatility→layer. Skips Layer if value not in layers:.

type SubdomainDraft added in v0.3.0

type SubdomainDraft struct {
	Module     string `yaml:"module"`
	Subdomain  string `yaml:"subdomain"`  // llm draft: core|supporting|generic
	Volatility string `yaml:"volatility"` // llm draft: low|medium|high (may be empty)
	Rationale  string `yaml:"rationale"`
	Status     string `yaml:"status"` // SubdomainStatusDraft or SubdomainStatusApproved
}

SubdomainDraft is one LLM-suggested subdomain assignment awaiting human review.

type SubdomainDraftFile added in v0.3.0

type SubdomainDraftFile struct {
	Version int              `yaml:"version"`
	Drafts  []SubdomainDraft `yaml:"drafts"`
}

SubdomainDraftFile is the top-level container for .archfit-subdomains.yaml.

func LoadSubdomainDrafts added in v0.3.0

func LoadSubdomainDrafts(path string) (SubdomainDraftFile, error)

LoadSubdomainDrafts loads the draft file from path. Returns an empty file (version 1, no drafts) when the file is absent.

func MergeSubdomainDrafts added in v0.3.0

func MergeSubdomainDrafts(existing SubdomainDraftFile, newDrafts []SubdomainDraft) SubdomainDraftFile

MergeSubdomainDrafts merges newDrafts into existing:

  • Approved entries are untouchable.
  • An existing draft for the same module is replaced by the new draft.
  • New modules not in existing are appended.
  • Output is sorted by Module for a deterministic file.

type SubdomainPin added in v0.3.0

type SubdomainPin struct {
	Module     string
	Subdomain  string // core|supporting|generic
	Volatility string // low|medium|high (may be empty)
	ReviewedAt time.Time
	ReviewedBy string
}

SubdomainPin is one approved subdomain assignment for a module.

type UpdateModulePathsEdit added in v0.2.0

type UpdateModulePathsEdit struct {
	Module string
	Paths  []string
}

UpdateModulePathsEdit replaces the paths: block of an existing module. If the module exists but has no paths:, inserts block-style paths: as first key. REJECTS inline flow-style paths: [...].

type UpdateReport added in v0.2.0

type UpdateReport struct {
	Added            []ModuleDef
	Removed          []ExistingModule
	PathDrift        []PathDelta
	Unclassified     []string
	StructuralInSync bool
}

UpdateReport is the result of DiffModules.

func DiffModules added in v0.2.0

func DiffModules(existing []ExistingModule, fresh []ModuleDef) UpdateReport

DiffModules computes the structural difference between the existing config modules and freshly discovered modules. Output slices are sorted by Name for determinism.

  • Added: modules in fresh (by Name) not in existing.
  • Removed: modules in existing (by Name) not in fresh.
  • PathDrift: modules present in both whose paths differ as normalized sets. ConfigPaths and DiscoveredPaths preserve their original ordering.
  • StructuralInSync: true when Added, Removed, and PathDrift are all empty.
  • Unclassified: non-removed existing modules missing any of subdomain/volatility/layer. Modules in Removed are excluded from Unclassified.

type ValueDraft added in v0.5.0

type ValueDraft struct {
	Module    string `yaml:"module"`
	Value     string `yaml:"value"`
	Rationale string `yaml:"rationale"`
	Status    string `yaml:"status"` // DraftStatusDraft or DraftStatusApproved
	// Confidence records how trustworthy the judgment is (high|medium|low).
	// Set by the LLM draft path; empty = unset (treated as high for human-approved entries).
	Confidence string `yaml:"confidence,omitempty"`
	// Provenance records the source of the judgment (human|llm|tool).
	// Set to "llm" by the LLM draft path; empty = unset (treated as human).
	Provenance string `yaml:"provenance,omitempty"`
}

ValueDraft is one LLM-suggested single-scalar module annotation awaiting human review. Owner and volatility share this shape (module → one value); the containing ValueDraftFile.Field records which module field the value targets. This mirrors SubdomainDraft but for fields that carry exactly one scalar.

type ValueDraftFile added in v0.5.0

type ValueDraftFile struct {
	Version int          `yaml:"version"`
	Field   string       `yaml:"field"` // "owner" | "volatility"
	Drafts  []ValueDraft `yaml:"drafts"`
}

ValueDraftFile is the top-level container for .archfit-owners.yaml / .archfit-volatility.yaml.

func LoadValueDrafts added in v0.5.0

func LoadValueDrafts(path, field string) (ValueDraftFile, error)

LoadValueDrafts loads the draft file from path. Returns an empty file (version 1, given field, no drafts) when the file is absent.

func MergeValueDrafts added in v0.5.0

func MergeValueDrafts(existing ValueDraftFile, newDrafts []ValueDraft) ValueDraftFile

MergeValueDrafts merges newDrafts into existing:

  • Approved entries are untouchable.
  • An existing draft for the same module is replaced by the new draft.
  • New modules not in existing are appended.
  • Output is sorted by Module for a deterministic file.

type ValuePin added in v0.5.0

type ValuePin struct {
	Module     string
	Value      string
	ReviewedAt time.Time
	ReviewedBy string
}

ValuePin is one approved single-scalar assignment (owner or volatility) for a module, stamped with the human reviewer identity.

Jump to

Keyboard shortcuts

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