pi-ghwebhook 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +68 -94
- package/README_zh.md +56 -82
- package/dist/bus/event-bus.d.ts +6 -6
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +31 -38
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/bus/matcher.d.ts +7 -0
- package/dist/bus/matcher.d.ts.map +1 -0
- package/dist/bus/matcher.js +122 -0
- package/dist/bus/matcher.js.map +1 -0
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +1 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/types/schema.d.ts +23 -49
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/types/schema.js +23 -37
- package/dist/types/schema.js.map +1 -1
- package/package.json +6 -1
- package/schemas/ghwebhook.schema.json +225 -0
- package/skills/setup-ghwebhook/SKILL.md +149 -0
- package/skills/setup-ghwebhook/assets/default-config.json +31 -0
- package/src/bus/event-bus.ts +33 -41
- package/src/bus/matcher.ts +164 -0
- package/src/config/loader.ts +4 -4
- package/src/index.ts +6 -5
- package/src/types/schema.ts +36 -68
- package/src/bus/guards.ts +0 -118
- package/src/bus/normalizer.ts +0 -32
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-11-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Built-in `setup-ghwebhook` Skill**: Packaged an interactive setup skill that auto-detects `gh` CLI credentials, auto-heals missing `cli/gh-webhook` extensions, senses repo workflow and review bot configs, and scaffolds tailored `.pi/ghwebhook.json` rules.
|
|
13
|
+
|
|
8
14
|
## [0.1.0] - 2026-11-09
|
|
9
15
|
|
|
10
16
|
### Added
|
package/README.md
CHANGED
|
@@ -16,17 +16,17 @@ English | [中文](README_zh.md)
|
|
|
16
16
|
|
|
17
17
|
## Overview
|
|
18
18
|
|
|
19
|
-
pi-ghwebhook eliminates manual polling and terminal switching after pushing code or creating pull requests. By hosting an in-process loopback server connected to GitHub CLI's `gh webhook forward` reverse tunnel, it receives real-time GitHub Webhook events, evaluates
|
|
19
|
+
pi-ghwebhook eliminates manual polling and terminal switching after pushing code or creating pull requests. By hosting an in-process loopback server connected to GitHub CLI's `gh webhook forward` reverse tunnel, it receives real-time GitHub Webhook events, evaluates GitHub Actions Workflow-style trigger rules, and wakes up the Pi Agent via `pi.sendUserMessage()` to close the loop.
|
|
20
20
|
|
|
21
21
|
## Key Architecture
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
- Zero
|
|
26
|
-
- Dynamic
|
|
27
|
-
- Table-
|
|
28
|
-
- Workspace
|
|
29
|
-
- Clean
|
|
23
|
+
- 100% GitHub Actions Workflow Specification: Uses official `on` and `jobs` syntax. Zero invented intermediate concepts.
|
|
24
|
+
- JSON Schema Autocompletion: Includes an official schema (`$schema`) for instant editor IntelliSense and validation.
|
|
25
|
+
- Zero Public Exposure: Uses `gh webhook forward --events=*` local reverse tunnels. No public server, ngrok, or exposed port required.
|
|
26
|
+
- Dynamic Port Allocation: Binds to `127.0.0.1:0`. The OS kernel dynamically assigns an unused port, eliminating port contention across concurrent sessions.
|
|
27
|
+
- Table-Driven Dispatch: O(1) event routing with strict complexity constraints (CCN <= 15, CogC <= 15).
|
|
28
|
+
- Workspace Detection: Probes `.pi/ghwebhook.json` on `session_start`. If absent, sleeps silently with zero resource consumption.
|
|
29
|
+
- Clean Teardown: Hooks into `session_shutdown` and process exit to terminate child processes and servers idempotently.
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
@@ -50,108 +50,82 @@ pi -e /path/to/pi-ghwebhook
|
|
|
50
50
|
|
|
51
51
|
## Configuration (`.pi/ghwebhook.json`)
|
|
52
52
|
|
|
53
|
-
Place `.pi/ghwebhook.json`
|
|
53
|
+
Place `.pi/ghwebhook.json` in the root of any repository. Point `$schema` to the hosted JSON Schema for auto-completion in VS Code, Cursor, and other editors:
|
|
54
54
|
|
|
55
55
|
```json
|
|
56
56
|
{
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"branch": "@current",
|
|
65
|
-
"workflow": "CI",
|
|
66
|
-
"conclusion": "failure"
|
|
67
|
-
},
|
|
68
|
-
"action": {
|
|
69
|
-
"deliverAs": "followUp",
|
|
70
|
-
"prompt": "CI run failed on branch {{ branch }}.\n\nRun URL: {{ run_url }}\nCommit: {{ head_sha }}\n\nPlease inspect the failed run logs and propose a fix."
|
|
71
|
-
}
|
|
57
|
+
"$schema": "https://raw.githubusercontent.com/xifan2333/pi-ghwebhook/main/schemas/ghwebhook.schema.json",
|
|
58
|
+
"name": "CI & Review Automation",
|
|
59
|
+
"on": {
|
|
60
|
+
"workflow_run": {
|
|
61
|
+
"workflows": ["CI"],
|
|
62
|
+
"types": ["completed"],
|
|
63
|
+
"branches": ["@current"]
|
|
72
64
|
},
|
|
73
|
-
{
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"branch": "@current",
|
|
77
|
-
"conclusion": "success"
|
|
78
|
-
},
|
|
79
|
-
"action": {
|
|
80
|
-
"deliverAs": "followUp",
|
|
81
|
-
"prompt": "Remote CI has passed on branch {{ branch }}.\n\nRun URL: {{ run_url }}\n\nPlease check PR status and verify if it is ready to merge."
|
|
82
|
-
}
|
|
65
|
+
"pull_request_review": {
|
|
66
|
+
"types": ["submitted"],
|
|
67
|
+
"branches": ["@current"]
|
|
83
68
|
},
|
|
84
|
-
{
|
|
85
|
-
"
|
|
86
|
-
"filters": {
|
|
87
|
-
"branch": "@current",
|
|
88
|
-
"isBot": true,
|
|
89
|
-
"senders": ["coderabbitai[bot]", "greptile[bot]"]
|
|
90
|
-
},
|
|
91
|
-
"action": {
|
|
92
|
-
"deliverAs": "steer",
|
|
93
|
-
"prompt": "Received review feedback from {{ sender }}:\n\nState: {{ review_state }}\nDetails:\n{{ review_body }}\n\nPlease evaluate these comments critically: fix genuine issues and refute false positives."
|
|
94
|
-
}
|
|
69
|
+
"issues": {
|
|
70
|
+
"types": ["opened"]
|
|
95
71
|
},
|
|
96
|
-
{
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
72
|
+
"push": {
|
|
73
|
+
"branches": ["main"]
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"jobs": {
|
|
77
|
+
"ci-failure": {
|
|
78
|
+
"if": "github.event.workflow_run.conclusion == 'failure'",
|
|
79
|
+
"prompt": "CI run failed on branch {{ branch }}.\n\nRun URL: {{ run_url }}\nCommit: {{ head_sha }}\n\nPlease run `gh run view {{ run_id }} --log-failed` to diagnose the error and prepare a fix.",
|
|
80
|
+
"deliverAs": "followUp"
|
|
81
|
+
},
|
|
82
|
+
"ci-success": {
|
|
83
|
+
"if": "github.event.workflow_run.conclusion == 'success'",
|
|
84
|
+
"prompt": "Remote CI has passed on branch {{ branch }}.\n\nRun URL: {{ run_url }}\n\nPlease check PR checks status and verify if it is ready to merge.",
|
|
85
|
+
"deliverAs": "followUp"
|
|
86
|
+
},
|
|
87
|
+
"review-bot": {
|
|
88
|
+
"if": "github.event.review.user.type == 'Bot'",
|
|
89
|
+
"prompt": "Received review comments from {{ sender }}:\n\nState: {{ review_state }}\nDetails:\n{{ review_body }}\n\nPlease evaluate these comments critically: triage true issues to fix, and refute false positives.",
|
|
90
|
+
"deliverAs": "steer"
|
|
91
|
+
},
|
|
92
|
+
"new-issue": {
|
|
93
|
+
"prompt": "New issue opened #{{ issue_number }}: {{ issue_title }}\n\n{{ issue_body }}"
|
|
101
94
|
},
|
|
102
|
-
{
|
|
103
|
-
"
|
|
104
|
-
"filters": {
|
|
105
|
-
"branch": "main"
|
|
106
|
-
},
|
|
107
|
-
"action": {
|
|
108
|
-
"prompt": "New commit pushed to main: {{ head_sha }} - {{ commit_message }}"
|
|
109
|
-
}
|
|
95
|
+
"push-main": {
|
|
96
|
+
"prompt": "New commit pushed to main: {{ head_sha }} - {{ commit_message }}"
|
|
110
97
|
}
|
|
111
|
-
|
|
98
|
+
}
|
|
112
99
|
}
|
|
113
100
|
```
|
|
114
101
|
|
|
115
102
|
### Configuration Fields
|
|
116
103
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- `
|
|
128
|
-
- `
|
|
129
|
-
- `filters`: Declarative matching criteria.
|
|
130
|
-
- `action`: Action payload containing `prompt` and optional `deliverAs` (`"steer"` or `"followUp"`).
|
|
131
|
-
|
|
132
|
-
### Filters
|
|
133
|
-
|
|
134
|
-
- `branch`: Target branch name or `@current` (dynamically resolved to current working branch).
|
|
135
|
-
- `conclusion`: Workflow conclusion (`success`, `failure`, `cancelled`, `skipped`, etc.).
|
|
136
|
-
- `workflow`: Workflow name (e.g., `CI`).
|
|
137
|
-
- `state`: Review state (`approved`, `changes_requested`, `commented`) or PR state.
|
|
138
|
-
- `senders`: Array of allowed sender logins (e.g., `["coderabbitai[bot]"]`).
|
|
139
|
-
- `isBot`: Boolean flag matching Bot (`true`) or Human (`false`) senders.
|
|
140
|
-
- `match`: Key-value map for matching nested payload fields using dot notation (e.g., `{"review.state": "changes_requested"}`).
|
|
104
|
+
- `name`: Optional workflow name.
|
|
105
|
+
- `repo`: GitHub repository (`owner/repo`). Defaults to `"auto"` (resolved from `git remote get-url origin`).
|
|
106
|
+
- `secret`: Optional webhook HMAC SHA-256 verification secret.
|
|
107
|
+
- `on`: GitHub Actions event triggers specification:
|
|
108
|
+
- `<event_name>`: Event name (e.g. `workflow_run`, `pull_request`, `push`, `issues`).
|
|
109
|
+
- `types`: Activity types filtering `payload.action` (e.g. `["completed"]`, `["opened"]`).
|
|
110
|
+
- `branches`: Branches filter supporting `@current` and globs (e.g. `["@current", "main", "feat/*"]`).
|
|
111
|
+
- `workflows`: Workflow names filter (e.g. `["CI"]`).
|
|
112
|
+
- `jobs`: Map of jobs to execute:
|
|
113
|
+
- `if`: Conditional expression (e.g. `github.event.workflow_run.conclusion == 'failure'`).
|
|
114
|
+
- `prompt`: Prompt template sent to Pi Agent.
|
|
115
|
+
- `deliverAs`: Pi message delivery mode: `"followUp"` (default) or `"steer"`.
|
|
141
116
|
|
|
142
117
|
### Template Variables
|
|
143
118
|
|
|
144
|
-
- `{{ branch }}`:
|
|
145
|
-
- `{{ head_sha }}`: Commit SHA
|
|
146
|
-
- `{{ commit_message }}`: Head commit message
|
|
147
|
-
- `{{ repo }}` / `{{ repo_url }}`: Repository name and URL
|
|
148
|
-
- `{{ run_id }}` / `{{ run_url }}` / `{{ workflow }}` / `{{ conclusion }}`: Workflow run metadata
|
|
149
|
-
- `{{ pr_number }}` / `{{ pr_title }}` / `{{ pr_url }}`: Pull request details
|
|
150
|
-
- `{{ issue_number }}` / `{{ issue_title }}` / `{{ issue_url }}` / `{{ issue_body }}`: Issue details
|
|
151
|
-
- `{{ sender }}` / `{{ sender_type }}`: Triggering actor
|
|
152
|
-
- `{{ review_state }}` / `{{ review_body }}` / `{{ review_url }}`: Review details
|
|
153
|
-
- `{{ comment_body }}` / `{{ comment_url }}`: Comment details
|
|
154
|
-
- `{{ action }}`: Payload action string
|
|
119
|
+
- `{{ branch }}`: Current head branch.
|
|
120
|
+
- `{{ head_sha }}`: Commit SHA.
|
|
121
|
+
- `{{ commit_message }}`: Head commit message.
|
|
122
|
+
- `{{ repo }}` / `{{ repo_url }}`: Repository name and URL.
|
|
123
|
+
- `{{ run_id }}` / `{{ run_url }}` / `{{ workflow }}` / `{{ conclusion }}`: Workflow run metadata.
|
|
124
|
+
- `{{ pr_number }}` / `{{ pr_title }}` / `{{ pr_url }}`: Pull request details.
|
|
125
|
+
- `{{ issue_number }}` / `{{ issue_title }}` / `{{ issue_url }}` / `{{ issue_body }}`: Issue details.
|
|
126
|
+
- `{{ sender }}` / `{{ sender_type }}`: Triggering actor.
|
|
127
|
+
- `{{ review_state }}` / `{{ review_body }}` / `{{ review_url }}`: Review details.
|
|
128
|
+
- `{{ comment_body }}` / `{{ comment_url }}`: Comment details.
|
|
155
129
|
|
|
156
130
|
## Development & Quality Gates
|
|
157
131
|
|
package/README_zh.md
CHANGED
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
## 概述
|
|
18
18
|
|
|
19
|
-
pi-ghwebhook 旨在彻底消除提交代码或创建 Pull Request 后的人工轮询与终端反复切换。通过在进程内运行回环 HTTP 服务并托管 GitHub CLI 的 `gh webhook forward` 反向长连接隧道,它可以实时接收 GitHub Webhook
|
|
19
|
+
pi-ghwebhook 旨在彻底消除提交代码或创建 Pull Request 后的人工轮询与终端反复切换。通过在进程内运行回环 HTTP 服务并托管 GitHub CLI 的 `gh webhook forward` 反向长连接隧道,它可以实时接收 GitHub Webhook 事件,依据 GitHub Actions Workflow 官方规范解析规则,并通过 `pi.sendUserMessage()` 主动唤醒 Pi Agent 进入闭环处置。
|
|
20
20
|
|
|
21
21
|
## 核心架构特性
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
23
|
+
- 100% 对齐 GitHub Actions Workflow 官方规范:完全采用官方标准的 `on` 触发器与 `jobs` 任务语法,零中间层生造概念。
|
|
24
|
+
- JSON Schema 智能提示:提供官方 Schema(`$schema`),在 VS Code、Cursor 等现代编辑器中编写即享全字段类型补全与静态校验。
|
|
25
25
|
- 零公网暴露:依托 `gh webhook forward --events=*` 反向隧道,无需公网服务器、固定 IP 或内网穿透工具。
|
|
26
|
-
- 动态端口分配:监听 `127.0.0.1:0
|
|
26
|
+
- 动态端口分配:监听 `127.0.0.1:0`,由操作系统内核动态分配空闲端口,杜绝多终端或多会话并发运行时的端口冲突。
|
|
27
27
|
- 表驱动分发:基于哈希表实现 O(1) 路由分发,执行严格的复杂度门禁(CCN <= 15, CogC <= 15)。
|
|
28
28
|
- 工作区自感知:在 `session_start` 生命周期探测 `.pi/ghwebhook.json`,无配置时静默休眠,零资源占用。
|
|
29
29
|
- 幂等清理保障:深度绑定 `session_shutdown` 与进程退出钩子,确保子进程与本地服务干净释放,不留孤儿进程。
|
|
@@ -50,94 +50,69 @@ pi -e /path/to/pi-ghwebhook
|
|
|
50
50
|
|
|
51
51
|
## 配置契约 (`.pi/ghwebhook.json`)
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
在仓库根目录下创建 `.pi/ghwebhook.json`。配置 `$schema` 即可享受现代 IDE 的实时悬浮提示与自动补全:
|
|
54
54
|
|
|
55
55
|
```json
|
|
56
56
|
{
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"branch": "@current",
|
|
65
|
-
"workflow": "CI",
|
|
66
|
-
"conclusion": "failure"
|
|
67
|
-
},
|
|
68
|
-
"action": {
|
|
69
|
-
"deliverAs": "followUp",
|
|
70
|
-
"prompt": "分支 {{ branch }} 的 CI 运行失败。\n\n运行链接: {{ run_url }}\n提交哈希: {{ head_sha }}\n\n请查看失败日志并给出修复方案。"
|
|
71
|
-
}
|
|
57
|
+
"$schema": "https://raw.githubusercontent.com/xifan2333/pi-ghwebhook/main/schemas/ghwebhook.schema.json",
|
|
58
|
+
"name": "CI & Review Automation",
|
|
59
|
+
"on": {
|
|
60
|
+
"workflow_run": {
|
|
61
|
+
"workflows": ["CI"],
|
|
62
|
+
"types": ["completed"],
|
|
63
|
+
"branches": ["@current"]
|
|
72
64
|
},
|
|
73
|
-
{
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"branch": "@current",
|
|
77
|
-
"conclusion": "success"
|
|
78
|
-
},
|
|
79
|
-
"action": {
|
|
80
|
-
"deliverAs": "followUp",
|
|
81
|
-
"prompt": "分支 {{ branch }} 的远程 CI 已全部通过。\n\n运行链接: {{ run_url }}\n\n请检查 PR 状态并确认是否准备合入。"
|
|
82
|
-
}
|
|
65
|
+
"pull_request_review": {
|
|
66
|
+
"types": ["submitted"],
|
|
67
|
+
"branches": ["@current"]
|
|
83
68
|
},
|
|
84
|
-
{
|
|
85
|
-
"
|
|
86
|
-
"filters": {
|
|
87
|
-
"branch": "@current",
|
|
88
|
-
"isBot": true,
|
|
89
|
-
"senders": ["coderabbitai[bot]", "greptile[bot]"]
|
|
90
|
-
},
|
|
91
|
-
"action": {
|
|
92
|
-
"deliverAs": "steer",
|
|
93
|
-
"prompt": "收到来自 {{ sender }} 的评审意见:\n\n评审状态: {{ review_state }}\n详情内容:\n{{ review_body }}\n\n请批判性评估上述审查意见:修复真实问题,驳回误报。"
|
|
94
|
-
}
|
|
69
|
+
"issues": {
|
|
70
|
+
"types": ["opened"]
|
|
95
71
|
},
|
|
96
|
-
{
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
72
|
+
"push": {
|
|
73
|
+
"branches": ["main"]
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"jobs": {
|
|
77
|
+
"ci-failure": {
|
|
78
|
+
"if": "github.event.workflow_run.conclusion == 'failure'",
|
|
79
|
+
"prompt": "分支 {{ branch }} 的 CI 运行失败。\n\n运行链接: {{ run_url }}\n提交哈希: {{ head_sha }}\n\n请使用 `gh run view {{ run_id }} --log-failed` 查看失败日志并进行修复。",
|
|
80
|
+
"deliverAs": "followUp"
|
|
81
|
+
},
|
|
82
|
+
"ci-success": {
|
|
83
|
+
"if": "github.event.workflow_run.conclusion == 'success'",
|
|
84
|
+
"prompt": "分支 {{ branch }} 的远程 CI 已全部通过。\n\n运行链接: {{ run_url }}\n\n请检查 PR 状态并确认是否准备合入。",
|
|
85
|
+
"deliverAs": "followUp"
|
|
86
|
+
},
|
|
87
|
+
"review-bot": {
|
|
88
|
+
"if": "github.event.review.user.type == 'Bot'",
|
|
89
|
+
"prompt": "收到来自 {{ sender }} 的审查意见:\n\n状态: {{ review_state }}\n详情:\n{{ review_body }}\n\n请批判性评估上述审查意见:修复真实问题,驳回误报。",
|
|
90
|
+
"deliverAs": "steer"
|
|
101
91
|
},
|
|
102
|
-
{
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
"action": {
|
|
108
|
-
"prompt": "main 分支收到新推送: {{ head_sha }} - {{ commit_message }}"
|
|
109
|
-
}
|
|
92
|
+
"new-issue": {
|
|
93
|
+
"prompt": "收到新 Issue #{{ issue_number }}: {{ issue_title }}\n\n{{ issue_body }}"
|
|
94
|
+
},
|
|
95
|
+
"push-main": {
|
|
96
|
+
"prompt": "main 分支收到新代码推送: {{ head_sha }} - {{ commit_message }}"
|
|
110
97
|
}
|
|
111
|
-
|
|
98
|
+
}
|
|
112
99
|
}
|
|
113
100
|
```
|
|
114
101
|
|
|
115
|
-
###
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- `
|
|
128
|
-
- `
|
|
129
|
-
- `filters`: 声明式过滤守卫。
|
|
130
|
-
- `action`: 唤醒动作负载,包含 `prompt` 模板和可选的 `deliverAs`(`"steer"` 或 `"followUp"`)。
|
|
131
|
-
|
|
132
|
-
### 过滤守卫 (Filters)
|
|
133
|
-
|
|
134
|
-
- `branch`: 目标分支名称,或特殊符号 `@current`(动态解析为本地当前分支)。
|
|
135
|
-
- `conclusion`: 工作流运行结论(`success`、`failure`、`cancelled`、`skipped` 等)。
|
|
136
|
-
- `workflow`: 工作流名称(如 `CI`)。
|
|
137
|
-
- `state`: 评审状态(`approved`、`changes_requested`、`commented`)或 PR 状态。
|
|
138
|
-
- `senders`: 允许的发送者登录名数组(如 `["coderabbitai[bot]"]`)。
|
|
139
|
-
- `isBot`: 布尔标志,匹配机器人(`true`)或人类(`false`)发送者。
|
|
140
|
-
- `match`: 任意键值映射表,使用点路径语法匹配负载深层字段(如 `{"review.state": "changes_requested"}`)。
|
|
102
|
+
### 字段说明
|
|
103
|
+
|
|
104
|
+
- `name`: 可选的工作流名称。
|
|
105
|
+
- `repo`: GitHub 仓库名(`owner/repo`)。默认为 `"auto"`(自动从 `git remote get-url origin` 提取)。
|
|
106
|
+
- `secret`: 可选的 Webhook HMAC SHA-256 鉴权密钥。
|
|
107
|
+
- `on`: GitHub Actions 事件触发器配置:
|
|
108
|
+
- `<event_name>`: 官方事件名称(如 `workflow_run`, `pull_request`, `push`, `issues`)。
|
|
109
|
+
- `types`: 官方活动类型过滤 `payload.action`(如 `["completed"]`, `["opened"]`)。
|
|
110
|
+
- `branches`: 分支过滤,支持 `@current` 动态分支与 glob(如 `["@current", "main", "feat/*"]`)。
|
|
111
|
+
- `workflows`: 工作流过滤(如 `["CI"]`)。
|
|
112
|
+
- `jobs`: 触发时执行的任务字典:
|
|
113
|
+
- `if`: 表达式条件(如 `github.event.workflow_run.conclusion == 'failure'`)。
|
|
114
|
+
- `prompt`: 传给 Pi Agent 的提示词内容模板。
|
|
115
|
+
- `deliverAs`: Pi 消息投递模式:`"followUp"`(默认排队执行)或 `"steer"`(实时插话打断)。
|
|
141
116
|
|
|
142
117
|
### 模板插值变量
|
|
143
118
|
|
|
@@ -151,7 +126,6 @@ pi -e /path/to/pi-ghwebhook
|
|
|
151
126
|
- `{{ sender }}` / `{{ sender_type }}`: 触发人或机器人
|
|
152
127
|
- `{{ review_state }}` / `{{ review_body }}` / `{{ review_url }}`: 评审详情
|
|
153
128
|
- `{{ comment_body }}` / `{{ comment_url }}`: 评论详情
|
|
154
|
-
- `{{ action }}`: 负载操作动作名
|
|
155
129
|
|
|
156
130
|
## 开发与质量门禁
|
|
157
131
|
|
package/dist/bus/event-bus.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GhWebhookWorkflow } from "../types/schema.js";
|
|
2
2
|
import type { WebhookPayload } from "../types/payload.js";
|
|
3
3
|
export interface ActionPlan {
|
|
4
4
|
prompt: string;
|
|
5
5
|
deliverAs: "steer" | "followUp";
|
|
6
|
-
|
|
6
|
+
jobId: string;
|
|
7
7
|
}
|
|
8
8
|
export declare class WebhookEventBus {
|
|
9
|
-
private readonly
|
|
10
|
-
constructor(
|
|
11
|
-
|
|
12
|
-
dispatch(
|
|
9
|
+
private readonly workflow;
|
|
10
|
+
constructor(workflow: GhWebhookWorkflow);
|
|
11
|
+
private getTriggerForEvent;
|
|
12
|
+
dispatch(incomingEvent: string, payload: WebhookPayload, currentBranch: string): ActionPlan[];
|
|
13
13
|
}
|
|
14
14
|
//# sourceMappingURL=event-bus.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/bus/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/bus/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAsB,MAAM,oBAAoB,CAAC;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,iBAAiB;IAExD,OAAO,CAAC,kBAAkB;IAkB1B,QAAQ,CACN,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,GACpB,UAAU,EAAE;CA2BhB"}
|
package/dist/bus/event-bus.js
CHANGED
|
@@ -1,51 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { normalizeEventKey, resolveIncomingEventKeys } from "./normalizer.js";
|
|
1
|
+
import { isTriggerMatched, evaluateCondition } from "./matcher.js";
|
|
3
2
|
import { extractVariables, renderTemplate } from "../template/renderer.js";
|
|
4
3
|
export class WebhookEventBus {
|
|
5
|
-
|
|
6
|
-
constructor(
|
|
7
|
-
|
|
8
|
-
const rawKey = rule.event ?? rule.topic;
|
|
9
|
-
if (!rawKey)
|
|
10
|
-
continue;
|
|
11
|
-
const normalizedKey = normalizeEventKey(rawKey);
|
|
12
|
-
const existing = this.routes.get(normalizedKey) ?? [];
|
|
13
|
-
existing.push(rule);
|
|
14
|
-
this.routes.set(normalizedKey, existing);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
getEvents() {
|
|
18
|
-
return Array.from(this.routes.keys());
|
|
4
|
+
workflow;
|
|
5
|
+
constructor(workflow) {
|
|
6
|
+
this.workflow = workflow;
|
|
19
7
|
}
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
matchedRules.push(rule);
|
|
32
|
-
}
|
|
8
|
+
getTriggerForEvent(incomingEvent) {
|
|
9
|
+
const on = this.workflow.on;
|
|
10
|
+
if (typeof on === "string") {
|
|
11
|
+
return on === incomingEvent ? null : undefined;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(on)) {
|
|
14
|
+
return on.includes(incomingEvent) ? null : undefined;
|
|
15
|
+
}
|
|
16
|
+
if (typeof on === "object" && on !== null) {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(on, incomingEvent)) {
|
|
18
|
+
return on[incomingEvent];
|
|
33
19
|
}
|
|
34
20
|
}
|
|
35
|
-
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
dispatch(incomingEvent, payload, currentBranch) {
|
|
24
|
+
const trigger = this.getTriggerForEvent(incomingEvent);
|
|
25
|
+
if (trigger === undefined) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
if (!isTriggerMatched(trigger, payload, currentBranch)) {
|
|
36
29
|
return [];
|
|
37
30
|
}
|
|
38
|
-
const ctx = { currentBranch, payload };
|
|
39
31
|
const vars = extractVariables(payload, currentBranch);
|
|
40
32
|
const plans = [];
|
|
41
|
-
for (const
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
prompt: renderTemplate(rule.action.prompt, vars),
|
|
45
|
-
deliverAs: rule.action.deliverAs ?? "followUp",
|
|
46
|
-
event: rule.event ?? rule.topic ?? event,
|
|
47
|
-
});
|
|
33
|
+
for (const [jobId, job] of Object.entries(this.workflow.jobs)) {
|
|
34
|
+
if (!evaluateCondition(job.if, payload, currentBranch)) {
|
|
35
|
+
continue;
|
|
48
36
|
}
|
|
37
|
+
plans.push({
|
|
38
|
+
prompt: renderTemplate(job.prompt, vars),
|
|
39
|
+
deliverAs: job.deliverAs ?? "followUp",
|
|
40
|
+
jobId,
|
|
41
|
+
});
|
|
49
42
|
}
|
|
50
43
|
return plans;
|
|
51
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../src/bus/event-bus.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../src/bus/event-bus.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAQ3E,MAAM,OAAO,eAAe;IACG;IAA7B,YAA6B,QAA2B;QAA3B,aAAQ,GAAR,QAAQ,CAAmB;IAAG,CAAC;IAEpD,kBAAkB,CACxB,aAAqB;QAErB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC3B,OAAO,EAAE,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QACjD,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QACvD,CAAC;QACD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC,EAAE,CAAC;gBAC5D,OAAO,EAAE,CAAC,aAAa,CAA8B,CAAC;YACxD,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,QAAQ,CACN,aAAqB,EACrB,OAAuB,EACvB,aAAqB;QAErB,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;QACvD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,CAAC;YACvD,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QACtD,MAAM,KAAK,GAAiB,EAAE,CAAC;QAE/B,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9D,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,CAAC;gBACvD,SAAS;YACX,CAAC;YAED,KAAK,CAAC,IAAI,CAAC;gBACT,MAAM,EAAE,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC;gBACxC,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,UAAU;gBACtC,KAAK;aACN,CAAC,CAAC;QACL,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WebhookPayload } from "../types/payload.js";
|
|
2
|
+
import type { EventTriggerConfig } from "../types/schema.js";
|
|
3
|
+
export declare function globMatch(pattern: string, text: string): boolean;
|
|
4
|
+
export declare function resolvePayloadBranch(payload: WebhookPayload): string | undefined;
|
|
5
|
+
export declare function isTriggerMatched(trigger: EventTriggerConfig | null | undefined, payload: WebhookPayload, currentBranch: string): boolean;
|
|
6
|
+
export declare function evaluateCondition(expr: string | undefined, payload: WebhookPayload, currentBranch: string): boolean;
|
|
7
|
+
//# sourceMappingURL=matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/bus/matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQhE;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,cAAc,GACtB,MAAM,GAAG,SAAS,CAUpB;AA8CD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAC9C,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,GACpB,OAAO,CAOT;AAmDD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,GACpB,OAAO,CA0BT"}
|