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/artifacts.py ADDED
@@ -0,0 +1,93 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Rendering for durable agent-produced workflow step artifacts."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Literal
8
+
9
+ RuleStatus = Literal[
10
+ "passed", "failed", "not applicable", "self-declared", "verified pass"
11
+ ]
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class RuleOutcome:
16
+ """One rule or check of a completed step, as its artifact reports it.
17
+
18
+ ``detail`` names what decided a rule without a command of its own: the
19
+ derived check that ran, or the verification item that gave the verdict.
20
+ """
21
+
22
+ id: str
23
+ status: RuleStatus
24
+ hook: bool = False
25
+ detail: str | None = None
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class RulesSummary:
30
+ """The ``## Rules`` section of a step artifact.
31
+
32
+ ``waived`` names the checks the step completed without, each with the
33
+ operator's reason; ``fix_attempts`` counts the completions ww rejected
34
+ before.
35
+ """
36
+
37
+ outcomes: tuple[RuleOutcome, ...]
38
+ fix_attempts: int = 0
39
+ waived: tuple[tuple[str, str], ...] = ()
40
+
41
+
42
+ def render_step_artifact(
43
+ *,
44
+ task_id: str,
45
+ workflow: str,
46
+ step: str,
47
+ step_number: int,
48
+ step_total: int,
49
+ skill: str,
50
+ result: str,
51
+ rules: RulesSummary | None = None,
52
+ ) -> str:
53
+ """Wrap an agent result in the built-in, stable Markdown artifact format."""
54
+ body = _normalize_result(result).rstrip()
55
+ return (
56
+ f"# {task_id} — {step}\n\n"
57
+ "## Workflow context\n\n"
58
+ f"- Workflow: {workflow}\n"
59
+ f"- Step: {step_number} of {step_total}\n"
60
+ f"- Skill: {skill}\n\n"
61
+ "## Result\n\n"
62
+ f"{body}\n" + (_rules_section(rules) if rules is not None else "")
63
+ )
64
+
65
+
66
+ def _rules_section(rules: RulesSummary) -> str:
67
+ lines = ["", "## Rules", ""]
68
+ lines.extend(
69
+ f"- `{outcome.id}`{' (hook)' if outcome.hook else ''}: {outcome.status}"
70
+ + (f" ({outcome.detail})" if outcome.detail else "")
71
+ for outcome in rules.outcomes
72
+ )
73
+ lines.append("")
74
+ reasons: dict[str, list[str]] = {}
75
+ for check_id, reason in rules.waived:
76
+ reasons.setdefault(reason, []).append(check_id)
77
+ for reason, waived in reasons.items():
78
+ names = ", ".join(f"`{check_id}`" for check_id in waived)
79
+ lines.append(f"Checks waived by the operator ({names}): {reason}")
80
+ attempts = rules.fix_attempts
81
+ lines.append(
82
+ f"Completions rejected before this one: {attempts}."
83
+ if attempts
84
+ else "No completion was rejected."
85
+ )
86
+ return "\n".join(lines) + "\n"
87
+
88
+
89
+ def _normalize_result(result: str) -> str:
90
+ """Restore line endings when Markdown arrived as one escaped CLI argument."""
91
+ if "\n" in result or "\r" in result:
92
+ return result
93
+ return result.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\\r", "\n")
ww/assessments.py ADDED
@@ -0,0 +1,181 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """The outcomes of an ``assess`` step, and when one is waiting to be chosen.
3
+
4
+ An assessment is completed like any agent step; its answer is then given to
5
+ ``next --outcome``. The transition that applies the answer and the pages that
6
+ ask for it both read the choice from here, so they cannot disagree about which
7
+ answers exist or what each one does.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+
14
+ from ww.errors import StateError
15
+ from ww.execution_models import ExecutionState
16
+ from ww.plan import PlanItem, WorkflowPlan
17
+
18
+ # The answers every assessment with declared outcomes accepts. One it does not
19
+ # declare runs nothing and continues after the assessment, so a gate declares
20
+ # only the outcome that has work. The compact form keeps its own rule:
21
+ # positive continues, negative ends the workflow.
22
+ STANDARD_OUTCOMES = ("positive", "negative", "mixed")
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class AssessmentOutcome:
27
+ """One answer: the step it runs first, or that it ends the workflow."""
28
+
29
+ label: str
30
+ stops: bool = False
31
+ first_step: str | None = None
32
+ # A standard answer the assessment does not declare: it skips every
33
+ # outcome's work and continues after the assessment.
34
+ declared: bool = True
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class PendingAssessment:
39
+ """A completed assessment whose outcome ``next`` must be given."""
40
+
41
+ index: int
42
+ question: str
43
+ outcomes: tuple[AssessmentOutcome, ...]
44
+ # Declared outcomes, as opposed to the compact form.
45
+ declared: bool
46
+
47
+ @property
48
+ def labels(self) -> tuple[str, ...]:
49
+ return tuple(outcome.label for outcome in self.outcomes)
50
+
51
+ def outcome(self, label: str) -> AssessmentOutcome | None:
52
+ return next((item for item in self.outcomes if item.label == label), None)
53
+
54
+
55
+ def assessment_outcomes(
56
+ plan: WorkflowPlan, index: int
57
+ ) -> tuple[AssessmentOutcome, ...]:
58
+ """What each answer to the assessment at ``index`` does."""
59
+ item = plan.items[index]
60
+ if not item.assessment_outcomes:
61
+ following = plan.items[index + 1].name if index + 1 < len(plan.items) else None
62
+ return (
63
+ AssessmentOutcome("positive", first_step=following),
64
+ AssessmentOutcome("negative", stops=True),
65
+ )
66
+ region = outcome_region(plan, index)
67
+ after = max(region, default=index) + 1
68
+ declared = tuple(
69
+ AssessmentOutcome(
70
+ label,
71
+ stops=label in item.assessment_stops,
72
+ first_step=next(
73
+ (
74
+ plan.items[position].name
75
+ for position in region
76
+ if plan.items[position].assessment_outcome == label
77
+ ),
78
+ None,
79
+ ),
80
+ )
81
+ for label in item.assessment_outcomes
82
+ )
83
+ implied = tuple(
84
+ AssessmentOutcome(
85
+ label,
86
+ first_step=plan.items[after].name if after < len(plan.items) else None,
87
+ declared=False,
88
+ )
89
+ for label in STANDARD_OUTCOMES
90
+ if label not in item.assessment_outcomes
91
+ )
92
+ return (*declared, *implied)
93
+
94
+
95
+ def outcome_region(plan: WorkflowPlan, index: int) -> tuple[int, ...]:
96
+ """The plan positions of every declared outcome's work, in order."""
97
+ step = plan.items[index].step
98
+ return tuple(
99
+ position
100
+ for position, candidate in enumerate(plan.items)
101
+ if _assessment_parent(candidate) == step
102
+ )
103
+
104
+
105
+ def pending_assessment(
106
+ state: ExecutionState, plan: WorkflowPlan
107
+ ) -> PendingAssessment | None:
108
+ """The assessment just completed whose outcome is still to be chosen.
109
+
110
+ Its outcome items, or for the compact form the step after it, are still
111
+ pending at the cursor until an outcome is chosen; the choice is recorded
112
+ on the assessment's record and the cursor moves into its work.
113
+ """
114
+ if not 0 < state.cursor < len(plan.items):
115
+ return None
116
+ if state.item_executions[state.cursor].status != "pending":
117
+ return None
118
+ item = plan.items[state.cursor]
119
+ parent = _assessment_parent(item)
120
+ if parent is not None:
121
+ group = [
122
+ index
123
+ for index, candidate in enumerate(plan.items)
124
+ if _assessment_parent(candidate) == parent
125
+ ]
126
+ if state.cursor != min(group):
127
+ return None
128
+ index = next(
129
+ (
130
+ index
131
+ for index, candidate in enumerate(plan.items)
132
+ if candidate.step == parent
133
+ and candidate.assessment_question is not None
134
+ ),
135
+ None,
136
+ )
137
+ if index is None:
138
+ raise StateError(f"assessment outcome has no parent assessment: {parent}")
139
+ return _unanswered(state, plan, index, declared=True)
140
+ previous = plan.items[state.cursor - 1]
141
+ if previous.assessment_question is not None and not previous.assessment_outcomes:
142
+ return _unanswered(state, plan, state.cursor - 1, declared=False)
143
+ return None
144
+
145
+
146
+ def _unanswered(
147
+ state: ExecutionState, plan: WorkflowPlan, index: int, *, declared: bool
148
+ ) -> PendingAssessment | None:
149
+ """The assessment at ``index``, unless its outcome is already chosen.
150
+
151
+ A chosen outcome is kept on the assessment's record, so a stop between
152
+ the answer and the next step, such as a pass gate, never asks again.
153
+ """
154
+ if state.item_executions[index].assessment_outcome is not None:
155
+ return None
156
+ return _pending(plan, index, declared=declared)
157
+
158
+
159
+ def _pending(plan: WorkflowPlan, index: int, *, declared: bool) -> PendingAssessment:
160
+ question = plan.items[index].assessment_question
161
+ assert question is not None # pragma: no cover - callers check it
162
+ return PendingAssessment(
163
+ index, question, assessment_outcomes(plan, index), declared
164
+ )
165
+
166
+
167
+ def _assessment_parent(item: PlanItem) -> str | None:
168
+ """Resolve template paths retained by previously materialized snapshots.
169
+
170
+ Keep the persisted plan unchanged so its digest and completed work remain
171
+ valid. Concrete paths bind each outcome to its own item or child.
172
+ """
173
+ parent = item.assessment_parent
174
+ if parent is None:
175
+ return None
176
+ parts = parent.split("/")
177
+ step_parts = item.step.split("/")
178
+ for index, part in enumerate(parts):
179
+ if part in {"{item}", "{child}"} and index < len(step_parts):
180
+ parts[index] = step_parts[index]
181
+ return "/".join(parts)
ww/assets/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Package data used when initializing a project."""
@@ -0,0 +1,49 @@
1
+ # Working with ww
2
+
3
+ ww saves progress, runs handlers, and names the next role; it is only for
4
+ development. Where ww is used by default, every file change goes through ww
5
+ unless the user asks you not to. Reviews, questions, investigations and other
6
+ read-only work never use ww, its skill or a task. When no workflow fits a change, run
7
+ `./ww lookup [<task>] --agent <agent>` and follow it; it never creates a task without operator confirmation.
8
+
9
+ ## Start here
10
+
11
+ Before starting new work, run:
12
+
13
+ ```console
14
+ ./ww discover
15
+ ```
16
+
17
+ It lists the project's workflows, modes, the start command, resume commands, and
18
+ whether to use ww unasked. If disabled, stop; if used only on request, use it only when
19
+ the user asks for ww. Choose the matching workflow and keep its default modes
20
+ unless the request matches another mode; ask only when the choice would
21
+ materially change the work.
22
+
23
+ When a request names an external ticket, use that as the task ID. Omit the ID
24
+ only when none is named or the workflow obtains it in its first step.
25
+
26
+ To continue an existing task, run `./ww instruction <task-id> --role manager`;
27
+ a worker resuming its assignment uses `--role worker`. Check status with
28
+ `./ww status <task-id>`.
29
+
30
+ ## Rules
31
+ - Follow each authoritative ww page: replace placeholders and supply requested
32
+ values until completion, error, or assignment end. One completion rarely ends a task.
33
+ - Perform only agent-owned work. Never run or bypass a ww handler, edit ww state, or read `ww.yaml` or ww source to infer the next step.
34
+ - For handler repair, fix the cause and complete with an artifact; ww retries it. In `auto`, the manager dispatches repairs with `next`.
35
+ - On an `interactive: true` step, converse in the operator session and finish
36
+ on clear contextual intent; ask if it is ambiguous. "Done for today" means
37
+ pause. Record both sides and end the interaction before completing it.
38
+ - Use a host choice tool when available, following its contract; otherwise use
39
+ a numbered chat list. Keep async choices pending until an answer arrives;
40
+ timeout, dismissal, and preselection are not answers.
41
+ - On nonzero exit, read the full response. For "Fix required", repair and complete again
42
+ (`./ww check <task-id>` previews checks) or `./ww dispute`; record impossible work
43
+ with `./ww fail` and the page's role/assignment. On `awaiting_operator`, stop and
44
+ report the reason and exact error; run only the operator's chosen recovery option;
45
+ never reset unasked. A standing operator authorization for routine repairs (dependency
46
+ installation, formatting, retries) covers such stops: apply it, asking only for a
47
+ material decision or an action outside it.
48
+ - Relay feedback-deduction suggestions after completion; use its skill and ww
49
+ commands. Rule approval and pruning are separate follow-ups.