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,115 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Bash allow rules for ww's role commands, in Claude Code's local settings.
3
+
4
+ ``init`` offers to write them so an agent running a task is not asked about
5
+ every ``ww`` call. The rules name the project's wrapper by absolute path,
6
+ never a bare ``ww``: only the project's own launcher is allowed. Merging adds
7
+ the missing rules and keeps every other key and rule as it was.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import subprocess
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from ww.errors import StateError
18
+
19
+ SETTINGS_FILE = ".claude/settings.local.json"
20
+ # The role commands an agent runs; ``discover`` and its variants take no space.
21
+ ROLE_COMMANDS = (
22
+ "instruction *",
23
+ "next *",
24
+ "complete *",
25
+ "fail *",
26
+ "dispute *",
27
+ "check *",
28
+ "status *",
29
+ "artifacts *",
30
+ "items *",
31
+ "item *",
32
+ "add-item *",
33
+ "update-item *",
34
+ "interact *",
35
+ "loop *",
36
+ "lookup *",
37
+ "discover*",
38
+ "requirements *",
39
+ )
40
+
41
+
42
+ def role_rules(wrapper: Path) -> tuple[str, ...]:
43
+ """The allow rules for the project wrapper at the absolute path ``wrapper``."""
44
+ return tuple(f"Bash({wrapper} {command})" for command in ROLE_COMMANDS)
45
+
46
+
47
+ def merged(document: dict[str, Any], rules: tuple[str, ...]) -> dict[str, Any]:
48
+ """``document`` with ``rules`` appended to ``permissions.allow``, none dropped."""
49
+ permissions = document.get("permissions", {})
50
+ if not isinstance(permissions, dict):
51
+ raise StateError("permissions in the settings file must be an object")
52
+ allowed = permissions.get("allow", [])
53
+ if not isinstance(allowed, list):
54
+ raise StateError("permissions.allow in the settings file must be a list")
55
+ missing = [rule for rule in rules if rule not in allowed]
56
+ return {
57
+ **document,
58
+ "permissions": {**permissions, "allow": [*allowed, *missing]},
59
+ }
60
+
61
+
62
+ def install_rules(root: Path, rules: tuple[str, ...]) -> bool:
63
+ """Merge ``rules`` into the project's local settings; ``True`` if it changed.
64
+
65
+ A file that is not a JSON object is never overwritten: the error names it.
66
+ """
67
+ path = root / SETTINGS_FILE
68
+ document: dict[str, Any] = {}
69
+ if path.exists():
70
+ try:
71
+ loaded = json.loads(path.read_text(encoding="utf-8"))
72
+ except (OSError, json.JSONDecodeError) as error:
73
+ raise StateError(f"cannot read {SETTINGS_FILE}: {error}") from error
74
+ if not isinstance(loaded, dict):
75
+ raise StateError(f"{SETTINGS_FILE} must hold a JSON object")
76
+ document = loaded
77
+ updated = merged(document, rules)
78
+ if updated == document:
79
+ return False
80
+ path.parent.mkdir(parents=True, exist_ok=True)
81
+ path.write_text(json.dumps(updated, indent=2) + "\n", encoding="utf-8")
82
+ return True
83
+
84
+
85
+ def ensure_ignored(root: Path) -> bool:
86
+ """Keep the local settings file out of Git; ``True`` if ``.gitignore`` changed."""
87
+ if _ignored(root):
88
+ return False
89
+ gitignore = root / ".gitignore"
90
+ text = gitignore.read_text(encoding="utf-8") if gitignore.is_file() else ""
91
+ separator = "\n" if text and not text.endswith("\n") else ""
92
+ gitignore.write_text(f"{text}{separator}{SETTINGS_FILE}\n", encoding="utf-8")
93
+ return True
94
+
95
+
96
+ def _ignored(root: Path) -> bool:
97
+ if (root / ".git").exists():
98
+ try:
99
+ checked = subprocess.run(
100
+ ["git", "check-ignore", "-q", SETTINGS_FILE],
101
+ cwd=root,
102
+ capture_output=True,
103
+ check=False,
104
+ )
105
+ except OSError:
106
+ pass
107
+ else:
108
+ return checked.returncode == 0
109
+ gitignore = root / ".gitignore"
110
+ if not gitignore.is_file():
111
+ return False
112
+ lines = {
113
+ line.strip() for line in gitignore.read_text(encoding="utf-8").splitlines()
114
+ }
115
+ return bool(lines & {SETTINGS_FILE, f"/{SETTINGS_FILE}", ".claude/*.local.json"})
ww/cli/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Public command-line entry points for ww."""
3
+
4
+ from .main import main
5
+ from .parser import build_parser
6
+
7
+ __all__ = ["build_parser", "main"]
ww/cli/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Run ww's command-line interface as a module."""
3
+
4
+ from .main import main
5
+
6
+ raise SystemExit(main())
ww/cli/audit.py ADDED
@@ -0,0 +1,129 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Narrow, redacted audit records for each ww invocation."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import shlex
8
+ from datetime import datetime, timezone
9
+
10
+ from ww import __version__
11
+
12
+
13
+ def _log_record(
14
+ command: str,
15
+ workflow: str | None,
16
+ task_id: str | None,
17
+ outcome: str,
18
+ error: str | None,
19
+ args: argparse.Namespace,
20
+ ) -> dict[str, str | None]:
21
+ return {
22
+ "invocation_id": args.invocation_id,
23
+ "timestamp": datetime.now(timezone.utc)
24
+ .replace(microsecond=0)
25
+ .isoformat()
26
+ .replace("+00:00", "Z"),
27
+ "command": command,
28
+ "invocation": _invocation(args),
29
+ "workflow": workflow,
30
+ "task_id": task_id,
31
+ "outcome": outcome,
32
+ # Errors, artifacts, command output, and task state can contain
33
+ # arbitrary user/provider text. The audit log is intentionally narrow
34
+ # and never duplicates that text.
35
+ "error": "protected runtime detail; see task state" if error else None,
36
+ "error_code": "command_failed" if error else None,
37
+ "ww_version": __version__,
38
+ # Who confirmed a gated choice: the operator at a terminal, or an
39
+ # agent carrying out the operator's decision with --yes.
40
+ "confirmation": getattr(args, "confirmation", None),
41
+ }
42
+
43
+
44
+ def _invocation(args: argparse.Namespace) -> str:
45
+ values = ["ww-agentic-workflows", args.command]
46
+ if args.command == "start":
47
+ values.extend(["--workflow", args.workflow])
48
+ task_id = getattr(args, "task_id", None)
49
+ if (
50
+ args.command
51
+ in {
52
+ "start",
53
+ "next",
54
+ "complete",
55
+ "fail",
56
+ "status",
57
+ "instruction",
58
+ "metadata",
59
+ "reset",
60
+ }
61
+ and task_id
62
+ ):
63
+ values.append(task_id)
64
+ if args.command == "start":
65
+ for mode in args.modes:
66
+ values.extend(["--mode", mode])
67
+ values.extend(
68
+ [
69
+ "--agent",
70
+ args.agent,
71
+ "--runtime",
72
+ args.workflow_runtime,
73
+ "--model",
74
+ args.model,
75
+ "--reasoning",
76
+ args.reasoning,
77
+ ]
78
+ )
79
+ if getattr(args, "role", None) is not None:
80
+ values.extend(["--role", args.role])
81
+ if getattr(args, "assignment", None) is not None:
82
+ values.extend(["--assignment", args.assignment])
83
+ if args.command == "plan":
84
+ values.extend(["--workflow", args.workflow, "--agent", args.agent])
85
+ if args.task_id:
86
+ values.extend(["--task-id", args.task_id])
87
+ if args.command == "next":
88
+ values.extend(["--model", args.model, "--reasoning", args.reasoning])
89
+ if args.selected_agent:
90
+ values.extend(["--selected-agent", args.selected_agent])
91
+ if args.force:
92
+ values.append("--force")
93
+ if args.retry:
94
+ values.append("--retry")
95
+ if args.force_reason is not None:
96
+ values.extend(["--reason", "<redacted>"])
97
+ if args.reassign:
98
+ values.append("--reassign")
99
+ if args.replan:
100
+ values.append("--replan")
101
+ if args.keep_plan:
102
+ values.append("--keep-plan")
103
+ if args.yes:
104
+ values.append("--yes")
105
+ if args.command in {"status", "instruction"} and args.run_id:
106
+ values.extend(["--run", args.run_id])
107
+ if args.command == "metadata" and args.project:
108
+ values.append("--project")
109
+ if args.command == "complete":
110
+ for key in ("selected_agent", "selected_model", "selected_reasoning"):
111
+ value = getattr(args, key, None)
112
+ if value is not None:
113
+ values.extend(["--" + key.replace("_", "-"), value])
114
+ for variable in args.variable:
115
+ values.extend(["--variable", _redacted_variable(variable)])
116
+ for metadata_value in args.metadata:
117
+ values.extend(["--metadata", _redacted_variable(metadata_value)])
118
+ if args.command == "fail":
119
+ values.extend(["--error", "<redacted>"])
120
+ if args.command == "reset" and args.yes:
121
+ values.append("--yes")
122
+ if getattr(args, "json_output", False):
123
+ values.append("--json")
124
+ return shlex.join(values)
125
+
126
+
127
+ def _redacted_variable(value: str) -> str:
128
+ name, separator, _ = value.partition("=")
129
+ return f"{name}=<redacted>" if separator else "<redacted>"
ww/cli/catalogs.py ADDED
@@ -0,0 +1,131 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """JSON catalogs of modes, workflows, runtimes, agents, and extensions."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+
8
+ from ww.config import load_configuration, load_modes
9
+ from ww.discovery import AGENT_DIRECTORIES, CUSTOM_AGENT_PREFIX
10
+ from ww.executable import ww_command
11
+ from ww.extensions import ExtensionRegistry
12
+ from ww.runtimes import RUNTIME_INSTRUCTIONS
13
+ from ww.storage import Storage
14
+
15
+
16
+ def _catalog_extensions(extensions: ExtensionRegistry) -> str:
17
+ """Render every discovered extension and what it contributes."""
18
+ return json.dumps(
19
+ {
20
+ "extensions": [
21
+ {
22
+ "id": extension.identifier,
23
+ "version": extension.version,
24
+ "api_version": extension.api_version,
25
+ "source": extensions.identity(extension.identifier).source,
26
+ "description": extension.description,
27
+ "handlers": [
28
+ {
29
+ "reference": (
30
+ f"ext/{extension.identifier}/handlers:{handler.name}"
31
+ ),
32
+ "description": handler.description,
33
+ "inputs": [value.to_dict() for value in handler.provide],
34
+ "outputs": list(handler.outputs),
35
+ }
36
+ for handler in extension.handlers
37
+ ],
38
+ "modes": [
39
+ {
40
+ "reference": (
41
+ f"ext/{extension.identifier}/modes:{mode.name}"
42
+ ),
43
+ "description": list(mode.description),
44
+ }
45
+ for mode in extension.modes
46
+ ],
47
+ "commands": [
48
+ {
49
+ "name": command.name,
50
+ "description": command.description,
51
+ "usage": (
52
+ f"{ww_command()} extension "
53
+ f"{extension.identifier} "
54
+ f"{command.usage or command.name}"
55
+ ),
56
+ }
57
+ for command in extension.commands
58
+ ],
59
+ "variable_overrides": [
60
+ variable.name for variable in extension.variables
61
+ ],
62
+ }
63
+ for extension in extensions.extensions
64
+ ]
65
+ },
66
+ indent=2,
67
+ )
68
+
69
+
70
+ def _catalog_modes(storage: Storage, extensions: ExtensionRegistry) -> str:
71
+ """Render configured and extension modes after validating the workflow file."""
72
+ modes = load_modes(storage.config_path, extensions)
73
+ modes.update({mode.name: mode for mode in extensions.qualified_modes()})
74
+ return json.dumps({"modes": [mode.to_dict() for mode in modes.values()]}, indent=2)
75
+
76
+
77
+ def _catalog_workflows(storage: Storage, extensions: ExtensionRegistry) -> str:
78
+ """Render the configured workflow choices from validated configuration."""
79
+ configuration = load_configuration(storage.config_path, extensions)
80
+ return json.dumps(
81
+ {
82
+ "workflows": [
83
+ {
84
+ "name": workflow.name,
85
+ "description": workflow.description,
86
+ "modes": list(workflow.modes),
87
+ "runtime": workflow.runtime,
88
+ }
89
+ for workflow in configuration.workflows
90
+ ]
91
+ },
92
+ indent=2,
93
+ )
94
+
95
+
96
+ def _catalog_runtimes() -> str:
97
+ """Render the fixed runtime registry."""
98
+ return json.dumps(
99
+ {
100
+ "runtimes": [
101
+ {"name": name, "instructions": list(instructions)}
102
+ for name, instructions in RUNTIME_INSTRUCTIONS.items()
103
+ ]
104
+ },
105
+ indent=2,
106
+ )
107
+
108
+
109
+ def _catalog_projects(extensions: ExtensionRegistry) -> str:
110
+ """Render the configured projects, the directories tasks may work in."""
111
+ return json.dumps(
112
+ {"projects": [project.to_dict() for project in extensions.config.projects]},
113
+ indent=2,
114
+ )
115
+
116
+
117
+ def _catalog_agents() -> str:
118
+ """Render supported agent integrations and the custom-agent escape hatch."""
119
+ return json.dumps(
120
+ {
121
+ "agents": [
122
+ {"name": name, "directory": directory}
123
+ for name, directory in AGENT_DIRECTORIES.items()
124
+ ],
125
+ "custom": {
126
+ "prefix": CUSTOM_AGENT_PREFIX,
127
+ "description": "Use custom:<name> for an arbitrary agent name.",
128
+ },
129
+ },
130
+ indent=2,
131
+ )