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
ww/variables.py ADDED
@@ -0,0 +1,275 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Names and values for workflow variables owned by ww core."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from collections.abc import Iterable, Mapping
7
+ from pathlib import Path
8
+
9
+ from ww.executable import ww_command
10
+ from ww.items import WorkItem
11
+ from ww.workspace import Workdir, item_workspace, resolve_workspace
12
+
13
+ TASK_ID = "ww.task.id"
14
+ WORKFLOWS = "ww.task.workflows"
15
+ TASK_WORKSPACE_DIR = "ww.task.workspace_dir"
16
+ # Kept in a run's values for ww/git, which offers it as
17
+ # ``{{ww.git.branch_strategy}}``; never a template name of its own.
18
+ BRANCH_NAMING_STRATEGY = "__branch_naming_strategy"
19
+ # The configured project a task works in, its directory, and every configured
20
+ # project name. ``ww.project.dir`` stays the project's own directory even
21
+ # when an extension moves the task workspace, for example into a Git worktree.
22
+ PROJECT = "ww.project.name"
23
+ PROJECT_DIR = "ww.project.dir"
24
+ PROJECTS = "ww.project.names"
25
+ # How a printed command invokes ww (``./ww`` or the configured executable), so
26
+ # a step's text can name a ww command the way ww's own pages do.
27
+ EXECUTABLE = "ww.executable"
28
+ # What a template reads about saved metadata, documents, and the stage's item.
29
+ METADATA_PREFIX = "ww.metadata."
30
+ PROJECT_METADATA_PREFIX = "ww.project_metadata."
31
+ DOCUMENTS_PREFIX = "ww.documents."
32
+ ITEM_ID = "ww.item.id"
33
+ ITEM_TEXT = "ww.item.text"
34
+ ITEM_FIELD_PREFIX = "ww.item.field."
35
+ ITEM_PREFIX = "ww.item."
36
+ # Kept in a step's values to say which work item the plan binds it to; never a
37
+ # template name of its own.
38
+ ITEM_BINDING = "__item_binding"
39
+ CHOICES = "ww.choices"
40
+ # Values resolved while the task runs rather than when its plan is compiled.
41
+ RUNTIME_PREFIXES = (
42
+ METADATA_PREFIX,
43
+ PROJECT_METADATA_PREFIX,
44
+ ITEM_PREFIX,
45
+ "ww.child.",
46
+ )
47
+
48
+ CORE_VARIABLE_NAMES = (
49
+ TASK_ID,
50
+ WORKFLOWS,
51
+ TASK_WORKSPACE_DIR,
52
+ PROJECT,
53
+ PROJECT_DIR,
54
+ PROJECTS,
55
+ EXECUTABLE,
56
+ CHOICES,
57
+ )
58
+ OVERRIDABLE_CORE_VARIABLE_NAMES = (TASK_WORKSPACE_DIR,)
59
+
60
+
61
+ def unknown_template_message(names: set[str] | tuple[str, ...]) -> str:
62
+ """Name the template values a handler references that are unavailable."""
63
+ return "handler references unavailable variable(s): " + ", ".join(sorted(names))
64
+
65
+
66
+ # Every ww-provided template value lives under ``ww.``; an extension's
67
+ # namespace sits there too (ww/git provides ``{{ww.git.branch}}``). The names
68
+ # ww keeps for its own values may not be claimed as an extension namespace.
69
+ WW_NAMESPACE = "ww"
70
+ RESERVED_NAMESPACES = (
71
+ "executable",
72
+ "task",
73
+ "project",
74
+ "documents",
75
+ "metadata",
76
+ "project_metadata",
77
+ "item",
78
+ "child",
79
+ "choices",
80
+ )
81
+
82
+
83
+ # What a per-child parent stage reads about its child. The plan compiler
84
+ # accepts these only under ``children.steps`` (and checks the exact names
85
+ # there); ``{{ww.child.field.<name>}}`` is open-ended, and every extension
86
+ # namespace value is offered for the child as ``{{ww.child.<namespace>.*}}``.
87
+ CHILD_VALUE_PREFIX = "ww.child."
88
+ CHILD_FIELD_PREFIX = "ww.child.field."
89
+ CHILD_VALUE_NAMES = ("ww.child.id", "ww.child.text", "ww.child.project")
90
+
91
+
92
+ def item_variable_values(
93
+ work: WorkItem, referenced: tuple[str, ...] = ()
94
+ ) -> dict[str, str]:
95
+ """``{{ww.item.*}}`` for a bound work item, the one mapping everything uses.
96
+
97
+ Agent instructions and automatic actions both read it, so they render the
98
+ same values. Representations are stable strings: text fields as stored,
99
+ booleans as ``true`` or ``false``, and a value never set (an empty text
100
+ field, no ``reference_to_id``, a custom field the item lacks) as the empty
101
+ string, the same convention as an unset metadata list. ``referenced``
102
+ names the ``ww.item.field.*`` values the caller's templates read, so one
103
+ the item lacks renders empty rather than as a missing variable.
104
+ ``ww.item.actual_solution`` is the item's resolution text.
105
+ """
106
+ values = {
107
+ ITEM_ID: work.id,
108
+ ITEM_TEXT: work.item,
109
+ "ww.item.processed_item": work.processed_item,
110
+ "ww.item.proposed_solution": work.proposed_solution,
111
+ "ww.item.actual_solution": work.actual_solution,
112
+ "ww.item.resolved": "true" if work.resolved else "false",
113
+ "ww.item.reported": "true" if work.reported else "false",
114
+ "ww.item.reference_to_id": work.reference_to_id or "",
115
+ }
116
+ values.update(
117
+ {name: "" for name in referenced if name.startswith(ITEM_FIELD_PREFIX)}
118
+ )
119
+ values.update({f"{ITEM_FIELD_PREFIX}{name}": value for name, value in work.fields})
120
+ return values
121
+
122
+
123
+ def item_binding_values(
124
+ item_id: str | None, work: WorkItem | None, referenced: tuple[str, ...] = ()
125
+ ) -> dict[str, str]:
126
+ """The item values of a step, with how the step is bound to its item.
127
+
128
+ ``item_id`` is the work item the plan binds the step to and ``work`` its
129
+ current record. Nothing is bound for a step without ``item_id``; a bound
130
+ step whose item is gone keeps only the binding, so a read of an item value
131
+ can say which of those it is (see ``item_context_error``).
132
+ """
133
+ if item_id is None:
134
+ return {}
135
+ values = {ITEM_BINDING: item_id}
136
+ if work is not None:
137
+ values.update(item_variable_values(work, referenced))
138
+ return values
139
+
140
+
141
+ def item_context_error(missing: Iterable[str], values: Mapping[str, str]) -> str | None:
142
+ """The context error for ``ww.item.*`` names ``values`` cannot give, if any.
143
+
144
+ The one wording for a command's arguments and an extension handler's:
145
+ no item bound to the step, the bound item no longer in the run, or a
146
+ ``ww.item`` name that does not exist.
147
+ """
148
+ names = sorted(name for name in missing if name.startswith(ITEM_PREFIX))
149
+ if not names:
150
+ return None
151
+ listed = ", ".join(names)
152
+ if ITEM_BINDING not in values:
153
+ return (
154
+ f"item variable(s) used where no work item is bound: {listed}; "
155
+ "ww.item.* is available only in the stages of an `items` step"
156
+ )
157
+ if ITEM_ID not in values:
158
+ return (
159
+ f"item variable(s) {listed} read the work item "
160
+ f"{values[ITEM_BINDING]!r}, which is no longer in the run's items"
161
+ )
162
+ valid = sorted(
163
+ (*item_variable_values(WorkItem("x", "x")), f"{ITEM_FIELD_PREFIX}<name>")
164
+ )
165
+ return f"unknown item variable(s): {listed}; valid names are " + ", ".join(valid)
166
+
167
+
168
+ def child_value_name(name: str) -> str:
169
+ """The child's counterpart of a ``ww.`` value (``ww.git.x``: ``ww.child.git.x``)."""
170
+ _, _, rest = name.partition(".")
171
+ return f"{CHILD_VALUE_PREFIX}{rest}"
172
+
173
+
174
+ def child_values(
175
+ child_id: str,
176
+ text: str,
177
+ project: str | None,
178
+ fields: tuple[tuple[str, str], ...],
179
+ ) -> dict[str, str]:
180
+ """``{{ww.child.*}}`` from a child's own record."""
181
+ return {
182
+ "ww.child.id": child_id,
183
+ "ww.child.text": text,
184
+ "ww.child.project": project or "",
185
+ **{f"{CHILD_FIELD_PREFIX}{name}": value for name, value in fields},
186
+ }
187
+
188
+
189
+ def namespaced(namespace: str, name: str) -> str:
190
+ """The template name of ``name`` in an extension's ``namespace``."""
191
+ return f"{WW_NAMESPACE}.{namespace}.{name}"
192
+
193
+
194
+ def is_reserved_name(name: str) -> bool:
195
+ """Whether a provided or output value name would shadow a ww value."""
196
+ return name.startswith("__") or name.split(".", 1)[0] == WW_NAMESPACE
197
+
198
+
199
+ # ww's own values, resolved when the plan is compiled or read as they stand
200
+ # (saved metadata, the stage's item); never a reason to stop before a step.
201
+ _CORE_NAMESPACES = tuple(
202
+ [namespace] for namespace in RESERVED_NAMESPACES if namespace != "child"
203
+ )
204
+
205
+
206
+ def unavailable_ww_values(
207
+ names: tuple[str, ...], values: Mapping[str, str]
208
+ ) -> tuple[str, ...]:
209
+ """The ``ww.`` names among ``names`` that ``values`` has no value for.
210
+
211
+ The plan compiler accepts only ``ww.`` names something provides, so one
212
+ missing here is a value its provider cannot give for this task yet, such
213
+ as ``{{ww.git.branch}}`` before ww/git recorded the branch. ww's own
214
+ values (``ww.task.*``, ``ww.metadata.*``, ``ww.item.*``, ...) are never
215
+ counted: they are known when the plan is compiled, or read as they stand.
216
+ """
217
+ return tuple(
218
+ dict.fromkeys(
219
+ name
220
+ for name in names
221
+ if name.split(".", 1)[0] == WW_NAMESPACE
222
+ and name.split(".")[1:2] not in _CORE_NAMESPACES
223
+ and name not in values
224
+ )
225
+ )
226
+
227
+
228
+ def compile_variable_values(
229
+ workflow_names: tuple[str, ...], task_id: str | None
230
+ ) -> dict[str, str]:
231
+ """Return core values known while a workflow plan is compiled."""
232
+ values = {WORKFLOWS: ",".join(workflow_names)}
233
+ if task_id is not None:
234
+ values[TASK_ID] = task_id
235
+ return values
236
+
237
+
238
+ def runtime_variable_values(
239
+ root: Path,
240
+ task_id: str,
241
+ workspace: str | None = None,
242
+ project: str | None = None,
243
+ projects: tuple[str, ...] = (),
244
+ project_dir: str | None = None,
245
+ ) -> dict[str, str]:
246
+ """Return core values resolved from current task execution state."""
247
+ directory = (root / workspace).resolve() if workspace else root.resolve()
248
+ return {
249
+ TASK_ID: task_id,
250
+ TASK_WORKSPACE_DIR: str(directory),
251
+ PROJECT: project or "",
252
+ PROJECT_DIR: project_dir or "",
253
+ PROJECTS: ",".join(projects),
254
+ EXECUTABLE: ww_command(),
255
+ }
256
+
257
+
258
+ def item_workspace_values(
259
+ root: Path,
260
+ workdir: Workdir,
261
+ working_directory: str | None,
262
+ values: Mapping[str, str],
263
+ ) -> tuple[Path | None, dict[str, str]]:
264
+ """The directory a plan item works in, and ``values`` as that item sees them.
265
+
266
+ An item with its own ``workdir`` reads that directory as
267
+ ``{{ww.task.workspace_dir}}``; a ``task`` item keeps the task's values, which
268
+ an extension may already have pointed at its checkout.
269
+ """
270
+ if workdir == "task":
271
+ return resolve_workspace(root, working_directory), dict(values)
272
+ directory = item_workspace(
273
+ root, workdir, working_directory, values.get(PROJECT_DIR) or None
274
+ )
275
+ return directory, {**values, TASK_WORKSPACE_DIR: str(directory)}