"""c_work_graph_execute (03-C §3.5): dynamic-workflow invocation skeleton. U-006 scope: write an orchestration script into the target project's `.claude/workflows/.js` and invoke it via `claude -p 'Run / with args {...}'`, reusing the exact code path the A-1 spike validated (spikes/a1-dynamic-workflows/, confirmed 2026-07-29). No budget-usage bookkeeping or CLASSIFIER_BLOCKED handling yet -- that's U-012's job ("骨架" here, per development-plan.md). """ import json import os import subprocess class WorkGraphExecuteError(Exception): """Raised when the claude subprocess exits non-zero, reports is_error, or produces no structured_output.""" def write_workflow_script(target_folder_path, name, script_content): """Writes `/.claude/workflows/.js` -- versioned with the target project's own git history (03-C §3.5 step 1), not graphlemon's.""" workflows_dir = os.path.join(target_folder_path, ".claude", "workflows") os.makedirs(workflows_dir, exist_ok=True) path = os.path.join(workflows_dir, name + ".js") with open(path, "w", encoding="utf-8") as f: f.write(script_content) return path def execute_work_graph(target_folder_path, name, args, output_schema, timeout=600): """Runs the workflow already written to `.claude/workflows/.js` (write_workflow_script) via `claude -p 'Run / with args {...}'`. No `--bare` (A-1 spike: skips credential/login discovery, unrelated to workflow presence). `--permission-mode auto`, never `bypassPermissions`/ `--dangerously-skip-permissions` (05 §5.3.1). Returns the parsed `structured_output` dict. """ prompt = "Run /{} with args {}".format(name, json.dumps(args)) result = subprocess.run( [ "claude", "-p", prompt, "--output-format", "json", "--json-schema", json.dumps(output_schema), "--permission-mode", "auto", ], cwd=target_folder_path, stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=timeout, ) if result.returncode != 0: raise WorkGraphExecuteError( "workflow {} exited {}: {}".format(name, result.returncode, result.stderr) ) payload = json.loads(result.stdout) if payload.get("is_error"): raise WorkGraphExecuteError("workflow {} reported is_error: {}".format(name, payload)) structured_output = payload.get("structured_output") if structured_output is None: raise WorkGraphExecuteError("workflow {} produced no structured_output: {}".format(name, payload)) return structured_output