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/changes.py ADDED
@@ -0,0 +1,225 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """The files a step changed, measured with git between two tree marks.
3
+
4
+ A mark is the tree git would commit if everything in the working directory,
5
+ tracked or not, were staged: ``git add -A`` then ``git write-tree`` against a
6
+ temporary copy of the index, so the real index, the stash, and the working
7
+ tree are never touched. The change set of a step is the difference between
8
+ the mark taken when the step began and the one taken when it completes, so
9
+ work that was already uncommitted before the step cancels out and a commit
10
+ made during the step still counts.
11
+
12
+ Without git there is no change set: a rule's globs then select every file
13
+ in the directory, and the report says so.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ import re
20
+ import shutil
21
+ import subprocess
22
+ import tempfile
23
+ from collections.abc import Iterable
24
+ from pathlib import Path
25
+
26
+ from ww.config_files import RULE_AUTOMATION_FILE
27
+
28
+ # Directories never part of the files a rule may check, and ww's own
29
+ # rule-automation store at the root, which verification itself writes.
30
+ _EXCLUDED = frozenset({".git", ".ww", RULE_AUTOMATION_FILE})
31
+
32
+
33
+ def take_mark(workdir: Path) -> str | None:
34
+ """The tree id of everything in ``workdir``, or ``None`` without git."""
35
+ if not _inside_work_tree(workdir):
36
+ return None
37
+ index = _git(workdir, "rev-parse", "--git-path", "index")
38
+ if index is None:
39
+ return None
40
+ real_index = Path(index.strip())
41
+ if not real_index.is_absolute():
42
+ real_index = workdir / real_index
43
+ with tempfile.TemporaryDirectory(prefix="ww-mark-") as scratch:
44
+ temporary = Path(scratch) / "index"
45
+ if real_index.is_file():
46
+ shutil.copyfile(real_index, temporary)
47
+ environment = {"GIT_INDEX_FILE": str(temporary)}
48
+ # ww's own rule-automation store is written while a step's
49
+ # verification runs; leaving it out keeps the mark of an unchanged
50
+ # tree stable, so a held completion's checks need not run twice.
51
+ added = _git(
52
+ workdir,
53
+ "add",
54
+ "-A",
55
+ "--",
56
+ ":/",
57
+ f":(exclude){RULE_AUTOMATION_FILE}",
58
+ environment=environment,
59
+ )
60
+ if added is None:
61
+ return None
62
+ tree = _git(workdir, "write-tree", environment=environment)
63
+ return tree.strip() if tree else None
64
+
65
+
66
+ def changed_files(workdir: Path, mark_a: str, mark_b: str) -> tuple[str, ...]:
67
+ """Files added, copied, modified, or renamed between two marks, sorted.
68
+
69
+ Paths are relative to ``workdir``. Deleted files are left out: there is
70
+ nothing in them to check. So are ww's own state under ``.ww`` and entries
71
+ that are not regular files, such as a nested repository.
72
+ """
73
+ output = _git(
74
+ workdir,
75
+ "diff-tree",
76
+ "-r",
77
+ "--name-only",
78
+ "--no-renames",
79
+ "--diff-filter=ACMR",
80
+ "--relative",
81
+ mark_a,
82
+ mark_b,
83
+ )
84
+ if output is None:
85
+ return ()
86
+ return tuple(
87
+ sorted(
88
+ line
89
+ for line in output.splitlines()
90
+ if line
91
+ and line.split("/", 1)[0] not in _EXCLUDED
92
+ and (workdir / line).is_file()
93
+ )
94
+ )
95
+
96
+
97
+ def all_files(workdir: Path) -> tuple[str, ...]:
98
+ """Every file under ``workdir``, relative and sorted, for a run without git.
99
+
100
+ ``.git``, ``.ww`` and the rule-automation store are skipped. Git
101
+ worktrees need git, so a directory without git has none to skip.
102
+ """
103
+ found: list[str] = []
104
+ for directory, names, files in os.walk(workdir):
105
+ relative = Path(directory).relative_to(workdir).as_posix()
106
+ names[:] = sorted(name for name in names if name not in _EXCLUDED)
107
+ found.extend(
108
+ _join(relative, name)
109
+ for name in files
110
+ if relative not in {"", "."} or name not in _EXCLUDED
111
+ )
112
+ return tuple(sorted(found))
113
+
114
+
115
+ def project_files(workdir: Path) -> tuple[str, ...]:
116
+ """The files a glob in ``workdir`` could match, relative and sorted.
117
+
118
+ With git: tracked files and untracked ones git does not ignore, so a
119
+ dependency directory listed in ``.gitignore`` does not count. Without
120
+ git: :func:`all_files`.
121
+ """
122
+ if not _inside_work_tree(workdir):
123
+ return all_files(workdir)
124
+ output = _git(
125
+ workdir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"
126
+ )
127
+ if output is None:
128
+ return all_files(workdir)
129
+ return tuple(
130
+ sorted(
131
+ {
132
+ path
133
+ for path in output.split("\0")
134
+ if path and path.split("/", 1)[0] not in _EXCLUDED
135
+ }
136
+ )
137
+ )
138
+
139
+
140
+ def select_files(files: Iterable[str], globs: tuple[str, ...]) -> tuple[str, ...]:
141
+ """The files matching any glob; every file when there are no globs.
142
+
143
+ ``*`` and ``?`` stay within one path segment, ``**`` spans any number of
144
+ segments, and a glob without a ``/`` matches a file's name anywhere, so
145
+ ``*.py`` and ``**/*.py`` mean the same.
146
+ """
147
+ if not globs:
148
+ return tuple(files)
149
+ patterns = tuple(_compile(glob) for glob in globs)
150
+ return tuple(
151
+ path for path in files if any(pattern.fullmatch(path) for pattern in patterns)
152
+ )
153
+
154
+
155
+ def _compile(glob: str) -> re.Pattern[str]:
156
+ glob = glob.strip().removeprefix("./")
157
+ if "/" not in glob.rstrip("/"):
158
+ glob = f"**/{glob}"
159
+ if glob.endswith("/"):
160
+ glob += "**"
161
+ parts: list[str] = []
162
+ segments = glob.split("/")
163
+ for index, segment in enumerate(segments):
164
+ last = index == len(segments) - 1
165
+ if segment == "**":
166
+ parts.append(".*" if last else "(?:[^/]+/)*")
167
+ continue
168
+ parts.append(_segment(segment) + ("" if last else "/"))
169
+ # The glob over a whole path: "src/**/*.py" matches "src/cli.py" and
170
+ # "src/ww/cli.py", but not "docs/src/cli.py".
171
+ return re.compile("".join(parts))
172
+
173
+
174
+ def _segment(segment: str) -> str:
175
+ result: list[str] = []
176
+ index = 0
177
+ while index < len(segment):
178
+ char = segment[index]
179
+ if char == "*":
180
+ result.append("[^/]*")
181
+ elif char == "?":
182
+ result.append("[^/]")
183
+ elif char == "[":
184
+ end = segment.find("]", index + 1)
185
+ if end == -1:
186
+ result.append(re.escape(char))
187
+ else:
188
+ body = segment[index + 1 : end]
189
+ if body.startswith("!"):
190
+ body = "^" + body[1:]
191
+ result.append(f"[{body}]")
192
+ index = end
193
+ else:
194
+ result.append(re.escape(char))
195
+ index += 1
196
+ return "".join(result)
197
+
198
+
199
+ def _join(directory: str, name: str) -> str:
200
+ return name if directory in {"", "."} else f"{directory}/{name}"
201
+
202
+
203
+ def _inside_work_tree(workdir: Path) -> bool:
204
+ output = _git(workdir, "rev-parse", "--is-inside-work-tree")
205
+ return output is not None and output.strip() == "true"
206
+
207
+
208
+ def _git(
209
+ workdir: Path, *arguments: str, environment: dict[str, str] | None = None
210
+ ) -> str | None:
211
+ """Run git in ``workdir``; ``None`` when git is missing or the call fails."""
212
+ try:
213
+ completed = subprocess.run(
214
+ ["git", *arguments],
215
+ cwd=workdir,
216
+ capture_output=True,
217
+ text=True,
218
+ check=False,
219
+ env={**os.environ, **(environment or {})},
220
+ )
221
+ except OSError:
222
+ return None
223
+ if completed.returncode != 0:
224
+ return None
225
+ return completed.stdout