diff --git a/workflow/02_skills/github_service_request.json b/workflow/02_skills/github_service_request.json new file mode 100644 index 0000000..2ebf32a --- /dev/null +++ b/workflow/02_skills/github_service_request.json @@ -0,0 +1,339 @@ +{ + "name": "robeing-github-service-request", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "skills/github/request", + "responseMode": "responseNode", + "options": {} + }, + "id": "gh-req-001", + "name": "Webhook In", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [200, 260] + }, + { + "parameters": { + "jsCode": "const body = $json.body || {};\nreturn {\n intent: body.intent || '',\n slots: body.slots || {},\n user_id: body.user_id || '',\n channel: body.channel || 'slack',\n robeing_id: body.robeing_id || 'rb8001'\n};" + }, + "id": "gh-req-002", + "name": "Normalize Payload", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [440, 260] + }, + { + "parameters": { + "jsCode": "const validIntents = new Set(['github_analyze', 'github_manage', 'git_ops']);\nreturn {\n ...$json,\n valid_intent: validIntents.has($json.intent)\n};" + }, + "id": "gh-req-003", + "name": "Validate Intent", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [700, 260] + }, + { + "parameters": { + "conditions": { + "boolean": [ + { + "value1": "={{ $json.valid_intent }}" + } + ] + } + }, + "id": "gh-req-004", + "name": "Intent OK?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [940, 260] + }, + { + "parameters": { + "jsCode": "const slots = $json.slots || {};\nconst risk = slots.risk_level || 'low';\nconst requiresConfirmation = Boolean(slots.requires_confirmation);\nconst criticalActions = new Set(['force_push', 'reset_hard', 'delete_branch', 'delete_release']);\nconst action = slots.action || '';\nreturn {\n ...$json,\n risk_level: risk,\n denied: criticalActions.has(action) || risk === 'critical',\n requires_confirmation: requiresConfirmation || risk === 'high'\n};" + }, + "id": "gh-req-005", + "name": "Risk Decision", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1180, 260] + }, + { + "parameters": { + "conditions": { + "boolean": [ + { + "value1": "={{ $json.denied }}" + } + ] + } + }, + "id": "gh-req-006", + "name": "Denied?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [1420, 180] + }, + { + "parameters": { + "conditions": { + "boolean": [ + { + "value1": "={{ $json.requires_confirmation }}" + } + ] + } + }, + "id": "gh-req-007", + "name": "Needs Confirmation?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [1420, 360] + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.intent }}", + "operation": "equals", + "value2": "github_analyze" + } + ] + } + }, + "id": "gh-req-008", + "name": "Route Analyze", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [1660, 500] + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.intent }}", + "operation": "equals", + "value2": "github_manage" + } + ] + } + }, + "id": "gh-req-009", + "name": "Route Manage", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [1900, 560] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { success: false, message: 'unknown_intent', intent: $json.intent } }}" + }, + "id": "gh-req-010", + "name": "Reject Unknown Intent", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1180, 420] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { success: false, message: 'critical_action_denied', intent: $json.intent, slots: $json.slots } }}" + }, + "id": "gh-req-011", + "name": "Reject Critical", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1660, 120] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { success: false, message: 'confirmation_required', intent: $json.intent, slots: $json.slots } }}" + }, + "id": "gh-req-012", + "name": "Return Confirmation", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1660, 320] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { success: true, dispatch: 'analyze_target', intent: $json.intent, slots: $json.slots } }}" + }, + "id": "gh-req-013", + "name": "Dispatch Analyze", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1900, 460] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { success: true, dispatch: 'manage_resource', intent: $json.intent, slots: $json.slots } }}" + }, + "id": "gh-req-014", + "name": "Dispatch Manage", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [2140, 520] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { success: true, dispatch: 'run_git_operation', intent: $json.intent, slots: $json.slots } }}" + }, + "id": "gh-req-015", + "name": "Dispatch Git Ops", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [2140, 620] + } + ], + "connections": { + "Webhook In": { + "main": [ + [ + { + "node": "Normalize Payload", + "type": "main", + "index": 0 + } + ] + ] + }, + "Normalize Payload": { + "main": [ + [ + { + "node": "Validate Intent", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validate Intent": { + "main": [ + [ + { + "node": "Intent OK?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Intent OK?": { + "main": [ + [ + { + "node": "Risk Decision", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Reject Unknown Intent", + "type": "main", + "index": 0 + } + ] + ] + }, + "Risk Decision": { + "main": [ + [ + { + "node": "Denied?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Denied?": { + "main": [ + [ + { + "node": "Reject Critical", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Needs Confirmation?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Needs Confirmation?": { + "main": [ + [ + { + "node": "Return Confirmation", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Route Analyze", + "type": "main", + "index": 0 + } + ] + ] + }, + "Route Analyze": { + "main": [ + [ + { + "node": "Dispatch Analyze", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Route Manage", + "type": "main", + "index": 0 + } + ] + ] + }, + "Route Manage": { + "main": [ + [ + { + "node": "Dispatch Manage", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Dispatch Git Ops", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "robeing-skills" + } +} diff --git a/workflow/02_skills/github_service_request.md b/workflow/02_skills/github_service_request.md new file mode 100644 index 0000000..6c54255 --- /dev/null +++ b/workflow/02_skills/github_service_request.md @@ -0,0 +1,125 @@ +# github_service_request 워크플로우 + +## 목적 +GitHub 관련 의도(`github_analyze`, `github_manage`, `git_ops`)를 하나의 서비스 계약으로 묶는다. `github_service.py`는 LLM이 SKILL.md 기반으로 분류한 intent와 slots를 받아, 분석/관리/실행을 위험도 기준으로 처리한다. + +## 흐름 +```text +Webhook In -> Normalize Payload -> Validate Intent/Slots -> Risk Gate + -> (github_analyze) Analyze Target + -> (github_manage) Manage GitHub Resource + -> (git_ops) Run Git Operation + -> Return Result +``` + +## 주요 노드 +| 노드 | 설명 | +|---|---| +| Webhook In | `POST /skills/github/request` 수신 | +| Normalize Payload | intent, slots, user_id, channel, robeing_id 정규화 | +| Validate Intent/Slots | `github_analyze`, `github_manage`, `git_ops`만 허용. `target_url`, `repo`, `action`, `risk_level`, `requires_confirmation` 확인 | +| Risk Gate | `risk_level`과 `requires_confirmation`으로 실행 가능 여부 판정 | +| Analyze Target | 읽기 전용 GitHub 분석. repo/PR/issue/blob/tree 요약, 코드 리뷰 초안 | +| Manage GitHub Resource | PR/Issue/Branch 메타 작업 | +| Run Git Operation | clone/status/log/diff/pull/commit/push 등 git 명령 실행 | +| Return Result | 실행 결과 또는 확인 요청 반환 | + +## `github_service.py` 인터페이스 계약 + +### 핵심 함수 시그니처 +```python +async def execute_github_intent( + *, + intent: str, + slots: dict[str, object], + user_id: str, + channel: str, + robeing_id: str = "rb8001", +) -> dict[str, object]: + ... + +async def analyze_target( + *, + target_url: str | None, + repo: str | None, + resource_type: str, + action: str, + user_id: str, +) -> dict[str, object]: + ... + +async def manage_resource( + *, + repo: str, + resource_type: str, + action: str, + slots: dict[str, object], + user_id: str, +) -> dict[str, object]: + ... + +async def run_git_operation( + *, + repo: str | None, + action: str, + slots: dict[str, object], + user_id: str, +) -> dict[str, object]: + ... + +def evaluate_risk( + *, + intent: str, + action: str, + slots: dict[str, object], +) -> dict[str, object]: + ... +``` + +### slots 계약 +- `target_url`: GitHub URL. repo/blob/tree/pull/issue/commit 중 하나 +- `repo`: `owner/name` +- `resource_type`: `repo|pull|issue|commit|blob|tree|branch` +- `action`: `summarize|review|history|clone|status|diff|pull|commit|push|create_pr|comment_pr|close_issue` +- `risk_level`: `low|medium|high|critical` +- `requires_confirmation`: `true|false` +- `branch`: 선택 +- `pr_number`, `issue_number`, `commit_sha`, `path`: 리소스별 선택 + +### 안전 계약 +- `low`: 자동 실행 가능. 읽기/분석/조회 +- `medium`: 로컬 변경 가능. 원격 반영 전 확인 필요 +- `high`: push, merge, close, delete 계열. 확인 필수 +- `critical`: `push --force`, `reset --hard`, branch delete, release delete. 기본 거부 + +## 인바운드 payload 예시 +```json +{ + "intent": "github_analyze", + "slots": { + "target_url": "https://github.com/owner/repo", + "repo": "owner/repo", + "resource_type": "repo", + "action": "summarize", + "risk_level": "low", + "requires_confirmation": false + }, + "user_id": "uuid", + "channel": "slack", + "robeing_id": "rb8001" +} +``` + +## 엔드포인트 +- 인바운드: `POST /skills/github/request` +- 내부 실행: `github_service.py` + +## 적용 기준 +- 1차 분류 기준은 `DOCS/skills/{skill-name}/SKILL.md` +- 이 워크플로우는 LLM이 분류한 intent를 실행하는 계약이다 +- 정규식 예외, URL 화이트리스트, command 하드코딩으로 intent를 덮어쓰지 않는다 + +## 관련 문서 +- [skill_calendar_request](./skill_calendar_request.md) +- [skill_email_send_request](./skill_email_send_request.md) +- [../README.md](../README.md) diff --git a/workflow/README.md b/workflow/README.md index ca4ebd9..f555d66 100644 --- a/workflow/README.md +++ b/workflow/README.md @@ -35,7 +35,11 @@ - 구글 캘린더 연동을 위한 브리지 워크플로우입니다. - 일정 조회, 등록, 삭제 요청을 `skill-calendar` 서비스로 전달합니다. -### 5. RAG 워크플로우 인덱스 (`03_rag/README.md`) +### 5. GitHub 스킬 브리지 (`02_skills/github_service_request.json`) +- GitHub 분석/관리/git 실행 의도를 하나의 서비스 계약으로 정규화합니다. +- `github_analyze`, `github_manage`, `git_ops` intent와 `risk_level`, `requires_confirmation` 슬롯을 받아 안전 정책에 따라 실행 또는 확인 요청으로 분기합니다. + +### 6. RAG 워크플로우 인덱스 (`03_rag/README.md`) - Company X 내부 문서를 로빙이 읽고 답변하도록 만드는 RAG 흐름의 진입점입니다. - 업로드, 검색, Grounding, 임베딩 브리지의 역할을 분리해서 봅니다. - 세부 절차는 각 흐름 문서에서 확인합니다.