Skip to content

Core

Config

chico.core.config

Configuration model and loader for chico.

Reads ~/.chico/config.yaml and produces typed configuration objects that the plan engine uses to instantiate sources and providers.

Config file location: ~/.chico/config.yaml (created by chico init).

Example config

::

providers:
  - name: kiro
    type: kiro
    level: global           # "global" (~/.kiro) or "project" (.kiro/)

sources:
  - name: kiro-configs
    type: github
    repo: org/kiro-config
    path: configs/
    branch: main            # optional, default: main
    token_env: GITHUB_TOKEN # optional, default: GITHUB_TOKEN
    source_prefix: configs/ # optional, strip before mapping to kiro_dir
    target: kiro            # optional, which provider to sync into

policy:
  strategy: safe            # "safe" requires manual apply; "auto" applies immediately
Usage

::

from chico.core.config import load_config

config = load_config()
source_cfg = config.get_source("kiro-configs")
provider_cfg = config.get_provider("kiro")

Config dataclass

The full parsed contents of ~/.chico/config.yaml.

Attributes:

Name Type Description
providers list[ProviderConfig]

List of configured providers.

sources list[SourceConfig]

List of configured sources.

policy PolicyConfig

Reconciliation policy settings.

Source code in chico/core/config.py
@dataclass
class Config:
    """The full parsed contents of ``~/.chico/config.yaml``.

    Attributes
    ----------
    providers:
        List of configured providers.
    sources:
        List of configured sources.
    policy:
        Reconciliation policy settings.
    """

    providers: list[ProviderConfig]
    sources: list[SourceConfig]
    policy: PolicyConfig

    def get_provider(self, name: str) -> ProviderConfig | None:
        """Return the provider with the given name, or ``None``."""
        return next((p for p in self.providers if p.name == name), None)

    def get_source(self, name: str) -> SourceConfig | None:
        """Return the source with the given name, or ``None``."""
        return next((s for s in self.sources if s.name == name), None)

    def filter_by_source(self, source_name: str) -> Config:
        """Return a new Config containing only the named source.

        Raises
        ------
        ConfigValidationError
            If no source with the given name exists.
        """
        filtered = [s for s in self.sources if s.name == source_name]
        if not filtered:
            available = ", ".join(s.name for s in self.sources) or "(none)"
            raise ConfigValidationError(
                f"Source {source_name!r} not found. Available sources: {available}"
            )
        return Config(providers=self.providers, sources=filtered, policy=self.policy)

filter_by_source(source_name)

Return a new Config containing only the named source.

Raises:

Type Description
ConfigValidationError

If no source with the given name exists.

Source code in chico/core/config.py
def filter_by_source(self, source_name: str) -> Config:
    """Return a new Config containing only the named source.

    Raises
    ------
    ConfigValidationError
        If no source with the given name exists.
    """
    filtered = [s for s in self.sources if s.name == source_name]
    if not filtered:
        available = ", ".join(s.name for s in self.sources) or "(none)"
        raise ConfigValidationError(
            f"Source {source_name!r} not found. Available sources: {available}"
        )
    return Config(providers=self.providers, sources=filtered, policy=self.policy)

get_provider(name)

Return the provider with the given name, or None.

Source code in chico/core/config.py
def get_provider(self, name: str) -> ProviderConfig | None:
    """Return the provider with the given name, or ``None``."""
    return next((p for p in self.providers if p.name == name), None)

get_source(name)

Return the source with the given name, or None.

Source code in chico/core/config.py
def get_source(self, name: str) -> SourceConfig | None:
    """Return the source with the given name, or ``None``."""
    return next((s for s in self.sources if s.name == name), None)

ConfigNotFoundError

Bases: Exception

Raised when ~/.chico/config.yaml does not exist.

Run chico init to create the default configuration file.

Source code in chico/core/config.py
class ConfigNotFoundError(Exception):
    """Raised when ``~/.chico/config.yaml`` does not exist.

    Run ``chico init`` to create the default configuration file.
    """

ConfigValidationError

Bases: Exception

Raised when the config file is missing required fields or has invalid values.

Source code in chico/core/config.py
class ConfigValidationError(Exception):
    """Raised when the config file is missing required fields or has invalid values."""

PolicyConfig dataclass

Reconciliation policy settings.

Attributes:

Name Type Description
strategy str

"safe" — always require explicit chico apply (default). "auto" — apply immediately after plan (used by the scheduler).

Source code in chico/core/config.py
@dataclass
class PolicyConfig:
    """Reconciliation policy settings.

    Attributes
    ----------
    strategy:
        ``"safe"`` — always require explicit ``chico apply`` (default).
        ``"auto"`` — apply immediately after plan (used by the scheduler).
    """

    strategy: str = "safe"

ProviderConfig dataclass

Configuration for a single provider.

Attributes:

Name Type Description
name str

Unique provider name.

type str

Provider type. Currently only "kiro" is supported.

level str

