docs: add github service workflow contract

This commit is contained in:
happybell80 2026-03-23 20:59:50 +09:00
parent d419f452cc
commit eed3967061
3 changed files with 469 additions and 1 deletions

View File

@ -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"
}
}

View File

@ -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)

View File

@ -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, 임베딩 브리지의 역할을 분리해서 봅니다.
- 세부 절차는 각 흐름 문서에서 확인합니다.