ww-agentic-workflows 1.0.0.dev3__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.
Files changed (167) hide show
  1. ww/__init__.py +18 -0
  2. ww/_bundled_extensions/ww/git/extension.py +1728 -0
  3. ww/action_execution.py +887 -0
  4. ww/actions/__init__.py +94 -0
  5. ww/actions/command.py +444 -0
  6. ww/actions/contracts.py +699 -0
  7. ww/actions/extension.py +197 -0
  8. ww/actions/mcp.py +84 -0
  9. ww/actions/prompt.py +74 -0
  10. ww/actions/skill.py +62 -0
  11. ww/actions/slash_command.py +63 -0
  12. ww/agents.py +151 -0
  13. ww/amendments.py +54 -0
  14. ww/artifacts.py +93 -0
  15. ww/assessments.py +181 -0
  16. ww/assets/__init__.py +2 -0
  17. ww/assets/agent_instructions.md +49 -0
  18. ww/assets/docs/examples.md +879 -0
  19. ww/assets/docs/features.md +4639 -0
  20. ww/assets/docs/specification.md +1876 -0
  21. ww/assets/noww_skill.md +11 -0
  22. ww/assets/workflows/catchall.yaml +26 -0
  23. ww/assets/workflows/onboarding.yaml +586 -0
  24. ww/assets/workflows/scriptize.yaml +130 -0
  25. ww/assets/ww-automate_skill.md +23 -0
  26. ww/assets/ww-deduce-feedback_skill.md +38 -0
  27. ww/assets/ww-feedback-rules_skill.md +48 -0
  28. ww/assets/ww-learn-project_skill.md +22 -0
  29. ww/assets/ww-refresh_skill.md +26 -0
  30. ww/assets/ww-rule_skill.md +83 -0
  31. ww/assets/ww-rules-from-artifacts_skill.md +22 -0
  32. ww/assets/ww-scriptize_skill.md +33 -0
  33. ww/assets/ww-setup_skill.md +94 -0
  34. ww/assets/ww-solve_skill.md +23 -0
  35. ww/assets/ww-suggest_skill.md +32 -0
  36. ww/assets/ww-wizard_skill.md +105 -0
  37. ww/assets/ww_skill.md +59 -0
  38. ww/assignments.py +283 -0
  39. ww/bootstrap.py +405 -0
  40. ww/builtin_workflows.py +215 -0
  41. ww/changes.py +225 -0
  42. ww/child_coordination.py +482 -0
  43. ww/children.py +106 -0
  44. ww/claude_permissions.py +115 -0
  45. ww/cli/__init__.py +7 -0
  46. ww/cli/__main__.py +6 -0
  47. ww/cli/audit.py +129 -0
  48. ww/cli/catalogs.py +131 -0
  49. ww/cli/discover.py +607 -0
  50. ww/cli/initialization.py +898 -0
  51. ww/cli/lookup.py +287 -0
  52. ww/cli/main.py +1768 -0
  53. ww/cli/parser.py +1200 -0
  54. ww/cli/prompts.py +217 -0
  55. ww/cli/updates.py +117 -0
  56. ww/completion_artifacts.py +156 -0
  57. ww/completion_inputs.py +39 -0
  58. ww/config/__init__.py +582 -0
  59. ww/config/actions.py +591 -0
  60. ww/config/composition.py +571 -0
  61. ww/config/rules.py +511 -0
  62. ww/config/steps.py +1220 -0
  63. ww/config/values.py +223 -0
  64. ww/config_files.py +191 -0
  65. ww/config_writes.py +264 -0
  66. ww/contracts.py +155 -0
  67. ww/control.py +41 -0
  68. ww/defaults.py +130 -0
  69. ww/design_docs.py +32 -0
  70. ww/discovery.py +104 -0
  71. ww/documents.py +217 -0
  72. ww/errors.py +18 -0
  73. ww/executable.py +43 -0
  74. ww/execution_models/__init__.py +64 -0
  75. ww/execution_models/construction.py +148 -0
  76. ww/execution_models/decoding.py +38 -0
  77. ww/execution_models/plan_codec.py +565 -0
  78. ww/execution_models/records.py +1206 -0
  79. ww/execution_models/runs.py +266 -0
  80. ww/extensions/__init__.py +40 -0
  81. ww/extensions/api.py +559 -0
  82. ww/extensions/registry.py +864 -0
  83. ww/extensions/store.py +78 -0
  84. ww/feedback.py +342 -0
  85. ww/handler_repairs.py +57 -0
  86. ww/hooks/__init__.py +40 -0
  87. ww/hooks/agents.py +380 -0
  88. ww/hooks/install.py +168 -0
  89. ww/hooks/notices.py +206 -0
  90. ww/hooks/records.py +209 -0
  91. ww/hooks/runtime.py +266 -0
  92. ww/hooks/transcripts.py +183 -0
  93. ww/inspect.py +896 -0
  94. ww/instructions/__init__.py +17 -0
  95. ww/instructions/builder.py +1682 -0
  96. ww/instructions/commands.py +335 -0
  97. ww/instructions/handoff.py +149 -0
  98. ww/instructions/models.py +686 -0
  99. ww/instructions/policy.py +219 -0
  100. ww/instructions/text.py +168 -0
  101. ww/interactions.py +187 -0
  102. ww/interpolation.py +37 -0
  103. ww/item_passes.py +167 -0
  104. ww/items.py +99 -0
  105. ww/locking.py +207 -0
  106. ww/metadata_publication.py +230 -0
  107. ww/onboarding.py +229 -0
  108. ww/open_work.py +236 -0
  109. ww/operations.py +193 -0
  110. ww/operator_ui/__init__.py +16 -0
  111. ww/operator_ui/page.html +351 -0
  112. ww/operator_ui/server.py +215 -0
  113. ww/operator_ui/session.py +389 -0
  114. ww/operator_ui/sheet.py +104 -0
  115. ww/operator_ui/view.py +109 -0
  116. ww/output.py +339 -0
  117. ww/output_adapters/__init__.py +12 -0
  118. ww/output_adapters/base.py +25 -0
  119. ww/output_adapters/json_adapter.py +37 -0
  120. ww/output_adapters/markdown.py +2293 -0
  121. ww/output_adapters/rule_pages.py +337 -0
  122. ww/output_adapters/terminal.py +21 -0
  123. ww/package_updates.py +167 -0
  124. ww/plan/__init__.py +38 -0
  125. ww/plan/actions.py +207 -0
  126. ww/plan/compiler.py +1492 -0
  127. ww/plan/constructs.py +456 -0
  128. ww/plan/models.py +665 -0
  129. ww/project_config.py +752 -0
  130. ww/recovery.py +401 -0
  131. ww/replanning.py +367 -0
  132. ww/results.py +77 -0
  133. ww/rule_checks.py +230 -0
  134. ww/rule_conversion.py +331 -0
  135. ww/rule_disputes.py +148 -0
  136. ww/rule_store.py +456 -0
  137. ww/rule_verification.py +714 -0
  138. ww/rule_views.py +447 -0
  139. ww/rule_writes.py +920 -0
  140. ww/run_coordination.py +158 -0
  141. ww/runtimes.py +105 -0
  142. ww/service.py +4405 -0
  143. ww/setup_apply.py +428 -0
  144. ww/step_values.py +20 -0
  145. ww/storage.py +447 -0
  146. ww/storage_adapters/__init__.py +36 -0
  147. ww/storage_adapters/base.py +540 -0
  148. ww/storage_adapters/filesystem.py +370 -0
  149. ww/storage_adapters/memory.py +195 -0
  150. ww/storage_adapters/project_metadata.py +69 -0
  151. ww/storage_adapters/task_document.py +484 -0
  152. ww/task_ids.py +114 -0
  153. ww/task_references.py +124 -0
  154. ww/transitions.py +1619 -0
  155. ww/updates.py +399 -0
  156. ww/upgrade.py +95 -0
  157. ww/validation.py +168 -0
  158. ww/variables.py +275 -0
  159. ww/workflow_config.py +854 -0
  160. ww/workflow_update.py +239 -0
  161. ww/workflow_validation.py +1260 -0
  162. ww/workspace.py +50 -0
  163. ww_agentic_workflows-1.0.0.dev3.dist-info/METADATA +690 -0
  164. ww_agentic_workflows-1.0.0.dev3.dist-info/RECORD +167 -0
  165. ww_agentic_workflows-1.0.0.dev3.dist-info/WHEEL +4 -0
  166. ww_agentic_workflows-1.0.0.dev3.dist-info/entry_points.txt +2 -0
  167. ww_agentic_workflows-1.0.0.dev3.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,266 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Run aggregates, plan snapshots, and the invariants that tie them together."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ from dataclasses import dataclass, replace