Kiro directory scope. "global" targets ~/.kiro/; "project" targets .kiro/ in a specific project directory. Defaults to "global".

path str

Absolute path to the target directory. Only used when level is "project". When set, chico syncs files directly into this path — no .kiro/ suffix is appended. This gives the user full control over the target directory and avoids double-nesting when the source files already live under .kiro/ in the repository. When omitted and level is "project", falls back to {cwd}/.kiro.

Source code in chico/core/config.py
@dataclass
class ProviderConfig:
    """Configuration for a single provider.

    Attributes
    ----------
    name:
        Unique provider name.
    type:
        Provider type. Currently only ``"kiro"`` is supported.
    level:
        Kiro directory scope. ``"global"`` targets ``~/.kiro/``;
        ``"project"`` targets ``.kiro/`` in a specific project directory.
        Defaults to ``"global"``.
    path:
        Absolute path to the target directory. Only used when ``level``
        is ``"project"``. When set, chico syncs files directly into this
        path — no ``.kiro/`` suffix is appended. This gives the user full
        control over the target directory and avoids double-nesting when
        the source files already live under ``.kiro/`` in the repository.
        When omitted and ``level`` is ``"project"``, falls back to
        ``{cwd}/.kiro``.
    """

    name: str
    type: str
    level: str = "global"
    path: str = ""

SourceConfig dataclass

Configuration for a single source.

Attributes:

Name Type Description
name str

Unique source name, referenced by target in provider configs.

type str

Source type. Currently only "github" is supported.

repo str

Full GitHub repository name in org/repo format.

path str

Directory (or file) path inside the repository to fetch from.

branch str

Branch to read from. Defaults to "main".

token_env str

Name of the environment variable holding the GitHub token. Defaults to "GITHUB_TOKEN". Token resolution also tries gh auth token and unauthenticated access automatically.

source_prefix str

Prefix to strip from source paths before mapping to the local provider directory. For example, "configs/" maps configs/steering/product.mdsteering/product.md.

target str

Name of the provider this source syncs into.

Source code in chico/core/config.py
@dataclass
class SourceConfig:
    """Configuration for a single source.

    Attributes
    ----------
    name:
        Unique source name, referenced by ``target`` in provider configs.
    type:
        Source type. Currently only ``"github"`` is supported.
    repo:
        Full GitHub repository name in ``org/repo`` format.
    path:
        Directory (or file) path inside the repository to fetch from.
    branch:
        Branch to read from. Defaults to ``"main"``.
    token_env:
        Name of the environment variable holding the GitHub token.
        Defaults to ``"GITHUB_TOKEN"``. Token resolution also tries
        ``gh auth token`` and unauthenticated access automatically.
    source_prefix:
        Prefix to strip from source paths before mapping to the local
        provider directory. For example, ``"configs/"`` maps
        ``configs/steering/product.md`` → ``steering/product.md``.
    target:
        Name of the provider this source syncs into.
    """

    name: str
    type: str
    repo: str
    path: str
    branch: str = "main"
    token_env: str = "GITHUB_TOKEN"
    source_prefix: str = ""
    target: str = ""

load_config()

Load and validate ~/.chico/config.yaml.

Returns:

Type Description
Config

The fully parsed configuration.

Raises:

Type Description
ConfigNotFoundError

If the config file does not exist. Run chico init to create it.

ConfigValidationError

If the config file is missing required fields.

Source code in chico/core/config.py
def load_config() -> Config:
    """Load and validate ``~/.chico/config.yaml``.

    Returns
    -------
    Config
        The fully parsed configuration.

    Raises
    ------
    ConfigNotFoundError
        If the config file does not exist. Run ``chico init`` to create it.
    ConfigValidationError
        If the config file is missing required fields.
    """
    if not CONFIG_FILE.exists():
        raise ConfigNotFoundError(
            f"Config file not found: {CONFIG_FILE}\n"
            "Run `chico init` to create the default configuration."
        )

    raw = yaml.safe_load(CONFIG_FILE.read_text(encoding="utf-8")) or {}

    providers = [_parse_provider(p) for p in raw.get("providers", [])]
    sources = [_parse_source(s) for s in raw.get("sources", [])]
    policy = _parse_policy(raw.get("policy", {}))

    return Config(providers=providers, sources=sources, policy=policy)

Plan

chico.core.plan

Plan computation for chico.

A Plan is the computed changeset for a single chico plan run. It collects all resource diffs across every source/provider pair declared in the configuration, assigns a risk level, and provides a stable identifier for auditing.

Example usage::

from chico.core.config import load_config
from chico.core.plan import compute_plan

config = load_config()
plan = compute_plan(config)

if plan.has_changes:
    for diff in plan.changes:
        print(diff.change_type, diff.resource_id)

Plan dataclass

A computed changeset produced by chico plan.

Attributes:

Name Type Description
plan_id str

Unique identifier for this plan run (UUID4).

changes list[Diff]

All resource diffs that require an action. Diffs with ChangeType.NONE are excluded.

