Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions changes/unreleased.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,16 @@
### Migration

- 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.
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration note has a grammatical issue that makes it harder to understand. Consider rephrasing to: “...delete world artifacts instead of using the removed Donna commands.”

Suggested change
- 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 your scripts and specs to use external tools or direct file edits to create, update, move, copy, or delete world artifacts instead of using the removed Donna commands.

Copilot uses AI. Check for mistakes.

### Changes

- `--tag` option is replaced with `--predicate` in all CLI commands that accept artifact patterns.
- 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.

### Breaking Changes

- `donna artifacts` no longer supports `update`, `copy`, `move`, or `remove`.
- Donna no longer mutates world artifacts through workspace APIs or world configuration.
10 changes: 4 additions & 6 deletions donna/artifacts/usage/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ There are four sets of commands:

- `donna -p <protocol> workspaces …` — manages workspaces. Most-likely it will be used once per your project to initialize it.
- `donna -p <protocol> sessions …` — manages sessions. You will use these commands to start, push forward, and manage your work.
- `donna -p <protocol> artifacts …` — manages artifacts. You will use these commands to read and update artifacts you are working with.
- `donna -p <protocol> artifacts …` — manages artifact discovery, reading, fetching, temporary files, and validation.
- `donna -p <protocol> journal …` — manages session actions journal. You will use these commands to log and inspect the history of actions performed during the session.

