config

package
v0.18.0 Latest Latest
Warning

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

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

Documentation

Overview

Package config loads and writes the consumer repo's `aiwf.yaml`.

The file is small and deliberately so — see docs/pocv3/design/design-decisions.md §"aiwf.yaml config". The fields are:

hosts: [claude-code]      # optional; PoC default and only supported value
status_md:                # optional; opt-out for the STATUS.md auto-update
  auto_update: false      # default true — see StatusMdAutoUpdate

Identity is runtime-derived (per `provenance-model.md`):

  • `--actor <role>/<id>` flag on the verb wins.
  • else `git config user.email` → `human/<localpart>`.
  • else verb refuses with a usage error.

Two legacy fields are tolerated on read for the migration window:

  • `actor:` (pre-I2.5) — captured into LegacyActor; ignored for identity. Stripped on `aiwf update`. See StripLegacyActor.
  • `aiwf_version:` (pre-G47) — captured into LegacyAiwfVersion; was a set-once pin that never auto-maintained itself, producing chronic doctor noise. Stripped on `aiwf update`. See StripLegacyAiwfVersion.

Validation rules:

  • ActorPattern is the published regex for `<role>/<id>`; callers that resolve identity at runtime (cmd/aiwf, initrepo) consult it.

Index

Constants

View Source
const DefaultAllocateTrunk = "refs/remotes/origin/main"

DefaultAllocateTrunk is the trunk ref the allocator falls back to when aiwf.yaml.allocate.trunk is unset. Mirrors what `git clone` produces for a standard upstream project.

View Source
const DefaultEntityTitleMaxLength = 80

DefaultEntityTitleMaxLength is the kernel-default cap on entity title (and slug) length. 80 chars matches the Conventional Commits subject-line convention so an entity-touching commit subject that quotes the title verbatim still fits within typical commit-subject guidelines.

View Source
const DefaultHTMLOutDir = "site"

DefaultHTMLOutDir is the path the renderer falls back to when aiwf.yaml.html.out_dir is unset.

View Source
const DefaultWorktreeDir = ".claude/worktrees"

DefaultWorktreeDir is the repo-relative directory the start rituals place worktrees under when aiwf.yaml.worktree.dir is unset. In-repo placement (ADR-0023): reachable as a session cwd inside a sandboxed devcontainer, persistent under the mounted workspace, and gitignored via `.claude/*`.

View Source
const FileName = "aiwf.yaml"

FileName is the canonical filename at the consumer repo root.

Variables

View Source
var ActorPattern = regexp.MustCompile(`^[^\s/]+/[^\s/]+$`)

ActorPattern enforces the actor format documented in docs/pocv3/design/design-decisions.md: `<role>/<identifier>`, exactly one '/', no whitespace, neither side empty.

View Source
var ErrNotFound = errors.New("aiwf.yaml not found")

ErrNotFound reports that aiwf.yaml does not exist in the queried directory. Callers (notably resolveActor) handle this gracefully, since the file is optional pre-`aiwf init`.

Functions

func StripLegacyActor

func StripLegacyActor(root string) (changed bool, err error)

StripLegacyActor removes any top-level `actor:` line from root/aiwf.yaml and rewrites the file in place. The strip is textual (line-based) rather than a YAML round-trip so user comments and key ordering survive — the legacy `actor:` key is the only field we know to be dead, and a re-marshal would regenerate the file in the marshaler's preferred shape.

Returns (false, nil) when no `actor:` line is present (file stays byte-identical), (true, nil) when one was removed, or an error when the file is unreadable / unwritable. Idempotent: callers may invoke on every `aiwf update` without churn.

`actor:` only matches at column 0 (i.e. a top-level YAML key). A nested key with an actor field name in some hypothetical future block would not be touched.

func StripLegacyAiwfVersion

func StripLegacyAiwfVersion(root string) (changed bool, err error)

StripLegacyAiwfVersion removes any top-level `aiwf_version:` line from root/aiwf.yaml and rewrites the file in place. Same shape as StripLegacyActor: textual line-based strip so user comments and key ordering survive.

Returns (false, nil) when no line is present, (true, nil) when one was removed. Idempotent — callers may invoke on every `aiwf update` without churn.

Filed under G47: the field was historically required and stamped at init time, but never auto-maintained, producing chronic doctor noise. The information is now reachable via `aiwf version` (the running binary) and `aiwf doctor --check-latest` (newer release available); the stored pin is dead weight.