risk_level RiskLevel

Estimated risk of applying this plan.

Source code in chico/core/plan.py
@dataclass
class Plan:
    """A computed changeset produced by ``chico plan``.

    Attributes
    ----------
    plan_id:
        Unique identifier for this plan run (UUID4).
    changes:
        All resource diffs that require an action. Diffs with
        ``ChangeType.NONE`` are excluded.
    risk_level:
        Estimated risk of applying this plan.
    """

    plan_id: str
    changes: list[Diff]
    risk_level: RiskLevel = field(default=RiskLevel.NONE)

    @property
    def has_changes(self) -> bool:
        """Return ``True`` if there is at least one actionable change."""
        return bool(self.changes)

has_changes property

Return True if there is at least one actionable change.

RiskLevel

Bases: StrEnum

Estimated risk of applying a plan.

Values

NONE: No changes — nothing would be modified. LOW: Only new resources would be created; nothing would be overwritten. MEDIUM: Existing resources would be modified. HIGH: Resources would be deleted.

Source code in chico/core/plan.py
class RiskLevel(StrEnum):
    """Estimated risk of applying a plan.

    Values
    ------
    NONE:
        No changes — nothing would be modified.
    LOW:
        Only new resources would be created; nothing would be overwritten.
    MEDIUM:
        Existing resources would be modified.
    HIGH:
        Resources would be deleted.
    """

    NONE = "none"
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

compute_plan(config)

Compute a plan from the given configuration.

For every source declared in config, fetches the desired state and computes a diff against the current local state. Aggregates all diffs into a single :class:Plan.

Parameters:

Name Type Description Default
config Config

The loaded chico configuration.

required

Returns:

Type Description
Plan

The computed changeset. :attr:Plan.changes is empty when everything is already in sync.

Raises:

Type Description
SourceFetchError

If any source fails to fetch the desired state.

ValueError

If a source or provider type declared in the config is not supported.

Source code in chico/core/plan.py
def compute_plan(config: Config) -> Plan:
    """Compute a plan from the given configuration.

    For every source declared in ``config``, fetches the desired state and
    computes a diff against the current local state. Aggregates all diffs
    into a single :class:`Plan`.

    Parameters
    ----------
    config:
        The loaded chico configuration.

    Returns
    -------
    Plan
        The computed changeset. :attr:`Plan.changes` is empty when
        everything is already in sync.

    Raises
    ------
    SourceFetchError
        If any source fails to fetch the desired state.
    ValueError
        If a source or provider type declared in the config is not supported.
    """
    all_changes: list[Diff] = []

    for source_cfg in config.sources:
        logger.info(
            "plan.source.processing",
            extra={
                "source": source_cfg.name,
                "repo": source_cfg.repo,
                "path": source_cfg.path,
                "branch": source_cfg.branch,
                "target": source_cfg.target,
            },
        )
        source = _build_source(source_cfg)
        fetch_result = source.fetch()

        provider_cfg = config.get_provider(source_cfg.target)
        if provider_cfg is None:
            logger.warning(
                "plan.provider.not_found",
                extra={"source": source_cfg.name, "target": source_cfg.target},
            )
            continue

        kiro_dir = _resolve_kiro_dir(provider_cfg)
        logger.info(
            "plan.provider.found",
            extra={"provider": provider_cfg.name, "kiro_dir": str(kiro_dir)},
        )
        provider = _build_provider(
            provider_cfg, fetch_result, source_cfg.source_prefix, kiro_dir
        )

        resources = provider.list_resources()
        logger.info(
            "plan.resources.listed",
            extra={"source": source_cfg.name, "count": len(resources)},
        )

        for resource in resources:
            diff = resource.diff()
            logger.info(
                "plan.resource.diff",
                extra={
                    "resource_id": resource.resource_id,
                    "change_type": str(diff.change_type),
                },
            )
            if diff.has_changes:
                all_changes.append(diff)

    return Plan(
        plan_id=str(uuid.uuid4()),
        changes=all_changes,
        risk_level=_compute_risk_level(all_changes),
    )

Apply

chico.core.apply

Apply engine for chico.

Fetches desired state from every configured source, diffs it against local state, applies all changes, and persists the result to ~/.chico/state.json.

Example usage::

from chico.core.config import load_config
from chico.core.apply import execute_apply

config = load_config()
result = execute_apply(config)

print(f"Applied {result.ok_count}, {result.error_count} error(s).")

ApplyResult dataclass

The outcome of execute_apply.

Attributes:

Name Type Description
plan Plan

The computed changeset that was applied.

results list[Result]

One :class:~chico.core.resource.Result per resource that had a change. Resources already in sync are excluded.

