pi-ghwebhook 0.0.0-stage → 0.1.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -2
  4. package/README_zh.md +168 -0
  5. package/dist/bus/event-bus.d.ts +14 -0
  6. package/dist/bus/event-bus.d.ts.map +1 -0
  7. package/dist/bus/event-bus.js +53 -0
  8. package/dist/bus/event-bus.js.map +1 -0
  9. package/dist/bus/guards.d.ts +10 -0
  10. package/dist/bus/guards.d.ts.map +1 -0
  11. package/dist/bus/guards.js +88 -0
  12. package/dist/bus/guards.js.map +1 -0
  13. package/dist/bus/normalizer.d.ts +14 -0
  14. package/dist/bus/normalizer.d.ts.map +1 -0
  15. package/dist/bus/normalizer.js +25 -0
  16. package/dist/bus/normalizer.js.map +1 -0
  17. package/dist/config/loader.d.ts +6 -0
  18. package/dist/config/loader.d.ts.map +1 -0
  19. package/dist/config/loader.js +63 -0
  20. package/dist/config/loader.js.map +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +81 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/template/renderer.d.ts +4 -0
  26. package/dist/template/renderer.d.ts.map +1 -0
  27. package/dist/template/renderer.js +121 -0
  28. package/dist/template/renderer.js.map +1 -0
  29. package/dist/transport/forwarder.d.ts +18 -0
  30. package/dist/transport/forwarder.d.ts.map +1 -0
  31. package/dist/transport/forwarder.js +73 -0
  32. package/dist/transport/forwarder.js.map +1 -0
  33. package/dist/transport/loopback.d.ts +20 -0
  34. package/dist/transport/loopback.d.ts.map +1 -0
  35. package/dist/transport/loopback.js +96 -0
  36. package/dist/transport/loopback.js.map +1 -0
  37. package/dist/types/enums.d.ts +41 -0
  38. package/dist/types/enums.d.ts.map +1 -0
  39. package/dist/types/enums.js +44 -0
  40. package/dist/types/enums.js.map +1 -0
  41. package/dist/types/payload.d.ts +28 -0
  42. package/dist/types/payload.d.ts.map +1 -0
  43. package/dist/types/payload.js +2 -0
  44. package/dist/types/payload.js.map +1 -0
  45. package/dist/types/schema.d.ts +59 -0
  46. package/dist/types/schema.d.ts.map +1 -0
  47. package/dist/types/schema.js +49 -0
  48. package/dist/types/schema.js.map +1 -0
  49. package/package.json +64 -4
  50. package/src/bus/event-bus.ts +72 -0
  51. package/src/bus/guards.ts +118 -0
  52. package/src/bus/normalizer.ts +32 -0
  53. package/src/config/loader.ts +80 -0
  54. package/src/index.ts +106 -0
  55. package/src/template/renderer.ts +145 -0
  56. package/src/transport/forwarder.ts +92 -0
  57. package/src/transport/loopback.ts +130 -0
  58. package/src/types/enums.ts +42 -0
  59. package/src/types/payload.ts +75 -0
  60. package/src/types/schema.ts +105 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-11-09