func Write

func Write(root string, cfg *Config) error

Write marshals cfg to root/aiwf.yaml. Refuses to overwrite an existing file — callers (notably `aiwf init`) decide what to do when one is already there.

Empty-config-only by contract. The sole caller is `aiwf init`, which writes an empty &Config{} (areas omitted). Write must NOT be used to serialize a populated areas block: yaml.Marshal would emit every Member in mapping form (`- name: app-a`), churning a legacy bare-string member and breaking the M-0179 zero-migration parity. Post-init edits to the areas block route through the comment-preserving aiwfyaml writer (aiwfyaml.Doc.RenameAreaMember), which rewrites only the renamed name token and leaves each member's existing form (bare string or mapping) untouched.

Types

type Allocate

type Allocate struct {
	Trunk string `yaml:"trunk,omitempty"`
}

Allocate carries the consumer's id-allocator configuration. Trunk names the git ref the trunk-aware allocator unions into its view of existing ids; an empty value means "use the default trunk ref" and AllocateTrunkRef returns DefaultAllocateTrunk in that case.

See docs/pocv3/design/id-allocation.md for the full model.

type Archive added in v0.8.0

type Archive struct {
	SweepThreshold *int `yaml:"sweep_threshold,omitempty"`
}

Archive carries the consumer's drift-control configuration for the per-kind archive convention (ADR-0004). SweepThreshold is a tristate via *int: nil means "not specified, take the default (no threshold — `archive-sweep-pending` stays advisory)", &N is an explicit hard threshold past which the aggregate finding escalates from warning to error. Mirrors the StatusMd.AutoUpdate tristate so "unset" is always distinguishable from a meaningful zero. Use the getter Config.ArchiveSweepThreshold rather than reading the pointer directly so callers don't have to repeat the default.

Default behavior (empty Archive block, or absent archive.sweep_threshold): `archive-sweep-pending` stays advisory regardless of count. Teams choose their own discipline; the kernel does not nag.

type Areas added in v0.17.0

type Areas struct {
	Members []Member `yaml:"members,omitempty"`
	Default string   `yaml:"default,omitempty"`
	// Required (M-0178) opts the 1:1 monorepo into strictness: when true,
	// an untagged entity of a self-tagging root kind is a blocking
	// `area-required` (error) finding. Default false (absent) leaves the
	// pre-knob (E-0043) behavior byte-for-byte unchanged. validate()
	// rejects required:true with zero members (an unsatisfiable "every
	// entity must be a member of the empty set").
	Required bool `yaml:"required,omitempty"`
	// CoverageRoots (M-0185) opts the multi-project monorepo into the
	// scoped-coverage law: each listed directory's immediate child
	// directories are projects expected to be claimed by some area's
	// `paths:` glob, and an unclaimed child raises area-unslotted. Its
	// presence is the activation signal — absent, the coverage check is
	// inert, so the single-project / semantic-section repo is never
	// flagged wholesale. Each root is a repo-relative directory path
	// ('/'-separated, no leading slash, no `..` segments); roots may be
	// nested and several may be declared for a mixed-depth layout (the
	// immediate-children contract is per declared root). Adding it means
	// adding `coverage_roots` to knownAreasKeys, or the areas-block
	// strict-key guard rejects it as unknown on read.
	CoverageRoots []string `yaml:"coverage_roots,omitempty"`
}

Areas declares the closed set of workstream area tags (E-0043, E-0044). Members is the closed member set the optional `area` frontmatter field validates against (label + optional location). Default is a DISPLAY LABEL ONLY for the untagged complement in grouped views — never a member of the tag set, never written to an entity. An empty block (no members) leaves the `area` field inert.

Members accepts a backward-compatible dual form (E-0044, M-0179): a bare string (`- app-a`, the legacy E-0043 shape) or a `name`/`paths` mapping (`- {name: app-a, paths: [projects/app-a/**]}`). Name-consuming readers go through MemberNames(), the derived single source of truth for the member label set.

func (Areas) MemberNames added in v0.18.0

func (a Areas) MemberNames() []string

MemberNames returns the declared member names in declaration order — the derived single source of truth every name-consuming reader (`add --area` validation, the `area-unknown` check, the grouping resolver, `--area` completion) reads. Returns nil for an empty member set, matching the prior `[]string` field's nil-when-absent semantics so consumers that compare against nil are unaffected by the label+location migration.