9
+ from typing import Any
10
+
11
+ from ww.actions import PlannedAction, actions
12
+ from ww.children import ChildTask
13
+ from ww.contracts import BOOTSTRAP_REQUEST_PREFIX, RunStatus, run_is_open
14
+ from ww.errors import StateError
15
+ from ww.items import WorkItem
16
+ from ww.plan import PlanItem, WorkflowPlan
17
+ from ww.validation import (
18
+ expect_optional_string,
19
+ expect_positive_int,
20
+ expect_string,
21
+ is_strict_int,
22
+ require_keys,
23
+ )
24
+
25
+ from .decoding import _from_path
26
+ from .plan_codec import _plan_from_dict
27
+ from .records import ExecutionState
28
+
29
+ # Schema 2 records explicit ``items`` pass identity on plan items.
30
+ PLAN_SCHEMA_VERSION = 2
31
+ # Recorded on every snapshot; informational until a reader needs to branch on it.
32
+ PLAN_COMPILER_VERSION = "plan-v10"
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class WorkflowRunSummary:
37
+ """Task-level projection of one independently persisted workflow run."""
38
+
39
+ run_id: str
40
+ workflow: str
41
+ status: RunStatus
42
+ summary: str | None = None
43
+
44
+ def to_dict(self) -> dict[str, str | None]:
45
+ return self.__dict__.copy()
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class TaskRunAggregate:
50
+ """Authoritative, atomically published state for one workflow run.
51
+
52
+ ``plan`` and ``state`` are the source of truth. Items and children are
53
+ included in the same record because they participate in transitions (and
54
+ are therefore not safe as independently committed side files).
55
+ """
56
+
57
+ run_id: str
58
+ workflow: str
59
+ snapshot: PlanSnapshot
60
+ state: ExecutionState
61
+ items: tuple[WorkItem, ...] = ()
62
+ children: tuple[ChildTask, ...] = ()
63
+ bootstrap_request_id: str | None = None
64
+
65
+ def to_dict(self) -> dict[str, Any]:
66
+ return {
67
+ "run_id": self.run_id,
68
+ "workflow": self.workflow,
69
+ "snapshot": self.snapshot.to_dict(),
70
+ "state": self.state.to_dict(),
71
+ "items": [item.to_dict() for item in self.items],
72
+ "children": [child.to_dict() for child in self.children],
73
+ "bootstrap_request_id": self.bootstrap_request_id,
74
+ }
75
+
76
+ @classmethod
77
+ def from_dict(cls, data: Any) -> TaskRunAggregate:
78
+ """Decode one run without coercing malformed persisted values."""
79
+ if not isinstance(data, dict):
80
+ raise ValueError("run must be a mapping")
81
+ require_keys(data, {"run_id", "workflow", "snapshot", "state"}, "run")
82
+ run_id = expect_string(data["run_id"], "run.run_id")
83
+ raw_items = data.get("items", [])
84
+ raw_children = data.get("children", [])
85
+ if not isinstance(raw_items, list):
86
+ raise ValueError("run.items must be a list")
87
+ if not isinstance(raw_children, list):
88
+ raise ValueError("run.children must be a list")
89
+ snapshot = _from_path(PlanSnapshot.from_dict, data["snapshot"], "run.snapshot")
90
+ state = _from_path(ExecutionState.from_dict, data["state"], "run.state")
91
+ if state.plan_digest is not None:
92
+ # The digest is derived from the plan stored beside it, and how a
93
+ # plan serializes depends on the ww version that wrote it: a
94
+ # field it knew and this one does not, or a default only one of
95
+ # them fills in. Re-derive it from this version's reading of the
96
+ # same plan, so state another version wrote keeps loading.
97
+ state = replace(state, plan_digest=snapshot.plan_digest)
98
+ return cls(
99
+ run_id=run_id,
100
+ workflow=expect_string(data["workflow"], "run.workflow"),
101
+ snapshot=snapshot,
102
+ state=state,
103
+ items=tuple(
104
+ _from_path(WorkItem.from_dict, item, f"run.items[{index}]")
105
+ for index, item in enumerate(raw_items)
106
+ ),
107
+ children=tuple(
108
+ _from_path(ChildTask.from_dict, child, f"run.children[{index}]")
109
+ for index, child in enumerate(raw_children)
110
+ ),
111
+ bootstrap_request_id=expect_optional_string(
112
+ data.get("bootstrap_request_id"), "run.bootstrap_request_id"
113
+ ),
114
+ )
115
+
116
+
117
+ @dataclass(frozen=True)
118
+ class PlanSnapshot:
119
+ """A task-local plan revision plus its immutable compiled template."""
120
+
121
+ schema_version: int
122
+ compiler_version: str
123
+ configuration_digest: str
124
+ compiled_at: str
125
+ plan: WorkflowPlan
126
+ plan_revision: int = 1
127
+ template_plan: WorkflowPlan | None = None
128
+ # The step a bootstrap request already performed, which the plan omits;
129
+ # kept so a replan compiles the workflow the way the run started.
130
+ bootstrap_step: str | None = None
131
+
132
+ def __post_init__(self) -> None:
133
+ if self.template_plan is None:
134
+ object.__setattr__(self, "template_plan", self.plan)
135
+
136
+ @property
137
+ def plan_digest(self) -> str:
138
+ canonical = json.dumps(
139
+ self.plan.to_dict(), sort_keys=True, separators=(",", ":")
140
+ )
141
+ return hashlib.sha256(canonical.encode()).hexdigest()
142
+
143
+ def to_dict(self) -> dict[str, Any]:
144
+ template = self.template_plan if self.template_plan is not None else self.plan
145
+ return {
146
+ "schema_version": self.schema_version,
147
+ "compiler_version": self.compiler_version,
148
+ "configuration_digest": self.configuration_digest,
149
+ "compiled_at": self.compiled_at,
150
+ "plan": self.plan.to_dict(),
151
+ "plan_revision": self.plan_revision,
152
+ "template_plan": template.to_dict(),
153
+ **(
154
+ {"bootstrap_step": self.bootstrap_step}
155
+ if self.bootstrap_step is not None
156
+ else {}
157
+ ),
158
+ }
159
+
160
+ @classmethod
161
+ def from_dict(cls, data: Any) -> PlanSnapshot:
162
+ if not isinstance(data, dict):
163
+ raise ValueError("plan snapshot must be a mapping")
164
+ required = {
165
+ "schema_version",
166
+ "compiler_version",
167
+ "configuration_digest",
168
+ "compiled_at",
169
+ "plan",
170
+ }
171
+ require_keys(data, required, "plan snapshot")
172
+ schema_version = data["schema_version"]
173
+ if not is_strict_int(schema_version) or schema_version != PLAN_SCHEMA_VERSION:
174
+ raise ValueError(f"unsupported plan snapshot schema: {schema_version!r}")
175
+ template = (
176
+ _plan_from_dict(data["template_plan"]) if "template_plan" in data else None
177
+ )
178
+ plan = _plan_from_dict(data["plan"])
179
+ return cls(
180
+ schema_version=schema_version,
181
+ compiler_version=expect_string(
182
+ data["compiler_version"], "compiler_version"
183
+ ),
184
+ configuration_digest=expect_string(
185
+ data["configuration_digest"], "configuration_digest"
186
+ ),
187
+ compiled_at=expect_string(data["compiled_at"], "compiled_at"),
188
+ plan=plan,
189
+ plan_revision=expect_positive_int(
190
+ data.get("plan_revision", 1), "plan snapshot.plan_revision"
191
+ ),
192
+ template_plan=template if template is not None else plan,
193
+ bootstrap_step=expect_optional_string(
194
+ data.get("bootstrap_step"), "plan snapshot.bootstrap_step"
195
+ ),
196
+ )
197
+
198
+
199
+ def validate_task_runs(task_id: str, runs: tuple[TaskRunAggregate, ...]) -> None:
200
+ """Reject an aggregate whose runs, plans, and records disagree.
201
+
202
+ Every storage adapter calls this on read and before commit, so the
203
+ invariants live with the model rather than being copied per backend.
204
+ """
205
+ run_ids = [run.run_id for run in runs]
206
+ if len(run_ids) != len(set(run_ids)):
207
+ raise StateError(f"task {task_id!r} aggregate has duplicate run IDs")
208
+ if sum(run_is_open(run.state.status) for run in runs) > 1:
209
+ raise StateError(f"task {task_id!r} aggregate has multiple active runs")
210
+ for run in runs:
211
+ _validate_run(task_id, run)
212
+
213
+
214
+ def _validate_run(task_id: str, run: TaskRunAggregate) -> None:
215
+ state, plan = run.state, run.snapshot.plan
216
+ if state.task_id != task_id or state.run_id != run.run_id:
217
+ raise StateError(f"task {task_id!r} aggregate has mismatched run identity")
218
+ if plan.task_id != task_id:
219
+ raise StateError(f"task {task_id!r} aggregate has mismatched plan task ID")
220
+ binding = run.bootstrap_request_id
221
+ if binding is not None and not binding.startswith(BOOTSTRAP_REQUEST_PREFIX):
222
+ raise StateError(f"task {task_id!r} aggregate has invalid bootstrap binding")
223
+ if state.workflow != run.workflow or plan.workflow != run.workflow:
224
+ raise StateError(f"task {task_id!r} aggregate has mismatched workflow identity")
225
+ if (
226
+ state.agent != plan.agent
227
+ or state.snapshot_digest != run.snapshot.configuration_digest
228
+ ):
229
+ raise StateError(f"task {task_id!r} aggregate has mismatched plan identity")
230
+ if state.plan_revision != run.snapshot.plan_revision:
231
+ raise StateError(f"task {task_id!r} aggregate has mismatched plan revision")
232
+ if state.plan_digest is not None and state.plan_digest != run.snapshot.plan_digest:
233
+ raise StateError(f"task {task_id!r} aggregate has mismatched plan digest")
234
+ if len(state.item_executions) != len(plan.items):
235
+ raise StateError(f"task {task_id!r} aggregate has mismatched plan/state")
236
+ if not 0 <= state.cursor <= len(plan.items):
237
+ raise StateError(f"task {task_id!r} aggregate has invalid cursor")
238
+ item_ids = [item.id for item in plan.items]
239
+ if len(item_ids) != len(set(item_ids)) or any(
240
+ item.position != position for position, item in enumerate(plan.items, 1)
241
+ ):
242
+ raise StateError(f"task {task_id!r} aggregate has invalid plan identity")
243
+ for item, record in zip(plan.items, state.item_executions, strict=True):
244
+ if item.id != record.plan_item_id or item.position != record.position:
245
+ raise StateError(f"task {task_id!r} aggregate has mismatched item identity")
246
+ expected = _expected_command_count(item)
247
+ if expected is not None and expected != len(record.commands):
248
+ raise StateError(f"task {task_id!r} aggregate has mismatched commands")
249
+ if any(
250
+ command.index != index for index, command in enumerate(record.commands, 1)
251
+ ):
252
+ raise StateError(f"task {task_id!r} aggregate has invalid command index")
253
+ if state.active_item_id is not None and (
254
+ state.cursor >= len(plan.items)
255
+ or plan.items[state.cursor].id != state.active_item_id
256
+ ):
257
+ raise StateError(f"task {task_id!r} aggregate has an invalid active item")
258
+
259
+
260
+ def _expected_command_count(item: PlanItem) -> int | None:
261
+ """Return how many command records the item's action declares, if known."""
262
+ if not isinstance(item.operation, PlannedAction) or not actions.contains(item.kind):
263
+ return None
264
+ implementation = actions.get(item.kind)
265
+ traits = implementation.traits(item.payload_as(implementation.planned_type))
266
+ return len(traits.command_segments or ())
@@ -0,0 +1,40 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Pluggable extensions: handlers, modes, and commands from other vendors."""
3
+
4
+ from ww.extensions.api import (
5
+ EXTENSION_API_VERSION,
6
+ Extension,
7
+ ExtensionCheckResult,
8
+ ExtensionCommand,
9
+ ExtensionContext,
10
+ ExtensionHandler,
11
+ ExtensionNamespace,
12
+ ExtensionResult,
13
+ ExtensionVariable,
14
+ RuleGroupContribution,
15
+ )
16
+ from ww.extensions.registry import (
17
+ ExtensionReference,
18
+ ExtensionRegistry,
19
+ is_extension_reference,
20
+ parse_reference,
21
+ )
22
+ from ww.extensions.store import ExtensionStore
23
+
24
+ __all__ = [
25
+ "EXTENSION_API_VERSION",
26
+ "Extension",
27
+ "ExtensionCommand",
28
+ "ExtensionContext",
29
+ "ExtensionCheckResult",
30
+ "ExtensionHandler",
31
+ "ExtensionNamespace",
32
+ "ExtensionReference",
33
+ "ExtensionRegistry",
34
+ "ExtensionResult",
35
+ "ExtensionVariable",
36
+ "ExtensionStore",
37
+ "RuleGroupContribution",
38
+ "is_extension_reference",
39
+ "parse_reference",
40
+ ]