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 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 declarative filters, and wakes up the Pi Agent via `pi.sendUserMessage()` to close the loop.
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
- - Upstream wire protocol: directly matches GitHub Webhook events (`<event>` or `<event>.<action>`). Supports all 100+ GitHub Webhook events.
24
- - Declarative filters: condition evaluation (`branch`, `conclusion`, `workflow`, `state`, `senders`, `isBot`, `match`) is offloaded to configuration rules rather than rigid code branches.
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) hash map 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.
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` (or `.agents/ghwebhook.json`) in the root of any repository:
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
- "version": 1,
58
- "enabled": true,
59
- "repo": "auto",
60
- "subscriptions": [
61
- {
62
- "event": "workflow_run.completed",
63
- "filters": {
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
- "event": "workflow_run.completed",
75
- "filters": {
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
- "event": "pull_request_review.submitted",
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
- "event": "issues.opened",
98
- "action": {
99
- "prompt": "New issue opened #{{ issue_number }}: {{ issue_title }}\n\n{{ issue_body }}"
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
- "event": "push",
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
- | Field | Type | Default | Description |
118
- | --------------- | --------- | ----------- | -------------------------------------------------------------------------------------------------------- |
119
- | `version` | `1` | `1` | Schema version (must be 1). |
120
- | `enabled` | `boolean` | `true` | Enable or disable the extension for this workspace. |
121
- | `repo` | `string` | `"auto"` | Target GitHub repository (`owner/repo`). Resolved from `git remote get-url origin` when set to `"auto"`. |
122
- | `secret` | `string` | `undefined` | Optional webhook HMAC SHA-256 secret. |
123
- | `subscriptions` | `Array` | `[]` | List of event subscription rules. |
124
-
125
- ### Subscription Rule Fields
126
-
127
- - `event`: Upstream GitHub Webhook event identifier (e.g., `workflow_run.completed`, `pull_request.opened`, `push`, `issues.labeled`).
128
- - `topic`: Optional alias for `event`.
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 }}`: Branch name
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 事件,依据声明式守卫规则进行快速匹配过滤,并通过 `pi.sendUserMessage()` 主动唤醒 Pi Agent 进入闭环处置。
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
- - 上游协议 1:1 对齐:直接匹配 GitHub 官方线缆事件(`<event>` 或 `<event>.<action>`),天然支持所有 100+ 种 GitHub Webhook 事件。
24
- - 声明式灵活过滤:将状态与结论判断(`branch`、`conclusion`、`workflow`、`state`、`senders`、`isBot`、`match`)全部下沉至配置规则中,拒绝代码硬编码伪语义。
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
- 在需要启用事件驱动的仓库根目录下创建 `.pi/ghwebhook.json`(或 `.agents/ghwebhook.json`):
53
+ 在仓库根目录下创建 `.pi/ghwebhook.json`。配置 `$schema` 即可享受现代 IDE 的实时悬浮提示与自动补全:
54
54
 
55
55
  ```json
56
56
  {
57
- "version": 1,
58
- "enabled": true,
59
- "repo": "auto",
60
- "subscriptions": [
61
- {
62
- "event": "workflow_run.completed",
63
- "filters": {
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
- "event": "workflow_run.completed",
75
- "filters": {
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
- "event": "pull_request_review.submitted",
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
- "event": "issues.opened",
98
- "action": {
99
- "prompt": "收到新 Issue #{{ issue_number }}: {{ issue_title }}\n\n{{ issue_body }}"
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
- "event": "push",
104
- "filters": {
105
- "branch": "main"
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
- | `version` | `1` | `1` | 契约版本号(必须为 1)。 |
120
- | `enabled` | `boolean` | `true` | 是否在当前工作区启用该扩展。 |
121
- | `repo` | `string` | `"auto"` | 目标 GitHub 仓库名(`owner/repo`)。为 `"auto"` 时自动从 `git remote get-url origin` 提取。 |
122
- | `secret` | `string` | `undefined` | 可选的 Webhook HMAC SHA-256 鉴权密钥。 |
123
- | `subscriptions` | `Array` | `[]` | 声明式事件订阅规则列表。 |
124
-
125
- ### 订阅规则字段
126
-
127
- - `event`: 上游 GitHub Webhook 事件名称(如 `workflow_run.completed`、`pull_request.opened`、`push`、`issues.labeled`)。
128
- - `topic`: `event` 字段的可选别名。
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
 
@@ -1,14 +1,14 @@
1
- import type { SubscriptionRule } from "../types/schema.js";
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
- event: string;
6
+ jobId: string;
7
7
  }
8
8
  export declare class WebhookEventBus {
9
- private readonly routes;
10
- constructor(rules: SubscriptionRule[]);
11
- getEvents(): string[];
12
- dispatch(event: string, payload: WebhookPayload, currentBranch: string): ActionPlan[];
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,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;gBAEpD,KAAK,EAAE,gBAAgB,EAAE;IAYrC,SAAS,IAAI,MAAM,EAAE;IAIrB,QAAQ,CACN,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,GACpB,UAAU,EAAE;CAoChB"}
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"}
@@ -1,51 +1,44 @@
1
- import { evaluateGuards } from "./guards.js";
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
- routes = new Map();
6
- constructor(rules) {
7
- for (const rule of rules) {
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
- dispatch(event, payload, currentBranch) {
21
- const candidateKeys = resolveIncomingEventKeys(event, payload);
22
- const seenRules = new Set();
23
- const matchedRules = [];
24
- for (const key of candidateKeys) {
25
- const rules = this.routes.get(key);
26
- if (!rules)
27
- continue;
28
- for (const rule of rules) {
29
- if (!seenRules.has(rule)) {
30
- seenRules.add(rule);
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
- if (matchedRules.length === 0) {
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 rule of matchedRules) {
42
- if (evaluateGuards(rule.filters, ctx)) {
43
- plans.push({
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,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAQ3E,MAAM,OAAO,eAAe;IACT,MAAM,GAAG,IAAI,GAAG,EAA8B,CAAC;IAEhE,YAAY,KAAyB;QACnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC;YACxC,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,aAAa,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;YAEhD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;YACtD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,SAAS;QACP,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,CAAC;IAED,QAAQ,CACN,KAAa,EACb,OAAuB,EACvB,aAAqB;QAErB,MAAM,aAAa,GAAG,wBAAwB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC/D,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC9C,MAAM,YAAY,GAAuB,EAAE,CAAC;QAE5C,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACpB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,GAAG,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QACtD,MAAM,KAAK,GAAiB,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAChC,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;gBACtC,KAAK,CAAC,IAAI,CAAC;oBACT,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;oBAChD,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,UAAU;oBAC9C,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,KAAK;iBACzC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
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"}