{
"event": "PreToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Controller\/ContentPipelineController.php",
"old_string": " \/**\n * GET \/content-pipeline\/{id}\/status (AJAX)\n *\/\n public function status(string $id): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->json(['error' => 'Pipeline nicht gefunden'], 404);\n\n return;\n }\n\n $this->json([\n 'pipeline_id' => (int) $id,\n 'run' => $this->repository->findLatestRun((int) $id),\n ]);\n }",
"new_string": " \/**\n * GET \/content-pipeline\/{id}\/status (AJAX)\n *\/\n public function status(string $id): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->json(['error' => 'Pipeline nicht gefunden'], 404);\n\n return;\n }\n\n $this->json([\n 'pipeline_id' => (int) $id,\n 'run' => $this->repository->findLatestRun((int) $id),\n ]);\n }\n\n \/**\n * GET \/content-pipeline\/{id}\/run\/{runId}\/status\n *\/\n public function runStatus(string $id, string $runId): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->notFound('Pipeline nicht gefunden');\n }\n\n $run = $this->repository->findRunById((int) $runId);\n\n if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {\n $this->notFound('Run nicht gefunden');\n }\n\n $this->view('content-pipeline.run-status', [\n 'title' => 'Pipeline Status: ' . $pipeline['name'],\n 'pipeline' => $pipeline,\n 'run' => $run,\n ]);\n }\n\n \/**\n * GET \/content-pipeline\/{id}\/run\/{runId}\/poll (AJAX\/HTMX)\n *\/\n public function runStatusPoll(string $id, string $runId): void\n {\n $run = $this->repository->findRunById((int) $runId);\n\n if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {\n $this->json(['error' => 'Run nicht gefunden'], 404);\n\n return;\n }\n\n \/\/ Berechne verstrichene Zeit\n $startedAt = strtotime($run['started_at'] ?? 'now');\n $elapsed = time() - $startedAt;\n\n \/\/ Fortschrittsberechnung\n $total = (int) ($run['documents_total'] ?? 0);\n $processed = (int) ($run['documents_processed'] ?? 0);\n $progress = $total > 0 ? round(($processed \/ $total) * 100) : 0;\n\n \/\/ Geschätzte Restzeit\n $estimatedRemaining = null;\n if ($processed > 0 && $total > $processed) {\n $avgTimePerDoc = $elapsed \/ $processed;\n $remaining = $total - $processed;\n $estimatedRemaining = (int) ($avgTimePerDoc * $remaining);\n }\n\n \/\/ Stall-Erkennung (keine Aktivität seit >60s)\n $lastUpdate = strtotime($run['last_update_at'] ?? $run['started_at'] ?? 'now');\n $isStalled = (time() - $lastUpdate) > 60 && $run['status'] === 'running';\n\n \/\/ Terminal-Status (Polling stoppen wenn fertig)\n $isTerminal = in_array($run['status'], ['completed', 'failed', 'cancelled'], true);\n\n $this->json([\n 'status' => $run['status'],\n 'current_step' => $run['current_step'],\n 'current_document' => $run['current_document'],\n 'documents_total' => $total,\n 'documents_processed' => $processed,\n 'documents_failed' => (int) ($run['documents_failed'] ?? 0),\n 'chunks_created' => (int) ($run['chunks_created'] ?? 0),\n 'embeddings_created' => (int) ($run['embeddings_created'] ?? 0),\n 'progress' => $progress,\n 'elapsed' => $elapsed,\n 'elapsed_formatted' => gmdate('i:s', $elapsed),\n 'estimated_remaining' => $estimatedRemaining,\n 'estimated_formatted' => $estimatedRemaining !== null ? gmdate('i:s', $estimatedRemaining) : null,\n 'log_tail' => $run['log_tail'] ?? '',\n 'is_stalled' => $isStalled,\n 'is_terminal' => $isTerminal,\n 'completed_at' => $run['completed_at'],\n 'error_log' => $run['error_log'],\n ]);\n }\n\n \/**\n * POST \/content-pipeline\/{id}\/run\/{runId}\/cancel\n *\/\n public function runCancel(string $id, string $runId): void\n {\n $this->requireCsrf();\n\n $run = $this->repository->findRunById((int) $runId);\n\n if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {\n $this->notFound('Run nicht gefunden');\n }\n\n \/\/ Nur laufende Runs können abgebrochen werden\n if ($run['status'] !== 'running') {\n $_SESSION['error'] = 'Run kann nicht abgebrochen werden (Status: ' . $run['status'] . ')';\n $this->redirect('\/content-pipeline\/' . $id . '\/run\/' . $runId . '\/status');\n }\n\n \/\/ Status auf cancelled setzen (Python-Script prüft das)\n $this->repository->updateRun((int) $runId, [\n 'status' => 'cancelled',\n 'completed_at' => date('Y-m-d H:i:s'),\n ]);\n\n $_SESSION['success'] = 'Pipeline-Run wurde abgebrochen.';\n $this->redirect('\/content-pipeline\/' . $id . '\/run\/' . $runId . '\/status');\n }"
}
}
{
"tool_response": {
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Controller\/ContentPipelineController.php",
"oldString": " \/**\n * GET \/content-pipeline\/{id}\/status (AJAX)\n *\/\n public function status(string $id): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->json(['error' => 'Pipeline nicht gefunden'], 404);\n\n return;\n }\n\n $this->json([\n 'pipeline_id' => (int) $id,\n 'run' => $this->repository->findLatestRun((int) $id),\n ]);\n }",
"newString": " \/**\n * GET \/content-pipeline\/{id}\/status (AJAX)\n *\/\n public function status(string $id): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->json(['error' => 'Pipeline nicht gefunden'], 404);\n\n return;\n }\n\n $this->json([\n 'pipeline_id' => (int) $id,\n 'run' => $this->repository->findLatestRun((int) $id),\n ]);\n }\n\n \/**\n * GET \/content-pipeline\/{id}\/run\/{runId}\/status\n *\/\n public function runStatus(string $id, string $runId): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->notFound('Pipeline nicht gefunden');\n }\n\n $run = $this->repository->findRunById((int) $runId);\n\n if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {\n $this->notFound('Run nicht gefunden');\n }\n\n $this->view('content-pipeline.run-status', [\n 'title' => 'Pipeline Status: ' . $pipeline['name'],\n 'pipeline' => $pipeline,\n 'run' => $run,\n ]);\n }\n\n \/**\n * GET \/content-pipeline\/{id}\/run\/{runId}\/poll (AJAX\/HTMX)\n *\/\n public function runStatusPoll(string $id, string $runId): void\n {\n $run = $this->repository->findRunById((int) $runId);\n\n if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {\n $this->json(['error' => 'Run nicht gefunden'], 404);\n\n return;\n }\n\n \/\/ Berechne verstrichene Zeit\n $startedAt = strtotime($run['started_at'] ?? 'now');\n $elapsed = time() - $startedAt;\n\n \/\/ Fortschrittsberechnung\n $total = (int) ($run['documents_total'] ?? 0);\n $processed = (int) ($run['documents_processed'] ?? 0);\n $progress = $total > 0 ? round(($processed \/ $total) * 100) : 0;\n\n \/\/ Geschätzte Restzeit\n $estimatedRemaining = null;\n if ($processed > 0 && $total > $processed) {\n $avgTimePerDoc = $elapsed \/ $processed;\n $remaining = $total - $processed;\n $estimatedRemaining = (int) ($avgTimePerDoc * $remaining);\n }\n\n \/\/ Stall-Erkennung (keine Aktivität seit >60s)\n $lastUpdate = strtotime($run['last_update_at'] ?? $run['started_at'] ?? 'now');\n $isStalled = (time() - $lastUpdate) > 60 && $run['status'] === 'running';\n\n \/\/ Terminal-Status (Polling stoppen wenn fertig)\n $isTerminal = in_array($run['status'], ['completed', 'failed', 'cancelled'], true);\n\n $this->json([\n 'status' => $run['status'],\n 'current_step' => $run['current_step'],\n 'current_document' => $run['current_document'],\n 'documents_total' => $total,\n 'documents_processed' => $processed,\n 'documents_failed' => (int) ($run['documents_failed'] ?? 0),\n 'chunks_created' => (int) ($run['chunks_created'] ?? 0),\n 'embeddings_created' => (int) ($run['embeddings_created'] ?? 0),\n 'progress' => $progress,\n 'elapsed' => $elapsed,\n 'elapsed_formatted' => gmdate('i:s', $elapsed),\n 'estimated_remaining' => $estimatedRemaining,\n 'estimated_formatted' => $estimatedRemaining !== null ? gmdate('i:s', $estimatedRemaining) : null,\n 'log_tail' => $run['log_tail'] ?? '',\n 'is_stalled' => $isStalled,\n 'is_terminal' => $isTerminal,\n 'completed_at' => $run['completed_at'],\n 'error_log' => $run['error_log'],\n ]);\n }\n\n \/**\n * POST \/content-pipeline\/{id}\/run\/{runId}\/cancel\n *\/\n public function runCancel(string $id, string $runId): void\n {\n $this->requireCsrf();\n\n $run = $this->repository->findRunById((int) $runId);\n\n if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {\n $this->notFound('Run nicht gefunden');\n }\n\n \/\/ Nur laufende Runs können abgebrochen werden\n if ($run['status'] !== 'running') {\n $_SESSION['error'] = 'Run kann nicht abgebrochen werden (Status: ' . $run['status'] . ')';\n $this->redirect('\/content-pipeline\/' . $id . '\/run\/' . $runId . '\/status');\n }\n\n \/\/ Status auf cancelled setzen (Python-Script prüft das)\n $this->repository->updateRun((int) $runId, [\n 'status' => 'cancelled',\n 'completed_at' => date('Y-m-d H:i:s'),\n ]);\n\n $_SESSION['success'] = 'Pipeline-Run wurde abgebrochen.';\n $this->redirect('\/content-pipeline\/' . $id . '\/run\/' . $runId . '\/status');\n }",
"originalFile": "<?php\n\nnamespace Controller;\n\nuse Application\\PipelineStepService;\nuse Framework\\Controller;\nuse Infrastructure\\AI\\ModelConfig;\nuse Infrastructure\\Config\\PipelineStepConfig;\nuse Infrastructure\\Persistence\\PipelineRepository;\n\nclass ContentPipelineController extends Controller\n{\n private PipelineRepository $repository;\n private PipelineStepService $stepService;\n\n public function __construct()\n {\n $this->repository = new PipelineRepository();\n $this->stepService = new PipelineStepService($this->repository);\n }\n\n \/**\n * GET \/content-pipeline\n *\/\n public function index(): void\n {\n $this->view('content-pipeline.index', [\n 'title' => 'Content Pipeline',\n 'pipelines' => $this->repository->findAll(),\n 'stats' => $this->repository->getStatistics(),\n ]);\n }\n\n \/**\n * GET \/content-pipeline\/import\n *\/\n public function import(): void\n {\n $pipeline = $this->repository->findDefault();\n\n if ($pipeline === null) {\n $pipelines = $this->repository->findAll(1);\n $pipeline = $pipelines[0] ?? null;\n }\n\n $this->view('content-pipeline.import', [\n 'title' => 'Import Pipeline',\n 'pipeline' => $pipeline,\n 'latestRun' => $pipeline !== null\n ? $this->repository->findLatestRun((int) $pipeline['id'])\n : null,\n ]);\n }\n\n \/**\n * GET \/content-pipeline\/new\n *\/\n public function pipelineNew(): void\n {\n $this->view('content-pipeline.form', [\n 'title' => 'Neue Pipeline',\n 'pipeline' => null,\n 'stepTypes' => PipelineStepConfig::getStepTypes(),\n ]);\n }\n\n \/**\n * GET \/content-pipeline\/{id}\n *\/\n public function show(string $id): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->notFound('Pipeline nicht gefunden');\n }\n\n $this->view('content-pipeline.show', [\n 'title' => 'Pipeline: ' . $pipeline['name'],\n 'pipeline' => $pipeline,\n 'runs' => $this->repository->findRuns((int) $id, 10),\n 'stepTypes' => PipelineStepConfig::getStepTypes(),\n 'models' => ModelConfig::getAll(),\n 'defaultModel' => ModelConfig::DEFAULT_MODEL,\n 'collections' => PipelineStepConfig::getCollections(),\n ]);\n }\n\n \/**\n * GET \/content-pipeline\/{id}\/edit\n *\/\n public function edit(string $id): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->notFound('Pipeline nicht gefunden');\n }\n\n $this->view('content-pipeline.form', [\n 'title' => 'Pipeline bearbeiten: ' . $pipeline['name'],\n 'pipeline' => $pipeline,\n 'stepTypes' => PipelineStepConfig::getStepTypes(),\n ]);\n }\n\n \/**\n * POST \/content-pipeline\n *\/\n public function store(): void\n {\n $this->requireCsrf();\n\n $name = trim($_POST['name'] ?? '');\n\n if ($name === '') {\n $_SESSION['error'] = 'Name ist erforderlich.';\n $this->redirect('\/content-pipeline\/new');\n }\n\n $pipelineId = $this->repository->create([\n 'name' => $name,\n 'description' => trim($_POST['description'] ?? ''),\n 'source_path' => trim($_POST['source_path'] ?? '\/var\/www\/nextcloud\/data\/root\/files\/Documents'),\n 'extensions' => PipelineStepConfig::parseExtensions($_POST['extensions'] ?? ''),\n 'is_default' => isset($_POST['is_default']) ? 1 : 0,\n ]);\n\n $this->stepService->createDefaultSteps($pipelineId);\n\n $_SESSION['success'] = 'Pipeline erfolgreich erstellt.';\n $this->redirect('\/content-pipeline\/' . $pipelineId);\n }\n\n \/**\n * POST \/content-pipeline\/{id}\n *\/\n public function update(string $id): void\n {\n $this->requireCsrf();\n\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->notFound('Pipeline nicht gefunden');\n }\n\n $name = trim($_POST['name'] ?? '');\n\n if ($name === '') {\n $_SESSION['error'] = 'Name ist erforderlich.';\n $this->redirect('\/content-pipeline\/' . $id . '\/edit');\n }\n\n $this->repository->update((int) $id, [\n 'name' => $name,\n 'description' => trim($_POST['description'] ?? ''),\n 'source_path' => trim($_POST['source_path'] ?? ''),\n 'extensions' => PipelineStepConfig::parseExtensions($_POST['extensions'] ?? ''),\n 'is_default' => isset($_POST['is_default']) ? 1 : 0,\n ]);\n\n $_SESSION['success'] = 'Pipeline aktualisiert.';\n $this->redirect('\/content-pipeline\/' . $id);\n }\n\n \/**\n * POST \/content-pipeline\/{id}\/run\n *\/\n public function run(string $id): void\n {\n $this->requireCsrf();\n\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->notFound('Pipeline nicht gefunden');\n }\n\n $runId = $this->repository->createRun((int) $id);\n\n \/\/ Pipeline im Hintergrund starten\n $cmd = sprintf(\n 'nohup %s %s all --pipeline-id=%d --run-id=%d > %s 2>&1 &',\n escapeshellarg('\/opt\/scripts\/pipeline\/venv\/bin\/python'),\n escapeshellarg('\/opt\/scripts\/pipeline\/pipeline.py'),\n (int) $id,\n $runId,\n escapeshellarg('\/tmp\/pipeline_run_' . $runId . '.log')\n );\n\n exec($cmd);\n\n \/\/ Redirect zur Live-Status-Seite\n $this->redirect('\/content-pipeline\/' . $id . '\/run\/' . $runId . '\/status');\n }\n\n \/**\n * GET \/content-pipeline\/{id}\/status (AJAX)\n *\/\n public function status(string $id): void\n {\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->json(['error' => 'Pipeline nicht gefunden'], 404);\n\n return;\n }\n\n $this->json([\n 'pipeline_id' => (int) $id,\n 'run' => $this->repository->findLatestRun((int) $id),\n ]);\n }\n\n \/**\n * POST \/content-pipeline\/{id}\/steps\/{stepId}\/toggle\n *\/\n public function toggleStep(string $id, string $stepId): void\n {\n $this->requireCsrf();\n\n if (!$this->stepService->toggleStep((int) $id, (int) $stepId)) {\n $this->notFound('Pipeline oder Schritt nicht gefunden');\n }\n\n $this->redirect('\/content-pipeline\/' . $id);\n }\n\n \/**\n * POST \/content-pipeline\/{id}\/steps\/{stepId}\/model (AJAX)\n *\/\n public function updateStepModel(string $id, string $stepId): void\n {\n $result = $this->stepService->updateModel(\n (int) $id,\n (int) $stepId,\n $_POST['model'] ?? ''\n );\n\n if (!$result['success']) {\n $this->json(['error' => $result['error']], $result['error'] === 'Schritt nicht gefunden' ? 404 : 400);\n\n return;\n }\n\n $this->json($result);\n }\n\n \/**\n * POST \/content-pipeline\/{id}\/steps\/{stepId}\/collection (AJAX)\n *\/\n public function updateStepCollection(string $id, string $stepId): void\n {\n $result = $this->stepService->updateCollection(\n (int) $id,\n (int) $stepId,\n $_POST['collection'] ?? ''\n );\n\n if (!$result['success']) {\n $this->json(['error' => $result['error']], $result['error'] === 'Schritt nicht gefunden' ? 404 : 400);\n\n return;\n }\n\n $this->json($result);\n }\n\n \/**\n * POST \/content-pipeline\/{id}\/delete\n *\/\n public function delete(string $id): void\n {\n $this->requireCsrf();\n\n $pipeline = $this->repository->findById((int) $id);\n\n if ($pipeline === null) {\n $this->notFound('Pipeline nicht gefunden');\n }\n\n $this->repository->delete((int) $id);\n\n $_SESSION['success'] = 'Pipeline geloescht.';\n $this->redirect('\/content-pipeline');\n }\n}\n",
"structuredPatch": [
{
"oldStart": 215,
"oldLines": 6,
"newStart": 215,
"newLines": 118,
"lines": [
" }",
" ",
" \/**",
"+ * GET \/content-pipeline\/{id}\/run\/{runId}\/status",
"+ *\/",
"+ public function runStatus(string $id, string $runId): void",
"+ {",
"+ $pipeline = $this->repository->findById((int) $id);",
"+",
"+ if ($pipeline === null) {",
"+ $this->notFound('Pipeline nicht gefunden');",
"+ }",
"+",
"+ $run = $this->repository->findRunById((int) $runId);",
"+",
"+ if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {",
"+ $this->notFound('Run nicht gefunden');",
"+ }",
"+",
"+ $this->view('content-pipeline.run-status', [",
"+ 'title' => 'Pipeline Status: ' . $pipeline['name'],",
"+ 'pipeline' => $pipeline,",
"+ 'run' => $run,",
"+ ]);",
"+ }",
"+",
"+ \/**",
"+ * GET \/content-pipeline\/{id}\/run\/{runId}\/poll (AJAX\/HTMX)",
"+ *\/",
"+ public function runStatusPoll(string $id, string $runId): void",
"+ {",
"+ $run = $this->repository->findRunById((int) $runId);",
"+",
"+ if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {",
"+ $this->json(['error' => 'Run nicht gefunden'], 404);",
"+",
"+ return;",
"+ }",
"+",
"+ \/\/ Berechne verstrichene Zeit",
"+ $startedAt = strtotime($run['started_at'] ?? 'now');",
"+ $elapsed = time() - $startedAt;",
"+",
"+ \/\/ Fortschrittsberechnung",
"+ $total = (int) ($run['documents_total'] ?? 0);",
"+ $processed = (int) ($run['documents_processed'] ?? 0);",
"+ $progress = $total > 0 ? round(($processed \/ $total) * 100) : 0;",
"+",
"+ \/\/ Geschätzte Restzeit",
"+ $estimatedRemaining = null;",
"+ if ($processed > 0 && $total > $processed) {",
"+ $avgTimePerDoc = $elapsed \/ $processed;",
"+ $remaining = $total - $processed;",
"+ $estimatedRemaining = (int) ($avgTimePerDoc * $remaining);",
"+ }",
"+",
"+ \/\/ Stall-Erkennung (keine Aktivität seit >60s)",
"+ $lastUpdate = strtotime($run['last_update_at'] ?? $run['started_at'] ?? 'now');",
"+ $isStalled = (time() - $lastUpdate) > 60 && $run['status'] === 'running';",
"+",
"+ \/\/ Terminal-Status (Polling stoppen wenn fertig)",
"+ $isTerminal = in_array($run['status'], ['completed', 'failed', 'cancelled'], true);",
"+",
"+ $this->json([",
"+ 'status' => $run['status'],",
"+ 'current_step' => $run['current_step'],",
"+ 'current_document' => $run['current_document'],",
"+ 'documents_total' => $total,",
"+ 'documents_processed' => $processed,",
"+ 'documents_failed' => (int) ($run['documents_failed'] ?? 0),",
"+ 'chunks_created' => (int) ($run['chunks_created'] ?? 0),",
"+ 'embeddings_created' => (int) ($run['embeddings_created'] ?? 0),",
"+ 'progress' => $progress,",
"+ 'elapsed' => $elapsed,",
"+ 'elapsed_formatted' => gmdate('i:s', $elapsed),",
"+ 'estimated_remaining' => $estimatedRemaining,",
"+ 'estimated_formatted' => $estimatedRemaining !== null ? gmdate('i:s', $estimatedRemaining) : null,",
"+ 'log_tail' => $run['log_tail'] ?? '',",
"+ 'is_stalled' => $isStalled,",
"+ 'is_terminal' => $isTerminal,",
"+ 'completed_at' => $run['completed_at'],",
"+ 'error_log' => $run['error_log'],",
"+ ]);",
"+ }",
"+",
"+ \/**",
"+ * POST \/content-pipeline\/{id}\/run\/{runId}\/cancel",
"+ *\/",
"+ public function runCancel(string $id, string $runId): void",
"+ {",
"+ $this->requireCsrf();",
"+",
"+ $run = $this->repository->findRunById((int) $runId);",
"+",
"+ if ($run === null || (int) $run['pipeline_id'] !== (int) $id) {",
"+ $this->notFound('Run nicht gefunden');",
"+ }",
"+",
"+ \/\/ Nur laufende Runs können abgebrochen werden",
"+ if ($run['status'] !== 'running') {",
"+ $_SESSION['error'] = 'Run kann nicht abgebrochen werden (Status: ' . $run['status'] . ')';",
"+ $this->redirect('\/content-pipeline\/' . $id . '\/run\/' . $runId . '\/status');",
"+ }",
"+",
"+ \/\/ Status auf cancelled setzen (Python-Script prüft das)",
"+ $this->repository->updateRun((int) $runId, [",
"+ 'status' => 'cancelled',",
"+ 'completed_at' => date('Y-m-d H:i:s'),",
"+ ]);",
"+",
"+ $_SESSION['success'] = 'Pipeline-Run wurde abgebrochen.';",
"+ $this->redirect('\/content-pipeline\/' . $id . '\/run\/' . $runId . '\/status');",
"+ }",
"+",
"+ \/**",
" * POST \/content-pipeline\/{id}\/steps\/{stepId}\/toggle",
" *\/",
" public function toggleStep(string $id, string $stepId): void"
]
}
],
"userModified": false,
"replaceAll": false
}
}