generated from coulomb/repo-seed
declarative Markdown workflow layer
This commit is contained in:
@@ -72,6 +72,7 @@ from markitect_tool.template import (
|
||||
analyze_template,
|
||||
render_template,
|
||||
)
|
||||
from markitect_tool.workflow import WorkflowError, WorkflowRunner, load_workflow_file
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -1124,6 +1125,90 @@ def search(
|
||||
_emit_search_results(data, output_format)
|
||||
|
||||
|
||||
@main.group()
|
||||
def workflow() -> None:
|
||||
"""Inspect, plan, and run declarative Markdown workflows."""
|
||||
|
||||
|
||||
@workflow.command("inspect")
|
||||
@click.argument("workflow_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
"--format",
|
||||
"output_format",
|
||||
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
|
||||
default="text",
|
||||
show_default=True,
|
||||
)
|
||||
def workflow_inspect(workflow_file: Path, output_format: str) -> None:
|
||||
"""Inspect a workflow definition without executing steps."""
|
||||
|
||||
try:
|
||||
plan = load_workflow_file(workflow_file)
|
||||
result = WorkflowRunner(plan).inspect()
|
||||
except WorkflowError as exc:
|
||||
raise click.ClickException(str(exc)) from exc
|
||||
_emit_workflow_result(result.to_dict() | {"workflow": plan.to_dict()}, output_format)
|
||||
raise click.exceptions.Exit(0 if result.valid else 1)
|
||||
|
||||
|
||||
@workflow.command("plan")
|
||||
@click.argument("workflow_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
"--output-dir",
|
||||
type=click.Path(file_okay=False, path_type=Path),
|
||||
help="Output root for path-safety checks. No files are written in plan mode.",
|
||||
)
|
||||
@click.option(
|
||||
"--format",
|
||||
"output_format",
|
||||
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
|
||||
default="text",
|
||||
show_default=True,
|
||||
)
|
||||
def workflow_plan(workflow_file: Path, output_dir: Path | None, output_format: str) -> None:
|
||||
"""Run a workflow in dry-run mode and report planned outputs."""
|
||||
|
||||
try:
|
||||
plan = load_workflow_file(workflow_file)
|
||||
result = WorkflowRunner(plan, output_dir=output_dir).run(dry_run=True)
|
||||
except WorkflowError as exc:
|
||||
raise click.ClickException(str(exc)) from exc
|
||||
_emit_workflow_result(result.to_dict(), output_format)
|
||||
raise click.exceptions.Exit(0 if result.valid else 1)
|
||||
|
||||
|
||||
@workflow.command("run")
|
||||
@click.argument("workflow_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
"--output-dir",
|
||||
type=click.Path(file_okay=False, path_type=Path),
|
||||
help="Output root for workflow outputs. Defaults to the workflow directory.",
|
||||
)
|
||||
@click.option("--dry-run", is_flag=True, help="Execute without writing output files.")
|
||||
@click.option(
|
||||
"--format",
|
||||
"output_format",
|
||||
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
|
||||
default="text",
|
||||
show_default=True,
|
||||
)
|
||||
def workflow_run(
|
||||
workflow_file: Path,
|
||||
output_dir: Path | None,
|
||||
dry_run: bool,
|
||||
output_format: str,
|
||||
) -> None:
|
||||
"""Run a deterministic Markdown workflow."""
|
||||
|
||||
try:
|
||||
plan = load_workflow_file(workflow_file)
|
||||
result = WorkflowRunner(plan, output_dir=output_dir).run(dry_run=dry_run)
|
||||
except WorkflowError as exc:
|
||||
raise click.ClickException(str(exc)) from exc
|
||||
_emit_workflow_result(result.to_dict(), output_format)
|
||||
raise click.exceptions.Exit(0 if result.valid else 1)
|
||||
|
||||
|
||||
@main.group()
|
||||
def template() -> None:
|
||||
"""Render and inspect deterministic Markdown templates."""
|
||||
@@ -1560,6 +1645,32 @@ def _emit_search_results(data: dict, output_format: str) -> None:
|
||||
click.echo(f" {preview[:160]}")
|
||||
|
||||
|
||||
def _emit_workflow_result(data: dict, output_format: str) -> None:
|
||||
if output_format == "json":
|
||||
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
elif output_format == "yaml":
|
||||
click.echo(yaml.safe_dump(data, sort_keys=False))
|
||||
else:
|
||||
click.echo("valid" if data.get("valid", True) else "invalid")
|
||||
click.echo(f"workflow: {data.get('workflow_id') or data.get('workflow', {}).get('id', '<unknown>')}")
|
||||
if data.get("sources"):
|
||||
click.echo(f"sources: {len(data['sources'])}")
|
||||
for source_id, source_data in data["sources"].items():
|
||||
click.echo(f"- {source_id}: {source_data.get('count', 1)}")
|
||||
if data.get("steps"):
|
||||
click.echo(f"steps: {len(data['steps'])}")
|
||||
for step_id, step_data in data["steps"].items():
|
||||
click.echo(f"- {step_id}: {step_data.get('kind', '<unknown>')}")
|
||||
if data.get("outputs"):
|
||||
click.echo(f"outputs: {len(data['outputs'])}")
|
||||
for output in data["outputs"]:
|
||||
status = "written" if output.get("written") else "planned"
|
||||
path = output.get("path") or "<memory>"
|
||||
click.echo(f"- {output['id']}: {status} {path}")
|
||||
for diagnostic in data.get("diagnostics", []):
|
||||
click.echo(f"! [{diagnostic['severity']}] {diagnostic['code']}: {diagnostic['message']}")
|
||||
|
||||
|
||||
def _emit_reference_result(data: dict, output_format: str) -> None:
|
||||
if output_format == "json":
|
||||
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
|
||||
Reference in New Issue
Block a user