Source code in chico/core/apply.py
@dataclass
class ApplyResult:
    """The outcome of ``execute_apply``.

    Attributes
    ----------
    plan:
        The computed changeset that was applied.
    results:
        One :class:`~chico.core.resource.Result` per resource that had a
        change. Resources already in sync are excluded.
    """

    plan: Plan
    results: list[Result] = field(default_factory=list)

    @property
    def ok_count(self) -> int:
        """Number of resources applied successfully."""
        return sum(1 for r in self.results if r.ok)

    @property
    def error_count(self) -> int:
        """Number of resources that failed to apply."""
        return sum(1 for r in self.results if r.status == ResultStatus.ERROR)

    @property
    def has_errors(self) -> bool:
        """Return ``True`` if at least one resource failed to apply."""
        return self.error_count > 0

error_count property

Number of resources that failed to apply.

has_errors property

Return True if at least one resource failed to apply.

ok_count property

Number of resources applied successfully.

execute_apply(config)

Fetch desired state, apply all changes, and persist state.

For every source declared in config, fetches the desired state, computes a diff, and calls :meth:~chico.core.resource.Resource.apply on every resource that has changes. Updates ~/.chico/state.json with the versions and results afterwards.

Parameters:

Name Type Description Default
config Config

The loaded chico configuration.

required

Returns:

Type Description
ApplyResult

The plan that was executed and the per-resource results.

Raises:

Type Description
SourceFetchError

If any source fails to fetch the desired state.

ValueError

If a source or provider type declared in the config is not supported.

Source code in chico/core/apply.py
def execute_apply(config: Config) -> ApplyResult:
    """Fetch desired state, apply all changes, and persist state.

    For every source declared in ``config``, fetches the desired state,
    computes a diff, and calls :meth:`~chico.core.resource.Resource.apply`
    on every resource that has changes. Updates ``~/.chico/state.json``
    with the versions and results afterwards.

    Parameters
    ----------
    config:
        The loaded chico configuration.

    Returns
    -------
    ApplyResult
        The plan that was executed and the per-resource results.

    Raises
    ------
    SourceFetchError
        If any source fails to fetch the desired state.
    ValueError
        If a source or provider type declared in the config is not supported.
    """
    all_changes: list[Diff] = []
    source_versions: dict[str, str] = {}
    to_apply: list[tuple[Resource, str]] = []

    for source_cfg in config.sources:
        logger.info(
            "apply.source.processing",
            extra={
                "source": source_cfg.name,
                "repo": source_cfg.repo,
                "path": source_cfg.path,
                "branch": source_cfg.branch,
                "target": source_cfg.target,
            },
        )
        source = _build_source(source_cfg)
        fetch_result = source.fetch()
        source_versions[source_cfg.name] = fetch_result.version
        logger.info(
            "apply.fetch.completed",
            extra={"source": source_cfg.name, "version": fetch_result.version},
        )

        provider_cfg = config.get_provider(source_cfg.target)
        if provider_cfg is None:
            logger.warning(
                "apply.provider.not_found",
                extra={"source": source_cfg.name, "target": source_cfg.target},
            )
            continue

        kiro_dir = _resolve_kiro_dir(provider_cfg)
        logger.info(
            "apply.provider.found",
            extra={"provider": provider_cfg.name, "kiro_dir": str(kiro_dir)},
        )
        provider = _build_provider(
            provider_cfg, fetch_result, source_cfg.source_prefix, kiro_dir
        )

        resources = provider.list_resources()
        logger.info(
            "apply.resources.listed",
            extra={"source": source_cfg.name, "count": len(resources)},
        )

        for resource in resources:
            diff = resource.diff()
            logger.info(
                "apply.resource.diff",
                extra={
                    "resource_id": resource.resource_id,
                    "change_type": str(diff.change_type),
                },
            )
            if diff.has_changes:
                all_changes.append(diff)
                to_apply.append((resource, source_cfg.name))

    plan = Plan(
        plan_id=str(uuid.uuid4()),
        changes=all_changes,
        risk_level=_compute_risk_level(all_changes),
    )

    results: list[Result] = []
    result_sources: list[str] = []
    for resource, source_name in to_apply:
        result = resource.apply()
        results.append(result)
        result_sources.append(source_name)
        if result.ok:
            logger.info(
                "resource.apply.ok", extra={"resource_id": resource.resource_id}
            )
        else:
            logger.error(
                "resource.apply.error",
                extra={"resource_id": resource.resource_id, "detail": result.message},
            )

    _persist_state(plan, results, source_versions, result_sources)

    return ApplyResult(plan=plan, results=results)

State

chico.core.state

State management for chico.

The state file (~/.chico/state.json) is the source of truth for what chico last applied. It tracks the commit hash (or equivalent version) of every source that has been synced, enabling drift detection on subsequent runs.

Example usage::

from chico.core.state import State, load_state, save_state

state = load_state()

# After a successful apply from a GitHub source:
state.record_version("kiro-configs", "abc123def456")
save_state(state)

# On the next run, check if the source has moved ahead:
last_hash = state.get_version("kiro-configs")  # "abc123def456"

State dataclass

Represents the persisted state of chico.

Attributes:

Name Type Description
status str

High-level status of the last operation ("idle", etc.).

last_run dict | None