Use:
Expand Down Expand Up @@ -146,13 +146,11 @@ Use the next commands to work with artifacts:
- `donna -p <protocol> artifacts view <artifact-pattern>` — 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 <protocol> artifacts fetch <world>:<artifact>` — download the original source of the artifact content, outputs the file path to the artifact's copy, you can change. Use this command when you need to change the content of the artifact.
- `donna -p <protocol> artifacts tmp <slug>.<extension>` — create a temporary file for artifact-related work and output its path.
- `donna -p <protocol> artifacts update <world>:<artifact> <file-path|-> [--extension <extension>]` — upload content from a file path or from stdin (`-`) as the artifact. Donna gets an extension from three sources: `--extension`, `<file-path>`, and a stored artifact; if there are multiple extensions or no extensions at all, Donna returns an error.
- `donna -p <protocol> artifacts copy <artifact-id-from> <artifact-id-to>` — copy an artifact source to another artifact ID (can be in a different world). This overwrites the destination if it exists.
- `donna -p <protocol> artifacts move <artifact-id-from> <artifact-id-to>` — copy an artifact source to another artifact ID and remove the original. This overwrites the destination if it exists.
- `donna -p <protocol> artifacts remove <artifact-pattern>` — remove artifacts matching a pattern. Use this command when you need to delete artifacts.
- `donna -p <protocol> artifacts validate [<artifact-pattern>]` — validate all artifacts corresponding to the given pattern. If `<artifact-pattern>` is omitted, validate all artifacts in all worlds.

Commands that accept an artifact pattern (`artifacts list`, `artifacts view`, `artifacts remove`, `artifacts validate`) also accept `--predicate/-p <python-expression>` to filter by artifact primary section. The expression is evaluated as `bool` with `section` global available (for example: `--predicate '"workflow" in section.tags'`).
Donna does not mutate artifacts stored in worlds. Developers and external tools are responsible for creating, updating, moving, copying, or deleting world artifacts before Donna reads or validates them.

Commands that accept an artifact pattern (`artifacts list`, `artifacts view`, `artifacts validate`) also accept `--predicate/-p <python-expression>` 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 `<artifact-pattern>` is as follows:

Expand Down
9 changes: 5 additions & 4 deletions donna/artifacts/usage/worlds.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ Default worlds and there locations are:

All worlds have a free layout, defined by developers who own the particular world.

## Write Access
## Artifact Access

By default, worlds are read-only. Besides the next exceptions:
Donna has read access to artifacts stored in worlds. It discovers, fetches, renders, and validates world artifacts, but it does not create, update, move, copy, or delete them.

- `session` in the project world is read-write, Donna stores its current state of work here.
- `project` is read-write when the developer explicitly asks Donna to change it. For example, to add the result of performed work into docs.
Developers and external tools are responsible for mutating world artifacts before Donna reads or validates them.

Donna still writes its own session state and journal data in the `session` world, but that internal state storage is separate from world-artifact mutation.

## `<world>:intro` artifact

Expand Down
120 changes: 2 additions & 118 deletions donna/cli/commands/artifacts.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import builtins
import pathlib
import sys
from collections.abc import Iterable

import typer

from donna.cli import errors as cli_errors
from donna.cli.application import app
from donna.cli.types import (
ExtensionOption,
FullArtifactIdArgument,
FullArtifactIdPatternArgument,
InputPathArgument,
OutputPathOption,
PredicateOption,
)
Expand Down Expand Up @@ -47,31 +42,6 @@ def _log_artifact_operation(message: str) -> None:
machine_journal.add(message=message)


def _resolve_update_extension(
input_path: pathlib.Path | None,
extension: str | None,
) -> str | None:
normalized_extension = extension.lstrip(".").lower() if extension is not None else None
if input_path is None:
return normalized_extension

inferred_extension = input_path.suffix.lstrip(".").lower() or None
if (
normalized_extension is not None
and inferred_extension is not None
and normalized_extension != inferred_extension
):
raise typer.BadParameter(
(
f"Option extension '{normalized_extension}' does not match input file extension "
f"'{inferred_extension}'"
),
param_hint="--extension",
)

return normalized_extension if normalized_extension is not None else inferred_extension


def _log_operation_on_artifacts(
message: str,
pattern: FullArtifactIdPattern,
Expand Down Expand Up @@ -131,9 +101,7 @@ def fetch(id: FullArtifactIdArgument, output: OutputPathOption = None) -> Iterab
]


@artifacts_cli.command(
help="Create a temporary file for artifact-related work and print its path. Use it to create new artifacts"
)
@artifacts_cli.command(help="Create a temporary file for artifact-related work and print its path.")
@cells_cli
def tmp(
slug_with_extension: str = typer.Argument(..., help="Temporary file slug with extension (example: 'draft.md').")
Expand All @@ -151,90 +119,6 @@ def tmp(
]


@artifacts_cli.command(help="Create or replace an artifact from a file path or stdin.")
@cells_cli
def update(
id: FullArtifactIdArgument,
input: InputPathArgument,
extension: ExtensionOption = None,
) -> Iterable[Cell]:
input_path: pathlib.Path | None

if input == pathlib.Path("-"):
content_bytes = sys.stdin.buffer.read()
input_display = "stdin"
input_path = None
else:
content_bytes = input.read_bytes()
input_display = str(input)
input_path = input

resolved_extension = _resolve_update_extension(input_path, extension)

_log_artifact_operation(f"Update artifact `{id}` from '{input_display}'")

context().artifacts.update(
id,
content_bytes,
extension=resolved_extension,
).unwrap()
return [
operation_succeeded(
f"Artifact `{id}` updated from '{input_display}'",
artifact_id=str(id),
input_path=input_display,
)
]


@artifacts_cli.command(help="Copy an artifact to another artifact ID (possibly across worlds).")
@cells_cli
def copy(source_id: FullArtifactIdArgument, target_id: FullArtifactIdArgument) -> Iterable[Cell]:
_log_artifact_operation(f"Copy artifact from `{source_id}` to `{target_id}`")

context().artifacts.copy(source_id, target_id).unwrap()
return [
operation_succeeded(
f"Artifact `{source_id}` copied to `{target_id}`",
source_id=str(source_id),
target_id=str(target_id),
)
]


@artifacts_cli.command(help="Move an artifact to another artifact ID (possibly across worlds).")
@cells_cli
def move(source_id: FullArtifactIdArgument, target_id: FullArtifactIdArgument) -> Iterable[Cell]:
_log_artifact_operation(f"Move artifact from `{source_id}` to `{target_id}`")

context().artifacts.move(source_id, target_id).unwrap()
return [
operation_succeeded(
f"Artifact `{source_id}` moved to `{target_id}`",
source_id=str(source_id),
target_id=str(target_id),
)
]


@artifacts_cli.command(help="Remove artifacts matching a pattern.")
@cells_cli
def remove(
pattern: FullArtifactIdPatternArgument,
predicate: PredicateOption = None,
) -> Iterable[Cell]:
_log_operation_on_artifacts("Remove artifacts", pattern, predicate)

artifacts = context().artifacts.list(pattern, RENDER_CONTEXT_VIEW, predicate=predicate).unwrap()

cells: builtins.list[Cell] = []
for artifact in artifacts:
context().artifacts.remove(artifact.id).unwrap()
cells.append(operation_succeeded(f"Artifact `{artifact.id}` removed", artifact_id=str(artifact.id)))

return cells


@artifacts_cli.command(help="Validate artifacts matching a pattern (defaults to all artifacts) and return any errors.")
@cells_cli
def validate(
Expand All @@ -261,5 +145,5 @@ def validate(
app.add_typer(
artifacts_cli,
name="artifacts",
help="Inspect, fetch, update, and validate stored artifacts across all Donna worlds.",
help="Inspect, fetch, and validate stored artifacts across all Donna worlds.",
)
27 changes: 0 additions & 27 deletions donna/cli/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pathlib
import re
from typing import Annotated

import typer
Expand Down Expand Up @@ -71,19 +70,6 @@ def _parse_protocol_mode(value: str) -> Mode:
raise typer.BadParameter(f"Unsupported protocol mode '{value}'. Expected one of: {allowed}.") from exc


def _parse_extension(value: str) -> str:
normalized = value.strip().lower().lstrip(".")
if not normalized:
raise typer.BadParameter("Extension must not be empty.")

if re.fullmatch(r"[a-z0-9][a-z0-9_-]*", normalized) is None:
raise typer.BadParameter(
"Invalid extension format. Use letters, digits, underscore, and dash (for example: md, yaml)."
)

return normalized


def _parse_input_path(value: str) -> pathlib.Path:
normalized = value.strip()
if normalized == "-":
Expand Down Expand Up @@ -183,19 +169,6 @@ def _parse_input_path(value: str) -> pathlib.Path:
]


ExtensionOption = Annotated[
str | None,
typer.Option(
"--extension",
parser=_parse_extension,
help=(
"Optional artifact source extension to use for update "
"(for example: md, yaml). Accepts values with or without leading dot."
),
),
]


InputPathArgument = Annotated[
pathlib.Path,
typer.Argument(
Expand Down
42 changes: 0 additions & 42 deletions donna/context/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,48 +115,6 @@ def resolve_section(
artifact = self.load(target_id.full_artifact_id, render_context).unwrap()
return Ok(artifact.get_section(target_id.local_id).unwrap())

@unwrap_to_error
def update(
self,
full_id: FullArtifactId,
content_bytes: bytes,
extension: str | None = None,
) -> Result[None, ErrorsList]:
from donna.workspaces import artifacts as workspace_artifacts

workspace_artifacts.update_artifact(
full_id=full_id,
content_bytes=content_bytes,
extension=extension,
).unwrap()
self.invalidate(full_id)
return Ok(None)

@unwrap_to_error
def copy(self, source_id: FullArtifactId, target_id: FullArtifactId) -> Result[None, ErrorsList]:
from donna.workspaces import artifacts as workspace_artifacts

workspace_artifacts.copy_artifact(source_id, target_id).unwrap()
self.invalidate(target_id)
return Ok(None)

@unwrap_to_error
def move(self, source_id: FullArtifactId, target_id: FullArtifactId) -> Result[None, ErrorsList]:
from donna.workspaces import artifacts as workspace_artifacts

workspace_artifacts.move_artifact(source_id, target_id).unwrap()
self.invalidate(source_id)
self.invalidate(target_id)
return Ok(None)

@unwrap_to_error
def remove(self, full_id: FullArtifactId) -> Result[None, ErrorsList]:
from donna.workspaces import artifacts as workspace_artifacts

workspace_artifacts.remove_artifact(full_id).unwrap()
self.invalidate(full_id)
return Ok(None)

@unwrap_to_error
def file_extension(self, full_id: FullArtifactId) -> Result[str, ErrorsList]:
from donna.workspaces import artifacts as workspace_artifacts
Expand Down
Loading
Loading