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.
- ww/__init__.py +18 -0
- ww/_bundled_extensions/ww/git/extension.py +1728 -0
- ww/action_execution.py +887 -0
- ww/actions/__init__.py +94 -0
- ww/actions/command.py +444 -0
- ww/actions/contracts.py +699 -0
- ww/actions/extension.py +197 -0
- ww/actions/mcp.py +84 -0
- ww/actions/prompt.py +74 -0
- ww/actions/skill.py +62 -0
- ww/actions/slash_command.py +63 -0
- ww/agents.py +151 -0
- ww/amendments.py +54 -0
- ww/artifacts.py +93 -0
- ww/assessments.py +181 -0
- ww/assets/__init__.py +2 -0
- ww/assets/agent_instructions.md +49 -0
- ww/assets/docs/examples.md +879 -0
- ww/assets/docs/features.md +4639 -0
- ww/assets/docs/specification.md +1876 -0
- ww/assets/noww_skill.md +11 -0
- ww/assets/workflows/catchall.yaml +26 -0
- ww/assets/workflows/onboarding.yaml +586 -0
- ww/assets/workflows/scriptize.yaml +130 -0
- ww/assets/ww-automate_skill.md +23 -0
- ww/assets/ww-deduce-feedback_skill.md +38 -0
- ww/assets/ww-feedback-rules_skill.md +48 -0
- ww/assets/ww-learn-project_skill.md +22 -0
- ww/assets/ww-refresh_skill.md +26 -0
- ww/assets/ww-rule_skill.md +83 -0
- ww/assets/ww-rules-from-artifacts_skill.md +22 -0
- ww/assets/ww-scriptize_skill.md +33 -0
- ww/assets/ww-setup_skill.md +94 -0
- ww/assets/ww-solve_skill.md +23 -0
- ww/assets/ww-suggest_skill.md +32 -0
- ww/assets/ww-wizard_skill.md +105 -0
- ww/assets/ww_skill.md +59 -0
- ww/assignments.py +283 -0
- ww/bootstrap.py +405 -0
- ww/builtin_workflows.py +215 -0
- ww/changes.py +225 -0
- ww/child_coordination.py +482 -0
- ww/children.py +106 -0
- ww/claude_permissions.py +115 -0
- ww/cli/__init__.py +7 -0
- ww/cli/__main__.py +6 -0
- ww/cli/audit.py +129 -0
- ww/cli/catalogs.py +131 -0
- ww/cli/discover.py +607 -0
- ww/cli/initialization.py +898 -0
- ww/cli/lookup.py +287 -0
- ww/cli/main.py +1768 -0
- ww/cli/parser.py +1200 -0
- ww/cli/prompts.py +217 -0
- ww/cli/updates.py +117 -0
- ww/completion_artifacts.py +156 -0
- ww/completion_inputs.py +39 -0
- ww/config/__init__.py +582 -0
- ww/config/actions.py +591 -0
- ww/config/composition.py +571 -0
- ww/config/rules.py +511 -0
- ww/config/steps.py +1220 -0
- ww/config/values.py +223 -0
- ww/config_files.py +191 -0
- ww/config_writes.py +264 -0
- ww/contracts.py +155 -0
- ww/control.py +41 -0
- ww/defaults.py +130 -0
- ww/design_docs.py +32 -0
- ww/discovery.py +104 -0
- ww/documents.py +217 -0
- ww/errors.py +18 -0
- ww/executable.py +43 -0
- ww/execution_models/__init__.py +64 -0
- ww/execution_models/construction.py +148 -0
- ww/execution_models/decoding.py +38 -0
- ww/execution_models/plan_codec.py +565 -0
- ww/execution_models/records.py +1206 -0
- ww/execution_models/runs.py +266 -0
- ww/extensions/__init__.py +40 -0
- ww/extensions/api.py +559 -0
- ww/extensions/registry.py +864 -0
- ww/extensions/store.py +78 -0
- ww/feedback.py +342 -0
- ww/handler_repairs.py +57 -0
- ww/hooks/__init__.py +40 -0
- ww/hooks/agents.py +380 -0
- ww/hooks/install.py +168 -0
- ww/hooks/notices.py +206 -0
- ww/hooks/records.py +209 -0
- ww/hooks/runtime.py +266 -0
- ww/hooks/transcripts.py +183 -0
- ww/inspect.py +896 -0
- ww/instructions/__init__.py +17 -0
- ww/instructions/builder.py +1682 -0
- ww/instructions/commands.py +335 -0
- ww/instructions/handoff.py +149 -0
- ww/instructions/models.py +686 -0
- ww/instructions/policy.py +219 -0
- ww/instructions/text.py +168 -0
- ww/interactions.py +187 -0
- ww/interpolation.py +37 -0
- ww/item_passes.py +167 -0
- ww/items.py +99 -0
- ww/locking.py +207 -0
- ww/metadata_publication.py +230 -0
- ww/onboarding.py +229 -0
- ww/open_work.py +236 -0
- ww/operations.py +193 -0
- ww/operator_ui/__init__.py +16 -0
- ww/operator_ui/page.html +351 -0
- ww/operator_ui/server.py +215 -0
- ww/operator_ui/session.py +389 -0
- ww/operator_ui/sheet.py +104 -0
- ww/operator_ui/view.py +109 -0
- ww/output.py +339 -0
- ww/output_adapters/__init__.py +12 -0
- ww/output_adapters/base.py +25 -0
- ww/output_adapters/json_adapter.py +37 -0
- ww/output_adapters/markdown.py +2293 -0
- ww/output_adapters/rule_pages.py +337 -0
- ww/output_adapters/terminal.py +21 -0
- ww/package_updates.py +167 -0
- ww/plan/__init__.py +38 -0
- ww/plan/actions.py +207 -0
- ww/plan/compiler.py +1492 -0
- ww/plan/constructs.py +456 -0
- ww/plan/models.py +665 -0
- ww/project_config.py +752 -0
- ww/recovery.py +401 -0
- ww/replanning.py +367 -0
- ww/results.py +77 -0
- ww/rule_checks.py +230 -0
- ww/rule_conversion.py +331 -0
- ww/rule_disputes.py +148 -0
- ww/rule_store.py +456 -0
- ww/rule_verification.py +714 -0
- ww/rule_views.py +447 -0
- ww/rule_writes.py +920 -0
- ww/run_coordination.py +158 -0
- ww/runtimes.py +105 -0
- ww/service.py +4405 -0
- ww/setup_apply.py +428 -0
- ww/step_values.py +20 -0
- ww/storage.py +447 -0
- ww/storage_adapters/__init__.py +36 -0
- ww/storage_adapters/base.py +540 -0
- ww/storage_adapters/filesystem.py +370 -0
- ww/storage_adapters/memory.py +195 -0
- ww/storage_adapters/project_metadata.py +69 -0
- ww/storage_adapters/task_document.py +484 -0
- ww/task_ids.py +114 -0
- ww/task_references.py +124 -0
- ww/transitions.py +1619 -0
- ww/updates.py +399 -0
- ww/upgrade.py +95 -0
- ww/validation.py +168 -0
- ww/variables.py +275 -0
- ww/workflow_config.py +854 -0
- ww/workflow_update.py +239 -0
- ww/workflow_validation.py +1260 -0
- ww/workspace.py +50 -0
- ww_agentic_workflows-1.0.0.dev3.dist-info/METADATA +690 -0
- ww_agentic_workflows-1.0.0.dev3.dist-info/RECORD +167 -0
- ww_agentic_workflows-1.0.0.dev3.dist-info/WHEEL +4 -0
- ww_agentic_workflows-1.0.0.dev3.dist-info/entry_points.txt +2 -0
- 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
|