Metadata about the most recent run (result, change count, etc.). None if chico has never been applied.

resources list[dict]

List of resource snapshots recorded during the last apply.

versions dict[str, str]

Mapping of source name → version string (e.g. commit SHA) of the last successfully applied snapshot for that source.

Source code in chico/core/state.py
@dataclass
class State:
    """Represents the persisted state of chico.

    Attributes
    ----------
    status:
        High-level status of the last operation (``"idle"``, etc.).
    last_run:
        Metadata about the most recent run (result, change count, etc.).
        ``None`` if chico has never been applied.
    resources:
        List of resource snapshots recorded during the last apply.
    versions:
        Mapping of source name → version string (e.g. commit SHA) of the
        last successfully applied snapshot for that source.
    """

    status: str = "idle"
    last_run: dict | None = None
    resources: list[dict] = field(default_factory=list)
    versions: dict[str, str] = field(default_factory=dict)

    def record_version(self, source_name: str, version: str) -> None:
        """Record the version of a source that was just applied.

        Parameters
        ----------
        source_name:
            The name of the source as declared in ``config.yaml``.
        version:
            The version identifier (e.g. full Git commit SHA) of the
            snapshot that was applied.
        """
        self.versions[source_name] = version

    def get_version(self, source_name: str) -> str | None:
        """Return the last applied version for a source, or ``None``.

        Parameters
        ----------
        source_name:
            The name of the source to look up.
        """
        return self.versions.get(source_name)

get_version(source_name)

Return the last applied version for a source, or None.

Parameters:

Name Type Description Default
source_name str

The name of the source to look up.

required
Source code in chico/core/state.py
def get_version(self, source_name: str) -> str | None:
    """Return the last applied version for a source, or ``None``.

    Parameters
    ----------
    source_name:
        The name of the source to look up.
    """
    return self.versions.get(source_name)

record_version(source_name, version)

Record the version of a source that was just applied.

Parameters:

Name Type Description Default
source_name str

The name of the source as declared in config.yaml.

required
version str

The version identifier (e.g. full Git commit SHA) of the snapshot that was applied.

required
Source code in chico/core/state.py
def record_version(self, source_name: str, version: str) -> None:
    """Record the version of a source that was just applied.

    Parameters
    ----------
    source_name:
        The name of the source as declared in ``config.yaml``.
    version:
        The version identifier (e.g. full Git commit SHA) of the
        snapshot that was applied.
    """
    self.versions[source_name] = version

load_state()

Load state from ~/.chico/state.json.

Returns a default :class:State if the file does not exist yet (e.g. right after chico init).

Source code in chico/core/state.py
def load_state() -> State:
    """Load state from ``~/.chico/state.json``.

    Returns a default :class:`State` if the file does not exist yet
    (e.g. right after ``chico init``).
    """
    if not STATE_FILE.exists():
        return State()

    raw = json.loads(STATE_FILE.read_text(encoding="utf-8"))
    return State(
        status=raw.get("status", "idle"),
        last_run=raw.get("last_run"),
        resources=raw.get("resources", []),
        versions=raw.get("versions", {}),
    )

save_state(state)

Persist a :class:State to ~/.chico/state.json.

Creates the parent directory if it does not exist.

Parameters:

Name Type Description Default
state State

The state to write.

required
Source code in chico/core/state.py
def save_state(state: State) -> None:
    """Persist a :class:`State` to ``~/.chico/state.json``.

    Creates the parent directory if it does not exist.

    Parameters
    ----------
    state:
        The state to write.
    """
    STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
    STATE_FILE.write_text(
        json.dumps(
            {
                "status": state.status,
                "last_run": state.last_run,
                "resources": state.resources,
                "versions": state.versions,
            },
            indent=2,
        ),
        encoding="utf-8",
    )

Resource

chico.core.resource

Core resource abstractions for chico.

This module defines the fundamental building blocks of chico's reconciliation model. Every system that chico manages is represented as a :class:Resource. Changes between desired and current state are described as a :class:Diff, and the outcome of applying a change is captured in a :class:Result.

Example usage::

from chico.core.resource import Resource, Diff, Result, ChangeType, ResultStatus

class MyResource:
    @property
    def resource_id(self) -> str:
        return "my-resource"

    def desired_state(self) -> dict:
        return {"enabled": True}

    def current_state(self) -> dict:
        return {"enabled": False}

    def diff(self) -> Diff:
        return Diff(
            change_type=ChangeType.MODIFY,
            resource_id=self.resource_id,
            changes={"enabled": FieldChange(from_value=False, to_value=True)},
        )

    def apply(self) -> Result:
        # ... apply logic ...
        return Result(status=ResultStatus.OK, resource_id=self.resource_id)

ChangeType

Bases: StrEnum

Describes the nature of a change to a resource.

Values

ADD: The resource does not exist in the current state and will be created. MODIFY: The resource exists but one or more fields differ from the desired state. REMOVE: The resource exists in the current state but is absent from the desired state. NONE: The resource is fully in sync — no action required.