func (*Areas) UnmarshalYAML added in v0.17.0

func (a *Areas) UnmarshalYAML(value *yaml.Node) error

UnmarshalYAML decodes the areas block, accepting each member in either the legacy string form or the `name`/`paths` mapping form (E-0044, M-0179).

A bare scalar member must be a YAML string (`!!str`): the explicit tag check keeps an unquoted `42`/`true`/`~` from silently becoming a string member, routing it to the malformed-member guard instead. A quoted numeric (`"42"`) is a string and is accepted. A mapping member decodes `name`/`paths`; an explicit empty `paths: []` is normalized to nil so it equals an absent `paths` (both express "no paths"). Decode-time errors (a non-list `paths`, a member node that is neither scalar nor mapping) are wrapped to name the offending member rather than shipping the bare yaml.v3 text. Semantic rules (emptiness, whitespace, uniqueness, path hygiene, default) live in validate().

type Config

type Config struct {
	LegacyAiwfVersion string   `yaml:"aiwf_version,omitempty"`
	LegacyActor       string   `yaml:"actor,omitempty"`
	Hosts             []string `yaml:"hosts,omitempty"`
	StatusMd          StatusMd `yaml:"status_md,omitempty"`
	TDD               TDD      `yaml:"tdd,omitempty"`
	HTML              HTML     `yaml:"html,omitempty"`
	Allocate          Allocate `yaml:"allocate,omitempty"`
	Tree              Tree     `yaml:"tree,omitempty"`
	Archive           Archive  `yaml:"archive,omitempty"`
	Entities          Entities `yaml:"entities,omitempty"`
	Guidance          Guidance `yaml:"guidance,omitempty"`
	Areas             Areas    `yaml:"areas,omitempty"`
	Worktree          Worktree `yaml:"worktree,omitempty"`
}

Config is the in-memory shape of aiwf.yaml. Hosts is omitted when the on-disk file leaves it absent (which is the typical case).

StatusMd is the opt-out surface for the pre-commit hook that keeps `STATUS.md` in sync with the entity tree. Default behavior (block absent, or block present with `auto_update` absent) is on; an explicit `auto_update: false` opts out. See StatusMdAutoUpdate.

LegacyActor captures any pre-I2.5 `actor:` key still present in the on-disk file. The value is ignored for identity resolution (which is runtime-derived); the field exists so `aiwf doctor` can surface a deprecation note pointing the user at `git config user.email`.

func Load

func Load(root string) (*Config, error)

Load reads aiwf.yaml from root. Returns ErrNotFound when the file is absent so callers can distinguish "missing config" (acceptable pre-init) from "malformed config" (always an error).

func (*Config) AllocateTrunkRef

func (c *Config) AllocateTrunkRef() (ref string, explicit bool)

AllocateTrunkRef returns the configured trunk ref (or the default) and whether the value was explicitly set in aiwf.yaml. The "explicit" bit drives the missing-ref policy: an explicitly-named ref that doesn't resolve is a hard error; an unconfigured default that doesn't resolve falls back to working-tree-only when the repo also has no remotes.

func (*Config) ArchiveSweepThreshold added in v0.8.0

func (c *Config) ArchiveSweepThreshold() (n int, set bool)

ArchiveSweepThreshold returns the configured threshold and a bool indicating whether the consumer explicitly set one. When set=false, `aiwf check` does not escalate `archive-sweep-pending` regardless of count (the default-permissive behavior per ADR-0004 §"Drift control" layer 2). Tolerant of a nil receiver so callers in `cmd/aiwf/main.go` can invoke before / without a loaded Config.

func (*Config) EntityTitleMaxLength added in v0.8.0

func (c *Config) EntityTitleMaxLength() int

EntityTitleMaxLength returns the configured title-length cap or the kernel default when unset. A non-positive configured value is treated as the default — the cap exists to prevent filesystem and table-layout pathologies, not to enable disabling them.

func (*Config) HTMLOutDir

func (c *Config) HTMLOutDir() string

HTMLOutDir returns the configured output directory or the default when unset. Callers should resolve to an absolute path against the repo root before passing to the renderer.

func (*Config) StatusMdAutoUpdate

func (c *Config) StatusMdAutoUpdate() bool

StatusMdAutoUpdate returns whether the consumer wants the pre-commit hook installed and `STATUS.md` regenerated on every commit. Default true: the framework's opt-out, not opt-in. The committed `STATUS.md` is the user's content once tracked; flipping the flag controls whether the *hook* is installed, not whether the file is deleted.

