donna 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- donna/__init__.py +1 -0
- donna/artifacts/__init__.py +0 -0
- donna/artifacts/usage/__init__.py +0 -0
- donna/artifacts/usage/artifacts.md +224 -0
- donna/artifacts/usage/cli.md +117 -0
- donna/artifacts/usage/worlds.md +36 -0
- donna/artifacts/work/__init__.py +0 -0
- donna/artifacts/work/do_it.md +142 -0
- donna/artifacts/work/do_it_fast.md +98 -0
- donna/artifacts/work/planning.md +245 -0
- donna/cli/__init__.py +0 -0
- donna/cli/__main__.py +6 -0
- donna/cli/application.py +17 -0
- donna/cli/commands/__init__.py +0 -0
- donna/cli/commands/artifacts.py +110 -0
- donna/cli/commands/projects.py +49 -0
- donna/cli/commands/sessions.py +77 -0
- donna/cli/types.py +138 -0
- donna/cli/utils.py +53 -0
- donna/core/__init__.py +0 -0
- donna/core/entities.py +27 -0
- donna/core/errors.py +126 -0
- donna/core/result.py +99 -0
- donna/core/utils.py +37 -0
- donna/domain/__init__.py +0 -0
- donna/domain/errors.py +47 -0
- donna/domain/ids.py +497 -0
- donna/lib/__init__.py +21 -0
- donna/lib/sources.py +5 -0
- donna/lib/worlds.py +7 -0
- donna/machine/__init__.py +0 -0
- donna/machine/action_requests.py +50 -0
- donna/machine/artifacts.py +200 -0
- donna/machine/changes.py +91 -0
- donna/machine/errors.py +122 -0
- donna/machine/operations.py +31 -0
- donna/machine/primitives.py +77 -0
- donna/machine/sessions.py +215 -0
- donna/machine/state.py +244 -0
- donna/machine/tasks.py +89 -0
- donna/machine/templates.py +83 -0
- donna/primitives/__init__.py +1 -0
- donna/primitives/artifacts/__init__.py +0 -0
- donna/primitives/artifacts/specification.py +20 -0
- donna/primitives/artifacts/workflow.py +195 -0
- donna/primitives/directives/__init__.py +0 -0
- donna/primitives/directives/goto.py +44 -0
- donna/primitives/directives/task_variable.py +73 -0
- donna/primitives/directives/view.py +45 -0
- donna/primitives/operations/__init__.py +0 -0
- donna/primitives/operations/finish_workflow.py +37 -0
- donna/primitives/operations/request_action.py +89 -0
- donna/primitives/operations/run_script.py +250 -0
- donna/protocol/__init__.py +0 -0
- donna/protocol/cell_shortcuts.py +9 -0
- donna/protocol/cells.py +44 -0
- donna/protocol/errors.py +17 -0
- donna/protocol/formatters/__init__.py +0 -0
- donna/protocol/formatters/automation.py +25 -0
- donna/protocol/formatters/base.py +15 -0
- donna/protocol/formatters/human.py +36 -0
- donna/protocol/formatters/llm.py +39 -0
- donna/protocol/modes.py +40 -0
- donna/protocol/nodes.py +59 -0
- donna/world/__init__.py +0 -0
- donna/world/artifacts.py +122 -0
- donna/world/artifacts_discovery.py +90 -0
- donna/world/config.py +198 -0
- donna/world/errors.py +232 -0
- donna/world/initialization.py +42 -0
- donna/world/markdown.py +267 -0
- donna/world/sources/__init__.py +1 -0
- donna/world/sources/base.py +62 -0
- donna/world/sources/markdown.py +260 -0
- donna/world/templates.py +181 -0
- donna/world/tmp.py +33 -0
- donna/world/worlds/__init__.py +0 -0
- donna/world/worlds/base.py +68 -0
- donna/world/worlds/filesystem.py +189 -0
- donna/world/worlds/python.py +196 -0
- donna-0.2.0.dist-info/METADATA +44 -0
- donna-0.2.0.dist-info/RECORD +85 -0
- donna-0.2.0.dist-info/WHEEL +4 -0
- donna-0.2.0.dist-info/entry_points.txt +3 -0
- donna-0.2.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, ClassVar, Iterable, cast
|
|
2
|
+
|
|
3
|
+
from donna.core import errors as core_errors
|
|
4
|
+
from donna.core.errors import ErrorsList
|
|
5
|
+
from donna.core.result import Err, Ok, Result, unwrap_to_error
|
|
6
|
+
from donna.domain.ids import ArtifactSectionId, FullArtifactId
|
|
7
|
+
from donna.machine.artifacts import Artifact, ArtifactSection, ArtifactSectionConfig, ArtifactSectionMeta
|
|
8
|
+
from donna.machine.errors import ArtifactValidationError
|
|
9
|
+
from donna.machine.operations import FsmMode, OperationMeta
|
|
10
|
+
from donna.machine.primitives import Primitive
|
|
11
|
+
from donna.world import markdown
|
|
12
|
+
from donna.world.sources.markdown import MarkdownSectionMixin
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from donna.machine.changes import Change
|
|
16
|
+
from donna.machine.tasks import Task, WorkUnit
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InternalError(core_errors.InternalError):
|
|
20
|
+
"""Base class for internal errors in donna.primitives.artifacts.workflow."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WorkflowSectionMissingMetadata(InternalError):
|
|
24
|
+
message: str = "Workflow section is missing workflow metadata."
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WrongStartOperation(ArtifactValidationError):
|
|
28
|
+
code: str = "donna.workflows.wrong_start_operation"
|
|
29
|
+
message: str = "Can not find the start operation `{error.start_operation_id}` in the workflow."
|
|
30
|
+
ways_to_fix: list[str] = ["Ensure that the artifact contains the section with the specified start operation ID."]
|
|
31
|
+
start_operation_id: ArtifactSectionId
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SectionIsNotAnOperation(ArtifactValidationError):
|
|
35
|
+
code: str = "donna.workflows.section_is_not_an_operation"
|
|
36
|
+
message: str = "Section `{error.workflow_section_id}` is not an operation and cannot be part of the workflow."
|
|
37
|
+
ways_to_fix: list[str] = ["Ensure that the section has a kind of one of operation primitives."]
|
|
38
|
+
workflow_section_id: ArtifactSectionId
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class WorkflowSectionNotWorkflow(ArtifactValidationError):
|
|
42
|
+
code: str = "donna.workflows.section_not_workflow"
|
|
43
|
+
message: str = "Section `{error.section_id}` is not a workflow section."
|
|
44
|
+
ways_to_fix: list[str] = ["Ensure the section uses the workflow primitive and includes workflow metadata."]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FinalOperationHasTransitions(ArtifactValidationError):
|
|
48
|
+
code: str = "donna.workflows.final_operation_has_transitions"
|
|
49
|
+
message: str = "Final operation `{error.workflow_section_id}` should not have outgoing transitions."
|
|
50
|
+
ways_to_fix: list[str] = [
|
|
51
|
+
"Approach A: Remove all outgoing transitions from this operation.",
|
|
52
|
+
"Approach B: Change the `fsm_mode` of this operation from `final` to `normal`",
|
|
53
|
+
"Approach C: Remove the `fsm_mode` setting from this operation, as `normal` is the default.",
|
|
54
|
+
]
|
|
55
|
+
workflow_section_id: ArtifactSectionId
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class NoOutgoingTransitions(ArtifactValidationError):
|
|
59
|
+
code: str = "donna.workflows.no_outgoing_transitions"
|
|
60
|
+
message: str = (
|
|
61
|
+
"Operation `{error.workflow_section_id}` must have at least one outgoing transition or be marked as final."
|
|
62
|
+
)
|
|
63
|
+
ways_to_fix: list[str] = [
|
|
64
|
+
"Approach A: Add at least one outgoing transition from this operation.",
|
|
65
|
+
"Approach B: Change the kind of this operation to `donna.lib.finish`",
|
|
66
|
+
"Approach C: Mark this operation as final by setting its `fsm_mode` to `final`.",
|
|
67
|
+
]
|
|
68
|
+
workflow_section_id: ArtifactSectionId
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def find_workflow_sections(start_operation_id: ArtifactSectionId, artifact: Artifact) -> set[ArtifactSectionId]:
|
|
72
|
+
workflow_sections = set()
|
|
73
|
+
to_visit = [start_operation_id]
|
|
74
|
+
|
|
75
|
+
while to_visit:
|
|
76
|
+
current = to_visit.pop()
|
|
77
|
+
|
|
78
|
+
if current in workflow_sections:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
workflow_sections.add(current)
|
|
82
|
+
|
|
83
|
+
section_result = artifact.get_section(current)
|
|
84
|
+
if section_result.is_err():
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
section = section_result.unwrap()
|
|
88
|
+
|
|
89
|
+
if not isinstance(section.meta, OperationMeta):
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
to_visit.extend(section.meta.allowed_transtions)
|
|
93
|
+
|
|
94
|
+
return workflow_sections
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class WorkflowConfig(ArtifactSectionConfig):
|
|
98
|
+
start_operation_id: ArtifactSectionId
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class WorkflowMeta(ArtifactSectionMeta):
|
|
102
|
+
start_operation_id: ArtifactSectionId
|
|
103
|
+
|
|
104
|
+
def cells_meta(self) -> dict[str, object]:
|
|
105
|
+
return {"start_operation_id": str(self.start_operation_id)}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Workflow(MarkdownSectionMixin, Primitive):
|
|
109
|
+
config_class: ClassVar[type[WorkflowConfig]] = WorkflowConfig
|
|
110
|
+
|
|
111
|
+
def markdown_construct_meta(
|
|
112
|
+
self,
|
|
113
|
+
artifact_id: FullArtifactId,
|
|
114
|
+
source: markdown.SectionSource,
|
|
115
|
+
section_config: ArtifactSectionConfig,
|
|
116
|
+
description: str,
|
|
117
|
+
primary: bool = False,
|
|
118
|
+
) -> Result[ArtifactSectionMeta, ErrorsList]:
|
|
119
|
+
workflow_config = cast(WorkflowConfig, section_config)
|
|
120
|
+
return Ok(WorkflowMeta(start_operation_id=workflow_config.start_operation_id))
|
|
121
|
+
|
|
122
|
+
def execute_section(self, task: "Task", unit: "WorkUnit", section: ArtifactSection) -> Iterable["Change"]:
|
|
123
|
+
from donna.machine.changes import ChangeAddWorkUnit
|
|
124
|
+
|
|
125
|
+
if not isinstance(section.meta, WorkflowMeta):
|
|
126
|
+
raise WorkflowSectionMissingMetadata()
|
|
127
|
+
|
|
128
|
+
full_id = section.artifact_id.to_full_local(section.meta.start_operation_id)
|
|
129
|
+
|
|
130
|
+
yield ChangeAddWorkUnit(task_id=task.id, operation_id=full_id)
|
|
131
|
+
|
|
132
|
+
@unwrap_to_error
|
|
133
|
+
def validate_section( # noqa: CCR001, CFQ001
|
|
134
|
+
self, artifact: Artifact, section_id: ArtifactSectionId
|
|
135
|
+
) -> Result[None, ErrorsList]:
|
|
136
|
+
section = artifact.get_section(section_id).unwrap()
|
|
137
|
+
|
|
138
|
+
if not isinstance(section.meta, WorkflowMeta):
|
|
139
|
+
return Err([WorkflowSectionNotWorkflow(artifact_id=artifact.id, section_id=section_id)])
|
|
140
|
+
|
|
141
|
+
start_operation_id = section.meta.start_operation_id
|
|
142
|
+
|
|
143
|
+
errors: ErrorsList = []
|
|
144
|
+
|
|
145
|
+
start_operation_result = artifact.get_section(start_operation_id)
|
|
146
|
+
if start_operation_result.is_err():
|
|
147
|
+
errors.extend(start_operation_result.unwrap_err())
|
|
148
|
+
errors.append(
|
|
149
|
+
WrongStartOperation(
|
|
150
|
+
artifact_id=artifact.id, section_id=section_id, start_operation_id=start_operation_id
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
workflow_sections = find_workflow_sections(start_operation_id, artifact)
|
|
155
|
+
|
|
156
|
+
for workflow_section_id in workflow_sections:
|
|
157
|
+
workflow_section_result = artifact.get_section(workflow_section_id)
|
|
158
|
+
if workflow_section_result.is_err():
|
|
159
|
+
errors.extend(workflow_section_result.unwrap_err())
|
|
160
|
+
continue
|
|
161
|
+
workflow_section = workflow_section_result.unwrap()
|
|
162
|
+
|
|
163
|
+
if isinstance(workflow_section.meta, WorkflowMeta):
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
if not isinstance(workflow_section.meta, OperationMeta):
|
|
167
|
+
errors.append(
|
|
168
|
+
SectionIsNotAnOperation(
|
|
169
|
+
artifact_id=artifact.id, section_id=section.id, workflow_section_id=workflow_section.id
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
if workflow_section.meta.fsm_mode == FsmMode.final and workflow_section.meta.allowed_transtions:
|
|
175
|
+
errors.append(
|
|
176
|
+
FinalOperationHasTransitions(
|
|
177
|
+
artifact_id=artifact.id, section_id=section_id, workflow_section_id=workflow_section.id
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
if workflow_section.meta.fsm_mode == FsmMode.final:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
if not workflow_section.meta.allowed_transtions:
|
|
186
|
+
errors.append(
|
|
187
|
+
NoOutgoingTransitions(
|
|
188
|
+
artifact_id=artifact.id, section_id=section_id, workflow_section_id=workflow_section.id
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if errors:
|
|
193
|
+
return Err(errors)
|
|
194
|
+
|
|
195
|
+
return Ok(None)
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from jinja2.runtime import Context
|
|
4
|
+
|
|
5
|
+
from donna.core import errors as core_errors
|
|
6
|
+
from donna.core.errors import ErrorsList
|
|
7
|
+
from donna.core.result import Err, Ok, Result
|
|
8
|
+
from donna.domain.ids import FullArtifactSectionId
|
|
9
|
+
from donna.machine.templates import Directive, PreparedDirectiveResult
|
|
10
|
+
from donna.protocol.modes import mode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EnvironmentError(core_errors.EnvironmentError):
|
|
14
|
+
cell_kind: str = "directive_error"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GoToInvalidArguments(EnvironmentError):
|
|
18
|
+
code: str = "donna.directives.goto.invalid_arguments"
|
|
19
|
+
message: str = "GoTo directive requires exactly one argument: next_operation_id (got {error.provided_count})."
|
|
20
|
+
ways_to_fix: list[str] = ["Provide exactly one argument: next_operation_id."]
|
|
21
|
+
provided_count: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GoTo(Directive):
|
|
25
|
+
def _prepare_arguments(
|
|
26
|
+
self,
|
|
27
|
+
context: Context,
|
|
28
|
+
*argv: Any,
|
|
29
|
+
) -> PreparedDirectiveResult:
|
|
30
|
+
if argv is None or len(argv) != 1:
|
|
31
|
+
return Err([GoToInvalidArguments(provided_count=0 if argv is None else len(argv))])
|
|
32
|
+
|
|
33
|
+
artifact_id = context["artifact_id"]
|
|
34
|
+
|
|
35
|
+
next_operation_id = artifact_id.to_full_local(argv[0])
|
|
36
|
+
|
|
37
|
+
return Ok((next_operation_id,))
|
|
38
|
+
|
|
39
|
+
def render_view(self, context: Context, next_operation_id: FullArtifactSectionId) -> Result[Any, ErrorsList]:
|
|
40
|
+
protocol = mode().value
|
|
41
|
+
return Ok(f"donna -p {protocol} sessions action-request-completed <action-request-id> '{next_operation_id}'")
|
|
42
|
+
|
|
43
|
+
def render_analyze(self, context: Context, next_operation_id: FullArtifactSectionId) -> Result[Any, ErrorsList]:
|
|
44
|
+
return Ok(f"$$donna {self.analyze_id} {next_operation_id.local_id} donna$$")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
|
|
3
|
+
from jinja2.runtime import Context
|
|
4
|
+
|
|
5
|
+
from donna.core import errors as core_errors
|
|
6
|
+
from donna.core.errors import ErrorsList
|
|
7
|
+
from donna.core.result import Err, Ok, Result
|
|
8
|
+
from donna.machine.templates import Directive, PreparedDirectiveResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EnvironmentError(core_errors.EnvironmentError):
|
|
12
|
+
cell_kind: str = "directive_error"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TaskVariableInvalidArguments(EnvironmentError):
|
|
16
|
+
code: str = "donna.directives.task_variable.invalid_arguments"
|
|
17
|
+
message: str = "TaskVariable directive requires exactly one argument: variable_name (got {error.provided_count})."
|
|
18
|
+
ways_to_fix: list[str] = ["Provide exactly one argument: variable_name."]
|
|
19
|
+
provided_count: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TaskVariableTaskContextMissing(EnvironmentError):
|
|
23
|
+
code: str = "donna.directives.task_variable.task_context_missing"
|
|
24
|
+
message: str = "TaskVariable directive requires task context, but none is available."
|
|
25
|
+
ways_to_fix: list[str] = ["Ensure the directive is rendered with a task context present."]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TaskVariable(Directive):
|
|
29
|
+
def _prepare_arguments(
|
|
30
|
+
self,
|
|
31
|
+
context: Context,
|
|
32
|
+
*argv: Any,
|
|
33
|
+
) -> PreparedDirectiveResult:
|
|
34
|
+
if argv is None or len(argv) != 1:
|
|
35
|
+
return Err([TaskVariableInvalidArguments(provided_count=0 if argv is None else len(argv))])
|
|
36
|
+
|
|
37
|
+
variable_name = str(argv[0])
|
|
38
|
+
|
|
39
|
+
return Ok((variable_name,))
|
|
40
|
+
|
|
41
|
+
def render_view(self, context: Context, variable_name: str) -> Result[Any, ErrorsList]:
|
|
42
|
+
return Ok(
|
|
43
|
+
"$$donna at the time of execution of this section here will placed a value "
|
|
44
|
+
f"of the task variable '{variable_name}' donna$$"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def render_execute(self, context: Context, variable_name: str) -> Result[Any, ErrorsList]:
|
|
48
|
+
task_context = self._resolve_task_context(context)
|
|
49
|
+
if task_context is None:
|
|
50
|
+
return Err([TaskVariableTaskContextMissing()])
|
|
51
|
+
|
|
52
|
+
if variable_name not in task_context:
|
|
53
|
+
# Since we render the whole artifact, instead of a particular executed section
|
|
54
|
+
# some variables may be missing
|
|
55
|
+
# TODO: we may want to timprove this behavior later to avoid possible confusion
|
|
56
|
+
return Ok(
|
|
57
|
+
f"$$donna {self.analyze_id} variable '{variable_name}' does not found. "
|
|
58
|
+
"If you are an LLM agent and see that message AS AN INSTRUCTION TO EXECUTE, "
|
|
59
|
+
"stop your work and notify developer about the problems in workflow donna$$"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return Ok(task_context[variable_name])
|
|
63
|
+
|
|
64
|
+
def _resolve_task_context(self, context: Context) -> dict[str, Any] | None:
|
|
65
|
+
task = context.get("current_task")
|
|
66
|
+
if task is not None and hasattr(task, "context"):
|
|
67
|
+
return cast(dict[str, Any], task.context)
|
|
68
|
+
|
|
69
|
+
task_context = context.get("task_context")
|
|
70
|
+
if isinstance(task_context, dict):
|
|
71
|
+
return cast(dict[str, Any], task_context)
|
|
72
|
+
|
|
73
|
+
return None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from jinja2.runtime import Context
|
|
4
|
+
|
|
5
|
+
from donna.core import errors as core_errors
|
|
6
|
+
from donna.core.errors import ErrorsList
|
|
7
|
+
from donna.core.result import Err, Ok, Result
|
|
8
|
+
from donna.domain.ids import FullArtifactId
|
|
9
|
+
from donna.machine.templates import Directive, PreparedDirectiveResult
|
|
10
|
+
from donna.protocol.modes import mode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EnvironmentError(core_errors.EnvironmentError):
|
|
14
|
+
cell_kind: str = "directive_error"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ViewInvalidArguments(EnvironmentError):
|
|
18
|
+
code: str = "donna.directives.view.invalid_arguments"
|
|
19
|
+
message: str = "View directive requires exactly one argument: specification_id (got {error.provided_count})."
|
|
20
|
+
ways_to_fix: list[str] = ["Provide exactly one argument: specification_id."]
|
|
21
|
+
provided_count: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class View(Directive):
|
|
25
|
+
def _prepare_arguments(
|
|
26
|
+
self,
|
|
27
|
+
context: Context,
|
|
28
|
+
*argv: Any,
|
|
29
|
+
) -> PreparedDirectiveResult:
|
|
30
|
+
if argv is None or len(argv) != 1:
|
|
31
|
+
return Err([ViewInvalidArguments(provided_count=0 if argv is None else len(argv))])
|
|
32
|
+
|
|
33
|
+
artifact_id_result = FullArtifactId.parse(str(argv[0]))
|
|
34
|
+
errors = artifact_id_result.err()
|
|
35
|
+
if errors is not None:
|
|
36
|
+
return Err(errors)
|
|
37
|
+
|
|
38
|
+
artifact_id = artifact_id_result.ok()
|
|
39
|
+
assert artifact_id is not None
|
|
40
|
+
|
|
41
|
+
return Ok((artifact_id,))
|
|
42
|
+
|
|
43
|
+
def render_view(self, context: Context, specification_id: FullArtifactId) -> Result[Any, ErrorsList]:
|
|
44
|
+
protocol = mode().value
|
|
45
|
+
return Ok(f"donna -p {protocol} artifacts view '{specification_id}'")
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, ClassVar, Iterator, Literal, cast
|
|
2
|
+
|
|
3
|
+
from donna.core.errors import ErrorsList
|
|
4
|
+
from donna.core.result import Ok, Result
|
|
5
|
+
from donna.domain.ids import FullArtifactId
|
|
6
|
+
from donna.machine.artifacts import ArtifactSection, ArtifactSectionConfig, ArtifactSectionMeta
|
|
7
|
+
from donna.machine.operations import FsmMode, OperationConfig, OperationKind, OperationMeta
|
|
8
|
+
from donna.world import markdown
|
|
9
|
+
from donna.world.sources.markdown import MarkdownSectionMixin
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from donna.machine.changes import Change
|
|
13
|
+
from donna.machine.tasks import Task, WorkUnit
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FinishWorkflowConfig(OperationConfig):
|
|
17
|
+
fsm_mode: Literal[FsmMode.final] = FsmMode.final
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FinishWorkflow(MarkdownSectionMixin, OperationKind):
|
|
21
|
+
def execute_section(self, task: "Task", unit: "WorkUnit", operation: ArtifactSection) -> Iterator["Change"]:
|
|
22
|
+
from donna.machine.changes import ChangeFinishTask
|
|
23
|
+
|
|
24
|
+
yield ChangeFinishTask(task_id=task.id)
|
|
25
|
+
|
|
26
|
+
config_class: ClassVar[type[FinishWorkflowConfig]] = FinishWorkflowConfig
|
|
27
|
+
|
|
28
|
+
def markdown_construct_meta(
|
|
29
|
+
self,
|
|
30
|
+
artifact_id: "FullArtifactId",
|
|
31
|
+
source: markdown.SectionSource,
|
|
32
|
+
section_config: ArtifactSectionConfig,
|
|
33
|
+
description: str,
|
|
34
|
+
primary: bool = False,
|
|
35
|
+
) -> Result[ArtifactSectionMeta, ErrorsList]:
|
|
36
|
+
finish_config = cast(FinishWorkflowConfig, section_config)
|
|
37
|
+
return Ok(OperationMeta(fsm_mode=finish_config.fsm_mode, allowed_transtions=set()))
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import TYPE_CHECKING, ClassVar, Iterator, cast
|
|
3
|
+
|
|
4
|
+
import pydantic
|
|
5
|
+
|
|
6
|
+
from donna.core.errors import ErrorsList
|
|
7
|
+
from donna.core.result import Ok, Result
|
|
8
|
+
from donna.domain import errors as domain_errors
|
|
9
|
+
from donna.domain.ids import ArtifactSectionId, FullArtifactId
|
|
10
|
+
from donna.machine.action_requests import ActionRequest
|
|
11
|
+
from donna.machine.artifacts import ArtifactSection, ArtifactSectionConfig, ArtifactSectionMeta
|
|
12
|
+
from donna.machine.operations import FsmMode, OperationConfig, OperationKind, OperationMeta
|
|
13
|
+
from donna.world import markdown
|
|
14
|
+
from donna.world.sources.markdown import MarkdownSectionMixin
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from donna.machine.changes import Change
|
|
18
|
+
from donna.machine.tasks import Task, WorkUnit
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def extract_transitions(text: str) -> set[ArtifactSectionId]:
|
|
22
|
+
"""Extracts all transitions from the text of action request.
|
|
23
|
+
|
|
24
|
+
Transition is specified as render of `goto` directive in the format:
|
|
25
|
+
```
|
|
26
|
+
$$donna goto <full_artifact_local_id> donna$$
|
|
27
|
+
```
|
|
28
|
+
"""
|
|
29
|
+
pattern = r"\$\$donna\s+goto\s+([a-zA-Z0-9_\-./:]+)\s+donna\$\$"
|
|
30
|
+
matches = re.findall(pattern, text)
|
|
31
|
+
|
|
32
|
+
transitions: set[ArtifactSectionId] = set()
|
|
33
|
+
|
|
34
|
+
for match in matches:
|
|
35
|
+
transition_result = ArtifactSectionId.parse(match)
|
|
36
|
+
if transition_result.is_err():
|
|
37
|
+
raise domain_errors.InvalidIdentifier(value=match)
|
|
38
|
+
transitions.add(transition_result.unwrap())
|
|
39
|
+
|
|
40
|
+
return transitions
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RequestActionConfig(OperationConfig):
|
|
44
|
+
@pydantic.field_validator("fsm_mode", mode="after")
|
|
45
|
+
@classmethod
|
|
46
|
+
def validate_fsm_mode(cls, v: FsmMode) -> FsmMode:
|
|
47
|
+
if v == FsmMode.final:
|
|
48
|
+
raise ValueError("RequestAction operation cannot have 'final' fsm_mode")
|
|
49
|
+
|
|
50
|
+
return v
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RequestAction(MarkdownSectionMixin, OperationKind):
|
|
54
|
+
config_class: ClassVar[type[RequestActionConfig]] = RequestActionConfig
|
|
55
|
+
|
|
56
|
+
def markdown_construct_meta(
|
|
57
|
+
self,
|
|
58
|
+
artifact_id: "FullArtifactId",
|
|
59
|
+
source: markdown.SectionSource,
|
|
60
|
+
section_config: ArtifactSectionConfig,
|
|
61
|
+
description: str,
|
|
62
|
+
primary: bool = False,
|
|
63
|
+
) -> Result[ArtifactSectionMeta, ErrorsList]:
|
|
64
|
+
request_config = cast(RequestActionConfig, section_config)
|
|
65
|
+
analysis = source.as_analysis_markdown(with_title=True)
|
|
66
|
+
|
|
67
|
+
return Ok(
|
|
68
|
+
OperationMeta(
|
|
69
|
+
fsm_mode=request_config.fsm_mode,
|
|
70
|
+
allowed_transtions=extract_transitions(analysis),
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def execute_section(self, task: "Task", unit: "WorkUnit", operation: ArtifactSection) -> Iterator["Change"]:
|
|
75
|
+
from donna.machine.changes import ChangeAddActionRequest
|
|
76
|
+
|
|
77
|
+
context: dict[str, object] = {
|
|
78
|
+
"scheme": operation,
|
|
79
|
+
"task": task,
|
|
80
|
+
"work_unit": unit,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
request_text = operation.description.format(**context)
|
|
84
|
+
|
|
85
|
+
full_operation_id = unit.operation_id
|
|
86
|
+
|
|
87
|
+
request = ActionRequest.build(request_text, full_operation_id)
|
|
88
|
+
|
|
89
|
+
yield ChangeAddActionRequest(action_request=request)
|