diff --git a/README.md b/README.md index 36780687..75cad7a3 100644 --- a/README.md +++ b/README.md @@ -190,11 +190,12 @@ Donna still prints newly created internal journal records immediately using the Use `donna --help` for a quick reference. -You find detailed documentation in the agent instructions — they are readable and always accurate: +You find detailed documentation in the built-in skill documents — they are readable and always accurate: -- [CLI specification](./.agents/donna/usage/cli.donna.md) — full list of commands and how to use them. -- [Artifacts](./.agents/donna/usage/artifacts.donna.md) — what Donna artifacts are and how to use them. -- [Filesystem layout](./.agents/donna/usage/worlds.donna.md) — how Donna discovers and manages artifacts on the filesystem. +- `donna skill usage` — full list of commands and how to use them. +- `donna skill artifacts` — what Donna artifacts are and how to use them on the filesystem. +- `donna skill configuration` — how to configure `.donna/config.toml`. +- `donna skill initialization` — how to initialize or refresh a Donna workspace. The documentation below covers aspects important to humans and partially duplicates the agent's instructions. @@ -249,7 +250,7 @@ Artifact ids are project-relative filepaths prefixed with `@/`. Section ids appe Examples: - `@/specs/work/polish.donna.md` -- `@/.agents/donna/usage/cli.donna.md` +- `@/.agents/donna/work/polish.donna.md` - `@/.donna/session/execute_rfc.donna.md:review_changes` You and agents can `list`, `view`, and `validate` artifacts. @@ -316,7 +317,7 @@ To execute a workflow, Donna uses a simplified virtual machine (VM) that maintai ### Operations -You can find detailed docs on built-in operations in the [artifacts documentation](./.agents/donna/usage/artifacts.donna.md). +You can find detailed docs on built-in operations in `donna skill artifacts`. Here is a short list of them: @@ -391,7 +392,7 @@ Donna defines a set of built-in Jinja2 functions that provide artifacts with the Directives are used in the next way: `{{ python.import.path() }}`. -You can find a detailed documentation of all built-in directives in the [artifacts documentation](./.agents/donna/usage/artifacts.donna.md). +You can find detailed documentation of all built-in directives in `donna skill artifacts`. Here they are: diff --git a/changes/unreleased.md b/changes/unreleased.md index 73cf4db8..a4b6cfc0 100644 --- a/changes/unreleased.md +++ b/changes/unreleased.md @@ -1,38 +1,10 @@ -### Migration -- Move project-specific specs from `.donna/project` to `specs`, or set an explicit `project` world path in your Donna workspace config before upgrading. -- Run `donna workspaces update` in existing projects so bundled Donna specs are installed into `.agents/donna` for the new filesystem-backed `donna` world. -- Update your scripts and specs to use external tools or direct file edits to create, update, move, copy, or delete world artifacts instead using removed Donna commands. -- Update artifact references from legacy ids like `specs:intro` to filepath ids like `@/specs/intro.md`, and include file extensions on all artifact references. -- Set `journal.cmd` in the `.donna/config.toml` as a list of command arguments if you relied on `donna journal` output or session journal files. +**This release is dedicated to a full rethinking of what Donna is and how it works.** -### Changes +The scope of tool functionality was reduced to interpreting state machines for agents (no artifact management, no session management, etc.). -- Added configurable artifact file filters. Donna will see only files that pass all of the filters. -- Replaced artifact ids with project-relative filepaths like `@/specs/intro.md` and `@/.donna/session/plans/plan.md:finish`. -- Changed the default location of project specs to `specs/`. - - Updated the default `project` world path to load from `specs/` instead of `.donna/project/`. - - Rewrote the moved project specs and repository docs to reference the new `specs/` location. -- `--tag` option is replaced with `--predicate` in all CLI commands that accept artifact patterns. -- Removed the Python donna world. - - Added workspace spec dumping into `.agents/donna` on `donna workspaces init` and `donna workspaces update`. - - Reconfigured the default `donna` world to load bundled specs from `.agents/donna` through the filesystem world and removed the Python world implementation. -- Removed world artifact mutation support. - - Removed `donna artifacts` mutation commands and the supporting artifact-mutation code paths. - - Removed `readonly` world-artifact mutability modeling from workspace config and world abstractions. - - Updated artifact and world usage specs to state that developers and external tools mutate world artifacts while Donna validates them. -- Removed `donna artifacts fetch` and `donna artifacts tmp` commands and all related code. -- Removed native journal CLI functionality in favor of external journal integrations. +That's why it is difficult to provide migration instructions or a proper list of changes. -### Breaking Changes +**Treat this version of Donna as a totally new tool** => read the documentation from scratch and adjust your usage accordingly. -- Donna artifact ids now use project-relative filepaths with required file extensions, and legacy colon-delimited artifact ids are no longer supported. -- Donna no longer exposes bundled specs through the Python-backed `donna` world; `donna workspaces init|update` now sync them into `.agents/donna`. -- `donna artifacts` no longer supports `update`, `copy`, `move`, or `remove`. -- Donna no longer mutates world artifacts through workspace APIs or world configuration. -- Donna no longer exposes the `donna journal` CLI command or session journal viewing/following. - -### Removals - -- Removed the Python world implementation and the `donna.artifacts` package-backed source of bundled Donna specs. -- Removed the `donna journal` CLI command and session journal JSONL read/follow support. +Sorry for the inconvenience. There should be no such breaking changes in the future. diff --git a/donna/cli/__main__.py b/donna/cli/__main__.py index 197d48cc..c79b436d 100644 --- a/donna/cli/__main__.py +++ b/donna/cli/__main__.py @@ -1,6 +1,7 @@ from donna.cli.application import app # noqa: F401 from donna.cli.commands import artifacts # noqa: F401 from donna.cli.commands import sessions # noqa: F401 +from donna.cli.commands import skills # noqa: F401 from donna.cli.commands import version # noqa: F401 from donna.cli.commands import workspaces # noqa: F401 diff --git a/donna/cli/application.py b/donna/cli/application.py index 4348b905..67d192ca 100644 --- a/donna/cli/application.py +++ b/donna/cli/application.py @@ -1,7 +1,7 @@ import typer +from donna.cli.entities import GLOBAL_OPTIONS_CONTEXT_KEY, GlobalOptions from donna.cli.types import ProtocolModeOption, RootOption -from donna.cli.utils import try_initialize_donna from donna.protocol.modes import Mode app = typer.Typer(help="Donna CLI: manage hierarchical state machines to guide your AI agents.") @@ -9,7 +9,8 @@ @app.callback() def initialize( + context: typer.Context, protocol: ProtocolModeOption = Mode.human, root_dir: RootOption = None, ) -> None: - try_initialize_donna(project_dir=root_dir, protocol=protocol) + context.meta[GLOBAL_OPTIONS_CONTEXT_KEY] = GlobalOptions(protocol=protocol, root_dir=root_dir) diff --git a/donna/cli/commands/artifacts.py b/donna/cli/commands/artifacts.py index 6fa1beea..a3853a50 100644 --- a/donna/cli/commands/artifacts.py +++ b/donna/cli/commands/artifacts.py @@ -1,15 +1,12 @@ -from collections.abc import Iterable - import typer from donna.cli.application import app from donna.cli.types import ArtifactIdPatternArgument, PredicateOption, validate_supported_artifact_pattern -from donna.cli.utils import cells_cli +from donna.cli.utils import command_context from donna.context.context import context from donna.domain.artifact_ids import ArtifactIdPattern from donna.machine import journal as machine_journal from donna.protocol.cell_shortcuts import operation_succeeded -from donna.protocol.cells import Cell from donna.workspaces.artifacts import RENDER_CONTEXT_VIEW artifacts_cli = typer.Typer() @@ -38,32 +35,34 @@ def _log_operation_on_artifacts( "and show their status summaries. Lists all artifacts by default." ) ) -@cells_cli def list( + typer_context: typer.Context, pattern: ArtifactIdPatternArgument = DEFAULT_ARTIFACT_PATTERN, predicate: PredicateOption = None, -) -> Iterable[Cell]: - validate_supported_artifact_pattern(pattern) - _log_operation_on_artifacts("List artifacts", pattern, predicate) +) -> None: + with command_context(typer_context) as command: + validate_supported_artifact_pattern(pattern) + _log_operation_on_artifacts("List artifacts", pattern, predicate) - artifacts = context().artifacts.list(pattern, RENDER_CONTEXT_VIEW, predicate=predicate).unwrap() + artifacts = context().artifacts.list(pattern, RENDER_CONTEXT_VIEW, predicate=predicate).unwrap() - return [artifact.node().status() for artifact in artifacts] + command.write_cells(artifact.node().status() for artifact in artifacts) @artifacts_cli.command( help="Display artifacts matching a pattern or specific id that uses a supported source extension." ) -@cells_cli def view( + typer_context: typer.Context, pattern: ArtifactIdPatternArgument, predicate: PredicateOption = None, -) -> Iterable[Cell]: - validate_supported_artifact_pattern(pattern) - _log_operation_on_artifacts("View artifacts", pattern, predicate) +) -> None: + with command_context(typer_context) as command: + validate_supported_artifact_pattern(pattern) + _log_operation_on_artifacts("View artifacts", pattern, predicate) - artifacts = context().artifacts.list(pattern, RENDER_CONTEXT_VIEW, predicate=predicate).unwrap() - return [artifact.node().info() for artifact in artifacts] + artifacts = context().artifacts.list(pattern, RENDER_CONTEXT_VIEW, predicate=predicate).unwrap() + command.write_cells(artifact.node().info() for artifact in artifacts) @artifacts_cli.command( @@ -72,27 +71,29 @@ def view( "(defaults to all artifacts) and return any errors." ) ) -@cells_cli def validate( + typer_context: typer.Context, pattern: ArtifactIdPatternArgument = DEFAULT_ARTIFACT_PATTERN, predicate: PredicateOption = None, -) -> Iterable[Cell]: # noqa: CCR001 - validate_supported_artifact_pattern(pattern) - _log_operation_on_artifacts("Validate artifacts", pattern, predicate) +) -> None: # noqa: CCR001 + with command_context(typer_context) as command: + validate_supported_artifact_pattern(pattern) + _log_operation_on_artifacts("Validate artifacts", pattern, predicate) - artifacts = context().artifacts.list(pattern, RENDER_CONTEXT_VIEW, predicate=predicate).unwrap() + artifacts = context().artifacts.list(pattern, RENDER_CONTEXT_VIEW, predicate=predicate).unwrap() - errors = [] + errors = [] - for artifact in artifacts: - result = artifact.validate_artifact() - if result.is_err(): - errors.extend(result.unwrap_err()) + for artifact in artifacts: + result = artifact.validate_artifact() + if result.is_err(): + errors.extend(result.unwrap_err()) - if errors: - return [error.node().info() for error in errors] + if errors: + command.write_cells(error.node().info() for error in errors) + return - return [operation_succeeded("All artifacts are valid")] + command.write_cells([operation_succeeded("All artifacts are valid")]) app.add_typer( diff --git a/donna/cli/commands/sessions.py b/donna/cli/commands/sessions.py index 66cc0ab8..3d47a7a6 100644 --- a/donna/cli/commands/sessions.py +++ b/donna/cli/commands/sessions.py @@ -1,5 +1,3 @@ -from collections.abc import Iterable - import typer from donna.cli.application import app @@ -10,62 +8,63 @@ validate_supported_artifact_id, validate_supported_artifact_section_id, ) -from donna.cli.utils import cells_cli +from donna.cli.utils import command_context from donna.machine import sessions -from donna.protocol.cells import Cell sessions_cli = typer.Typer() @sessions_cli.command(help="Start a new session, reset session state, remove all session artifacts.") -@cells_cli -def start() -> Iterable[Cell]: - return sessions.start().unwrap() +def start(context: typer.Context) -> None: + with command_context(context) as command: + command.write_cells(sessions.start().unwrap()) @sessions_cli.command(help="Reset the current session state, keeps session artifacts.") -@cells_cli -def reset() -> Iterable[Cell]: - return sessions.reset().unwrap() +def reset(context: typer.Context) -> None: + with command_context(context) as command: + command.write_cells(sessions.reset().unwrap()) @sessions_cli.command( name="continue", help="Continue the current session and emit the next queued action request(s).", ) -@cells_cli -def continue_() -> Iterable[Cell]: - return sessions.continue_().unwrap() +def continue_(context: typer.Context) -> None: + with command_context(context) as command: + command.write_cells(sessions.continue_().unwrap()) @sessions_cli.command(help="Show a concise status summary for the current session, including pending action requests.") -@cells_cli -def status() -> Iterable[Cell]: - return sessions.status().unwrap() +def status(context: typer.Context) -> None: + with command_context(context) as command: + command.write_cells(sessions.status().unwrap()) @sessions_cli.command(help="Show detailed session state, including action requests.") -@cells_cli -def details() -> Iterable[Cell]: - return sessions.details().unwrap() +def details(context: typer.Context) -> None: + with command_context(context) as command: + command.write_cells(sessions.details().unwrap()) @sessions_cli.command(help="Run a workflow from an artifact to drive the current session forward.") -@cells_cli -def run(workflow_id: ArtifactIdArgument) -> Iterable[Cell]: - validate_supported_artifact_id(workflow_id) - return sessions.start_workflow(workflow_id).unwrap() +def run(context: typer.Context, workflow_id: ArtifactIdArgument) -> None: + with command_context(context) as command: + validate_supported_artifact_id(workflow_id) + command.write_cells(sessions.start_workflow(workflow_id).unwrap()) @sessions_cli.command( help="Mark an action request as completed and advance the workflow to the specified next operation." ) -@cells_cli def action_request_completed( - request_id: ActionRequestIdArgument, next_operation_id: ArtifactSectionIdArgument -) -> Iterable[Cell]: - validate_supported_artifact_section_id(next_operation_id) - return sessions.complete_action_request(request_id, next_operation_id).unwrap() + context: typer.Context, + request_id: ActionRequestIdArgument, + next_operation_id: ArtifactSectionIdArgument, +) -> None: + with command_context(context) as command: + validate_supported_artifact_section_id(next_operation_id) + command.write_cells(sessions.complete_action_request(request_id, next_operation_id).unwrap()) app.add_typer( diff --git a/donna/cli/commands/skills.py b/donna/cli/commands/skills.py new file mode 100644 index 00000000..091a822e --- /dev/null +++ b/donna/cli/commands/skills.py @@ -0,0 +1,23 @@ +from typing import Annotated + +import typer + +from donna.cli.application import app +from donna.cli.utils import command_context +from donna.protocol.cells import Cell +from donna.skills.entities import SkillDocument +from donna.skills.fixtures import load_skill_text + + +@app.command("skill", help="Print built-in Donna skill documentation.") +def skill(context: typer.Context, document: Annotated[SkillDocument, typer.Argument()] = SkillDocument.usage) -> None: + with command_context(context, load_environment=False) as command: + command.write_cells( + [ + Cell.build_markdown( + kind="skill", + content=load_skill_text(document), + document=document.value, + ) + ] + ) diff --git a/donna/cli/commands/workspaces.py b/donna/cli/commands/workspaces.py index 6f15d4e4..278c717e 100644 --- a/donna/cli/commands/workspaces.py +++ b/donna/cli/commands/workspaces.py @@ -1,44 +1,32 @@ -import pathlib -from collections.abc import Iterable - import typer from donna.cli.application import app from donna.cli.types import SkillsOption, SpecsOption -from donna.cli.utils import cells_cli +from donna.cli.utils import command_context from donna.protocol.cell_shortcuts import operation_succeeded -from donna.protocol.cells import Cell -from donna.workspaces import config as workspace_config from donna.workspaces.initialization import initialize_workspace, update_workspace workspaces_cli = typer.Typer() -def _resolve_target_dir() -> pathlib.Path: - if workspace_config.project_dir.is_set(): - return workspace_config.project_dir() - - return pathlib.Path.cwd() - - @workspaces_cli.command(help="Initialize Donna workspace.") -@cells_cli -def init(skills: SkillsOption = True, specs: SpecsOption = True) -> Iterable[Cell]: - target_dir = _resolve_target_dir() +def init(context: typer.Context, skills: SkillsOption = True, specs: SpecsOption = True) -> None: + with command_context(context, load_environment=False) as command: + target_dir = command.target_dir() - initialize_workspace(target_dir, install_skills=skills, install_specs=specs).unwrap() + initialize_workspace(target_dir, install_skills=skills, install_specs=specs).unwrap() - return [operation_succeeded("Workspace initialized successfully")] + command.write_cells([operation_succeeded("Workspace initialized successfully")]) @workspaces_cli.command(help="Update Donna workspace files.") -@cells_cli -def update(skills: SkillsOption = True, specs: SpecsOption = True) -> Iterable[Cell]: - target_dir = _resolve_target_dir() +def update(context: typer.Context, skills: SkillsOption = True, specs: SpecsOption = True) -> None: + with command_context(context) as command: + target_dir = command.target_dir() - update_workspace(target_dir, install_skills=skills, install_specs=specs).unwrap() + update_workspace(target_dir, install_skills=skills, install_specs=specs).unwrap() - return [operation_succeeded("Workspace updated successfully")] + command.write_cells([operation_succeeded("Workspace updated successfully")]) app.add_typer( diff --git a/donna/cli/entities.py b/donna/cli/entities.py new file mode 100644 index 00000000..e81ef339 --- /dev/null +++ b/donna/cli/entities.py @@ -0,0 +1,11 @@ +import pathlib + +from donna.core.entities import BaseEntity +from donna.protocol.modes import Mode + +GLOBAL_OPTIONS_CONTEXT_KEY = "donna_global_options" + + +class GlobalOptions(BaseEntity): + protocol: Mode + root_dir: pathlib.Path | None = None diff --git a/donna/cli/utils.py b/donna/cli/utils.py index b617ae16..c6049bee 100644 --- a/donna/cli/utils.py +++ b/donna/cli/utils.py @@ -1,17 +1,17 @@ -import functools import pathlib import sys -from collections.abc import Iterable -from typing import Callable, ParamSpec +from collections.abc import Iterable, Iterator +from contextlib import contextmanager import typer +from donna.cli.entities import GLOBAL_OPTIONS_CONTEXT_KEY, GlobalOptions from donna.core.errors import EnvironmentError, ErrorsList from donna.core.result import UnwrapError from donna.protocol.cells import Cell from donna.protocol.modes import Mode, get_cell_formatter from donna.workspaces import config as workspace_config -from donna.workspaces.initialization import initialize_runtime +from donna.workspaces.initialization import load_workspace def output_cells(cells: Iterable[Cell]) -> None: @@ -22,70 +22,91 @@ def output_cells(cells: Iterable[Cell]) -> None: sys.stdout.buffer.write(output) -P = ParamSpec("P") +def global_options(context: typer.Context) -> GlobalOptions: + global_options = context.find_root().meta.get(GLOBAL_OPTIONS_CONTEXT_KEY) + if isinstance(global_options, GlobalOptions): + return global_options -def _write_errors_to_journal(errors: ErrorsList) -> None: - from donna.machine import journal as machine_journal + return GlobalOptions(protocol=Mode.human) - for error in errors: - message = f"Error: {error.node().journal_message()} [{error.code}]" - machine_journal.add( - message=message, - actor_id="donna", - ) +class CommandContext: + __slots__ = ("global_options", "protocol") + + def __init__(self, context: typer.Context) -> None: + self.global_options = global_options(context) + self.protocol = self.global_options.protocol + def install_protocol(self) -> None: + if not workspace_config.protocol.is_set(): + workspace_config.protocol.set(self.protocol) -def cells_cli(func: Callable[P, Iterable[Cell]]) -> Callable[P, None]: # noqa: CCR001 + def load_workspace(self) -> workspace_config.Workspace: + workspace = load_workspace(root_dir=self.global_options.root_dir).unwrap() + workspace_config.install_workspace(workspace) + return workspace - @functools.wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> None: # noqa: CCR001 - try: - cells = func(*args, **kwargs) - except UnwrapError as e: - errors: ErrorsList - if isinstance(e.arguments["error"], EnvironmentError): - errors = [e.arguments["error"]] - elif isinstance(e.arguments["error"], Iterable): - errors = [error for error in e.arguments["error"] if isinstance(error, EnvironmentError)] - else: - raise + def target_dir(self) -> pathlib.Path: + if self.global_options.root_dir is not None: + return self.global_options.root_dir - _write_errors_to_journal(errors) - cells = [error.node().info() for error in errors] + if workspace_config.project_dir.is_set(): + return workspace_config.project_dir() + return pathlib.Path.cwd() + + def write_cells(self, cells: Iterable[Cell]) -> None: output_cells(cells) - return wrapper +@contextmanager +def command_context(context: typer.Context, *, load_environment: bool = True) -> Iterator[CommandContext]: + from donna.context import Context, set_context -def _is_workspace_init_command() -> bool: - args = sys.argv[1:] - if "workspaces" not in args: - return False + command = CommandContext(context) - index = args.index("workspaces") - return len(args) > index + 1 and args[index + 1] == "init" + try: + command.install_protocol() + if load_environment: + command.load_workspace() + set_context(Context()) -def try_initialize_donna(project_dir: pathlib.Path | None, protocol: Mode) -> None: - from donna.context import Context, set_context + yield command + except UnwrapError as error: + command.write_cells(_cells_from_unwrap(error)) + raise typer.Exit(code=0) from error + + +def _write_errors_to_journal(errors: ErrorsList) -> None: + from donna.machine import journal as machine_journal + + for error in errors: + message = f"Error: {error.node().journal_message()} [{error.code}]" + + machine_journal.add( + message=message, + actor_id="donna", + ) + + +def _errors_from_unwrap(error: UnwrapError) -> ErrorsList: + unwrapped = error.arguments["error"] + + if isinstance(unwrapped, EnvironmentError): + return [unwrapped] - if _is_workspace_init_command(): - workspace_config.protocol.set(protocol) - if project_dir is not None: - workspace_config.project_dir.set(project_dir) - return + if isinstance(unwrapped, Iterable): + return [item for item in unwrapped if isinstance(item, EnvironmentError)] - result = initialize_runtime(root_dir=project_dir, protocol=protocol) + raise error - if result.is_ok(): - set_context(Context()) - return - errors = result.unwrap_err() +def _cells_from_unwrap(error: UnwrapError) -> Iterable[Cell]: + errors = _errors_from_unwrap(error) - output_cells([error.node().info() for error in errors]) + if workspace_config.config.is_set(): + _write_errors_to_journal(errors) - raise typer.Exit(code=0) + return [item.node().info() for item in errors] diff --git a/donna/fixtures/specs/intro.donna.md b/donna/fixtures/specs/intro.donna.md index 0eb043c4..2bc3aab6 100644 --- a/donna/fixtures/specs/intro.donna.md +++ b/donna/fixtures/specs/intro.donna.md @@ -30,7 +30,7 @@ Artifact type tags: ## Instructions -1. On start of the YOUR session you **MUST** read and understand instruction on using the Donna tool `{{ donna.lib.view("./usage/cli.donna.md") }}`. It **MUST** be a one time operation. Do not repeat it unless you forget how to use the tool. +1. On start of the YOUR session you **MUST** read and understand instruction on using the Donna tool by running `donna skill usage`. It **MUST** be a one time operation. Do not repeat it unless you forget how to use the tool. 2. If you need to perform a work with Donna, you **MUST** select an appropriate Donna workflow to perform the work and run it. 3. If there is no appropriate workflow, ask the developer for a precise instructions on what to do. 4. If you are executing a workflow operation and need to perform a complex action or changes, you SHOULD search for an appropriate workflow and run it as a child workflow — it is the intended way to use Donna. @@ -39,7 +39,7 @@ Artifact type tags: ## Journaling -Donna creates internal journal records for important workflow events, according to the description in `{{ donna.lib.view("./usage/cli.donna.md") }}`. +Donna creates internal journal records for important workflow events, according to the description in `donna skill usage`. Journal records can be forwarded to a third-party tool by configuring `[journal].cmd` in `/.donna/config.toml`. diff --git a/donna/fixtures/specs/research/specs/report.donna.md b/donna/fixtures/specs/research/specs/report.donna.md index 08f4ab68..8e469176 100644 --- a/donna/fixtures/specs/research/specs/report.donna.md +++ b/donna/fixtures/specs/research/specs/report.donna.md @@ -16,7 +16,7 @@ The agent (via workflows) creates the artifact and updates it iteratively as the ## Research report structure -The research report is a Donna artifact (check `{{ donna.lib.view("../../usage/artifacts.donna.md") }}`) with the next structure: +The research report is a Donna artifact (check `donna skill artifacts`) with the next structure: - **Primary section** -- title and short description of the research problem. - **Original problem description** -- original problem statement from the developer or parent workflow. @@ -35,7 +35,7 @@ The research report is a Donna artifact (check `{{ donna.lib.view("../../usage/a ## General language and format - You MUST follow [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119.txt) for keywords like MUST, SHOULD, MAY, etc. -- You MUST follow `{{ donna.lib.view("../../usage/artifacts.donna.md") }}`. +- You MUST follow `donna skill artifacts`. - You MUST follow the structure specified in this document. ### List format diff --git a/donna/fixtures/specs/research/work/research.donna.md b/donna/fixtures/specs/research/work/research.donna.md index a211f5eb..694f4fe8 100644 --- a/donna/fixtures/specs/research/work/research.donna.md +++ b/donna/fixtures/specs/research/work/research.donna.md @@ -20,7 +20,7 @@ kind = "donna.lib.request_action" fsm_mode = "start" ``` -1. Read the specification `{{ donna.lib.view("../../usage/artifacts.donna.md") }}` if you haven't done it yet. +1. Read the artifact instructions by running `donna skill artifacts` if you haven't done it yet. 2. Read the specification `{{ donna.lib.view("../specs/report.donna.md") }}` if you haven't done it yet. 3. `{{ donna.lib.goto("ensure_problem_description_exists") }}` diff --git a/donna/fixtures/specs/rfc/specs/design.donna.md b/donna/fixtures/specs/rfc/specs/design.donna.md index 3a9d3b2b..4f9169e8 100644 --- a/donna/fixtures/specs/rfc/specs/design.donna.md +++ b/donna/fixtures/specs/rfc/specs/design.donna.md @@ -24,7 +24,7 @@ The Design document MUST NOT be a high-level description of the problem and solu ## Design document structure -The RFC document is Donna artifact (check `{{ donna.lib.view("../../usage/artifacts.donna.md") }}`) with the next structure: +The RFC document is Donna artifact (check `donna skill artifacts`) with the next structure: - **Primary section** — title and short description of the proposed change. - **Inputs** — list of input documents that are relevant for the proposed change, starting from the RFC document. @@ -40,7 +40,7 @@ The RFC document is Donna artifact (check `{{ donna.lib.view("../../usage/artifa ## General language and format - You MUST follow [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119.txt) for keywords like MUST, SHOULD, MAY, etc. -- You MUST follow `{{ donna.lib.view("../../usage/artifacts.donna.md") }}`. +- You MUST follow `donna skill artifacts`. - You MUST follow the structure specified in this document. ### List format diff --git a/donna/fixtures/specs/rfc/specs/request_for_change.donna.md b/donna/fixtures/specs/rfc/specs/request_for_change.donna.md index ecace694..8f135d49 100644 --- a/donna/fixtures/specs/rfc/specs/request_for_change.donna.md +++ b/donna/fixtures/specs/rfc/specs/request_for_change.donna.md @@ -16,7 +16,7 @@ If not otherwise specified, RFC documents for the session MUST be stored as `@/. ## RFC structure -The RFC document is Donna artifact (check `{{ donna.lib.view("../../usage/artifacts.donna.md") }}`) with the next structure: +The RFC document is Donna artifact (check `donna skill artifacts`) with the next structure: - **Primary section** — title and short description of the proposed change. - **Original description** — original description of the requested changes from the developer or parent workflow. @@ -34,7 +34,7 @@ The RFC document is Donna artifact (check `{{ donna.lib.view("../../usage/artifa ## General language and format - You MUST follow [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119.txt) for keywords like MUST, SHOULD, MAY, etc. -- You MUST follow `{{ donna.lib.view("../../usage/artifacts.donna.md") }}`. +- You MUST follow `donna skill artifacts`. - You MUST follow the structure specified in this document. ### List format diff --git a/donna/fixtures/specs/rfc/work/design.donna.md b/donna/fixtures/specs/rfc/work/design.donna.md index 18b8bfeb..8eecf84c 100644 --- a/donna/fixtures/specs/rfc/work/design.donna.md +++ b/donna/fixtures/specs/rfc/work/design.donna.md @@ -16,7 +16,7 @@ fsm_mode = "start" ``` 1. Read the specification `{{ donna.lib.view("../specs/design.donna.md") }}` if you haven't done it yet. -2. Read the specification `{{ donna.lib.view("../../usage/artifacts.donna.md") }}` if you haven't done it yet. +2. Read the artifact instructions by running `donna skill artifacts` if you haven't done it yet. 3. `{{ donna.lib.goto("ensure_rfc_artifact_exists") }}` ## Ensure RFC artifact exists diff --git a/donna/fixtures/specs/rfc/work/plan.donna.md b/donna/fixtures/specs/rfc/work/plan.donna.md index d620889f..b640db26 100644 --- a/donna/fixtures/specs/rfc/work/plan.donna.md +++ b/donna/fixtures/specs/rfc/work/plan.donna.md @@ -18,7 +18,7 @@ fsm_mode = "start" 1. Read the Design document that the developer or parent workflow wants you to implement. 2. Read the RFC document that the developer or parent workflow wants you to implement, if it exists. -3. Read the specification `{{ donna.lib.view("../../usage/artifacts.donna.md") }}` if you haven't done it yet. +3. Read the artifact instructions by running `donna skill artifacts` if you haven't done it yet. 4. `{{ donna.lib.goto("prepare_workflow_artifact") }}` ## Prepare workflow artifact diff --git a/donna/fixtures/specs/rfc/work/request.donna.md b/donna/fixtures/specs/rfc/work/request.donna.md index f66d58a3..2eefefc1 100644 --- a/donna/fixtures/specs/rfc/work/request.donna.md +++ b/donna/fixtures/specs/rfc/work/request.donna.md @@ -17,7 +17,7 @@ fsm_mode = "start" ``` 1. Read the specification `{{ donna.lib.view("../specs/request_for_change.donna.md") }}` if you haven't done it yet. -2. Read the specification `{{ donna.lib.view("../../usage/artifacts.donna.md") }}` if you haven't done it yet. +2. Read the artifact instructions by running `donna skill artifacts` if you haven't done it yet. 3. `{{ donna.lib.goto("ensure_work_description_exists") }}` ## Ensure work description exists diff --git a/donna/fixtures/specs/usage/artifacts.donna.md b/donna/fixtures/specs/usage/artifacts.donna.md deleted file mode 100644 index 145923b0..00000000 --- a/donna/fixtures/specs/usage/artifacts.donna.md +++ /dev/null @@ -1,271 +0,0 @@ -# Default Text Artifacts Behavior - -```toml donna -kind = "donna.lib.specification" -``` - -This document describes the default format and behavior of Donna's text artifacts. -This format and behavior is what should be expected by default from an artifact if not specified otherwise. - -## Overview - -An artifact is any text or binary document that Donna manages in the project filesystem. For example, via CLI commands `donna -p artifacts …`. - -The text artifact has a source and one or more rendered representations, produced in specific rendering modes. - -— The source is the raw text content of the artifact as it is stored on disk or in remote storage. -- The representation is the rendered version of the artifact for a specific rendering mode. In practice, the same source is rendered in `view` mode for CLI display, `execute` mode for workflow execution, and `analysis` mode for internal parsing and validation (see "Rendering artifacts"). - -To change the artifact, developers and agents edit its source. - -To get information from the artifact, developers, agents and Donna view one of its representations (typically via the view rendering mode). - -**If you need an information from the artifact, you MUST view its representation**. Artifact sources are only for editing. - -Read the specification `{{ donna.lib.view("./cli.donna.md") }}` to learn how to work with artifacts via Donna CLI. - -## Source Format and Rendering - -The source of the text artifact is a Jinja2 template of a Markdown document. - -When rendering the artifact, Donna processes the Jinja2 template with a predefined context (at minimum `render_mode` and `artifact_id`, and optionally `current_task`/`current_work_unit` during workflow execution), then renders the resulting Markdown content into the desired representation based on the selected rendering mode. - -**Artifact source should not use Jinja2 inheritance features** like `{{ "{% extends %}" }}` and `{{ "{% block %}" }}`. - -Donna provides a set of special directives that can and MUST be used in the artifact source to enhance its behavior. Some of these directives are valid for all artifacts, some are valid only for specific section kinds. - -Here are some examples: - -- `{{ "{{ donna.lib.view() }}" }}` — references another artifact (supports `*` and `**` wildcard patterns). In `view`/`execute` modes it renders an exact CLI command to view the artifact; in `analysis` mode it renders a `$$donna ... $$` marker used for internal parsing. -- `{{ "{{ donna.lib.list() }}" }}` — references artifact listing by pattern (supports `*` and `**` wildcard patterns). In `view`/`execute` modes it renders an exact CLI command to list artifacts; in `analysis` mode it renders a `$$donna ... $$` marker used for internal parsing. -- `{{ "{{ donna.lib.goto() }}" }}` — references the next workflow operation to execute. In `view`/`execute` modes it renders an exact CLI command to advance the workflow; in `analysis` mode it renders a `$$donna goto ... $$` marker used to extract workflow transitions. - -## Jinja2 rendering - -Donna allows all of Jinja2 expressions in artifact sources, except inheritance-related once: `{{ "{% extends %}" }}` , `{{ "{% block %}" }}`, etc. - -Donna intentionally hides some parts of the source in the rendered output, but they remain visible in the source files themselves (on filesystem): - -- fenced code blocks with the `donna` marker (they contain technical information for the Donna, not information for the agent). -- Jinja2 comments like `{{ "{# ... #}" }}`. - -## Rendering artifacts - -Donna renders the same artifact source into different representations depending on the rendering mode. The mode is internal to Donna (users do not select it directly) and controls how directives are expanded and which metadata is included. - -- `view` — default representation used when the CLI loads artifacts for display (`artifacts view`, `artifacts list`, `artifacts validate`). This is the human/agent-facing output. -- `execute` — representation used when Donna executes workflow operations (`sessions run`). It renders directives with task/work-unit context so the resulting text is actionable for the agent. -- `analysis` — internal representation used during parsing and validation. It emits `$$donna ... $$` markers so Donna can extract workflow transitions and other structured signals. - -## Structure of a Text Artifact - -Technically, any valid Markdown document is a valid text artifact. - -However, Donna assigns special meaning to some elements of the Markdown document to provide enhanced behavior and capabilities. - -### Sections - -Artifact is divided into multiple sections: - -- H1 header and all text till the first H2 header is considered the `head section` of the artifact. -- Each H2 header and all text till the next H2 header (or end of document) is considered a `tail section` of the artifact. - -Head section provides a description of the artifact and its purpose and MUST contain a configuration block of the artifact. The head section is also the artifact's `primary section` and is used when Donna needs to show a brief summary of the artifact, for example, when listing artifacts or when an operation targets the artifact without specifying a section. - -Tail sections describes one of the components of the artifact and CAN contain configuration blocks as well. Configuration blocks placed in subsections (h3 and below) count as part of the parent tail section. - -The content of the header (text after `#` or `##`) is considered the section title. - -Donna always interprets the head section as a general description of the artifact and treats it as the primary section. - -Donna interprets a tail section according to the primary section kind and configuration blocks in that section. - -### Configuration Blocks - -Configuration blocks are fenced code blocks with specified primary format, followed by the `donna` keyword and, optionally, list of properties. - -The supported primary formats are: TOML, JSON, YAML. **You MUST prefer TOML for configuration blocks**. - -The configuration block properties format is `property1 property2=value2 property3=value3"`, which will be parsed into a dictionary like: - -```python -{ - "property1": True, - "property2": "value2", - "property3": "value3", -} -``` - -The content of the block is parsed according to the primary format and interpreted according its properties. - -Configuration blocks are parsed by Donna and removed from rendered Markdown representations (see "Jinja2 rendering"); they remain in the source for editing and inspection on the file system. - -Fences without `donna` keyword are considered regular code blocks and have no special meaning for Donna. - -### Configuration Merging - -When a section contains multiple configuration blocks, Donna merges them in document order. - -- The merge is applied per section: the head section is merged independently, and each tail section has its own merged configuration. -- Config blocks are merged in the order they appear; later blocks override earlier keys. -- The merge is shallow: if a key maps to a nested object, a later block replaces the whole value (there is no deep merge). -- Config blocks in subsections (H3 and below) belong to their parent H2 tail section and are merged into that section's configuration. - -### Artifact Tags - -Artifacts can include semantic tags via a `tags` field in the section configuration. Tags are a list of strings and default to an empty list `[]` when omitted. - -Tags are used for deterministic artifact filtering and discovery (for example, via `donna -p artifacts list ... --predicate '"workflow" in section.tags'`). Tags are typically attached to the primary section and describe the artifact as a whole. - -The canonical list of standard tags is documented in `../intro.donna.md`. - -## Section Kinds, Their Formats and Behaviors - -### Header section - -Header section MUST contain a config block with a `kind` property. The `kind` MUST be a full Python import path pointing to the primary section kind instance. - -Example (`donna` keyword skipped for examples): - -```toml -kind = "donna.lib.specification" -``` - -Header section MUST also contain short human-readable description of the artifact outside of the config block. - -### Kind: Specification - -Specification artifacts describe various aspects of the project in a structured way. - -Currently there is no additional structure or semantics for this kind of artifact. - -### Kind: Workflow - -Workflow artifacts describe a sequence of operations that Donna and agents can perform to achieve a specific goal. - -Workflow is a Finite State Machine (FSM) where each tail section describes one operation in the workflow. - -Donna validates workflows by ensuring the start operation exists, reachable sections are valid operations, final operations have no outgoing transitions, and non-final operations have at least one outgoing transition. It does not currently report unreachable sections. - -Workflow start operation MUST be declared in the workflow head-section config via `start_operation_id` -and MUST reference an existing operation section. - -Example (`donna` keyword skipped for examples): - -```toml -kind = "donna.lib.workflow" -start_operation_id = "start_operation" -``` - -Each tail section MUST contain config block with `id` and `kind` properties that specifies the identifier and kind of the operation. - -Example (`donna` keyword skipped for examples): - -```toml -id = "operation_id" -kind = "donna.lib.request_action" -``` - -The title of the workflow section MUST be a short human-readable description of the operation in the form of an imperative verb phrase, for example, `Implement the feature X`, `Create a document Y`. - -#### Kind: Operation - -The title of the operation section MUST be a short human-readable description of the operation in the form of an imperative verb phrase, for example, `Run tests`, `Format the codebase`, `Implement function X in the module Y`.x - -##### `donna.lib.request_action` - -`donna.lib.request_action` operation indicates that Donna will request the agent to perform some action. - -The content of the tail section is the text instructions for the agent on what to do. - -Example of the instructions: - -``` -1. Run `some cli command` to do something. -2. If no errors encountered `{{ '{{ donna.lib.goto("next_operation") }}' }}` -3. If errors encountered `{{ '{{ donna.lib.goto("error_handling_operation") }}' }}` - -Here may be any additional instructions, requirements, notes, references, etc. -``` - -`donna.lib.goto` directive will be rendered in the direct instruction for agent of what to call after it completed the action. - -**The body of the operation MUST contain a neat strictly defined algorithm for the agent to follow.** - -##### `donna.lib.run_script` - -`donna.lib.run_script` operation executes a script from the operation body without agent/user interaction. - -The body of the operation MUST include exactly one fenced code block whose info string includes ` donna script`. -Any other text in the operation body is ignored. - -Script example: - -```bash donna script -#!/usr/bin/bash - -echo "Hello, World!" -``` - -Configuration options: - -```toml -id = "" -kind = "donna.lib.run_script" - -save_stdout_to = "" # optional -save_stderr_to = "" # optional - -goto_on_success = "" # required -goto_on_failure = "" # required -goto_on_code = { # optional - "1" = "" - "2" = "" -} - -timeout = 60 # optional, in seconds -``` - -Routing rules: - -- Exit code `0` routes to `goto_on_success`. -- Non-zero exit codes first check `goto_on_code`, then fall back to `goto_on_failure`. -- Timeouts are treated as exit code `124`. - -Scripts are executed with the current project root as working directory. - -When `save_stdout_to` and/or `save_stderr_to` are set, the operation stores captured output in the task context -under the specified variable names. - -##### `donna.lib.output` - -`donna.lib.output` operation emits its body as an output cell and then continues to the configured next step. - -The body of the operation is rendered as an output cell during execution. - -Configuration options: - -```toml -id = "" -kind = "donna.lib.output" -next_operation_id = "" # required -``` - -##### `donna.lib.finish` - -`donna.lib.finish` operation indicates that the workflow is finished. - -The body of the operation is rendered as an output cell before the workflow completes. - -Each possible path through the workflow MUST end with this operation. - -## Directives - -Donna provides multiple directives that MUST be used in the artifact source to enhance its behavior. - -Here they are: - -1. `{{ "{{ donna.lib.view() }}" }}` — references another artifact (supports `*` and `**` wildcard patterns). In `view`/`execute` modes it renders an exact CLI command to view the artifact; in `analysis` mode it renders a `$$donna ... $$` marker. -2. `{{ "{{ donna.lib.list() }}" }}` — references artifact listing by pattern (supports `*` and `**` wildcard patterns). In `view`/`execute` modes it renders an exact CLI command to list artifacts; in `analysis` mode, it renders a `$$donna ... $$` marker. -3. `{{ "{{ donna.lib.goto() }}" }}` — references the next workflow operation to execute. In `view`/`execute` modes it renders an exact CLI command to advance the workflow; in `analysis` mode, it renders a `$$donna goto ... $$` marker used for transition extraction. -4. `{{ "{{ donna.lib.task_variable() }}" }}` — in `view` mode renders a placeholder note about task-variable substitution, in `execute` mode renders the actual task-context value (or an explicit error marker if missing), and in `analysis` mode renders a `$$donna task_variable ... $$` marker. diff --git a/donna/fixtures/specs/usage/cli.donna.md b/donna/fixtures/specs/usage/cli.donna.md deleted file mode 100644 index 6504fe9d..00000000 --- a/donna/fixtures/specs/usage/cli.donna.md +++ /dev/null @@ -1,240 +0,0 @@ -# Donna Usage Instructions - -```toml donna -kind = "donna.lib.specification" -``` - -This document describes how agents MUST use Donna CLI to manage and perform their workflows. - -**Agents MUST follow the instructions and guidelines outlined in this document precisely.** - -## Overview - -`donna` is a CLI tool that helps manage the work of AI agents like OpenAI Codex. - -It is designed to invert control flow: instead of agents deciding what to do next, the Donna tells agents what to do. The tool achieves this by following predefined workflows that describe how to perform various tasks. One may look at workflows as hierarchical state machines (HSM) that guide agents through complex processes step by step. - -The core idea is that most high-level workflows are more algorithmic than it may seem at first glance. For example, it may be difficult to fix a particular problem in the codebase, but the overall process of polishing it is quite linear: - -1. Run tests, if they fail, fix the problems. -2. Format the code. -3. Run linters, if there are issues, fix them. -4. Go to the step 1 if you changed something in the process. -5. Finish. - -We may need coding agents on the each step of the process, but there no reason for agents to manage the whole loop by themselves — it takes longer time, spends tokens and confuses agents because they need to reason over long contexts. - -## Primary rules for agents - -- All work is always done in the context of a session. There is only one active session at a time. -- You MUST always work on one task assigned to you. -- You MUST keep all the information about the session in your memory. -- You always can ask the `donna` tool for the session details if you forget something. - -## CLI - -### Protocol - -Protocol selects the output formatting and behavior of Donna's CLI for different consumers (humans, LLMs, automation). -When an agent invokes Donna, it SHOULD use the `llm` protocol (pass an `-p llm` argument) unless the developer explicitly instructs otherwise. - -### Project root - -`-r ` sets the project root explicitly for any command (long form: `--root`). -If it is omitted, Donna discovers the project root by searching from the current working directory upwards for the `.donna` workspace directory. -Use this option when you run Donna from outside the project tree or when you want to target a specific project. - -### Protocol cells - -Donna communicates its progress and requests by outputting inrofmation organized in "cells". There are two kinds of cells output: - -- Log cells — `DONNA LOG: ` — one line messages describing what Donna is doing. Mostly it is an information about the next operation being executed. -- Info cells — multiline cells with structured header and freeform body. - -An example of an info cell: - -``` ---DONNA-CELL eZVkOwNPTHmadXpaHDUBNA BEGIN-- -kind=action_request -media_type=text/markdown -action_request_id=AR-65-bd - - - ---DONNA-CELL eZVkOwNPTHmadXpaHDUBNA END-- -``` - -Donna can omit log cell start and end markers if a command produces only a single cell. - -Donna renders cells differently, depending on the protocol used. - -### Commands - -There are three sets of commands: - -- `donna -p workspaces …` — manages workspaces. Most-likely it will be used once per your project to initialize it. -- `donna -p sessions …` — manages sessions. You will use these commands to start, push forward, and manage your work. -- `donna -p artifacts …` — manages artifact discovery, reading, and validation. - -Use: - -- `donna -p --help` to get the list of available subcommands. -- `donna -p --help` to get the help on specific subcommand. - -### Workspaces - -Run `donna -p workspaces init []` to initialize Donna workspace in the given directory. If `` is omitted, Donna will initialize workspace in the current working directory. - -It is a one time operation you need to perform once per project to create a place where Donna will store all its data. - -### Starting sessions - -The developer is responsible for starting a new session. - -You are allowed to start a new session in the next cases: - -1. There is no active session. -2. The developer explicitly instructed you to start a new session. - -You start session by calling `donna -p sessions start`. - -### Session flow - -After the session starts you MUST follow the next workflow to perform your work: - -1. List all possible workflows with command `donna -p artifacts list`. -2. Choose the most appropriate workflow for the task you are going to work on or ask the developer if you are not sure which workflow to choose. -3. Start chosen workflow by calling `donna -p sessions run `. -4. Donna will output descriptions of all operations it performs to complete the work. -5. Donna will output **action requests** that you MUST perform. You MUST follow these instructions precisely. -6. When you done processing an action request, call `donna -p sessions action-request-completed ` to report request completion. `` MUST contain the full identifier of the next operation, for example `@/.donna/session/execute_rfc.donna.md:review_changes`. -7. After you complete an action request, Donna will continue workflow execution and output what you need to do next. - -You MUST continue following Donna's instructions until the workflow is completed. - -### Session state - -- `donna -p sessions status` — get the status of the current session. -- `donna -p sessions details` — get detailed information about the current session, including list of active action requests. -- `donna -p sessions start` — start a new session. This command resets session state AND removes all session-level artifacts. -- Run `donna -p sessions reset` to reset the current session. This command resets session state BUT keeps all session-level artifacts. Use this command when you need to restart the worklow but keep all the artifacts you created during the session. - -### Starting work - -If the developer asked you to do something new: - -- Run `donna -p sessions status` to get the status of the current session. -- If there is no active session, start a new session by calling `donna -p sessions start`. -- If the session is active and there are unfinished work in it, you MUST ask the developer whether to continue the work in the current session or start a new one. -- If the session is active and there are no unfinished work in it, follow the instructions in the `Session flow` section to choose and start a new workflow. - -### Continuing work - -If the developer asked you to continue your work, you MUST call `donna -p sessions continue` to get your instructions on what to do next. - -If Donna tells you there is no work left, you MUST inform the developer that there is no work left in the current session. - -### Working with artifacts - -An artifact is a markdown document with extra metadata stored in the project workspace. - -Use the next commands to work with artifacts: - -- `donna -p artifacts list []` — list all artifacts corresponding to the given pattern. If `` is omitted, list all artifacts in the project workspace. Use this command when you need to find an artifact or see what artifacts are available. -- `donna -p artifacts view ` — get the meaningful (rendered) content of all matching artifacts. This command shows the rendered information about each artifact. Use this command when you need to read artifact content. -- `donna -p artifacts validate []` — validate all artifacts corresponding to the given pattern. If `` is omitted, validate all artifacts in the project workspace. - -These commands only operate on artifact files admitted by the configured -`/.donna/config.toml:file_filters`. - -Donna does not mutate artifacts stored in the project workspace. Developers and external tools are responsible for creating, updating, moving, copying, or deleting artifacts before Donna reads or validates them. - -Commands that accept an artifact pattern (`artifacts list`, `artifacts view`, `artifacts validate`) also accept `--predicate/-p ` to filter by artifact primary section. The expression is evaluated as `bool` with `section` global available (for example: `--predicate '"workflow" in section.tags'`). - -The format of `` is as follows: - -- full artifact identifier: `@/...` -- `/` separates path levels; wildcard characters do not match `/` -- `*` — matches zero or more characters inside one path level. Examples: - - `@/*.donna.md` — matches all artifacts directly under the project root. - - `@/**/test_*.donna.md` — matches artifacts whose filename starts with `test_` and ends with `.donna.md`. -- `?` — matches exactly one character inside one path level. Examples: - - `@/**/step?.donna.md` — matches `step1.donna.md` and `stepA.donna.md`, but not `step10.donna.md`. -- `[]` — character class that matches one character inside one path level. Examples: - - `@/**/step[0-9].donna.md` — matches artifacts with a single digit after `step`. - - `@/**/[ab]rchive.donna.md` — matches `archive.donna.md` and `brchive.donna.md`. -- `**` — recursive wildcard that matches zero or more path levels. Examples: - - `@/**/intro.donna.md` — matches all artifacts with filename `intro.donna.md` anywhere in the project workspace. - - `@/.donna/**` — matches all artifacts under `.donna`. - -CLI arguments MUST NOT use relative artifact paths like `./...` or `../../...`; use absolute `@/...` paths or rooted wildcard forms. - -### Journal integrations - -Donna creates internal `JournalRecord` values for important workflow events. -Donna does not expose a journal CLI command. - -To forward journal records to a third-party tool, configure the workspace -`/.donna/config.toml` file: - -```toml -[journal] -cmd = ["cli-tool", "--message", "{message}"] -``` - -`cmd` is a list of command arguments. If an argument starts with `{` and ends -with `}`, Donna treats the whole argument as a `JournalRecord` attribute name -and replaces it with that value. Donna validates placeholders when loading -config. - -Supported attributes: - -- `timestamp` — record creation time, formatted as ISO-8601. -- `actor_id` — actor that created the record; empty string when unknown. -- `message` — single-line journal message. -- `current_task_id` — current task id; empty string when no task is active. -- `current_work_unit_id` — current work unit id; empty string when no work unit is active. -- `current_operation_id` — current operation artifact section id; empty string when no operation is active. - -If `journal.cmd` is omitted, Donna treats it as `None` and performs no journal -writing. - -Donna still prints newly created internal journal records immediately using the -selected protocol formatter, so agents receive live feedback even when no -external journal command is configured. - -## IMPORTANT ON DONNA TOOL USAGE - -**Strictly follow described command syntax** - -**You MUST follow `donna` call conventions specified in**, by priority: - - 1. Direct instructions from the developer. - 2. `AGENTS.md` document. - 3. Project-relative specifications under `../../../specs/**` or `../**`. - 4. This document. - -**All Donna CLI commands MUST include an explicit protocol selection using `-p `.** Like `donna -p llm `. - -**All Donna CLI commands MUST be run from the project root or its subdirectories unless you pass `-r `.** - -If you are not running from the project root or its subdirectories, add `-r ` to point Donna to the correct project. - -**Pass text arguments to the tool in quotes with respect to escaping.** The tool MUST receive the exact text you want to pass as an argument. - -Use one of the next approaches to correctly escape text arguments: - -``` -# option 1 -donna -p <...> $'# Long text\n\nwith escape sequences...' - -# option 2 -donna -p <...> \ - "$(cat <<'EOF' -# Long text - -with escape sequences... -EOF -)" - -``` diff --git a/donna/fixtures/specs/usage/worlds.donna.md b/donna/fixtures/specs/usage/worlds.donna.md deleted file mode 100644 index 126a823d..00000000 --- a/donna/fixtures/specs/usage/worlds.donna.md +++ /dev/null @@ -1,42 +0,0 @@ -# Donna Artifact Filesystem Layout - -```toml donna -kind = "donna.lib.specification" -``` - -This document describes how Donna discovers and manages its project artifacts on the filesystem. -Including usage docs, work workflows, operations, current work state and additional code. - -## Overview - -In order to function properly and to perform in a full potential, Donna relies on a set of artifacts -that guide its behavior and provide necessary capabilities. - -These artifacts are represented as text files, primary in Markdown format, however other text-based -formats can be used as well, if explicitly requested by the developer or by the workflows. - -Donna discovers these artifacts directly in the project filesystem rooted at ``. -The filesystem layout is still defined by code, but Donna MAY limit which files are visible as artifacts via -`/.donna/config.toml:file_filters`. - -The primary artifact areas are: - -- Artifacts under `/specs`, owned by the project itself. -- Synced Donna usage specs and workflows under `/.agents/donna`. -- Session artifacts under `/.donna/session`. - -The project filesystem has a free layout, defined by the developers who own the project. - -## Artifact Access - -Donna has read access to artifacts stored in the project filesystem. It discovers, fetches, renders, and validates project artifacts that are allowed by the configured file filters, but it does not create, update, move, copy, or delete them. - -Developers and external tools are responsible for mutating project artifacts before Donna reads or validates them. - -Donna still writes its own session state under `/.donna/session`, but that internal state storage is separate from project-artifact mutation. - -## Intro Artifacts - -It is a recommended practice to provide short introductory artifacts such as `../intro.donna.md` and `../../../specs/intro.donna.md` at meaningful roots inside the project filesystem. - -So, the agent can load the relevant introductions in commands such as `donna -p llm artifacts view '**/intro.donna.md'`. diff --git a/donna/skills/__init__.py b/donna/skills/__init__.py new file mode 100644 index 00000000..79fdb740 --- /dev/null +++ b/donna/skills/__init__.py @@ -0,0 +1,4 @@ +from donna.skills.entities import SkillDocument +from donna.skills.fixtures import load_skill_text + +__all__ = ["SkillDocument", "load_skill_text"] diff --git a/donna/skills/entities.py b/donna/skills/entities.py new file mode 100644 index 00000000..beec4356 --- /dev/null +++ b/donna/skills/entities.py @@ -0,0 +1,8 @@ +import enum + + +class SkillDocument(enum.StrEnum): + usage = "usage" + configuration = "configuration" + initialization = "initialization" + artifacts = "artifacts" diff --git a/donna/skills/fixtures.py b/donna/skills/fixtures.py new file mode 100644 index 00000000..ae89ecc7 --- /dev/null +++ b/donna/skills/fixtures.py @@ -0,0 +1,14 @@ +import importlib.resources + +from donna.skills.entities import SkillDocument + +_FIXTURES: dict[SkillDocument, str] = { + SkillDocument.usage: "usage.md", + SkillDocument.configuration: "configuration.md", + SkillDocument.initialization: "initialization.md", + SkillDocument.artifacts: "artifacts.md", +} + + +def load_skill_text(document: SkillDocument = SkillDocument.usage) -> str: + return importlib.resources.files(__package__).joinpath("fixtures", _FIXTURES[document]).read_text(encoding="utf-8") diff --git a/donna/skills/fixtures/artifacts.md b/donna/skills/fixtures/artifacts.md new file mode 100644 index 00000000..9030f2b0 --- /dev/null +++ b/donna/skills/fixtures/artifacts.md @@ -0,0 +1,196 @@ +# `donna` Artifacts + +Donna artifacts are project files that Donna can discover, render, validate, and execute as workflow input. They are usually Markdown files with the `.donna.md` extension. + +Donna reads artifacts from the project filesystem. It does not mutate project artifacts through `artifacts` commands. Developers and agents edit files directly, then ask Donna to list, view, or validate them. + +## Artifact Locations + +The common artifact areas are: + +- `/specs`: project-owned specifications and workflows. +- `/.agents/donna`: synced built-in Donna specs and workflows. +- `/.donna/session`: session artifacts and active workflow state. + +Example: + +```text +specs/intro.donna.md +.agents/donna/work/polish.donna.md +.donna/session/current_task.donna.md +``` + +Donna sees only files allowed by `.donna/config.toml:file_filters`. + +## List Artifacts + +List all visible artifacts: + +```bash +donna -p llm artifacts list '**' +``` + +List introductions: + +```bash +donna -p llm artifacts list '**/intro.donna.md' +``` + +List workflow artifacts: + +```bash +donna -p llm artifacts list '**' --predicate '"workflow" in section.tags' +``` + +## View Artifacts + +View rendered artifact content when you need information from an artifact: + +```bash +donna -p llm artifacts view '@/specs/intro.donna.md' +``` + +View all matching introductions: + +```bash +donna -p llm artifacts view '**/intro.donna.md' +``` + +Agents should prefer rendered views for reading. Read source files directly only when editing the artifact or investigating rendering problems. + +## Validate Artifacts + +Validate one artifact: + +```bash +donna -p llm artifacts validate '@/specs/intro.donna.md' +``` + +Validate all visible artifacts: + +```bash +donna -p llm artifacts validate '**' +``` + +Run validation after creating or editing Donna artifacts. + +## Artifact Patterns + +Use `@/` for project-root paths: + +```bash +donna -p llm artifacts view '@/specs/core/top_level_architecture.donna.md' +``` + +Use recursive wildcards when the exact location is unknown: + +```bash +donna -p llm artifacts list '**/*.donna.md' +``` + +Pattern examples: + +- `@/*.donna.md`: Donna Markdown artifacts directly under the project root. +- `@/**/intro.donna.md`: any introduction artifact. +- `@/.agents/donna/**`: synced Donna artifacts. +- `@/.donna/session/**`: session artifacts. + +Do not pass relative filesystem paths such as `./specs/intro.donna.md`. Use `@/specs/intro.donna.md`. + +## Creating Artifacts + +Create artifacts as source files in an included location. A minimal specification artifact: + +````markdown +# Example Specification + +```toml donna +kind = "donna.lib.specification" +tags = ["specification"] +``` + +This artifact documents one stable project rule. +```` + +After creating it, validate: + +```bash +donna -p llm artifacts validate '@/specs/example.donna.md' +``` + +## Creating Workflows + +A workflow artifact defines a finite-state machine. The head section declares workflow metadata and the start operation. Each H2 section declares one operation. + +Minimal workflow: + +````markdown +# Example Workflow + +```toml donna +kind = "donna.lib.workflow" +tags = ["workflow"] +start_operation_id = "ask_agent" +``` + +This workflow asks the agent to do one thing and finish. + +## Do The Work + +```toml donna +id = "ask_agent" +kind = "donna.lib.request_action" +``` + +Perform the requested change. + +When done, continue with `{{ donna.lib.goto("finish") }}`. + +## Finish + +```toml donna +id = "finish" +kind = "donna.lib.finish" +``` + +The workflow is complete. +```` + +Validate the workflow before running it: + +```bash +donna -p llm artifacts validate '@/specs/work/example.donna.md' +``` + +Run it: + +```bash +donna -p llm sessions run '@/specs/work/example.donna.md' +``` + +## Managing Artifacts + +Use direct file edits to create, update, move, or delete artifact sources. Then use Donna to inspect the result. + +Recommended loop: + +1. Edit the artifact source file. +2. View the rendered artifact: + +```bash +donna -p llm artifacts view '@/specs/example.donna.md' +``` + +3. Validate the artifact: + +```bash +donna -p llm artifacts validate '@/specs/example.donna.md' +``` + +4. If it is a workflow, list it with the workflow predicate: + +```bash +donna -p llm artifacts list '**' --predicate '"workflow" in section.tags' +``` + +Keep artifact files concise. Put project-wide explanations in specifications and operational step-by-step instructions in workflows. diff --git a/donna/skills/fixtures/configuration.md b/donna/skills/fixtures/configuration.md new file mode 100644 index 00000000..954ce088 --- /dev/null +++ b/donna/skills/fixtures/configuration.md @@ -0,0 +1,166 @@ +# `donna` Configuration + +Donna workspace configuration lives at: + +```text +/.donna/config.toml +``` + +The file is created by `donna -p llm workspaces init`. Edit it when the project needs custom artifact sources, artifact visibility rules, cache behavior, or journal forwarding. + +## Minimal Configuration + +A default workspace can use the generated configuration without manual edits. The effective defaults are: + +```toml +[[sources]] +kind = "donna.lib.sources.markdown" +extension = ".donna.md" + +[[file_filters]] +mode = "include" +pattern = "@/.donna/session/**/*.donna.md" + +[[file_filters]] +mode = "include" +pattern = "@/.agents/**/*.donna.md" + +[[file_filters]] +mode = "ignore" +pattern = ".*/**" + +[[file_filters]] +mode = "include" +pattern = "**/*.donna.md" + +[[file_filters]] +mode = "ignore" +pattern = "**" + +[journal] + +cache_lifetime = 1.0 +``` + +## Sources + +`sources` tell Donna how to load artifacts with specific filename extensions. + +Default Markdown source: + +```toml +[[sources]] +kind = "donna.lib.sources.markdown" +extension = ".donna.md" +``` + +Fields: + +- `kind`: full Python path to a Donna source constructor. +- `extension`: filename suffix handled by that source. + +Add a source only when Donna has a source implementation for that artifact format. Keep `.donna.md` configured unless the project intentionally disables default Markdown artifacts. + +Example with an additional custom source: + +```toml +[[sources]] +kind = "donna.lib.sources.markdown" +extension = ".donna.md" + +[[sources]] +kind = "project.donna_sources.yaml" +extension = ".donna.yaml" +``` + +## File Filters + +`file_filters` control which project files Donna can see as artifacts. Filters are evaluated in order. The first matching rule decides whether a file is included, ignored, or required. + +Modes: + +- `include`: admit matching files when they exist. +- `ignore`: hide matching files. +- `required`: admit matching files and treat missing expected files as an error when relevant. + +Patterns are Donna artifact patterns rooted at the project. Use `@/` for explicit project-root paths and `**` for recursive matching. + +Example: + +```toml +[[file_filters]] +mode = "include" +pattern = "@/specs/**/*.donna.md" + +[[file_filters]] +mode = "ignore" +pattern = "@/specs/archive/**" + +[[file_filters]] +mode = "ignore" +pattern = "**" +``` + +Put narrow rules before broad rules. Keep a final `ignore "**"` rule when you want an allow-list. + +## Journal Forwarding + +`journal.cmd` forwards Donna journal records to an external command. Omit it or set it to `null` to disable forwarding. + +Example: + +```toml +[journal] +cmd = ["./bin/taskwarior.sh", "log", "+journal", "+donna", "{message}"] +``` + +The command is configured as a list of arguments. Donna does not run a shell for this command. + +Supported placeholders: + +- `{timestamp}`: ISO-8601 record timestamp. +- `{actor_id}`: actor that created the record. +- `{message}`: journal message. +- `{current_task_id}`: current task id, if any. +- `{current_work_unit_id}`: current work unit id, if any. +- `{current_operation_id}`: current operation artifact section id, if any. + +Invalid placeholder names make configuration loading fail. + +Example with explicit fields: + +```toml +[journal] +cmd = [ + "./bin/taskwarior.sh", + "log", + "+journal", + "+donna", + "actor:{actor_id}", + "operation:{current_operation_id}", + "{message}", +] +``` + +## Cache Lifetime + +`cache_lifetime` controls how long Donna may reuse cached workspace data, in seconds. + +Example: + +```toml +cache_lifetime = 0.25 +``` + +Use a smaller value when artifacts are edited rapidly by external tools. Use the default unless stale reads are observed. + +## Validation Workflow + +After editing `.donna/config.toml`, run: + +```bash +donna -p llm artifacts list '**' +donna -p llm artifacts validate '**' +``` + +If Donna cannot load the workspace, inspect the reported configuration error and fix the TOML or unsupported source path before continuing workflow work. diff --git a/donna/skills/fixtures/initialization.md b/donna/skills/fixtures/initialization.md new file mode 100644 index 00000000..dad1f8b6 --- /dev/null +++ b/donna/skills/fixtures/initialization.md @@ -0,0 +1,109 @@ +# `donna` Initialization + +Initialization creates the Donna workspace and optionally installs built-in Donna skills and specs into the project. + +Use this document when a project has no `.donna` directory, when built-in Donna fixtures are missing, or when synced fixture files need to be refreshed. + +## What Initialization Creates + +`donna -p llm workspaces init` creates: + +```text +/.donna/ +/.donna/config.toml +/.donna/session/ +/.agents/skills/ +/.agents/donna/ +``` + +The `.donna` directory stores configuration and session state. The `.agents/skills` and `.agents/donna` directories contain built-in agent-facing Donna skills, workflows, and specifications. + +## Initialize The Current Directory + +Run from the directory that should become the project root: + +```bash +donna -p llm workspaces init +``` + +This command fails if `.donna` already exists. + +## Initialize Another Directory + +Pass an explicit root directory: + +```bash +donna -p llm --root /path/to/project workspaces init +``` + +The target directory must already exist. Donna creates `.donna` inside it. + +## Install Only Part Of The Fixtures + +Skip built-in skills: + +```bash +donna -p llm workspaces init --no-skills +``` + +Skip synced Donna specs and workflows: + +```bash +donna -p llm workspaces init --no-specs +``` + +Use these options only when the project deliberately manages those files another way. + +## Refresh Existing Fixtures + +Use `update`, not `init`, for an existing workspace: + +```bash +donna -p llm workspaces update +``` + +Refresh only built-in skills: + +```bash +donna -p llm workspaces update --no-specs +``` + +Refresh only synced Donna specs and workflows: + +```bash +donna -p llm workspaces update --no-skills +``` + +`update` requires an existing `.donna` directory. + +## First Checks After Initialization + +Verify the workspace can load: + +```bash +donna -p llm sessions status +``` + +List available artifacts: + +```bash +donna -p llm artifacts list '**' +``` + +List available workflows: + +```bash +donna -p llm artifacts list '**' --predicate '"workflow" in section.tags' +``` + +Validate artifacts: + +```bash +donna -p llm artifacts validate '**' +``` + +## Agent Guidance + +Initialize a workspace only when the developer asks for it or when the task explicitly requires Donna and no workspace exists. + +Do not overwrite project-owned workflows or specifications by hand. Use `workspaces update` for built-in fixtures, and edit project-owned artifacts directly when the developer asks for project-specific behavior changes. diff --git a/donna/skills/fixtures/usage.md b/donna/skills/fixtures/usage.md new file mode 100644 index 00000000..81db9254 --- /dev/null +++ b/donna/skills/fixtures/usage.md @@ -0,0 +1,200 @@ +# `donna` Usage + +Donna is a CLI tool for orchestrating AI-agent work with project-local workflows, artifacts, and session state. + +Use this document as the first reference for command usage. For narrower topics, use: + +- `donna skill configuration` for `.donna/config.toml`. +- `donna skill initialization` for creating or refreshing Donna workspace files. +- `donna skill artifacts` for artifact layout, discovery, and authoring rules. +- `donna skill usage` for this command overview. + +## Project Root + +Donna works inside a project root. If `--root/-r` is omitted, commands that load a workspace discover the project root by searching upward from the current directory for `.donna`. + +Use `--root PATH` when running Donna from outside the project tree or when targeting a specific project: + +```bash +donna -p llm --root /path/to/project sessions status +``` + +`donna skill ...` does not load a workspace and can run from any directory. + +## Output Protocols + +Donna supports three protocol modes: + +- `llm`: structured cells optimized for agents. +- `human`: compact terminal output for people. +- `automation`: output intended for programs. + +Agents should use `-p llm` for normal Donna workflow commands: + +```bash +donna -p llm sessions status +``` + +The root option goes before the command: + +```bash +donna -p llm --root /path/to/project artifacts list '**' +``` + +## Skill Documents + +The `skill` command prints built-in agent documentation as plain Markdown. It does not require an initialized workspace. + +Examples: + +```bash +donna skill usage +donna skill configuration +donna skill initialization +donna skill artifacts +``` + +Use these documents when an agent needs stable instructions before a workspace exists or when synced artifacts are not available. + +## Workspace Commands + +Workspace commands create or refresh Donna-owned files. + +Initialize a workspace in the current directory: + +```bash +donna -p llm workspaces init +``` + +Initialize a workspace in an explicit existing directory: + +```bash +donna -p llm --root /path/to/project workspaces init +``` + +Refresh synced Donna skills and specifications in an existing workspace: + +```bash +donna -p llm workspaces update +``` + +Use `--no-skills` or `--no-specs` when only one fixture family should be changed: + +```bash +donna -p llm workspaces update --no-specs +donna -p llm workspaces update --no-skills +``` + +## Session Commands + +All workflow execution happens in the active session. Session state lives under `/.donna/session`. + +Start a new session: + +```bash +donna -p llm sessions start +``` + +Starting a session resets session state and removes session artifacts. Only start a new session when the developer asks for it or when no active session exists. + +Show concise status: + +```bash +donna -p llm sessions status +``` + +Show detailed session state and action requests: + +```bash +donna -p llm sessions details +``` + +Continue queued workflow execution: + +```bash +donna -p llm sessions continue +``` + +Run a workflow artifact: + +```bash +donna -p llm sessions run @/.agents/donna/work/polish.donna.md +``` + +Complete an action request by passing its id and the next operation id exactly as Donna instructed: + +```bash +donna -p llm sessions action-request-completed AR-12-x @/.donna/session/workflow.donna.md:next_step +``` + +## Artifact Commands + +Artifacts are project files admitted by Donna's configured sources and file filters. Agents use artifacts to discover workflows, read specifications, and validate Donna-readable files. + +List all visible artifacts: + +```bash +donna -p llm artifacts list '**' +``` + +List workflows: + +```bash +donna -p llm artifacts list '**' --predicate '"workflow" in section.tags' +``` + +View an artifact: + +```bash +donna -p llm artifacts view '@/specs/intro.donna.md' +``` + +Validate all visible artifacts: + +```bash +donna -p llm artifacts validate '**' +``` + +Artifact patterns use `@/` for project-root paths. Recursive `**` patterns are allowed: + +```bash +donna -p llm artifacts view '**/intro.donna.md' +``` + +## Normal Agent Flow + +1. Read project instructions and `donna skill usage`. +2. Check session state: + +```bash +donna -p llm sessions status +``` + +3. If there is no active work and a workflow is needed, list workflows: + +```bash +donna -p llm artifacts list '**' --predicate '"workflow" in section.tags' +``` + +4. Start the selected workflow: + +```bash +donna -p llm sessions run @/.agents/donna/work/polish.donna.md +``` + +5. Execute Donna action requests exactly. +6. Report completion with `sessions action-request-completed`. +7. Continue until Donna finishes the workflow. + +## Journal Forwarding + +Donna creates internal journal records for workflow events. To forward them to another tool, configure `[journal].cmd` in `/.donna/config.toml`. + +Example: + +```toml +[journal] +cmd = ["./bin/taskwarior.sh", "log", "+journal", "+donna", "{message}"] +``` + +Supported placeholders are `timestamp`, `actor_id`, `message`, `current_task_id`, `current_work_unit_id`, and `current_operation_id`. diff --git a/donna/workspaces/config.py b/donna/workspaces/config.py index e3e14547..84311184 100644 --- a/donna/workspaces/config.py +++ b/donna/workspaces/config.py @@ -168,6 +168,14 @@ def supported_extensions(self) -> set[str]: return extensions +class Workspace(BaseEntity): + model_config = pydantic.ConfigDict(arbitrary_types_allowed=True) + + root: pydantic.DirectoryPath + config_dir: pydantic.DirectoryPath + config: Config + + class GlobalConfig[V](): __slots__ = ("_value",) @@ -197,3 +205,14 @@ def __call__(self) -> V: config_dir = GlobalConfig[pathlib.Path]() config = GlobalConfig[Config]() protocol: GlobalConfig["Mode"] = GlobalConfig() + + +def install_workspace(workspace: Workspace) -> None: + if not project_dir.is_set(): + project_dir.set(pathlib.Path(workspace.root)) + + if not config_dir.is_set(): + config_dir.set(pathlib.Path(workspace.config_dir)) + + if not config.is_set(): + config.set(workspace.config) diff --git a/donna/workspaces/initialization.py b/donna/workspaces/initialization.py index ba58432a..26649431 100644 --- a/donna/workspaces/initialization.py +++ b/donna/workspaces/initialization.py @@ -50,17 +50,8 @@ def _sync_donna_specs(project_dir: pathlib.Path) -> None: @unwrap_to_error -def initialize_runtime( # noqa: CCR001 - root_dir: pathlib.Path | None = None, - protocol: Mode | None = None, -) -> Result[None, core_errors.ErrorsList]: - """Initialize the runtime environment for the application. - - This function MUST be called before any other operations. - """ - if protocol is not None: - config.protocol.set(protocol) - +def load_workspace(root_dir: pathlib.Path | None = None) -> Result[config.Workspace, core_errors.ErrorsList]: + """Load workspace configuration without mutating process-global state.""" if root_dir is None: project_dir = utils.discover_project_dir(config.DONNA_DIR_NAME).unwrap() else: @@ -68,17 +59,12 @@ def initialize_runtime( # noqa: CCR001 if not (project_dir / config.DONNA_DIR_NAME).is_dir(): return Err([core_errors.ProjectDirNotFound(donna_dir_name=config.DONNA_DIR_NAME)]) - config.project_dir.set(project_dir) - config_dir = project_dir / config.DONNA_DIR_NAME - config.config_dir.set(config_dir) - config_path = config_dir / config.DONNA_CONFIG_NAME if not config_path.exists(): - config.config.set(config.Config()) - return Ok(None) + return Ok(config.Workspace(root=project_dir, config_dir=config_dir, config=config.Config())) try: data = tomllib.loads(config_path.read_text(encoding="utf-8")) @@ -90,9 +76,25 @@ def initialize_runtime( # noqa: CCR001 except Exception as e: return Err([world_errors.ConfigValidationFailed(config_path=config_path, details=str(e))]) - config.config.set(loaded_config) + return Ok(config.Workspace(root=project_dir, config_dir=config_dir, config=loaded_config)) - return Ok(None) + +@unwrap_to_error +def initialize_runtime( + root_dir: pathlib.Path | None = None, + protocol: Mode | None = None, +) -> Result[config.Workspace, core_errors.ErrorsList]: + """Initialize the runtime environment for the application. + + This function MUST be called before any other operations. + """ + if protocol is not None: + config.protocol.set(protocol) + + workspace = load_workspace(root_dir=root_dir).unwrap() + config.install_workspace(workspace) + + return Ok(workspace) @unwrap_to_error @@ -100,7 +102,7 @@ def initialize_workspace( project_dir: pathlib.Path, install_skills: bool = True, install_specs: bool = True, -) -> Result[None, core_errors.ErrorsList]: +) -> Result[config.Workspace, core_errors.ErrorsList]: """Initialize the physical workspace for the project (`.donna` directory).""" project_dir = project_dir.resolve() workspace_dir = project_dir / config.DONNA_DIR_NAME @@ -108,15 +110,11 @@ def initialize_workspace( if workspace_dir.exists(): return Err([world_errors.WorkspaceAlreadyInitialized(project_dir=project_dir)]) - if not config.project_dir.is_set(): - config.project_dir.set(project_dir) - - config.config_dir.set(workspace_dir) - workspace_dir.mkdir(parents=True, exist_ok=True) default_config = config.Config() - config.config.set(default_config) + workspace = config.Workspace(root=project_dir, config_dir=workspace_dir, config=default_config) + config.install_workspace(workspace) config_path = workspace_dir / config.DONNA_CONFIG_NAME config_path.write_text( @@ -132,7 +130,7 @@ def initialize_workspace( if install_specs: _sync_donna_specs(project_dir) - return Ok(None) + return Ok(workspace) @unwrap_to_error