@aivi/tracker-github 0.0.0-stage → 0.0.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/dist/cli.d.ts +10 -0
- package/dist/cli.js +34 -0
- package/dist/client.d.ts +247 -0
- package/dist/client.js +528 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.js +99 -0
- package/dist/desk.d.ts +13 -0
- package/dist/desk.js +67 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +10 -0
- package/dist/links.d.ts +56 -0
- package/dist/links.js +94 -0
- package/dist/module.d.ts +36 -0
- package/dist/module.js +817 -0
- package/dist/probe.d.ts +104 -0
- package/dist/probe.js +312 -0
- package/dist/projects.d.ts +41 -0
- package/dist/projects.js +117 -0
- package/dist/runprogress.d.ts +62 -0
- package/dist/runprogress.js +122 -0
- package/dist/setup-project.d.ts +125 -0
- package/dist/setup-project.js +638 -0
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +262 -0
- package/dist/tracker.d.ts +174 -0
- package/dist/tracker.js +505 -0
- package/dist/webhook.d.ts +102 -0
- package/dist/webhook.js +52 -0
- package/dist/work.d.ts +60 -0
- package/dist/work.js +83 -0
- package/docs/github-tracker.md +122 -0
- package/package.json +59 -4
- package/README.md +0 -3
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The GitHub tracker module's operator commands, mounted into the server CLI
|
|
2
|
+
* through the `./cli` subpath contract (@aivi/plugin's `(ctx) => Command`):
|
|
3
|
+
* status and resolve, over the same conversation store the module runs on.
|
|
4
|
+
* The command keeps the platform's short name — `aivi github` — for the same
|
|
5
|
+
* reason the conversations and the route do: they say who a conversation is
|
|
6
|
+
* on, not which package speaks for it. */
|
|
7
|
+
import type { PluginCliContext } from '@aivi/plugin';
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
export default _default;
|
|
10
|
+
declare function _default(ctx: PluginCliContext): Command;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolveBlocked } from '@aivi/plugin';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const enabled = async (ctx) => {
|
|
4
|
+
const loaded = await ctx.loaded();
|
|
5
|
+
const config = loaded.config.plugins['tracker-github'];
|
|
6
|
+
if (!config)
|
|
7
|
+
throw new Error('the GitHub tracker is not configured in config.json (no plugins.tracker-github block)');
|
|
8
|
+
return config;
|
|
9
|
+
};
|
|
10
|
+
export default (ctx) => {
|
|
11
|
+
const github = new Command('github').description('the GitHub tracker');
|
|
12
|
+
github
|
|
13
|
+
.command('status')
|
|
14
|
+
.description('Inspect GitHub conversations (workers and the assistant)')
|
|
15
|
+
.action(async () => {
|
|
16
|
+
await enabled(ctx);
|
|
17
|
+
const { describeWorkers, openGithubStore } = await import('./index.js');
|
|
18
|
+
await ctx.withStore(store => {
|
|
19
|
+
const inbox = openGithubStore(store);
|
|
20
|
+
ctx.print({ conversations: describeWorkers(inbox) });
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
github
|
|
24
|
+
.command('resolve <id>')
|
|
25
|
+
.description('Release a blocked worker')
|
|
26
|
+
.requiredOption('--reason <text>', 'what was found and done')
|
|
27
|
+
.requiredOption('--confirm-stopped', 'the external side has stopped')
|
|
28
|
+
.action(async (id, options) => {
|
|
29
|
+
await enabled(ctx);
|
|
30
|
+
const { openGithubStore } = await import('./index.js');
|
|
31
|
+
await resolveBlocked(ctx, id, options.reason, store => openGithubStore(store));
|
|
32
|
+
});
|
|
33
|
+
return github;
|
|
34
|
+
};
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/** Everything GitHub-shaped that the tracker asks of the API: the board
|
|
2
|
+
* reads and writes (Status and the `Agent` field), the issue facts, the
|
|
3
|
+
* comment trail, labels and reactions. Authentication is `@aivi/github`'s —
|
|
4
|
+
* one app, one installation, octokit's cached installation token — this file
|
|
5
|
+
* is only the queries and what they answer.
|
|
6
|
+
*
|
|
7
|
+
* The issue id in aivi's hands is `owner/repo#47`, the words a person
|
|
8
|
+
* types; the GraphQL node ids live here and nowhere above. Reads that lag
|
|
9
|
+
* their writes (live, 2026-10-07: a re-read right after a mutation answered
|
|
10
|
+
* the old value) are not trusted — the mutation's own answer is the truth,
|
|
11
|
+
* and the board's own webhook is the catch-up.
|
|
12
|
+
*/
|
|
13
|
+
import type { Logger } from '@aivi/core';
|
|
14
|
+
import { type GitHubApp } from '@aivi/github';
|
|
15
|
+
/** `owner/repo#47` — the issue id aivi speaks, parsed. `owner` and `name`
|
|
16
|
+
* are the two words octokit asks for; the full id is the string it came from. */
|
|
17
|
+
export interface IssueRef {
|
|
18
|
+
owner: string;
|
|
19
|
+
name: string;
|
|
20
|
+
number: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function issueRef(id: string): IssueRef;
|
|
23
|
+
export declare const repoIssueId: (owner: string, name: string, number: number) => string;
|
|
24
|
+
/** One single-select value of a board item: the option's name and id, and
|
|
25
|
+
* which field it answers (`Status`, `Agent`). */
|
|
26
|
+
export interface ItemSelectValue {
|
|
27
|
+
field: string;
|
|
28
|
+
name: string;
|
|
29
|
+
optionId: string;
|
|
30
|
+
}
|
|
31
|
+
/** One board item of an issue: the board's org-level number, the item's node
|
|
32
|
+
* id, and its single-select values. */
|
|
33
|
+
export interface BoardItem {
|
|
34
|
+
itemId: string;
|
|
35
|
+
boardNumber: number;
|
|
36
|
+
values: ItemSelectValue[];
|
|
37
|
+
}
|
|
38
|
+
/** A dependency as the issue reports it. */
|
|
39
|
+
export interface IssueDependency {
|
|
40
|
+
id: string;
|
|
41
|
+
completed: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** An issue as GitHub answers it — the client's own shape, before the
|
|
44
|
+
* adapter translates. `projectItems` carries the board facts; a caller who
|
|
45
|
+
* cares about one board filters by `boardNumber`. */
|
|
46
|
+
export interface GithubIssue {
|
|
47
|
+
id: string;
|
|
48
|
+
ref: string;
|
|
49
|
+
repo: string;
|
|
50
|
+
number: number;
|
|
51
|
+
title: string;
|
|
52
|
+
body: string | null;
|
|
53
|
+
url: string;
|
|
54
|
+
state: 'open' | 'closed';
|
|
55
|
+
stateReason: string | null;
|
|
56
|
+
labels: {
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
}[];
|
|
60
|
+
assignees: {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
}[];
|
|
64
|
+
projectItems: BoardItem[];
|
|
65
|
+
blockedBy: IssueDependency[];
|
|
66
|
+
}
|
|
67
|
+
/** A comment as GitHub answers it. */
|
|
68
|
+
export interface GithubComment {
|
|
69
|
+
id: number;
|
|
70
|
+
author: string | null;
|
|
71
|
+
authorIsBot: boolean;
|
|
72
|
+
body: string;
|
|
73
|
+
at: string;
|
|
74
|
+
}
|
|
75
|
+
/** The board's own fields, resolved once per start (the options a walk
|
|
76
|
+
* matches lanes against change when a person changes them, and a restart is
|
|
77
|
+
* when aivi learns of that): the node ids of `Status` and `Agent` and their
|
|
78
|
+
* options, in board order. */
|
|
79
|
+
export interface BoardFields {
|
|
80
|
+
projectId: string;
|
|
81
|
+
number: number;
|
|
82
|
+
statusFieldId: string;
|
|
83
|
+
statusOptions: {
|
|
84
|
+
id: string;
|
|
85
|
+
name: string;
|
|
86
|
+
}[];
|
|
87
|
+
agentFieldId: string;
|
|
88
|
+
agentOptions: {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
}[];
|
|
92
|
+
}
|
|
93
|
+
/** The dependency verdict in GitHub's words: closed is closed, whatever the
|
|
94
|
+
* reason — a `not_planned` blocker holds nothing back any more than a
|
|
95
|
+
* `completed` one does. */
|
|
96
|
+
export declare const dependencyCompleted: (state: string) => boolean;
|
|
97
|
+
/** The GraphQL node shapes these documents answer with: the looseness stays
|
|
98
|
+
* in the query documents, the mapping code below speaks types. Shared with
|
|
99
|
+
* setup-project, which reads the same board. */
|
|
100
|
+
export type GqlOption = {
|
|
101
|
+
id: string;
|
|
102
|
+
name: string;
|
|
103
|
+
};
|
|
104
|
+
export type GqlField = {
|
|
105
|
+
id: string;
|
|
106
|
+
name?: string | null;
|
|
107
|
+
options?: GqlOption[] | null;
|
|
108
|
+
};
|
|
109
|
+
export type GqlView = {
|
|
110
|
+
id: string;
|
|
111
|
+
name?: string | null;
|
|
112
|
+
fields?: {
|
|
113
|
+
nodes?: {
|
|
114
|
+
id?: string | null;
|
|
115
|
+
}[];
|
|
116
|
+
} | null;
|
|
117
|
+
};
|
|
118
|
+
export type GqlBoard = {
|
|
119
|
+
id: string;
|
|
120
|
+
title: string;
|
|
121
|
+
fields?: {
|
|
122
|
+
nodes?: (GqlField | null)[];
|
|
123
|
+
} | null;
|
|
124
|
+
views?: {
|
|
125
|
+
nodes?: (GqlView | null)[];
|
|
126
|
+
} | null;
|
|
127
|
+
};
|
|
128
|
+
/** Whether GitHub says the thing **does not exist** rather than "try
|
|
129
|
+
* again": a deleted issue answers its comments, its board item and its own
|
|
130
|
+
* node with 404 (REST) or `Could not resolve to a Node` (GraphQL). A
|
|
131
|
+
* closing owed to such a surface is dropped, not retried — the Linear
|
|
132
|
+
* tracker learned that on a grave (live, 2026-10-06). Anything else —
|
|
133
|
+
* a 502, a rate limit — is a retry. */
|
|
134
|
+
export declare const isGoneError: (error: unknown) => boolean;
|
|
135
|
+
/** The tracker's GitHub client: the app's octokit (from `@aivi/github`) with
|
|
136
|
+
* the board's queries and the issue REST on it. One per process, shared
|
|
137
|
+
* with nothing else: the forge's pull-request facts never come through here. */
|
|
138
|
+
export declare class GithubClient {
|
|
139
|
+
private readonly app;
|
|
140
|
+
private readonly boards;
|
|
141
|
+
constructor(app: GitHubApp);
|
|
142
|
+
/** Whose account made the grant (the org whose boards aivi reads). */
|
|
143
|
+
get org(): string;
|
|
144
|
+
/** The app's own login, GitHub's way: the bot suffix rides every post, and
|
|
145
|
+
* inbound filtering reads it back. */
|
|
146
|
+
get botLogin(): string;
|
|
147
|
+
/** The `Agent` option aivi delegates to: the option named for the app
|
|
148
|
+
* itself (D5) — the same word on every board, resolved to a per-board
|
|
149
|
+
* option id on use. */
|
|
150
|
+
get delegateName(): string;
|
|
151
|
+
/** Resolve a board's `Status` and `Agent` fields and their options, once.
|
|
152
|
+
* A board missing either is said by name — aivi never creates or renames
|
|
153
|
+
* a Status option (D4), and the `Agent` field is setup's job. */
|
|
154
|
+
boardFields(boardNumber: number): Promise<BoardFields>;
|
|
155
|
+
/** The board's Status options, in board order — the lane names a mapping
|
|
156
|
+
* is validated against. */
|
|
157
|
+
laneOptions(boardNumber: number): Promise<{
|
|
158
|
+
id: string;
|
|
159
|
+
name: string;
|
|
160
|
+
}[]>;
|
|
161
|
+
issue(id: string): Promise<GithubIssue>;
|
|
162
|
+
/** A repository's open issues, newest created first, each with its board
|
|
163
|
+
* facts: the walk's read (the plan's per-issue `projectItems`, paid in one
|
|
164
|
+
* round trip). Capped; a truncated read is said, never silently short. */
|
|
165
|
+
openIssues(repo: string, cap?: number): Promise<{
|
|
166
|
+
issues: GithubIssue[];
|
|
167
|
+
truncated: boolean;
|
|
168
|
+
}>;
|
|
169
|
+
/** The issue's lane on one board, read from its item: undefined when the
|
|
170
|
+
* issue is not on that board at all. */
|
|
171
|
+
laneOn(id: string, boardNumber: number): Promise<{
|
|
172
|
+
name: string;
|
|
173
|
+
optionId: string;
|
|
174
|
+
} | undefined>;
|
|
175
|
+
laneOf(issue: GithubIssue, boardNumber: number): {
|
|
176
|
+
name: string;
|
|
177
|
+
optionId: string;
|
|
178
|
+
} | undefined;
|
|
179
|
+
/** The issue's `Agent` value on one board: the option's name, or undefined
|
|
180
|
+
* when nobody speaks for the ticket. */
|
|
181
|
+
agentOn(issue: GithubIssue, boardNumber: number): string | undefined;
|
|
182
|
+
/** Move an issue into a board's Status option: onto the board first (the
|
|
183
|
+
* add is idempotent — a re-drive lands as nothing), then the option. The
|
|
184
|
+
* mutation's own answer is the truth; a re-read right after lags. */
|
|
185
|
+
setLane(id: string, boardNumber: number, lane: string): Promise<void>;
|
|
186
|
+
/** Set (or with null, clear) the `Agent` field — the delegate in GitHub's
|
|
187
|
+
* honest words (D5: an app cannot be an assignee, proven 403). */
|
|
188
|
+
setAgent(id: string, boardNumber: number, delegate: string | null): Promise<void>;
|
|
189
|
+
/** Open an issue in a repository — the desk's escape hatch. Labels are
|
|
190
|
+
* added by name (GitHub's own add is not create-on-demand, so each name
|
|
191
|
+
* is ensured first). Landing on the board in a lane is `setLane`'s. */
|
|
192
|
+
createIssue(repo: string, input: {
|
|
193
|
+
title: string;
|
|
194
|
+
body?: string;
|
|
195
|
+
labels?: string[];
|
|
196
|
+
}): Promise<GithubIssue>;
|
|
197
|
+
/** Rewrite the issue's own words; only what is named is touched. */
|
|
198
|
+
updateIssue(id: string, changes: {
|
|
199
|
+
title?: string;
|
|
200
|
+
description?: string;
|
|
201
|
+
}): Promise<GithubIssue>;
|
|
202
|
+
/** The comment trail, oldest first, with the author in GitHub's words —
|
|
203
|
+
* `authorIsBot` is how aivi's own posts are filtered inbound. */
|
|
204
|
+
comments(id: string, cap?: number): Promise<GithubComment[]>;
|
|
205
|
+
/** Post a comment and answer with GitHub's own id — `startSession` hands
|
|
206
|
+
* that back as the run's conversation handle (D6). */
|
|
207
|
+
createComment(id: string, body: string): Promise<{
|
|
208
|
+
id: number;
|
|
209
|
+
url: string;
|
|
210
|
+
}>;
|
|
211
|
+
updateComment(id: string, commentId: number, body: string): Promise<void>;
|
|
212
|
+
/** One comment's body by id — the status comment's own read, the only
|
|
213
|
+
* surface a progress edit may build on (never a remembered body). */
|
|
214
|
+
getComment(id: string, commentId: number): Promise<string | undefined>;
|
|
215
|
+
deleteComment(id: string, commentId: number): Promise<void>;
|
|
216
|
+
/** The `owner/repo#n` behind a GraphQL node id — the `projects_v2_item`
|
|
217
|
+
* payload names its issue only by node, and the board's events are worth
|
|
218
|
+
* nothing until that resolves. The number is the repository-local one;
|
|
219
|
+
* the node's `databaseId` is a global row id and resolves to no issue.
|
|
220
|
+
* Non-issue nodes answer undefined. */
|
|
221
|
+
nodeRef(nodeId: string): Promise<string | undefined>;
|
|
222
|
+
/** The repository's label catalogue — every name it knows. */
|
|
223
|
+
repoLabels(repo: string, cap?: number): Promise<string[]>;
|
|
224
|
+
/** Put one label on the ticket, creating it in the repository when none
|
|
225
|
+
* carries the name (add only, as the tool promises). */
|
|
226
|
+
addLabel(id: string, label: string): Promise<void>;
|
|
227
|
+
removeLabel(id: string, label: string): Promise<void>;
|
|
228
|
+
/** Create a label in the repository when none carries the name. The human
|
|
229
|
+
* label does not exist on a GitHub repository until aivi makes it —
|
|
230
|
+
* unlike Linear, where it must pre-exist. */
|
|
231
|
+
ensureLabel(repo: string, name: string): Promise<void>;
|
|
232
|
+
/** The assistant's marks (D10): reactions on the issue itself — `eyes`
|
|
233
|
+
* while the assistant works, `rocket` when it answers. An assistive
|
|
234
|
+
* surface only: no run state rides a reaction. */
|
|
235
|
+
addReaction(id: string, content: 'eyes' | 'rocket'): Promise<void>;
|
|
236
|
+
/** Take the assistant's own `eyes` off (a person's reactions are nobody to
|
|
237
|
+
* touch: only reactions by aivi's bot are removed). */
|
|
238
|
+
removeOwnReaction(id: string, content: 'eyes' | 'rocket'): Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
/** The tracker's client over the home's one app: connect through
|
|
241
|
+
* `@aivi/github`, and the webhook secret is read where it lives. A missing
|
|
242
|
+
* secret is a ConfigurationError the operator fixes in `<home>/.env` — no
|
|
243
|
+
* delivery is verifiable without it, so the module refuses to start. */
|
|
244
|
+
export declare function githubClient(log?: Logger): Promise<{
|
|
245
|
+
client: GithubClient;
|
|
246
|
+
webhookSecret: string;
|
|
247
|
+
}>;
|