Source code in chico/core/resource.py
class ChangeType(StrEnum):
    """Describes the nature of a change to a resource.

    Values
    ------
    ADD:
        The resource does not exist in the current state and will be created.
    MODIFY:
        The resource exists but one or more fields differ from the desired state.
    REMOVE:
        The resource exists in the current state but is absent from the desired state.
    NONE:
        The resource is fully in sync — no action required.
    """

    ADD = "add"
    MODIFY = "modify"
    REMOVE = "remove"
    NONE = "none"

Diff dataclass

Describes all changes required to bring a resource to its desired state.

A Diff is the output of :meth:Resource.diff. It tells the plan engine what would change and how, without actually applying anything.

Attributes:

Name Type Description
change_type ChangeType

The high-level category of change (add, modify, remove, or none).

resource_id str

The unique identifier of the resource this diff belongs to.

changes dict[str, FieldChange]

A mapping of field names to their :class:FieldChange. Only populated when change_type is :attr:ChangeType.MODIFY.

Properties

has_changes: True when there is at least one actionable change.

Source code in chico/core/resource.py
@dataclass
class Diff:
    """Describes all changes required to bring a resource to its desired state.

    A ``Diff`` is the output of :meth:`Resource.diff`. It tells the plan engine
    *what* would change and *how*, without actually applying anything.

    Attributes
    ----------
    change_type:
        The high-level category of change (add, modify, remove, or none).
    resource_id:
        The unique identifier of the resource this diff belongs to.
    changes:
        A mapping of field names to their :class:`FieldChange`. Only populated
        when ``change_type`` is :attr:`ChangeType.MODIFY`.

    Properties
    ----------
    has_changes:
        ``True`` when there is at least one actionable change.
    """

    change_type: ChangeType
    resource_id: str
    changes: dict[str, FieldChange] = field(default_factory=dict)

    @property
    def has_changes(self) -> bool:
        """Return ``True`` if this diff represents an actionable change."""
        return self.change_type != ChangeType.NONE

has_changes property

Return True if this diff represents an actionable change.

FieldChange dataclass

Captures a before/after value for a single field within a :class:Diff.

Attributes:

Name Type Description
from_value Any

The field's value in the current (live) state.

to_value Any

The field's value in the desired state.

Source code in chico/core/resource.py
@dataclass
class FieldChange:
    """Captures a before/after value for a single field within a :class:`Diff`.

    Attributes
    ----------
    from_value:
        The field's value in the current (live) state.
    to_value:
        The field's value in the desired state.
    """

    from_value: Any
    to_value: Any

Resource

Bases: Protocol

Protocol that every chico-managed resource must satisfy.

A resource is the fundamental unit of chico's reconciliation model. It knows its own desired state (what it should look like) and current state (what it actually looks like), can compute the diff between the two, and can apply that diff.

Implementors do not need to inherit from this class — any object that exposes the attributes and methods below is a valid Resource.

Example

Implement the protocol on any plain class::

class ConfigFileResource:
    def __init__(self, path: Path, desired: dict) -> None:
        self._path = path
        self._desired = desired

    @property
    def resource_id(self) -> str:
        return str(self._path)

    def desired_state(self) -> dict:
        return self._desired

    def current_state(self) -> dict:
        if self._path.exists():
            return yaml.safe_load(self._path.read_text())
        return {}

    def diff(self) -> Diff:
        ...

    def apply(self) -> Result:
        ...
Source code in chico/core/resource.py
@runtime_checkable
class Resource(Protocol):
    """Protocol that every chico-managed resource must satisfy.

    A resource is the fundamental unit of chico's reconciliation model. It
    knows its own desired state (what it *should* look like) and current state
    (what it *actually* looks like), can compute the diff between the two, and
    can apply that diff.

    Implementors do not need to inherit from this class — any object that
    exposes the attributes and methods below is a valid ``Resource``.

    Example
    -------
    Implement the protocol on any plain class::

        class ConfigFileResource:
            def __init__(self, path: Path, desired: dict) -> None:
                self._path = path
                self._desired = desired

            @property
            def resource_id(self) -> str:
                return str(self._path)

            def desired_state(self) -> dict:
                return self._desired

            def current_state(self) -> dict:
                if self._path.exists():
                    return yaml.safe_load(self._path.read_text())
                return {}

            def diff(self) -> Diff:
                ...

            def apply(self) -> Result:
                ...
    """

    @property
    def resource_id(self) -> str:
        """Unique, stable identifier for this resource within its provider."""
        ...

    def desired_state(self) -> dict[str, Any]:
        """Return the full desired state for this resource as a plain dict."""
        ...

    def current_state(self) -> dict[str, Any]:
        """Return the current live state of this resource as a plain dict."""
        ...

    def diff(self) -> Diff:
        """Compute the diff between desired and current state.

        Returns a :class:`Diff` with ``change_type=ChangeType.NONE`` when
        the resource is already in sync.
        """
        ...

    def apply(self) -> Result:
        """Apply the changes described by :meth:`diff` to the live system.

        This method must be idempotent: calling it when the resource is
        already in sync must return :attr:`ResultStatus.OK` without side
        effects.
        """
        ...