func (*Config) TrunkBranchShortName added in v0.12.0

func (c *Config) TrunkBranchShortName() string

TrunkBranchShortName returns the short branch name derived from the configured trunk ref (`AllocateTrunkRef`). It is the pure-derivation last-path-segment of the ref:

refs/remotes/<remote>/<name>  →  <name>
refs/heads/<name>             →  <name>

Used by the M-0161/AC-1 verb-layer authorize carve-out so the "main + ritual --branch" predicate honors the operator's configured trunk name rather than hardcoding the literal `"main"`. Pure: no git access, no I/O — the config's value is the single source of truth.

Returns the empty string when the configured ref does not have a parseable last segment (`"garbage"` with no slash, `"refs/heads/"` with trailing slash, etc.) — callers should treat empty as "no resolvable trunk name; do not match" rather than `==""` against an empty CurrentBranch (which would silently coincide on detached HEAD).

Empty `allocate.trunk` (or nil receiver) falls through to `DefaultAllocateTrunk` (`refs/remotes/origin/main`) → returns `"main"`, preserving backwards-compatibility for repos that never configured the value.

func (*Config) Validate

func (c *Config) Validate() error

Validate enforces the documented constraints. Called by Load and expected to be called by Write before serialization.

Identity (the actor field) is no longer stored — it's runtime- derived per `provenance-model.md`. Any incoming `actor:` key is captured by LegacyActor for the deprecation note in `aiwf doctor`, but is not validated here (a malformed legacy value is harmless since runtime resolution doesn't consult it).

`aiwf_version:` is no longer required (G47). Pre-G47 yamls still load fine; the legacy value is captured into LegacyAiwfVersion and stripped on `aiwf update` via StripLegacyAiwfVersion.

The areas block (E-0043) is the first cross-field constraint validated here; the method remains the entry point for future rules.

func (*Config) WireClaudeMd added in v0.14.0

func (c *Config) WireClaudeMd() bool

WireClaudeMd returns whether aiwf should maintain its guidance import in the consumer's `CLAUDE.md`. Default true (opt-out, not opt-in). Tolerant of a nil receiver so callers can invoke before / without a loaded Config.

func (*Config) WorktreeDir added in v0.18.0

func (c *Config) WorktreeDir() string

WorktreeDir returns the configured ritual-worktree placement directory or the kernel default when unset. Only a single repo-relative directory is honored (E-0046 YAGNI): an empty, whitespace-only, absolute, or repo-escaping (`../…`) value falls back to DefaultWorktreeDir. The escape rejection keeps the value the start rituals consume in-repo, so a configured worktree.dir can never place a worktree outside the repo and defeat ADR-0023 / the M-0188 loader guard. Tolerant of a nil receiver so callers can invoke before / without a loaded Config.

type Entities added in v0.8.0

type Entities struct {
	TitleMaxLength *int `yaml:"title_max_length,omitempty"`
}

Entities carries the consumer's policy for entity-shape constraints the kernel applies when writing new entity files. TitleMaxLength caps the length of `--title` accepted by mutating verbs that write titles (`aiwf add`, `aiwf retitle`, `aiwf import`) and the length of `<new-slug>` accepted by `aiwf rename`. Title and slug share the same budget so on-disk filenames and frontmatter titles stay in sync — every kernel render surface (CLI tables, HTML render, git-log subjects, `aiwf history`, filesystem) degrades uniformly rather than diverging.

Default behavior (empty Entities block, or absent entities.title_max_length): the cap is DefaultEntityTitleMaxLength (80 chars — the Conventional Commits subject-line convention). Consumers who want longer or shorter caps override here per G-0102.

type Guidance added in v0.14.0

type Guidance struct {
	WireClaudeMd *bool `yaml:"wire_claudemd,omitempty"`
}

Guidance carries the consumer's opt-out for aiwf maintaining its per-turn LLM guidance import in the repo-root `CLAUDE.md` (ADR-0018). WireClaudeMd is a tristate via *bool mirroring StatusMd.AutoUpdate: nil → default (true), &false → explicit opt-out, &true → explicit opt-in. Use the getter Config.WireClaudeMd, not the pointer.

