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/claude_permissions.py
ADDED
|
@@ -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
ww/cli/__main__.py
ADDED
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
|
+
)
|