resource_id property

Unique, stable identifier for this resource within its provider.

apply()

Apply the changes described by :meth:diff to the live system.

This method must be idempotent: calling it when the resource is already in sync must return :attr:ResultStatus.OK without side effects.

Source code in chico/core/resource.py
def apply(self) -> Result:
    """Apply the changes described by :meth:`diff` to the live system.

    This method must be idempotent: calling it when the resource is
    already in sync must return :attr:`ResultStatus.OK` without side
    effects.
    """
    ...

current_state()

Return the current live state of this resource as a plain dict.

Source code in chico/core/resource.py
def current_state(self) -> dict[str, Any]:
    """Return the current live state of this resource as a plain dict."""
    ...

desired_state()

Return the full desired state for this resource as a plain dict.

Source code in chico/core/resource.py
def desired_state(self) -> dict[str, Any]:
    """Return the full desired state for this resource as a plain dict."""
    ...

diff()

Compute the diff between desired and current state.

Returns a :class:Diff with change_type=ChangeType.NONE when the resource is already in sync.

Source code in chico/core/resource.py
def diff(self) -> Diff:
    """Compute the diff between desired and current state.

    Returns a :class:`Diff` with ``change_type=ChangeType.NONE`` when
    the resource is already in sync.
    """
    ...

Result dataclass

The outcome of calling :meth:Resource.apply.

Attributes:

Name Type Description
status ResultStatus

Whether the apply succeeded, failed, or was skipped.

resource_id str

The unique identifier of the resource that was acted on.

message str

Optional human-readable detail, especially useful on error.

Source code in chico/core/resource.py
@dataclass
class Result:
    """The outcome of calling :meth:`Resource.apply`.

    Attributes
    ----------
    status:
        Whether the apply succeeded, failed, or was skipped.
    resource_id:
        The unique identifier of the resource that was acted on.
    message:
        Optional human-readable detail, especially useful on error.
    """

    status: ResultStatus
    resource_id: str
    message: str = ""

    @property
    def ok(self) -> bool:
        """Return ``True`` when the result status is :attr:`ResultStatus.OK`."""
        return self.status == ResultStatus.OK

ok property

Return True when the result status is :attr:ResultStatus.OK.

ResultStatus

Bases: StrEnum

Outcome of applying a resource change.

Values

OK: The change was applied successfully. ERROR: The change failed. Inspect :attr:Result.message for details. SKIPPED: The change was intentionally not applied (e.g. dry-run mode).

Source code in chico/core/resource.py
class ResultStatus(StrEnum):
    """Outcome of applying a resource change.

    Values
    ------
    OK:
        The change was applied successfully.
    ERROR:
        The change failed. Inspect :attr:`Result.message` for details.
    SKIPPED:
        The change was intentionally not applied (e.g. dry-run mode).
    """

    OK = "ok"
    ERROR = "error"
    SKIPPED = "skipped"

Provider

chico.core.provider

Provider protocol for chico.

A provider encapsulates a target system — such as a filesystem directory, Kiro, or any future integration — and exposes the resources that system contains as a flat list of :class:~chico.core.resource.Resource objects.

The plan engine calls :meth:Provider.list_resources to discover what currently exists in a system before computing diffs.

Example usage::

from chico.core.provider import Provider
from chico.core.resource import Resource

class FilesystemProvider:
    def __init__(self, root: Path) -> None:
        self._root = root

    @property
    def name(self) -> str:
        return f"filesystem:{self._root}"

    def list_resources(self) -> list[Resource]:
        return [
            ConfigFileResource(path)
            for path in self._root.glob("**/*.yaml")
        ]

Provider

Bases: Protocol

Protocol that every chico provider must satisfy.

A provider is responsible for a single target system. It knows how to enumerate all :class:~chico.core.resource.Resource objects that belong to that system, which the plan engine uses to compute the full changeset.

Implementors do not need to inherit from this class — any object that exposes the attributes and methods below is a valid Provider.

Example

::

class KiroProvider:
    @property
    def name(self) -> str:
        return "kiro"

    def list_resources(self) -> list[Resource]:
        return [KiroPromptResource(p) for p in kiro.list_prompts()]
Source code in chico/core/provider.py
@runtime_checkable
class Provider(Protocol):
    """Protocol that every chico provider must satisfy.

    A provider is responsible for a single target system. It knows how to
    enumerate all :class:`~chico.core.resource.Resource` objects that belong
    to that system, which the plan engine uses to compute the full changeset.

    Implementors do not need to inherit from this class — any object that
    exposes the attributes and methods below is a valid ``Provider``.

    Example
    -------
    ::

        class KiroProvider:
            @property
            def name(self) -> str:
                return "kiro"

            def list_resources(self) -> list[Resource]:
                return [KiroPromptResource(p) for p in kiro.list_prompts()]
    """

    @property
    def name(self) -> str:
        """Unique name identifying this provider (e.g. ``"filesystem"``, ``"kiro"``)."""
        ...

    def list_resources(self) -> list[Resource]:
        """Return all resources managed by this provider.

        The returned list reflects the *current* state of the system.
        Each resource is responsible for computing its own desired state
        and diff when requested by the plan engine.
        """
        ...