9
+
10
+ ### Added
11
+
12
+ - **Universal Upstream-Aligned Event Bus**: Direct 1-to-1 match with GitHub Webhook wire protocol (`<event>` / `<event>.<action>`), natively supporting all 100+ GitHub events (`workflow_run.completed`, `pull_request.opened`, `push`, `issues.opened`, etc.).
13
+ - **Composable Declarative Filters**: Replaced rigid synthetic code topics with flexible filters (`branch`, `conclusion`, `workflow`, `state`, `senders`, `isBot`, `match`), empowering users to configure any combination of conditions directly in `.pi/ghwebhook.json`.
14
+ - **Official Schema Alignment**: Full type alignment with official `@octokit/openapi-webhooks-types`.
15
+ - **In-Process Loopback Server**: Ephemeral port listener on `127.0.0.1:0` with optional HMAC SHA-256 webhook signature validation.
16
+ - **Full-Event Reverse Tunnel Forwarding**: Managed `gh webhook forward --events=*` subprocess with automatic teardown on `session_shutdown` and `process.exit`.
17
+ - **Template Renderer**: Safe, single-pass $O(N)$ string interpolation engine for prompt synthesis.
18
+ - **Workspace Probing**: Zero-footprint detection of `.pi/ghwebhook.json` and `.agents/ghwebhook.json`.
19
+ - **Tooling & Quality Gates**: Native `mise` and `hk` orchestration with `oxlint`, `tsc`, `prettier`, `lizard` complexity audit, and `jscpd` code duplication gate.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xifan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,168 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ # pi-ghwebhook
4
+
5
+ **Universal GitHub Webhook event bus and wake-up driver for the Pi coding agent**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/pi-ghwebhook?logo=npm&logoColor=white)](https://www.npmjs.com/package/pi-ghwebhook)
8
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D22.19.0-339933?logo=nodedotjs&logoColor=white)](https://nodejs.org)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.8-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
10
+ [![Pi Package](https://img.shields.io/badge/pi--package-extension-black)](https://github.com/earendil-works/pi)
11
+ [![GitHub License](https://img.shields.io/github/license/xifan2333/pi-ghwebhook?logo=github&logoColor=white)](LICENSE)
12
+
13
+ English | [中文](README_zh.md)
14
+
15
+ </div>
16
+
17
+ ## Overview
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.
20
+
21
+ ## Key Architecture
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.
30
+
31
+ ## Installation
32
+
33
+ Install into your Pi coding agent:
34
+
35
+ ```bash
36
+ pi install npm:pi-ghwebhook
37
+ ```
38
+
39
+ Or install from GitHub:
40
+
41
+ ```bash
42
+ pi install git:github.com/xifan2333/pi-ghwebhook
43
+ ```
44
+
45
+ To test locally in an active session:
46
+
47
+ ```bash
48
+ pi -e /path/to/pi-ghwebhook
49
+ ```
50
+
51
+ ## Configuration (`.pi/ghwebhook.json`)
52
+
53
+ Place `.pi/ghwebhook.json` (or `.agents/ghwebhook.json`) in the root of any repository:
54
+
55
+ ```json
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
+ }
72
+ },
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
+ }
83
+ },
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
+ }
95
+ },
96
+ {
97
+ "event": "issues.opened",
98
+ "action": {
99
+ "prompt": "New issue opened #{{ issue_number }}: {{ issue_title }}\n\n{{ issue_body }}"
100
+ }
101
+ },
102
+ {
103
+ "event": "push",
104
+ "filters": {
105
+ "branch": "main"
106
+ },
107
+ "action": {
108
+ "prompt": "New commit pushed to main: {{ head_sha }} - {{ commit_message }}"
109
+ }
110
+ }
111
+ ]
112
+ }
113
+ ```
114
+
115
+ ### Configuration Fields
116
+
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"}`).
141
+
142
+ ### Template Variables
143
+
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
155
+
156
+ ## Development & Quality Gates
157
+
158
+ ```bash
159
+ mise run check # Run all quality gates (tsc, oxlint, prettier, lizard, jscpd, typos)
160
+ mise run check:plan # Preview execution plan
161
+ mise run check:changed # Check only staged/modified files (< 50ms)
162
+ mise run fix # Auto-format and fix lint/package issues
163
+ mise run test # Run test suite
164
+ ```
165
+
166
+ ## License
167
+
168
+ [MIT](LICENSE) © 2026 xifan
package/README_zh.md ADDED
@@ -0,0 +1,168 @@
1
+ <div align="center">
2
+
3
+ # pi-ghwebhook
4
+
5
+ **适用于 Pi coding agent 的通用 GitHub Webhook 事件总线与自动唤醒驱动**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/pi-ghwebhook?logo=npm&logoColor=white)](https://www.npmjs.com/package/pi-ghwebhook)
8
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D22.19.0-339933?logo=nodedotjs&logoColor=white)](https://nodejs.org)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.8-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
10
+ [![Pi Package](https://img.shields.io/badge/pi--package-extension-black)](https://github.com/earendil-works/pi)
11
+ [![GitHub License](https://img.shields.io/github/license/xifan2333/pi-ghwebhook?logo=github&logoColor=white)](LICENSE)
12
+
13
+ [English](README.md) | 简体中文
14
+
15
+ </div>
16
+
17
+ ## 概述
18
+
19
+ pi-ghwebhook 旨在彻底消除提交代码或创建 Pull Request 后的人工轮询与终端反复切换。通过在进程内运行回环 HTTP 服务并托管 GitHub CLI 的 `gh webhook forward` 反向长连接隧道,它可以实时接收 GitHub Webhook 事件,依据声明式守卫规则进行快速匹配过滤,并通过 `pi.sendUserMessage()` 主动唤醒 Pi Agent 进入闭环处置。
20
+
21
+ ## 核心架构特性
22
+
23
+ - 上游协议 1:1 对齐:直接匹配 GitHub 官方线缆事件(`<event>` 或 `<event>.<action>`),天然支持所有 100+ 种 GitHub Webhook 事件。
24
+ - 声明式灵活过滤:将状态与结论判断(`branch`、`conclusion`、`workflow`、`state`、`senders`、`isBot`、`match`)全部下沉至配置规则中,拒绝代码硬编码伪语义。
25
+ - 零公网暴露:依托 `gh webhook forward --events=*` 反向隧道,无需公网服务器、固定 IP 或内网穿透工具。
26
+ - 动态端口分配:监听 `127.0.0.1:0`,由操作系统内核分配空闲端口,杜绝多终端或多会话并发运行时的端口冲突。
27
+ - 表驱动分发:基于哈希表实现 O(1) 路由分发,执行严格的复杂度门禁(CCN <= 15, CogC <= 15)。
28
+ - 工作区自感知:在 `session_start` 生命周期探测 `.pi/ghwebhook.json`,无配置时静默休眠,零资源占用。
29
+ - 幂等清理保障:深度绑定 `session_shutdown` 与进程退出钩子,确保子进程与本地服务干净释放,不留孤儿进程。
30
+
31
+ ## 安装
32
+
33
+ 安装到 Pi coding agent:
34
+
35
+ ```bash
36
+ pi install npm:pi-ghwebhook
37
+ ```
38
+
39
+ 或直接通过 GitHub 仓库安装:
40
+
41
+ ```bash
42
+ pi install git:github.com/xifan2333/pi-ghwebhook
43
+ ```
44
+
45
+ 在当前会话中即时测试:
46
+
47
+ ```bash
48
+ pi -e /path/to/pi-ghwebhook
49
+ ```
50
+
51
+ ## 配置契约 (`.pi/ghwebhook.json`)
52
+
53
+ 在需要启用事件驱动的仓库根目录下创建 `.pi/ghwebhook.json`(或 `.agents/ghwebhook.json`):
54
+
55
+ ```json
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
+ }
72
+ },
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
+ }
83
+ },
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
+ }
95
+ },
96
+ {
97
+ "event": "issues.opened",
98
+ "action": {
99
+ "prompt": "收到新 Issue #{{ issue_number }}: {{ issue_title }}\n\n{{ issue_body }}"
100
+ }
101
+ },
102
+ {
103
+ "event": "push",
104
+ "filters": {
105
+ "branch": "main"
106
+ },
107
+ "action": {
108
+ "prompt": "main 分支收到新推送: {{ head_sha }} - {{ commit_message }}"
109
+ }
110
+ }
111
+ ]
112
+ }
113
+ ```
114
+
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"}`)。
141
+
142
+ ### 模板插值变量
143
+
144
+ - `{{ branch }}`: 分支名称
145
+ - `{{ head_sha }}`: 触发提交哈希
146
+ - `{{ commit_message }}`: 提交日志
147
+ - `{{ repo }}` / `{{ repo_url }}`: 仓库名及网页链接
148
+ - `{{ run_id }}` / `{{ run_url }}` / `{{ workflow }}` / `{{ conclusion }}`: 工作流运行元数据
149
+ - `{{ pr_number }}` / `{{ pr_title }}` / `{{ pr_url }}`: PR 详情
150
+ - `{{ issue_number }}` / `{{ issue_title }}` / `{{ issue_url }}` / `{{ issue_body }}`: Issue 详情
151
+ - `{{ sender }}` / `{{ sender_type }}`: 触发人或机器人
152
+ - `{{ review_state }}` / `{{ review_body }}` / `{{ review_url }}`: 评审详情
153
+ - `{{ comment_body }}` / `{{ comment_url }}`: 评论详情
154
+ - `{{ action }}`: 负载操作动作名
155
+
156
+ ## 开发与质量门禁
157
+
158
+ ```bash
159
+ mise run check # 执行全量质量检查(tsc, oxlint, prettier, lizard, jscpd, typos)
160
+ mise run check:plan # 预览执行计划
161
+ mise run check:changed # 增量检查暂存区及修改文件 (< 50ms)
162
+ mise run fix # 自动格式化并修复问题
163
+ mise run test # 运行测试套件
164
+ ```
165
+
166
+ ## 许可证
167
+
168
+ [MIT](LICENSE) © 2026 xifan
@@ -0,0 +1,14 @@
1
+ import type { SubscriptionRule } from "../types/schema.js";
2
+ import type { WebhookPayload } from "../types/payload.js";
3
+ export interface ActionPlan {
4
+ prompt: string;
5
+ deliverAs: "steer" | "followUp";
6
+ event: string;
7
+ }
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[];
13
+ }
14
+ //# sourceMappingURL=event-bus.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,53 @@
1
+ import { evaluateGuards } from "./guards.js";
2
+ import { normalizeEventKey, resolveIncomingEventKeys } from "./normalizer.js";
3
+ import { extractVariables, renderTemplate } from "../template/renderer.js";
4
+ 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());
19
+ }
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
+ }
33
+ }
34
+ }
35
+ if (matchedRules.length === 0) {
36
+ return [];
37
+ }
38
+ const ctx = { currentBranch, payload };
39
+ const vars = extractVariables(payload, currentBranch);
40
+ 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
+ });
48
+ }
49
+ }
50
+ return plans;
51
+ }
52
+ }
53
+ //# sourceMappingURL=event-bus.js.map
@@ -0,0 +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"}
@@ -0,0 +1,10 @@
1
+ import type { WebhookPayload } from "../types/payload.js";
2
+ import type { FilterConfig } from "../types/schema.js";
3
+ export interface FilterContext {
4
+ currentBranch: string;
5
+ payload: WebhookPayload;
6
+ }
7
+ export type GuardPredicate = (expectedValue: unknown, ctx: FilterContext) => boolean;
8
+ export declare const GUARDS: ReadonlyMap<keyof FilterConfig, GuardPredicate>;
9
+ export declare function evaluateGuards(filters: FilterConfig | undefined, ctx: FilterContext): boolean;
10
+ //# sourceMappingURL=guards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../src/bus/guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,CAC3B,aAAa,EAAE,OAAO,EACtB,GAAG,EAAE,aAAa,KACf,OAAO,CAAC;AAiFb,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,YAAY,EAAE,cAAc,CAQjE,CAAC;AAEH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,GAAG,EAAE,aAAa,GACjB,OAAO,CAYT"}
@@ -0,0 +1,88 @@
1
+ function resolvePayloadBranch(payload) {
2
+ return (payload.workflow_run?.head_branch ??
3
+ payload.pull_request?.head?.ref ??
4
+ payload.check_suite?.head_branch ??
5
+ payload.workflow_job?.head_branch ??
6
+ (typeof payload.ref === "string"
7
+ ? payload.ref.replace("refs/heads/", "")
8
+ : undefined));
9
+ }
10
+ function resolvePayloadSender(payload) {
11
+ return (payload.sender?.login ??
12
+ payload.review?.user?.login ??
13
+ payload.comment?.user?.login);
14
+ }
15
+ function getNestedValue(obj, path) {
16
+ const parts = path.split(".");
17
+ let current = obj;
18
+ for (const part of parts) {
19
+ if (current == null || typeof current !== "object")
20
+ return undefined;
21
+ current = current[part];
22
+ }
23
+ return current;
24
+ }
25
+ const branchGuard = (expected, { currentBranch, payload }) => {
26
+ const target = expected === "@current" ? currentBranch : expected;
27
+ const actualBranch = resolvePayloadBranch(payload);
28
+ return typeof target === "string" && actualBranch === target;
29
+ };
30
+ const conclusionGuard = (expected, { payload }) => {
31
+ const actual = payload.workflow_run?.conclusion ??
32
+ payload.check_suite?.conclusion ??
33
+ payload.workflow_job?.conclusion;
34
+ return typeof expected === "string" && actual === expected;
35
+ };
36
+ const workflowGuard = (expected, { payload }) => {
37
+ const workflowName = payload.workflow_run?.name ?? payload.workflow_job?.workflow_name;
38
+ return typeof expected === "string" && workflowName === expected;
39
+ };
40
+ const stateGuard = (expected, { payload }) => {
41
+ const actual = payload.review?.state ?? payload.pull_request?.state;
42
+ return typeof expected === "string" && actual === expected;
43
+ };
44
+ const sendersGuard = (allowedList, { payload }) => {
45
+ if (!Array.isArray(allowedList))
46
+ return true;
47
+ const sender = resolvePayloadSender(payload);
48
+ return typeof sender === "string" && allowedList.includes(sender);
49
+ };
50
+ const isBotGuard = (expected, { payload }) => {
51
+ const senderType = payload.review?.user?.type ?? payload.sender?.type;
52
+ const login = payload.review?.user?.login ?? payload.sender?.login ?? "";
53
+ const isBot = senderType === "Bot" || login.endsWith("[bot]");
54
+ return isBot === Boolean(expected);
55
+ };
56
+ const matchGuard = (matchObj, { payload }) => {
57
+ if (!matchObj || typeof matchObj !== "object")
58
+ return true;
59
+ for (const [path, expected] of Object.entries(matchObj)) {
60
+ const actual = getNestedValue(payload, path);
61
+ if (actual !== expected)
62
+ return false;
63
+ }
64
+ return true;
65
+ };
66
+ export const GUARDS = new Map([
67
+ ["branch", branchGuard],
68
+ ["conclusion", conclusionGuard],
69
+ ["workflow", workflowGuard],
70
+ ["state", stateGuard],
71
+ ["senders", sendersGuard],
72
+ ["isBot", isBotGuard],
73
+ ["match", matchGuard],
74
+ ]);
75
+ export function evaluateGuards(filters, ctx) {
76
+ if (!filters)
77
+ return true;
78
+ for (const [key, value] of Object.entries(filters)) {
79
+ if (value === undefined)
80
+ continue;
81
+ const guard = GUARDS.get(key);
82
+ if (guard && !guard(value, ctx)) {
83
+ return false;
84
+ }
85
+ }
86
+ return true;
87
+ }
88
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/bus/guards.ts"],"names":[],"mappings":"AAaA,SAAS,oBAAoB,CAAC,OAAuB;IACnD,OAAO,CACL,OAAO,CAAC,YAAY,EAAE,WAAW;QACjC,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG;QAC/B,OAAO,CAAC,WAAW,EAAE,WAAW;QAChC,OAAO,CAAC,YAAY,EAAE,WAAW;QACjC,CAAC,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;YAC9B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;YACxC,CAAC,CAAC,SAAS,CAAC,CACf,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,OAAuB;IACnD,OAAO,CACL,OAAO,CAAC,MAAM,EAAE,KAAK;QACrB,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK;QAC3B,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAC7B,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,GAAwB,EAAE,IAAY;IAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,OAAO,GAAQ,GAAG,CAAC;IACvB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACrE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,WAAW,GAAmB,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,EAAE;IAC3E,MAAM,MAAM,GAAG,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClE,MAAM,YAAY,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IACnD,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,YAAY,KAAK,MAAM,CAAC;AAC/D,CAAC,CAAC;AAEF,MAAM,eAAe,GAAmB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAChE,MAAM,MAAM,GACV,OAAO,CAAC,YAAY,EAAE,UAAU;QAChC,OAAO,CAAC,WAAW,EAAE,UAAU;QAC/B,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC;IACnC,OAAO,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,KAAK,QAAQ,CAAC;AAC7D,CAAC,CAAC;AAEF,MAAM,aAAa,GAAmB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC9D,MAAM,YAAY,GAChB,OAAO,CAAC,YAAY,EAAE,IAAI,IAAI,OAAO,CAAC,YAAY,EAAE,aAAa,CAAC;IACpE,OAAO,OAAO,QAAQ,KAAK,QAAQ,IAAI,YAAY,KAAK,QAAQ,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,UAAU,GAAmB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,IAAI,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC;IACpE,OAAO,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,KAAK,QAAQ,CAAC;AAC7D,CAAC,CAAC;AAEF,MAAM,YAAY,GAAmB,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAChE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACpE,CAAC,CAAC;AAEF,MAAM,UAAU,GAAmB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;IACtE,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;IACzE,MAAM,KAAK,GAAG,UAAU,KAAK,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9D,OAAO,KAAK,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AACrC,CAAC,CAAC;AAEF,MAAM,UAAU,GAAmB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC3D,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3D,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAC3C,QAAmC,CACpC,EAAE,CAAC;QACF,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC7C,IAAI,MAAM,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAoD,IAAI,GAAG,CAAC;IAC7E,CAAC,QAAQ,EAAE,WAAW,CAAC;IACvB,CAAC,YAAY,EAAE,eAAe,CAAC;IAC/B,CAAC,UAAU,EAAE,aAAa,CAAC;IAC3B,CAAC,OAAO,EAAE,UAAU,CAAC;IACrB,CAAC,SAAS,EAAE,YAAY,CAAC;IACzB,CAAC,OAAO,EAAE,UAAU,CAAC;IACrB,CAAC,OAAO,EAAE,UAAU,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,UAAU,cAAc,CAC5B,OAAiC,EACjC,GAAkB;IAElB,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAyB,CAAC,CAAC;QACpD,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { WebhookPayload } from "../types/payload.js";
2
+ /**
3
+ * Normalizes an event identifier by stripping optional "github." prefix.
4
+ * e.g. "github.workflow_run.completed" -> "workflow_run.completed"
5
+ */
6
+ export declare function normalizeEventKey(key: string): string;
7
+ /**
8
+ * Resolves candidate event keys from an incoming GitHub Webhook event and payload.
9
+ * Upstream convention:
10
+ * 1. If payload has action: `${event}.${action}` (e.g. "workflow_run.completed", "pull_request.opened")
11
+ * 2. Wildcard/base event: `${event}` (e.g. "workflow_run", "push", "issues")
12
+ */
13
+ export declare function resolveIncomingEventKeys(event: string, payload: WebhookPayload): string[];
14
+ //# sourceMappingURL=normalizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../src/bus/normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,GACtB,MAAM,EAAE,CAWV"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Normalizes an event identifier by stripping optional "github." prefix.
3
+ * e.g. "github.workflow_run.completed" -> "workflow_run.completed"
4
+ */
5
+ export function normalizeEventKey(key) {
6
+ const trimmed = key.trim();
7
+ return trimmed.startsWith("github.") ? trimmed.slice(7) : trimmed;
8
+ }
9
+ /**
10
+ * Resolves candidate event keys from an incoming GitHub Webhook event and payload.
11
+ * Upstream convention:
12
+ * 1. If payload has action: `${event}.${action}` (e.g. "workflow_run.completed", "pull_request.opened")
13
+ * 2. Wildcard/base event: `${event}` (e.g. "workflow_run", "push", "issues")
14
+ */
15
+ export function resolveIncomingEventKeys(event, payload) {
16
+ const normalizedEvent = normalizeEventKey(event);
17
+ const action = typeof payload.action === "string" ? payload.action.trim() : undefined;
18
+ const keys = [];
19
+ if (action) {
20
+ keys.push(`${normalizedEvent}.${action}`);
21
+ }
22
+ keys.push(normalizedEvent);
23
+ return keys;
24
+ }
25
+ //# sourceMappingURL=normalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizer.js","sourceRoot":"","sources":["../../src/bus/normalizer.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,OAAO,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAAa,EACb,OAAuB;IAEvB,MAAM,eAAe,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,MAAM,GACV,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAEzE,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC,IAAI,CAAC,GAAG,eAAe,IAAI,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { GhWebhookConfig } from "../types/schema.js";
2
+ export declare function loadProjectConfig(cwd: string): Promise<GhWebhookConfig | null>;
3
+ export declare function resolveGitBranch(cwd: string): Promise<string>;
4
+ export declare function parseGitHubRepo(remoteUrl: string): string | null;
5
+ export declare function resolveGitRepo(cwd: string): Promise<string | null>;
6
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAmB1D,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAmBjC;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWnE;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOhE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWxE"}