Default behavior (empty Guidance block, or absent guidance.wire_claudemd): aiwf wires and self-heals the marker-wrapped `@.claude/aiwf-guidance.md` import on every `aiwf init` / `aiwf update` — the framework's opt-out, not opt-in. There is deliberately no CLI flag; the wiring is automatic, like skill/hook materialization.

type HTML

type HTML struct {
	OutDir       string `yaml:"out_dir,omitempty"`
	CommitOutput bool   `yaml:"commit_output,omitempty"`
}

HTML holds the consumer's settings for the static-site render produced by `aiwf render --format=html`. OutDir is the directory the renderer writes into (relative to the repo root unless given as an absolute path); CommitOutput records the consumer's intent to commit the rendered files. The gitignore block managed by `aiwf init` / `aiwf update` is *derived* from CommitOutput — the consumer expresses intent here, and the framework reconciles the gitignore on the next admin verb run.

Default OutDir: "site" — the standard SSG convention. Default CommitOutput: false — gitignore the output and publish via CI.

type Member added in v0.18.0

type Member struct {
	Name  string   `yaml:"name"`
	Paths []string `yaml:"paths,omitempty"`
}

Member is a single declared workstream area (E-0044, M-0179): a Name (the tag entities carry in their `area:` frontmatter) and an optional Paths list locating the area's source in a monorepo. Paths are validated as well-formed strings only at this layer — glob matching against them is deferred to M-0180, where the first match call site lives. A member declared in the legacy string form (`members: [app-a]`) decodes with Name set and Paths nil.

Adding a field here means adding its key to knownMemberKeys, or the strict-key guard (G-0287) rejects it as unknown on read. There is no parallel writer struct to keep in lockstep: the areas-block writer no longer reconstructs members — `aiwf rename-area` surgically rewrites only the renamed name token (M-0195) — so a new sibling key or field is preserved through a rename automatically rather than being silently dropped.

type StatusMd

type StatusMd struct {
	AutoUpdate *bool `yaml:"auto_update,omitempty"`
}

StatusMd carries the opt-out for the pre-commit hook that regenerates `STATUS.md`. AutoUpdate is a tristate via *bool: nil means "not specified, take the default (true)", &false is an explicit opt-out, &true is an explicit opt-in. Use the getter Config.StatusMdAutoUpdate rather than reading the pointer directly so callers don't have to repeat the default.

type TDD

type TDD struct {
	RequireTestMetrics bool `yaml:"require_test_metrics,omitempty"`
	Strict             bool `yaml:"strict,omitempty"`
}

TDD carries opt-in governance for the TDD model. RequireTestMetrics gates the `acs-tdd-tests-missing` warning emitted by `aiwf check`: when true, every AC at `tdd_phase: done` under a `tdd: required` milestone must have at least one commit in its history carrying an `aiwf-tests:` trailer or the check warns. Default false — the trailer is informational metadata; consumers who want stricter governance opt in at the project level.

Strict promotes a defined set of TDD-related findings from warning to error so the pre-push hook blocks the push. The bumper covers `entity-body-empty` (M-066/AC-2) and `milestone-tdd-undeclared` (G-0268). Single source of truth for the project's TDD strictness posture — no parallel field, no second config knob. Default false. See check.ApplyTDDStrict for the precise set of codes covered.

type Tree

type Tree struct {
	AllowPaths []string `yaml:"allow_paths,omitempty"`
	Strict     bool     `yaml:"strict,omitempty"`
}

Tree is the consumer's policy for what may live under `work/`. AllowPaths is a list of repo-relative glob patterns (filepath.Match semantics) that exempt files from the tree-discipline check — useful for project-specific scratch dirs or templates the consumer genuinely wants alongside the entity tree. Strict promotes the `unexpected-tree-file` finding from a warning to an error so the pre-push hook blocks the push.

Default behavior (empty Tree block): contract artifact dirs are auto-exempt; everything else under work/ is reported as a warning. See docs/pocv3/design/tree-discipline.md.

type Worktree added in v0.18.0

type Worktree struct {
	Dir string `yaml:"dir,omitempty"`
}

Worktree carries the consumer's default placement for the git worktrees the start rituals (`aiwfx-start-epic` / `aiwfx-start-milestone`) create. Dir is a single repo-relative directory; the kernel default is DefaultWorktreeDir. See ADR-0023 and E-0046 for why in-repo placement is the default (a sandboxed devcontainer session can only root in a worktree under the mounted workspace).

Jump to

Keyboard shortcuts

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