name property

Unique name identifying this provider (e.g. "filesystem", "kiro").

list_resources()

Return all resources managed by this provider.

The returned list reflects the current state of the system. Each resource is responsible for computing its own desired state and diff when requested by the plan engine.

Source code in chico/core/provider.py
def list_resources(self) -> list[Resource]:
    """Return all resources managed by this provider.

    The returned list reflects the *current* state of the system.
    Each resource is responsible for computing its own desired state
    and diff when requested by the plan engine.
    """
    ...

Source

chico.core.source

Source protocol and FetchResult for chico.

A source is where desired state comes from — a GitHub repository, an S3 bucket, or any future backend. Sources are read-only: they fetch files and return the version (commit hash, ETag, etc.) that was retrieved.

The fetched version is recorded in ~/.chico/state.json after a successful apply so that drift can be detected on the next run.

Example usage::

from chico.core.source import Source, FetchResult

class MySource:
    @property
    def name(self) -> str:
        return "my-source"

    def fetch(self) -> FetchResult:
        return FetchResult(
            version="abc123def456",
            files={"steering.md": "# Steering file content"},
        )

FetchResult dataclass

The result of fetching desired state from a source.

Attributes:

Name Type Description
version str

An opaque string that uniquely identifies the fetched snapshot. For GitHub sources this is the full commit SHA. For S3 it would be an ETag or object version ID. Stored in state after apply to enable drift detection.

files dict[str, str]

A mapping of relative file paths to their text content, as fetched from the source.

Source code in chico/core/source.py
@dataclass
class FetchResult:
    """The result of fetching desired state from a source.

    Attributes
    ----------
    version:
        An opaque string that uniquely identifies the fetched snapshot.
        For GitHub sources this is the full commit SHA. For S3 it would
        be an ETag or object version ID. Stored in state after apply to
        enable drift detection.
    files:
        A mapping of relative file paths to their text content, as
        fetched from the source.
    """

    version: str
    files: dict[str, str] = field(default_factory=dict)

Source

Bases: Protocol

Protocol that every chico source must satisfy.

A source knows how to fetch the desired state for a set of files from an external system and return the version identifier of what was fetched.

Implementors do not need to inherit from this class — any object that exposes the attributes and methods below is a valid Source.

Example

::

class GitHubSource:
    @property
    def name(self) -> str:
        return "kiro-configs"

    def fetch(self) -> FetchResult:
        # ... fetch files from GitHub ...
        return FetchResult(version=commit_sha, files=files)
Source code in chico/core/source.py
@runtime_checkable
class Source(Protocol):
    """Protocol that every chico source must satisfy.

    A source knows how to fetch the desired state for a set of files from
    an external system and return the version identifier of what was fetched.

    Implementors do not need to inherit from this class — any object that
    exposes the attributes and methods below is a valid ``Source``.

    Example
    -------
    ::

        class GitHubSource:
            @property
            def name(self) -> str:
                return "kiro-configs"

            def fetch(self) -> FetchResult:
                # ... fetch files from GitHub ...
                return FetchResult(version=commit_sha, files=files)
    """

    @property
    def name(self) -> str:
        """Unique name identifying this source, matching the config entry."""
        ...

    def fetch(self) -> FetchResult:
        """Fetch the current desired state from the source.

        Returns a :class:`FetchResult` containing all files and the version
        identifier of the snapshot that was retrieved.

        Raises
        ------
        SourceFetchError
            If the source cannot be reached or authentication fails.
        """
        ...

name property

Unique name identifying this source, matching the config entry.

fetch()

Fetch the current desired state from the source.

Returns a :class:FetchResult containing all files and the version identifier of the snapshot that was retrieved.

Raises:

Type Description
SourceFetchError

If the source cannot be reached or authentication fails.

Source code in chico/core/source.py
def fetch(self) -> FetchResult:
    """Fetch the current desired state from the source.

    Returns a :class:`FetchResult` containing all files and the version
    identifier of the snapshot that was retrieved.

    Raises
    ------
    SourceFetchError
        If the source cannot be reached or authentication fails.
    """
    ...

SourceFetchError

Bases: Exception

Raised when a source fails to fetch desired state.

Wraps the underlying exception so callers can handle fetch failures without depending on source-specific exception types.

Source code in chico/core/source.py
class SourceFetchError(Exception):
    """Raised when a source fails to fetch desired state.

    Wraps the underlying exception so callers can handle fetch failures
    without depending on source-specific exception types.
    """