@happyvertical/smrt-projects 0.37.2 → 0.37.3
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/chunks/Issue-DITLxBl7.js +483 -0
- package/dist/chunks/Issue-DITLxBl7.js.map +1 -0
- package/dist/chunks/PullRequest-C6mck19s.js +255 -0
- package/dist/chunks/PullRequest-C6mck19s.js.map +1 -0
- package/dist/chunks/constants-BhVfX4Jn.js +6 -0
- package/dist/chunks/constants-BhVfX4Jn.js.map +1 -0
- package/dist/index.js +1486 -2455
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/playground.js +100 -126
- package/dist/playground.js.map +1 -1
- package/dist/smrt-knowledge.json +11 -11
- package/dist/types.js +0 -2
- package/dist/ui.js +90 -83
- package/dist/ui.js.map +1 -1
- package/package.json +23 -23
- package/dist/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,1018 +1,438 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { i as __exportAll, r as issueIncorporateFeedbackPrompt, t as Issue } from "./chunks/Issue-DITLxBl7.js";
|
|
2
|
+
import "./chunks/constants-BhVfX4Jn.js";
|
|
3
|
+
import { t as PullRequest } from "./chunks/PullRequest-C6mck19s.js";
|
|
4
|
+
import { PROJECTS_MODULE_META, PROJECTS_UI_SLOTS } from "./ui.js";
|
|
5
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
6
|
+
import { TenantScoped, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
|
|
6
7
|
import { createLogger } from "@happyvertical/logger";
|
|
7
8
|
import { getProject } from "@happyvertical/projects";
|
|
8
9
|
import { getModuleConfig } from "@happyvertical/smrt-config";
|
|
9
10
|
import { getRepository } from "@happyvertical/repos";
|
|
10
|
-
|
|
11
|
-
ObjectRegistry.registerPackageManifest(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
11
|
+
//#region src/__smrt-register__.ts
|
|
12
|
+
ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/collections/Issues.ts
|
|
15
|
+
var Issues_exports = /* @__PURE__ */ __exportAll({ IssueCollection: () => IssueCollection });
|
|
16
|
+
var IssueCollection = class extends SmrtCollection {
|
|
17
|
+
static _itemClass = Issue;
|
|
18
|
+
/**
|
|
19
|
+
* Discover issues from a repository and sync to database
|
|
20
|
+
*
|
|
21
|
+
* This method:
|
|
22
|
+
* 1. Fetches issues from the provider via SDK
|
|
23
|
+
* 2. Creates/updates SMRT Issue records in the database
|
|
24
|
+
* 3. Returns the synced Issue objects
|
|
25
|
+
*
|
|
26
|
+
* @param options - Discovery options
|
|
27
|
+
* @returns Array of Issue objects
|
|
28
|
+
*/
|
|
29
|
+
async discover(options) {
|
|
30
|
+
const { repository, filters } = options;
|
|
31
|
+
const repositoryId = repository.id ?? void 0;
|
|
32
|
+
if (!repositoryId) throw new Error("Repository must be saved before discovering issues");
|
|
33
|
+
const remoteIssues = await (await repository.getClient()).searchIssues("", filters);
|
|
34
|
+
const issues = [];
|
|
35
|
+
for (const remote of remoteIssues) {
|
|
36
|
+
let issue = await this.findOne({ where: {
|
|
37
|
+
repositoryId,
|
|
38
|
+
number: remote.number
|
|
39
|
+
} });
|
|
40
|
+
if (!issue) issue = await this.create({
|
|
41
|
+
repositoryId,
|
|
42
|
+
number: remote.number,
|
|
43
|
+
nodeId: remote.id,
|
|
44
|
+
title: remote.title,
|
|
45
|
+
body: remote.body,
|
|
46
|
+
state: remote.state,
|
|
47
|
+
author: remote.author.login,
|
|
48
|
+
labels: remote.labels.map((l) => l.name),
|
|
49
|
+
assignees: remote.assignees.map((a) => a.login),
|
|
50
|
+
commentsCount: remote.commentsCount,
|
|
51
|
+
lastSyncedAt: /* @__PURE__ */ new Date()
|
|
52
|
+
});
|
|
53
|
+
else {
|
|
54
|
+
issue.nodeId = remote.id;
|
|
55
|
+
issue.title = remote.title;
|
|
56
|
+
issue.body = remote.body;
|
|
57
|
+
issue.state = remote.state;
|
|
58
|
+
issue.author = remote.author.login;
|
|
59
|
+
issue.labels = remote.labels.map((l) => l.name);
|
|
60
|
+
issue.assignees = remote.assignees.map((a) => a.login);
|
|
61
|
+
issue.commentsCount = remote.commentsCount;
|
|
62
|
+
issue.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
63
|
+
}
|
|
64
|
+
await issue.save();
|
|
65
|
+
issues.push(issue);
|
|
66
|
+
}
|
|
67
|
+
return issues;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Find issues by repository
|
|
71
|
+
*
|
|
72
|
+
* @param repositoryId - Repository ID
|
|
73
|
+
* @returns Array of issues
|
|
74
|
+
*/
|
|
75
|
+
async findByRepository(repositoryId) {
|
|
76
|
+
return await this.list({ where: { repositoryId } });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Find open issues
|
|
80
|
+
*
|
|
81
|
+
* @param repositoryId - Optional repository filter
|
|
82
|
+
* @returns Array of open issues
|
|
83
|
+
*/
|
|
84
|
+
async findOpen(repositoryId) {
|
|
85
|
+
const where = { state: "open" };
|
|
86
|
+
if (repositoryId) where.repositoryId = repositoryId;
|
|
87
|
+
return await this.list({ where });
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Find issues by label
|
|
91
|
+
*
|
|
92
|
+
* @param label - Label name
|
|
93
|
+
* @param repositoryId - Optional repository filter
|
|
94
|
+
* @returns Array of issues with the label
|
|
95
|
+
*/
|
|
96
|
+
async findByLabel(label, repositoryId) {
|
|
97
|
+
return (await this.list({ where: repositoryId ? { repositoryId } : {} })).filter((issue) => issue.labels.includes(label));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Find issues by assignee
|
|
101
|
+
*
|
|
102
|
+
* @param assignee - Assignee login
|
|
103
|
+
* @param repositoryId - Optional repository filter
|
|
104
|
+
* @returns Array of issues assigned to the user
|
|
105
|
+
*/
|
|
106
|
+
async findByAssignee(assignee, repositoryId) {
|
|
107
|
+
return (await this.list({ where: repositoryId ? { repositoryId } : {} })).filter((issue) => issue.assignees.includes(assignee));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Find issues needing review (AI-powered)
|
|
111
|
+
*
|
|
112
|
+
* @param repositoryId - Optional repository filter
|
|
113
|
+
* @returns Array of issues that need review
|
|
114
|
+
*/
|
|
115
|
+
async findNeedingReview(repositoryId) {
|
|
116
|
+
const openIssues = await this.findOpen(repositoryId);
|
|
117
|
+
const needingReview = [];
|
|
118
|
+
for (const issue of openIssues) if (await issue.needsReview()) needingReview.push(issue);
|
|
119
|
+
return needingReview;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Find issue by number in a repository
|
|
123
|
+
*
|
|
124
|
+
* @param repositoryId - Repository ID
|
|
125
|
+
* @param number - Issue number
|
|
126
|
+
* @returns Issue or null
|
|
127
|
+
*/
|
|
128
|
+
async findByNumber(repositoryId, number) {
|
|
129
|
+
return (await this.list({
|
|
130
|
+
where: {
|
|
131
|
+
repositoryId,
|
|
132
|
+
number
|
|
133
|
+
},
|
|
134
|
+
limit: 1
|
|
135
|
+
}))[0] || null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get issues with unincorporated feedback
|
|
139
|
+
*
|
|
140
|
+
* Issues that have comments but haven't had feedback incorporated
|
|
141
|
+
*
|
|
142
|
+
* @param repositoryId - Optional repository filter
|
|
143
|
+
* @returns Array of issues
|
|
144
|
+
*/
|
|
145
|
+
async findWithUnincorporatedFeedback(repositoryId) {
|
|
146
|
+
return (await this.findOpen(repositoryId)).filter((issue) => issue.commentsCount > 0 && issue.synthesisCount === 0);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Batch sync issues from repository
|
|
150
|
+
*
|
|
151
|
+
* @param repository - Repository to sync from
|
|
152
|
+
* @param options - Sync options
|
|
153
|
+
* @returns Array of synced issues
|
|
154
|
+
*/
|
|
155
|
+
async batchSync(repository, options = {}) {
|
|
156
|
+
const issues = await this.findByRepository(repository.id);
|
|
157
|
+
const synced = [];
|
|
158
|
+
for (const issue of issues) {
|
|
159
|
+
await issue.sync(options);
|
|
160
|
+
synced.push(issue);
|
|
161
|
+
}
|
|
162
|
+
return synced;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Find issues by tenant ID
|
|
166
|
+
*
|
|
167
|
+
* @param tenantId - Tenant ID to filter by
|
|
168
|
+
* @returns Array of issues for the tenant
|
|
169
|
+
*/
|
|
170
|
+
async findByTenant(tenantId) {
|
|
171
|
+
return this.list({ where: { tenantId } });
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Find all global issues (no tenant association).
|
|
175
|
+
*
|
|
176
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
177
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
178
|
+
* flagged as an isolation violation). (#1600)
|
|
179
|
+
*
|
|
180
|
+
* @returns Array of global issues
|
|
181
|
+
*/
|
|
182
|
+
async findGlobal() {
|
|
183
|
+
return queryGlobal(this);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Find issues for a tenant plus all global issues.
|
|
187
|
+
*
|
|
188
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
189
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
190
|
+
*
|
|
191
|
+
* @param tenantId - Tenant ID to filter by
|
|
192
|
+
* @returns Array of tenant and global issues
|
|
193
|
+
*/
|
|
194
|
+
async findWithGlobals(tenantId) {
|
|
195
|
+
return queryWithGlobals(this, tenantId, "Issue.findWithGlobals");
|
|
196
|
+
}
|
|
50
197
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
repositoryId;
|
|
54
|
-
/**
|
|
55
|
-
* Issue number (provider-specific)
|
|
56
|
-
*/
|
|
57
|
-
number = 0;
|
|
58
|
-
/**
|
|
59
|
-
* Node ID for GraphQL operations (GitHub Projects API)
|
|
60
|
-
*/
|
|
61
|
-
nodeId = "";
|
|
62
|
-
/**
|
|
63
|
-
* Issue title
|
|
64
|
-
*/
|
|
65
|
-
title = "";
|
|
66
|
-
/**
|
|
67
|
-
* Issue body/description
|
|
68
|
-
*/
|
|
69
|
-
body = "";
|
|
70
|
-
/**
|
|
71
|
-
* Issue state
|
|
72
|
-
*/
|
|
73
|
-
state = "open";
|
|
74
|
-
/**
|
|
75
|
-
* Author's login/username
|
|
76
|
-
*/
|
|
77
|
-
author = "";
|
|
78
|
-
/**
|
|
79
|
-
* Labels attached to the issue
|
|
80
|
-
*/
|
|
81
|
-
labels = [];
|
|
82
|
-
/**
|
|
83
|
-
* Assignee logins
|
|
84
|
-
*/
|
|
85
|
-
assignees = [];
|
|
86
|
-
/**
|
|
87
|
-
* Number of comments on the issue
|
|
88
|
-
*/
|
|
89
|
-
commentsCount = 0;
|
|
90
|
-
/**
|
|
91
|
-
* Last sync timestamp
|
|
92
|
-
*/
|
|
93
|
-
lastSyncedAt = null;
|
|
94
|
-
/**
|
|
95
|
-
* Original body before any AI synthesis (for rollback)
|
|
96
|
-
*/
|
|
97
|
-
originalBody = "";
|
|
98
|
-
/**
|
|
99
|
-
* Number of times feedback has been incorporated
|
|
100
|
-
*/
|
|
101
|
-
synthesisCount = 0;
|
|
102
|
-
/**
|
|
103
|
-
* Transient: Cached repository (not persisted)
|
|
104
|
-
* Protected so PullRequest can access it
|
|
105
|
-
*/
|
|
106
|
-
_repository;
|
|
107
|
-
/**
|
|
108
|
-
* Transient: Cached client (not persisted)
|
|
109
|
-
*/
|
|
110
|
-
_client;
|
|
111
|
-
constructor(options = {}) {
|
|
112
|
-
super(options);
|
|
113
|
-
if (options.repositoryId !== void 0)
|
|
114
|
-
this.repositoryId = options.repositoryId;
|
|
115
|
-
if (options.number !== void 0) this.number = options.number;
|
|
116
|
-
if (options.nodeId !== void 0) this.nodeId = options.nodeId;
|
|
117
|
-
if (options.title !== void 0) this.title = options.title;
|
|
118
|
-
if (options.body !== void 0) this.body = options.body;
|
|
119
|
-
if (options.state !== void 0) this.state = options.state;
|
|
120
|
-
if (options.author !== void 0) this.author = options.author;
|
|
121
|
-
if (options.labels !== void 0) this.labels = options.labels;
|
|
122
|
-
if (options.assignees !== void 0) this.assignees = options.assignees;
|
|
123
|
-
if (options.commentsCount !== void 0)
|
|
124
|
-
this.commentsCount = options.commentsCount;
|
|
125
|
-
if (options.lastSyncedAt !== void 0)
|
|
126
|
-
this.lastSyncedAt = options.lastSyncedAt;
|
|
127
|
-
if (options.originalBody !== void 0)
|
|
128
|
-
this.originalBody = options.originalBody;
|
|
129
|
-
if (options.synthesisCount !== void 0)
|
|
130
|
-
this.synthesisCount = options.synthesisCount;
|
|
131
|
-
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Get the repository this issue belongs to
|
|
135
|
-
*/
|
|
136
|
-
async getRepository() {
|
|
137
|
-
if (this._repository) {
|
|
138
|
-
return this._repository;
|
|
139
|
-
}
|
|
140
|
-
if (!this.repositoryId) {
|
|
141
|
-
throw new Error("Issue has no repositoryId set");
|
|
142
|
-
}
|
|
143
|
-
const { RepositoryCollection: RepositoryCollection2 } = await Promise.resolve().then(() => Repositories);
|
|
144
|
-
const collection = await RepositoryCollection2.create(this.options);
|
|
145
|
-
const repo = await collection.get({ id: this.repositoryId });
|
|
146
|
-
if (!repo) {
|
|
147
|
-
throw new Error(`Repository ${this.repositoryId} not found`);
|
|
148
|
-
}
|
|
149
|
-
this._repository = repo;
|
|
150
|
-
return repo;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Get the repository client for API operations
|
|
154
|
-
*/
|
|
155
|
-
async getClient() {
|
|
156
|
-
if (this._client) {
|
|
157
|
-
return this._client;
|
|
158
|
-
}
|
|
159
|
-
const repo = await this.getRepository();
|
|
160
|
-
this._client = await repo.getClient();
|
|
161
|
-
return this._client;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Clear cached repository and client
|
|
165
|
-
*/
|
|
166
|
-
clearCache() {
|
|
167
|
-
this._repository = void 0;
|
|
168
|
-
this._client = void 0;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Sync issue data from the provider
|
|
172
|
-
*
|
|
173
|
-
* @param options - Sync options
|
|
174
|
-
* @returns This issue with updated fields
|
|
175
|
-
*/
|
|
176
|
-
async sync(options = {}) {
|
|
177
|
-
if (!options.force && this.lastSyncedAt && Date.now() - this.lastSyncedAt.getTime() < SYNC_THROTTLE_MS) {
|
|
178
|
-
return this;
|
|
179
|
-
}
|
|
180
|
-
const client = await this.getClient();
|
|
181
|
-
const issueData = await client.getIssue(this.number);
|
|
182
|
-
this.nodeId = issueData.id;
|
|
183
|
-
this.title = issueData.title;
|
|
184
|
-
this.body = issueData.body;
|
|
185
|
-
this.state = issueData.state;
|
|
186
|
-
this.author = issueData.author.login;
|
|
187
|
-
this.labels = issueData.labels.map((l) => l.name);
|
|
188
|
-
this.assignees = issueData.assignees.map((a) => a.login);
|
|
189
|
-
this.commentsCount = issueData.commentsCount;
|
|
190
|
-
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
191
|
-
await this.save();
|
|
192
|
-
return this;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Get comments on this issue
|
|
196
|
-
*
|
|
197
|
-
* @returns Array of Comment objects (SMRT models)
|
|
198
|
-
*/
|
|
199
|
-
async getComments() {
|
|
200
|
-
const client = await this.getClient();
|
|
201
|
-
const comments = await client.listComments(this.number);
|
|
202
|
-
const { Comment: CommentClass } = await Promise.resolve().then(() => Comment$1);
|
|
203
|
-
return comments.map(
|
|
204
|
-
(c) => new CommentClass({
|
|
205
|
-
...this.options,
|
|
206
|
-
issueId: this.id ?? void 0,
|
|
207
|
-
commentId: c.id,
|
|
208
|
-
body: c.body,
|
|
209
|
-
author: c.author.login,
|
|
210
|
-
createdAt: c.createdAt,
|
|
211
|
-
updatedAt: c.updatedAt,
|
|
212
|
-
url: c.url
|
|
213
|
-
})
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Add a comment to this issue
|
|
218
|
-
*
|
|
219
|
-
* @param body - Comment body text
|
|
220
|
-
* @returns Created Comment (SMRT model)
|
|
221
|
-
*/
|
|
222
|
-
async addComment(body) {
|
|
223
|
-
const client = await this.getClient();
|
|
224
|
-
const created = await client.addComment(this.number, body);
|
|
225
|
-
const { Comment: CommentClass } = await Promise.resolve().then(() => Comment$1);
|
|
226
|
-
const comment = new CommentClass({
|
|
227
|
-
...this.options,
|
|
228
|
-
issueId: this.id ?? void 0,
|
|
229
|
-
commentId: created.id,
|
|
230
|
-
body: created.body,
|
|
231
|
-
author: created.author.login,
|
|
232
|
-
createdAt: created.createdAt,
|
|
233
|
-
updatedAt: created.updatedAt,
|
|
234
|
-
url: created.url
|
|
235
|
-
});
|
|
236
|
-
await comment.save();
|
|
237
|
-
this.commentsCount++;
|
|
238
|
-
await this.save();
|
|
239
|
-
return comment;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Incorporate feedback from comments into the issue body
|
|
243
|
-
*
|
|
244
|
-
* This is the core "Living Spec" functionality:
|
|
245
|
-
* 1. Reads all comments on the issue
|
|
246
|
-
* 2. Uses AI to synthesize comments with the current body
|
|
247
|
-
* 3. Optionally updates the issue with the synthesized content
|
|
248
|
-
*
|
|
249
|
-
* @param options - Feedback incorporation options
|
|
250
|
-
* @returns Result with synthesized content and status
|
|
251
|
-
*/
|
|
252
|
-
async incorporateFeedback(options = {}) {
|
|
253
|
-
const comments = await this.getComments();
|
|
254
|
-
let relevantComments = comments;
|
|
255
|
-
if (options.since) {
|
|
256
|
-
const sinceDate = options.since;
|
|
257
|
-
relevantComments = comments.filter(
|
|
258
|
-
(c) => c.createdAt && c.createdAt > sinceDate
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
if (relevantComments.length === 0) {
|
|
262
|
-
return {
|
|
263
|
-
synthesized: this.body,
|
|
264
|
-
applied: false,
|
|
265
|
-
commentsAnalyzed: 0
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
const resolvedPrompt = await resolvePrompt(
|
|
269
|
-
issueIncorporateFeedbackPrompt.key,
|
|
270
|
-
{
|
|
271
|
-
db: this.options.db ?? this.options.persistence,
|
|
272
|
-
tenantId: this.tenantId,
|
|
273
|
-
variables: {
|
|
274
|
-
body: this.body,
|
|
275
|
-
comments: relevantComments.map((c) => `- ${c.author}: ${c.body}`).join("\n")
|
|
276
|
-
},
|
|
277
|
-
override: options.prompt ? { template: options.prompt } : void 0
|
|
278
|
-
}
|
|
279
|
-
);
|
|
280
|
-
const aiOptions = {
|
|
281
|
-
...resolvedPrompt.ai.params,
|
|
282
|
-
...resolvedPrompt.ai.model ? { model: resolvedPrompt.ai.model } : {}
|
|
283
|
-
};
|
|
284
|
-
let synthesized;
|
|
285
|
-
if (resolvedPrompt.ai.provider) {
|
|
286
|
-
synthesized = await this.runPromptWithResolvedAI(
|
|
287
|
-
resolvedPrompt.text,
|
|
288
|
-
resolvedPrompt.ai.provider,
|
|
289
|
-
aiOptions
|
|
290
|
-
);
|
|
291
|
-
} else {
|
|
292
|
-
synthesized = await this.do(resolvedPrompt.text, {
|
|
293
|
-
...aiOptions,
|
|
294
|
-
includeData: false
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
const result = {
|
|
298
|
-
synthesized,
|
|
299
|
-
applied: false,
|
|
300
|
-
commentsAnalyzed: relevantComments.length,
|
|
301
|
-
previousBody: this.body
|
|
302
|
-
};
|
|
303
|
-
if (options.apply) {
|
|
304
|
-
if (this.synthesisCount === 0) {
|
|
305
|
-
this.originalBody = this.body;
|
|
306
|
-
}
|
|
307
|
-
const client = await this.getClient();
|
|
308
|
-
await client.updateIssue(this.number, { body: synthesized });
|
|
309
|
-
this.body = synthesized;
|
|
310
|
-
this.synthesisCount++;
|
|
311
|
-
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
312
|
-
await this.save();
|
|
313
|
-
result.applied = true;
|
|
314
|
-
}
|
|
315
|
-
return result;
|
|
316
|
-
}
|
|
317
|
-
async runPromptWithResolvedAI(instructions, provider, options) {
|
|
318
|
-
const aiOption = this.options.ai;
|
|
319
|
-
if (this.isExplicitAiClientOption(aiOption)) {
|
|
320
|
-
const ai2 = await this.getAiClient();
|
|
321
|
-
return this.sendPromptMessage(ai2, instructions, options);
|
|
322
|
-
}
|
|
323
|
-
const globalAiConfig = config.toJSON().ai || {};
|
|
324
|
-
const instanceAiConfig = aiOption ?? {};
|
|
325
|
-
const aiConfig = loadEnvConfig(
|
|
326
|
-
{
|
|
327
|
-
...globalAiConfig,
|
|
328
|
-
...instanceAiConfig
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
packageName: "ai",
|
|
332
|
-
prefix: "SMRT",
|
|
333
|
-
schema: {
|
|
334
|
-
provider: "string",
|
|
335
|
-
model: "string",
|
|
336
|
-
apiKey: "string",
|
|
337
|
-
timeout: "number",
|
|
338
|
-
maxRetries: "number",
|
|
339
|
-
temperature: "number",
|
|
340
|
-
maxTokens: "number"
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
);
|
|
344
|
-
const env = process.env;
|
|
345
|
-
const apiKey = aiConfig.apiKey || (provider === "anthropic" ? env.ANTHROPIC_API_KEY : provider === "gemini" ? env.GEMINI_API_KEY : env.OPENAI_API_KEY);
|
|
346
|
-
const ai = await getAI({
|
|
347
|
-
...aiConfig,
|
|
348
|
-
provider,
|
|
349
|
-
type: provider,
|
|
350
|
-
model: options.model ?? aiConfig.model,
|
|
351
|
-
defaultModel: options.model ?? aiConfig.model,
|
|
352
|
-
apiKey
|
|
353
|
-
});
|
|
354
|
-
return this.sendPromptMessage(ai, instructions, options);
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Sends a fully-resolved instruction string to the given AI client.
|
|
358
|
-
*
|
|
359
|
-
* `incorporateFeedback()` curates the entire prompt (issue body + comments)
|
|
360
|
-
* via the resolved prompt template, so this path deliberately does NOT inject
|
|
361
|
-
* the object's own field data — that would duplicate the body. The `this.do()`
|
|
362
|
-
* fallback path passes `includeData: false` for the same reason, keeping both
|
|
363
|
-
* `incorporateFeedback()` paths consistent (#1567).
|
|
364
|
-
*/
|
|
365
|
-
async sendPromptMessage(ai, instructions, options) {
|
|
366
|
-
const prompt = `--- Beginning of instructions ---
|
|
367
|
-
${instructions}
|
|
368
|
-
--- End of instructions ---
|
|
369
|
-
Based on the content body, please follow the instructions and provide a response. Never make use of codeblocks.`;
|
|
370
|
-
const tools = this.getAvailableTools();
|
|
371
|
-
return await ai.message(prompt, {
|
|
372
|
-
...options,
|
|
373
|
-
tools: tools.length > 0 ? tools : void 0
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
isExplicitAiClientOption(aiOption) {
|
|
377
|
-
return !!(aiOption && typeof aiOption === "object" && typeof aiOption.embed === "function" && !aiOption.provider && !aiOption.type);
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Rollback to the original body before AI synthesis
|
|
381
|
-
*
|
|
382
|
-
* @returns Result with success status
|
|
383
|
-
*/
|
|
384
|
-
async rollback() {
|
|
385
|
-
if (!this.originalBody) {
|
|
386
|
-
return {
|
|
387
|
-
success: false,
|
|
388
|
-
message: "No original body to rollback to"
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
if (this.synthesisCount === 0) {
|
|
392
|
-
return {
|
|
393
|
-
success: false,
|
|
394
|
-
message: "No synthesis has been applied"
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
const client = await this.getClient();
|
|
398
|
-
await client.updateIssue(this.number, { body: this.originalBody });
|
|
399
|
-
this.body = this.originalBody;
|
|
400
|
-
this.originalBody = "";
|
|
401
|
-
this.synthesisCount = 0;
|
|
402
|
-
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
403
|
-
await this.save();
|
|
404
|
-
return {
|
|
405
|
-
success: true,
|
|
406
|
-
message: "Successfully rolled back to original body"
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* AI-powered: Check if this issue needs review
|
|
411
|
-
*
|
|
412
|
-
* @returns True if the issue likely needs attention
|
|
413
|
-
*/
|
|
414
|
-
async needsReview() {
|
|
415
|
-
return await this.is(
|
|
416
|
-
`This issue needs review because one or more of the following:
|
|
417
|
-
- It has been open for a long time without updates
|
|
418
|
-
- There are unresolved questions in the comments
|
|
419
|
-
- The requirements are unclear or incomplete
|
|
420
|
-
- There is conflicting feedback that needs resolution`
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* AI-powered: Check if the issue is a bug report
|
|
425
|
-
*/
|
|
426
|
-
async isBugReport() {
|
|
427
|
-
return await this.is(
|
|
428
|
-
"This issue describes a bug, defect, or unexpected behavior"
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* AI-powered: Check if the issue is a feature request
|
|
433
|
-
*/
|
|
434
|
-
async isFeatureRequest() {
|
|
435
|
-
return await this.is(
|
|
436
|
-
"This issue is a feature request or enhancement proposal"
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* AI-powered: Generate suggested labels based on content
|
|
441
|
-
*
|
|
442
|
-
* @returns Array of suggested label names
|
|
443
|
-
*/
|
|
444
|
-
async suggestLabels() {
|
|
445
|
-
const suggestion = await this.do(
|
|
446
|
-
`Based on the issue title and body, suggest appropriate labels.
|
|
447
|
-
Consider:
|
|
448
|
-
- Type: bug, feature, docs, chore, test
|
|
449
|
-
- Priority: P0 (critical), P1 (high), P2 (medium), P3 (low)
|
|
450
|
-
- Area: specific code areas or components
|
|
451
|
-
|
|
452
|
-
Return only a comma-separated list of labels, nothing else.`
|
|
453
|
-
);
|
|
454
|
-
return suggestion.split(",").map((l) => l.trim()).filter(Boolean);
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* Close this issue
|
|
458
|
-
*/
|
|
459
|
-
async close() {
|
|
460
|
-
const client = await this.getClient();
|
|
461
|
-
await client.closeIssue(this.number);
|
|
462
|
-
this.state = "closed";
|
|
463
|
-
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
464
|
-
await this.save();
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Add labels to this issue
|
|
468
|
-
*
|
|
469
|
-
* @param labels - Label names to add
|
|
470
|
-
*/
|
|
471
|
-
async addLabels(labels) {
|
|
472
|
-
const client = await this.getClient();
|
|
473
|
-
await client.addLabels(this.number, labels);
|
|
474
|
-
this.labels = [.../* @__PURE__ */ new Set([...this.labels, ...labels])];
|
|
475
|
-
await this.save();
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* Remove a label from this issue
|
|
479
|
-
*
|
|
480
|
-
* @param label - Label name to remove
|
|
481
|
-
*/
|
|
482
|
-
async removeLabel(label) {
|
|
483
|
-
const client = await this.getClient();
|
|
484
|
-
await client.removeLabel(this.number, label);
|
|
485
|
-
this.labels = this.labels.filter((l) => l !== label);
|
|
486
|
-
await this.save();
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Assign users to this issue
|
|
490
|
-
*
|
|
491
|
-
* @param assignees - User logins to assign
|
|
492
|
-
*/
|
|
493
|
-
async assign(assignees) {
|
|
494
|
-
const client = await this.getClient();
|
|
495
|
-
await client.assignIssue(this.number, assignees);
|
|
496
|
-
this.assignees = [.../* @__PURE__ */ new Set([...this.assignees, ...assignees])];
|
|
497
|
-
await this.save();
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Get issue URL
|
|
501
|
-
*/
|
|
502
|
-
getUrl() {
|
|
503
|
-
const repo = this._repository;
|
|
504
|
-
if (repo) {
|
|
505
|
-
return `https://github.com/${repo.owner}/${repo.name}/issues/${this.number}`;
|
|
506
|
-
}
|
|
507
|
-
return "";
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
__decorateClass$5([
|
|
511
|
-
tenantId({ nullable: true })
|
|
512
|
-
], Issue.prototype, "tenantId", 2);
|
|
513
|
-
__decorateClass$5([
|
|
514
|
-
foreignKey("Repository", { required: true })
|
|
515
|
-
], Issue.prototype, "repositoryId", 2);
|
|
516
|
-
Issue = __decorateClass$5([
|
|
517
|
-
TenantScoped({ mode: "optional" }),
|
|
518
|
-
smrt({
|
|
519
|
-
tableStrategy: "sti",
|
|
520
|
-
api: { include: ["list", "get", "create", "update"] },
|
|
521
|
-
mcp: { include: ["list", "get", "sync", "incorporateFeedback"] },
|
|
522
|
-
// sync/incorporateFeedback/rollback are operator commands invoked in-process
|
|
523
|
-
// via the CLI; they intentionally aren't exposed over HTTP today.
|
|
524
|
-
cli: {
|
|
525
|
-
include: ["list", "get", "sync", "incorporateFeedback", "rollback"],
|
|
526
|
-
skipApiCheck: true
|
|
527
|
-
}
|
|
528
|
-
})
|
|
529
|
-
], Issue);
|
|
530
|
-
const Issue$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
531
|
-
__proto__: null,
|
|
532
|
-
get Issue() {
|
|
533
|
-
return Issue;
|
|
534
|
-
}
|
|
535
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
536
|
-
class IssueCollection extends SmrtCollection {
|
|
537
|
-
static _itemClass = Issue;
|
|
538
|
-
/**
|
|
539
|
-
* Discover issues from a repository and sync to database
|
|
540
|
-
*
|
|
541
|
-
* This method:
|
|
542
|
-
* 1. Fetches issues from the provider via SDK
|
|
543
|
-
* 2. Creates/updates SMRT Issue records in the database
|
|
544
|
-
* 3. Returns the synced Issue objects
|
|
545
|
-
*
|
|
546
|
-
* @param options - Discovery options
|
|
547
|
-
* @returns Array of Issue objects
|
|
548
|
-
*/
|
|
549
|
-
async discover(options) {
|
|
550
|
-
const { repository, filters } = options;
|
|
551
|
-
const repositoryId = repository.id ?? void 0;
|
|
552
|
-
if (!repositoryId) {
|
|
553
|
-
throw new Error("Repository must be saved before discovering issues");
|
|
554
|
-
}
|
|
555
|
-
const repoClient = await repository.getClient();
|
|
556
|
-
const remoteIssues = await repoClient.searchIssues("", filters);
|
|
557
|
-
const issues = [];
|
|
558
|
-
for (const remote of remoteIssues) {
|
|
559
|
-
let issue = await this.findOne({
|
|
560
|
-
where: {
|
|
561
|
-
repositoryId,
|
|
562
|
-
number: remote.number
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
if (!issue) {
|
|
566
|
-
issue = await this.create({
|
|
567
|
-
repositoryId,
|
|
568
|
-
number: remote.number,
|
|
569
|
-
nodeId: remote.id,
|
|
570
|
-
title: remote.title,
|
|
571
|
-
body: remote.body,
|
|
572
|
-
state: remote.state,
|
|
573
|
-
author: remote.author.login,
|
|
574
|
-
labels: remote.labels.map((l) => l.name),
|
|
575
|
-
assignees: remote.assignees.map((a) => a.login),
|
|
576
|
-
commentsCount: remote.commentsCount,
|
|
577
|
-
lastSyncedAt: /* @__PURE__ */ new Date()
|
|
578
|
-
});
|
|
579
|
-
} else {
|
|
580
|
-
issue.nodeId = remote.id;
|
|
581
|
-
issue.title = remote.title;
|
|
582
|
-
issue.body = remote.body;
|
|
583
|
-
issue.state = remote.state;
|
|
584
|
-
issue.author = remote.author.login;
|
|
585
|
-
issue.labels = remote.labels.map((l) => l.name);
|
|
586
|
-
issue.assignees = remote.assignees.map((a) => a.login);
|
|
587
|
-
issue.commentsCount = remote.commentsCount;
|
|
588
|
-
issue.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
589
|
-
}
|
|
590
|
-
await issue.save();
|
|
591
|
-
issues.push(issue);
|
|
592
|
-
}
|
|
593
|
-
return issues;
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Find issues by repository
|
|
597
|
-
*
|
|
598
|
-
* @param repositoryId - Repository ID
|
|
599
|
-
* @returns Array of issues
|
|
600
|
-
*/
|
|
601
|
-
async findByRepository(repositoryId) {
|
|
602
|
-
return await this.list({
|
|
603
|
-
where: { repositoryId }
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Find open issues
|
|
608
|
-
*
|
|
609
|
-
* @param repositoryId - Optional repository filter
|
|
610
|
-
* @returns Array of open issues
|
|
611
|
-
*/
|
|
612
|
-
async findOpen(repositoryId) {
|
|
613
|
-
const where = { state: "open" };
|
|
614
|
-
if (repositoryId) {
|
|
615
|
-
where.repositoryId = repositoryId;
|
|
616
|
-
}
|
|
617
|
-
return await this.list({ where });
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Find issues by label
|
|
621
|
-
*
|
|
622
|
-
* @param label - Label name
|
|
623
|
-
* @param repositoryId - Optional repository filter
|
|
624
|
-
* @returns Array of issues with the label
|
|
625
|
-
*/
|
|
626
|
-
async findByLabel(label, repositoryId) {
|
|
627
|
-
const issues = await this.list({
|
|
628
|
-
where: repositoryId ? { repositoryId } : {}
|
|
629
|
-
});
|
|
630
|
-
return issues.filter((issue) => issue.labels.includes(label));
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Find issues by assignee
|
|
634
|
-
*
|
|
635
|
-
* @param assignee - Assignee login
|
|
636
|
-
* @param repositoryId - Optional repository filter
|
|
637
|
-
* @returns Array of issues assigned to the user
|
|
638
|
-
*/
|
|
639
|
-
async findByAssignee(assignee, repositoryId) {
|
|
640
|
-
const issues = await this.list({
|
|
641
|
-
where: repositoryId ? { repositoryId } : {}
|
|
642
|
-
});
|
|
643
|
-
return issues.filter((issue) => issue.assignees.includes(assignee));
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* Find issues needing review (AI-powered)
|
|
647
|
-
*
|
|
648
|
-
* @param repositoryId - Optional repository filter
|
|
649
|
-
* @returns Array of issues that need review
|
|
650
|
-
*/
|
|
651
|
-
async findNeedingReview(repositoryId) {
|
|
652
|
-
const openIssues = await this.findOpen(repositoryId);
|
|
653
|
-
const needingReview = [];
|
|
654
|
-
for (const issue of openIssues) {
|
|
655
|
-
if (await issue.needsReview()) {
|
|
656
|
-
needingReview.push(issue);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return needingReview;
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Find issue by number in a repository
|
|
663
|
-
*
|
|
664
|
-
* @param repositoryId - Repository ID
|
|
665
|
-
* @param number - Issue number
|
|
666
|
-
* @returns Issue or null
|
|
667
|
-
*/
|
|
668
|
-
async findByNumber(repositoryId, number) {
|
|
669
|
-
const results = await this.list({
|
|
670
|
-
where: { repositoryId, number },
|
|
671
|
-
limit: 1
|
|
672
|
-
});
|
|
673
|
-
return results[0] || null;
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Get issues with unincorporated feedback
|
|
677
|
-
*
|
|
678
|
-
* Issues that have comments but haven't had feedback incorporated
|
|
679
|
-
*
|
|
680
|
-
* @param repositoryId - Optional repository filter
|
|
681
|
-
* @returns Array of issues
|
|
682
|
-
*/
|
|
683
|
-
async findWithUnincorporatedFeedback(repositoryId) {
|
|
684
|
-
const issues = await this.findOpen(repositoryId);
|
|
685
|
-
return issues.filter(
|
|
686
|
-
(issue) => issue.commentsCount > 0 && issue.synthesisCount === 0
|
|
687
|
-
);
|
|
688
|
-
}
|
|
689
|
-
/**
|
|
690
|
-
* Batch sync issues from repository
|
|
691
|
-
*
|
|
692
|
-
* @param repository - Repository to sync from
|
|
693
|
-
* @param options - Sync options
|
|
694
|
-
* @returns Array of synced issues
|
|
695
|
-
*/
|
|
696
|
-
async batchSync(repository, options = {}) {
|
|
697
|
-
const issues = await this.findByRepository(repository.id);
|
|
698
|
-
const synced = [];
|
|
699
|
-
for (const issue of issues) {
|
|
700
|
-
await issue.sync(options);
|
|
701
|
-
synced.push(issue);
|
|
702
|
-
}
|
|
703
|
-
return synced;
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Find issues by tenant ID
|
|
707
|
-
*
|
|
708
|
-
* @param tenantId - Tenant ID to filter by
|
|
709
|
-
* @returns Array of issues for the tenant
|
|
710
|
-
*/
|
|
711
|
-
async findByTenant(tenantId2) {
|
|
712
|
-
return this.list({ where: { tenantId: tenantId2 } });
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* Find all global issues (no tenant association).
|
|
716
|
-
*
|
|
717
|
-
* Routes through the shared tenant-global helper so it does not throw under
|
|
718
|
-
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
719
|
-
* flagged as an isolation violation). (#1600)
|
|
720
|
-
*
|
|
721
|
-
* @returns Array of global issues
|
|
722
|
-
*/
|
|
723
|
-
async findGlobal() {
|
|
724
|
-
return queryGlobal(this);
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* Find issues for a tenant plus all global issues.
|
|
728
|
-
*
|
|
729
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
730
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
731
|
-
*
|
|
732
|
-
* @param tenantId - Tenant ID to filter by
|
|
733
|
-
* @returns Array of tenant and global issues
|
|
734
|
-
*/
|
|
735
|
-
async findWithGlobals(tenantId2) {
|
|
736
|
-
return queryWithGlobals(this, tenantId2, "Issue.findWithGlobals");
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
const Issues = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
740
|
-
__proto__: null,
|
|
741
|
-
IssueCollection
|
|
742
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/models/Project.ts
|
|
743
200
|
var __defProp$3 = Object.defineProperty;
|
|
744
|
-
var __getOwnPropDesc$
|
|
745
|
-
var __decorateClass$
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (kind && result) __defProp$3(target, key, result);
|
|
751
|
-
return result;
|
|
201
|
+
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
202
|
+
var __decorateClass$3 = (decorators, target, key, kind) => {
|
|
203
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
|
|
204
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
205
|
+
if (kind && result) __defProp$3(target, key, result);
|
|
206
|
+
return result;
|
|
752
207
|
};
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
* @returns Array of ProjectItems in that status
|
|
982
|
-
*/
|
|
983
|
-
async getItemsByStatus(status) {
|
|
984
|
-
return await this.listItems({ status });
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Move an item to a new status
|
|
988
|
-
*
|
|
989
|
-
* @param item - Issue or PullRequest
|
|
990
|
-
* @param status - Target status name
|
|
991
|
-
*/
|
|
992
|
-
async moveItem(item, status) {
|
|
993
|
-
const items = await this.listItems();
|
|
994
|
-
const projectItem = items.find((i) => i.contentId === item.nodeId);
|
|
995
|
-
if (!projectItem) {
|
|
996
|
-
throw new Error("Item not found in project");
|
|
997
|
-
}
|
|
998
|
-
await this.updateItemStatus(projectItem.id, status);
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* AI-powered: Analyze project health and suggest improvements
|
|
1002
|
-
*
|
|
1003
|
-
* @returns Analysis of project status
|
|
1004
|
-
*/
|
|
1005
|
-
async analyzeHealth() {
|
|
1006
|
-
const items = await this.listItems();
|
|
1007
|
-
const statuses = await this.getStatuses();
|
|
1008
|
-
const statusCounts = {};
|
|
1009
|
-
for (const status of statuses) {
|
|
1010
|
-
statusCounts[status.name] = items.filter(
|
|
1011
|
-
(i) => i.status === status.name
|
|
1012
|
-
).length;
|
|
1013
|
-
}
|
|
1014
|
-
return await this.do(
|
|
1015
|
-
`Analyze the health of this project board and suggest improvements.
|
|
208
|
+
var Project = class extends SmrtObject {
|
|
209
|
+
tenantId = null;
|
|
210
|
+
/**
|
|
211
|
+
* Provider-specific project ID (e.g., GitHub GraphQL node ID)
|
|
212
|
+
*/
|
|
213
|
+
projectId = "";
|
|
214
|
+
/**
|
|
215
|
+
* Human-readable project number
|
|
216
|
+
*/
|
|
217
|
+
projectNumber = 0;
|
|
218
|
+
/**
|
|
219
|
+
* Project title
|
|
220
|
+
*/
|
|
221
|
+
title = "";
|
|
222
|
+
/**
|
|
223
|
+
* Project description
|
|
224
|
+
*/
|
|
225
|
+
description = "";
|
|
226
|
+
/**
|
|
227
|
+
* Project owner (organization or user)
|
|
228
|
+
*/
|
|
229
|
+
owner = "";
|
|
230
|
+
/**
|
|
231
|
+
* Project URL
|
|
232
|
+
*/
|
|
233
|
+
url = "";
|
|
234
|
+
/**
|
|
235
|
+
* Project provider type
|
|
236
|
+
*/
|
|
237
|
+
providerType = "github";
|
|
238
|
+
/**
|
|
239
|
+
* Environment variable name or config key for token resolution
|
|
240
|
+
*/
|
|
241
|
+
tokenConfigKey = "GITHUB_TOKEN";
|
|
242
|
+
/**
|
|
243
|
+
* Available statuses (columns) in the project
|
|
244
|
+
*/
|
|
245
|
+
statuses = [];
|
|
246
|
+
/**
|
|
247
|
+
* Custom fields defined in the project
|
|
248
|
+
*/
|
|
249
|
+
fields = [];
|
|
250
|
+
/**
|
|
251
|
+
* Status field ID for GitHub Projects V2
|
|
252
|
+
*/
|
|
253
|
+
statusFieldId = "";
|
|
254
|
+
/**
|
|
255
|
+
* Maps status name to option ID (for GitHub Projects V2)
|
|
256
|
+
*/
|
|
257
|
+
statusOptions = {};
|
|
258
|
+
/**
|
|
259
|
+
* Last sync timestamp
|
|
260
|
+
*/
|
|
261
|
+
lastSyncedAt = null;
|
|
262
|
+
/**
|
|
263
|
+
* Transient: Cached project client (not persisted)
|
|
264
|
+
*/
|
|
265
|
+
_client;
|
|
266
|
+
constructor(options = {}) {
|
|
267
|
+
super(options);
|
|
268
|
+
if (options.projectId !== void 0) this.projectId = options.projectId;
|
|
269
|
+
if (options.projectNumber !== void 0) this.projectNumber = options.projectNumber;
|
|
270
|
+
if (options.title !== void 0) this.title = options.title;
|
|
271
|
+
if (options.description !== void 0) this.description = options.description;
|
|
272
|
+
if (options.owner !== void 0) this.owner = options.owner;
|
|
273
|
+
if (options.url !== void 0) this.url = options.url;
|
|
274
|
+
if (options.providerType !== void 0) this.providerType = options.providerType;
|
|
275
|
+
if (options.tokenConfigKey !== void 0) this.tokenConfigKey = options.tokenConfigKey;
|
|
276
|
+
if (options.statuses !== void 0) this.statuses = options.statuses;
|
|
277
|
+
if (options.fields !== void 0) this.fields = options.fields;
|
|
278
|
+
if (options.statusFieldId !== void 0) this.statusFieldId = options.statusFieldId;
|
|
279
|
+
if (options.statusOptions !== void 0) this.statusOptions = options.statusOptions;
|
|
280
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get the project client, resolving token from config
|
|
284
|
+
*
|
|
285
|
+
* @returns Project client for API operations
|
|
286
|
+
* @throws Error if token cannot be resolved
|
|
287
|
+
*/
|
|
288
|
+
async getClient() {
|
|
289
|
+
if (this._client) return this._client;
|
|
290
|
+
const token = process.env[this.tokenConfigKey] || getModuleConfig("smrt-projects", {})[this.tokenConfigKey];
|
|
291
|
+
if (!token) throw new Error(`Token not found for key '${this.tokenConfigKey}'. Set the ${this.tokenConfigKey} environment variable or configure it in smrt.config.`);
|
|
292
|
+
this._client = await getProject({
|
|
293
|
+
type: this.providerType,
|
|
294
|
+
projectId: this.projectId,
|
|
295
|
+
token,
|
|
296
|
+
statusFieldId: this.statusFieldId || void 0,
|
|
297
|
+
statusOptions: Object.keys(this.statusOptions).length > 0 ? this.statusOptions : void 0
|
|
298
|
+
});
|
|
299
|
+
return this._client;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Clear the cached client
|
|
303
|
+
*/
|
|
304
|
+
clearClient() {
|
|
305
|
+
this._client = void 0;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Sync project metadata from the provider
|
|
309
|
+
*
|
|
310
|
+
* @param options - Sync options
|
|
311
|
+
* @returns This project with updated fields
|
|
312
|
+
*/
|
|
313
|
+
async sync(options = {}) {
|
|
314
|
+
if (!options.force && this.lastSyncedAt && Date.now() - this.lastSyncedAt.getTime() < 3e5) return this;
|
|
315
|
+
const projectData = await (await this.getClient()).getProject();
|
|
316
|
+
this.title = projectData.title;
|
|
317
|
+
this.description = projectData.description || "";
|
|
318
|
+
this.owner = projectData.owner;
|
|
319
|
+
this.url = projectData.url;
|
|
320
|
+
this.statuses = projectData.statuses;
|
|
321
|
+
this.fields = projectData.fields;
|
|
322
|
+
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
323
|
+
await this.save();
|
|
324
|
+
return this;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Add an issue or PR to this project
|
|
328
|
+
*
|
|
329
|
+
* @param item - Issue or PullRequest to add
|
|
330
|
+
* @returns Created ProjectItem
|
|
331
|
+
*/
|
|
332
|
+
async addItem(item) {
|
|
333
|
+
if (!item.nodeId) throw new Error("Item must have a nodeId (sync the item first)");
|
|
334
|
+
return await (await this.getClient()).addItem(item.nodeId);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Remove an item from this project
|
|
338
|
+
*
|
|
339
|
+
* @param itemId - Project item ID to remove
|
|
340
|
+
*/
|
|
341
|
+
async removeItem(itemId) {
|
|
342
|
+
await (await this.getClient()).removeItem(itemId);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get a specific item from the project
|
|
346
|
+
*
|
|
347
|
+
* @param itemId - Project item ID
|
|
348
|
+
* @returns ProjectItem or null
|
|
349
|
+
*/
|
|
350
|
+
async getItem(itemId) {
|
|
351
|
+
return await (await this.getClient()).getItem(itemId);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* List items in this project
|
|
355
|
+
*
|
|
356
|
+
* @param filters - Optional filters
|
|
357
|
+
* @returns Array of ProjectItems
|
|
358
|
+
*/
|
|
359
|
+
async listItems(filters) {
|
|
360
|
+
return await (await this.getClient()).listItems(filters);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Update an item's status (column)
|
|
364
|
+
*
|
|
365
|
+
* @param itemId - Project item ID
|
|
366
|
+
* @param status - New status name
|
|
367
|
+
*/
|
|
368
|
+
async updateItemStatus(itemId, status) {
|
|
369
|
+
await (await this.getClient()).updateItemStatus(itemId, status);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Update an item's custom field
|
|
373
|
+
*
|
|
374
|
+
* @param itemId - Project item ID
|
|
375
|
+
* @param fieldId - Field ID
|
|
376
|
+
* @param value - New value
|
|
377
|
+
*/
|
|
378
|
+
async updateItemField(itemId, fieldId, value) {
|
|
379
|
+
await (await this.getClient()).updateItemField(itemId, fieldId, value);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get available statuses
|
|
383
|
+
*
|
|
384
|
+
* @returns Array of status definitions
|
|
385
|
+
*/
|
|
386
|
+
async getStatuses() {
|
|
387
|
+
if (this.statuses.length > 0) return this.statuses;
|
|
388
|
+
const client = await this.getClient();
|
|
389
|
+
this.statuses = await client.listStatuses();
|
|
390
|
+
await this.save();
|
|
391
|
+
return this.statuses;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Get available fields
|
|
395
|
+
*
|
|
396
|
+
* @returns Array of field definitions
|
|
397
|
+
*/
|
|
398
|
+
async getFields() {
|
|
399
|
+
if (this.fields.length > 0) return this.fields;
|
|
400
|
+
const client = await this.getClient();
|
|
401
|
+
this.fields = await client.listFields();
|
|
402
|
+
await this.save();
|
|
403
|
+
return this.fields;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Get items in a specific status/column
|
|
407
|
+
*
|
|
408
|
+
* @param status - Status name
|
|
409
|
+
* @returns Array of ProjectItems in that status
|
|
410
|
+
*/
|
|
411
|
+
async getItemsByStatus(status) {
|
|
412
|
+
return await this.listItems({ status });
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Move an item to a new status
|
|
416
|
+
*
|
|
417
|
+
* @param item - Issue or PullRequest
|
|
418
|
+
* @param status - Target status name
|
|
419
|
+
*/
|
|
420
|
+
async moveItem(item, status) {
|
|
421
|
+
const projectItem = (await this.listItems()).find((i) => i.contentId === item.nodeId);
|
|
422
|
+
if (!projectItem) throw new Error("Item not found in project");
|
|
423
|
+
await this.updateItemStatus(projectItem.id, status);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* AI-powered: Analyze project health and suggest improvements
|
|
427
|
+
*
|
|
428
|
+
* @returns Analysis of project status
|
|
429
|
+
*/
|
|
430
|
+
async analyzeHealth() {
|
|
431
|
+
const items = await this.listItems();
|
|
432
|
+
const statuses = await this.getStatuses();
|
|
433
|
+
const statusCounts = {};
|
|
434
|
+
for (const status of statuses) statusCounts[status.name] = items.filter((i) => i.status === status.name).length;
|
|
435
|
+
return await this.do(`Analyze the health of this project board and suggest improvements.
|
|
1016
436
|
|
|
1017
437
|
Project: ${this.title}
|
|
1018
438
|
Description: ${this.description}
|
|
@@ -1025,1473 +445,1084 @@ let Project = class extends SmrtObject {
|
|
|
1025
445
|
Provide:
|
|
1026
446
|
1. Overall health assessment
|
|
1027
447
|
2. Potential bottlenecks
|
|
1028
|
-
3. Suggestions for improvement`,
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
static async getByTitle(title, options = {}) {
|
|
1042
|
-
const { ProjectCollection: ProjectCollection2 } = await Promise.resolve().then(() => Projects);
|
|
1043
|
-
const collection = await ProjectCollection2.create(options);
|
|
1044
|
-
return await collection.findByTitle(title);
|
|
1045
|
-
}
|
|
448
|
+
3. Suggestions for improvement`, { includeData: false });
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Get project by title
|
|
452
|
+
*
|
|
453
|
+
* @param title - Project title
|
|
454
|
+
* @param options - Additional options
|
|
455
|
+
* @returns Project or null
|
|
456
|
+
*/
|
|
457
|
+
static async getByTitle(title, options = {}) {
|
|
458
|
+
const { ProjectCollection } = await Promise.resolve().then(() => Projects_exports);
|
|
459
|
+
return await (await ProjectCollection.create(options)).findByTitle(title);
|
|
460
|
+
}
|
|
1046
461
|
};
|
|
1047
|
-
__decorateClass$
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
async findGlobal() {
|
|
1227
|
-
return queryGlobal(this);
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Find projects for a tenant plus all global projects.
|
|
1231
|
-
*
|
|
1232
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
1233
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1234
|
-
*
|
|
1235
|
-
* @param tenantId - Tenant ID to filter by
|
|
1236
|
-
* @returns Array of tenant and global projects
|
|
1237
|
-
*/
|
|
1238
|
-
async findWithGlobals(tenantId2) {
|
|
1239
|
-
return queryWithGlobals(this, tenantId2, "Project.findWithGlobals");
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
const Projects = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1243
|
-
__proto__: null,
|
|
1244
|
-
ProjectCollection
|
|
1245
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
1246
|
-
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
1247
|
-
var __decorateClass$3 = (decorators, target, key, kind) => {
|
|
1248
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
|
|
1249
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
1250
|
-
if (decorator = decorators[i])
|
|
1251
|
-
result = decorator(result) || result;
|
|
1252
|
-
return result;
|
|
462
|
+
__decorateClass$3([tenantId({ nullable: true })], Project.prototype, "tenantId", 2);
|
|
463
|
+
Project = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
|
|
464
|
+
api: { include: [
|
|
465
|
+
"list",
|
|
466
|
+
"get",
|
|
467
|
+
"create",
|
|
468
|
+
"update"
|
|
469
|
+
] },
|
|
470
|
+
mcp: { include: [
|
|
471
|
+
"list",
|
|
472
|
+
"get",
|
|
473
|
+
"sync",
|
|
474
|
+
"addItem",
|
|
475
|
+
"updateItemStatus"
|
|
476
|
+
] },
|
|
477
|
+
cli: {
|
|
478
|
+
include: [
|
|
479
|
+
"list",
|
|
480
|
+
"get",
|
|
481
|
+
"sync",
|
|
482
|
+
"addItem",
|
|
483
|
+
"updateItemStatus",
|
|
484
|
+
"listItems"
|
|
485
|
+
],
|
|
486
|
+
skipApiCheck: true
|
|
487
|
+
}
|
|
488
|
+
})], Project);
|
|
489
|
+
//#endregion
|
|
490
|
+
//#region src/collections/Projects.ts
|
|
491
|
+
var Projects_exports = /* @__PURE__ */ __exportAll({ ProjectCollection: () => ProjectCollection });
|
|
492
|
+
var logger$3 = createLogger({ level: "info" });
|
|
493
|
+
var ProjectCollection = class extends SmrtCollection {
|
|
494
|
+
static _itemClass = Project;
|
|
495
|
+
/**
|
|
496
|
+
* Find project by title
|
|
497
|
+
*
|
|
498
|
+
* @param title - Project title
|
|
499
|
+
* @returns Project or null
|
|
500
|
+
*/
|
|
501
|
+
async findByTitle(title) {
|
|
502
|
+
return (await this.list({
|
|
503
|
+
where: { title },
|
|
504
|
+
limit: 1
|
|
505
|
+
}))[0] || null;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Find projects by owner
|
|
509
|
+
*
|
|
510
|
+
* @param owner - Project owner (organization or user)
|
|
511
|
+
* @returns Array of projects
|
|
512
|
+
*/
|
|
513
|
+
async findByOwner(owner) {
|
|
514
|
+
return await this.list({ where: { owner } });
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Find projects by provider type
|
|
518
|
+
*
|
|
519
|
+
* @param providerType - Provider type
|
|
520
|
+
* @returns Array of projects
|
|
521
|
+
*/
|
|
522
|
+
async findByProvider(providerType) {
|
|
523
|
+
return await this.list({ where: { providerType } });
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Get or create a project by ID
|
|
527
|
+
*
|
|
528
|
+
* @param projectId - Provider-specific project ID
|
|
529
|
+
* @param options - Additional options for creation
|
|
530
|
+
* @returns Project (existing or newly created)
|
|
531
|
+
*/
|
|
532
|
+
async getOrCreate(projectId, options = {}) {
|
|
533
|
+
let project = await this.findOne({ where: { projectId } });
|
|
534
|
+
if (!project) {
|
|
535
|
+
project = await this.create({
|
|
536
|
+
projectId,
|
|
537
|
+
title: options.title || "",
|
|
538
|
+
owner: options.owner || "",
|
|
539
|
+
providerType: options.providerType || "github",
|
|
540
|
+
tokenConfigKey: options.tokenConfigKey || "GITHUB_TOKEN",
|
|
541
|
+
statusFieldId: options.statusFieldId || "",
|
|
542
|
+
statusOptions: options.statusOptions || {}
|
|
543
|
+
});
|
|
544
|
+
await project.save();
|
|
545
|
+
await project.sync({ force: true });
|
|
546
|
+
}
|
|
547
|
+
return project;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Sync all projects
|
|
551
|
+
*
|
|
552
|
+
* @param options - Sync options
|
|
553
|
+
* @returns Array of synced projects
|
|
554
|
+
*/
|
|
555
|
+
async syncAll(options = {}) {
|
|
556
|
+
const projects = await this.list({});
|
|
557
|
+
const synced = [];
|
|
558
|
+
for (const project of projects) {
|
|
559
|
+
await project.sync(options);
|
|
560
|
+
synced.push(project);
|
|
561
|
+
}
|
|
562
|
+
return synced;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Find projects with items in a specific status
|
|
566
|
+
*
|
|
567
|
+
* @param status - Status name
|
|
568
|
+
* @returns Array of projects
|
|
569
|
+
*/
|
|
570
|
+
async findWithItemsInStatus(status) {
|
|
571
|
+
const projects = await this.list({});
|
|
572
|
+
const matching = [];
|
|
573
|
+
for (const project of projects) try {
|
|
574
|
+
if ((await project.getItemsByStatus(status)).length > 0) matching.push(project);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
logger$3.warn(`Error accessing items for project ${project.projectId}`, { error: error instanceof Error ? error.message : error });
|
|
577
|
+
}
|
|
578
|
+
return matching;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Get project statistics
|
|
582
|
+
*
|
|
583
|
+
* @param projectId - Project ID
|
|
584
|
+
* @returns Statistics object
|
|
585
|
+
*/
|
|
586
|
+
async getStatistics(projectId) {
|
|
587
|
+
const project = await this.findOne({ where: { projectId } });
|
|
588
|
+
if (!project) throw new Error(`Project ${projectId} not found`);
|
|
589
|
+
const items = await project.listItems();
|
|
590
|
+
const statuses = await project.getStatuses();
|
|
591
|
+
const itemsByStatus = {};
|
|
592
|
+
for (const status of statuses) itemsByStatus[status.name] = 0;
|
|
593
|
+
const itemsByType = {
|
|
594
|
+
Issue: 0,
|
|
595
|
+
PullRequest: 0,
|
|
596
|
+
DraftIssue: 0
|
|
597
|
+
};
|
|
598
|
+
for (const item of items) {
|
|
599
|
+
if (item.status && itemsByStatus[item.status] !== void 0) itemsByStatus[item.status]++;
|
|
600
|
+
if (itemsByType[item.type] !== void 0) itemsByType[item.type]++;
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
totalItems: items.length,
|
|
604
|
+
itemsByStatus,
|
|
605
|
+
itemsByType
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Find projects by tenant ID
|
|
610
|
+
*
|
|
611
|
+
* @param tenantId - Tenant ID to filter by
|
|
612
|
+
* @returns Array of projects for the tenant
|
|
613
|
+
*/
|
|
614
|
+
async findByTenant(tenantId) {
|
|
615
|
+
return this.list({ where: { tenantId } });
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Find all global projects (no tenant association).
|
|
619
|
+
*
|
|
620
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
621
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
622
|
+
* flagged as an isolation violation). (#1600)
|
|
623
|
+
*
|
|
624
|
+
* @returns Array of global projects
|
|
625
|
+
*/
|
|
626
|
+
async findGlobal() {
|
|
627
|
+
return queryGlobal(this);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Find projects for a tenant plus all global projects.
|
|
631
|
+
*
|
|
632
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
633
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
634
|
+
*
|
|
635
|
+
* @param tenantId - Tenant ID to filter by
|
|
636
|
+
* @returns Array of tenant and global projects
|
|
637
|
+
*/
|
|
638
|
+
async findWithGlobals(tenantId) {
|
|
639
|
+
return queryWithGlobals(this, tenantId, "Project.findWithGlobals");
|
|
640
|
+
}
|
|
1253
641
|
};
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
Changes: ${this.changedFiles} files changed
|
|
1462
|
-
|
|
1463
|
-
Consider:
|
|
1464
|
-
- Code owners for the affected areas
|
|
1465
|
-
- Team members with relevant expertise
|
|
1466
|
-
- People who have previously worked on related code
|
|
1467
|
-
|
|
1468
|
-
Return only a comma-separated list of GitHub usernames, nothing else.
|
|
1469
|
-
If you cannot determine reviewers, return an empty string.`,
|
|
1470
|
-
// Title/description hand-rolled above; skip do()'s object-data injection.
|
|
1471
|
-
{ includeData: false }
|
|
1472
|
-
);
|
|
1473
|
-
return suggestion.split(",").map((r) => r.trim()).filter(Boolean);
|
|
1474
|
-
}
|
|
1475
|
-
/**
|
|
1476
|
-
* Get PR URL
|
|
1477
|
-
*/
|
|
1478
|
-
getUrl() {
|
|
1479
|
-
const repo = this._repository;
|
|
1480
|
-
if (repo) {
|
|
1481
|
-
return `https://github.com/${repo.owner}/${repo.name}/pull/${this.number}`;
|
|
1482
|
-
}
|
|
1483
|
-
return "";
|
|
1484
|
-
}
|
|
1485
|
-
/**
|
|
1486
|
-
* Get the change size classification
|
|
1487
|
-
*
|
|
1488
|
-
* @returns Size classification (xs, s, m, l, xl)
|
|
1489
|
-
*/
|
|
1490
|
-
getChangeSize() {
|
|
1491
|
-
const total = this.additions + this.deletions;
|
|
1492
|
-
if (total < 10) return "xs";
|
|
1493
|
-
if (total < 50) return "s";
|
|
1494
|
-
if (total < 200) return "m";
|
|
1495
|
-
if (total < 500) return "l";
|
|
1496
|
-
return "xl";
|
|
1497
|
-
}
|
|
642
|
+
//#endregion
|
|
643
|
+
//#region src/collections/PullRequests.ts
|
|
644
|
+
var PullRequests_exports = /* @__PURE__ */ __exportAll({ PullRequestCollection: () => PullRequestCollection });
|
|
645
|
+
var logger$2 = createLogger({ level: "info" });
|
|
646
|
+
var PullRequestCollection = class extends SmrtCollection {
|
|
647
|
+
static _itemClass = PullRequest;
|
|
648
|
+
/**
|
|
649
|
+
* Discover pull requests from a repository and sync to database
|
|
650
|
+
*
|
|
651
|
+
* @param options - Discovery options
|
|
652
|
+
* @returns Array of PullRequest objects
|
|
653
|
+
*/
|
|
654
|
+
async discover(options) {
|
|
655
|
+
const { repository, filters } = options;
|
|
656
|
+
const repositoryId = repository.id ?? void 0;
|
|
657
|
+
if (!repositoryId) throw new Error("Repository must be saved before discovering pull requests");
|
|
658
|
+
const repoClient = await repository.getClient();
|
|
659
|
+
const remoteItems = await repoClient.searchIssues("is:pr", filters);
|
|
660
|
+
const pullRequests = [];
|
|
661
|
+
for (const remote of remoteItems) {
|
|
662
|
+
let prData;
|
|
663
|
+
try {
|
|
664
|
+
prData = await repoClient.getPullRequest(remote.number);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
logger$2.warn(`Failed to fetch PR #${remote.number} from ${repository.owner}/${repository.name}`, { error: error instanceof Error ? error.message : error });
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
let pr = await this.findOne({ where: {
|
|
670
|
+
repositoryId,
|
|
671
|
+
number: remote.number
|
|
672
|
+
} });
|
|
673
|
+
if (!pr) pr = await this.create({
|
|
674
|
+
repositoryId,
|
|
675
|
+
number: prData.number,
|
|
676
|
+
nodeId: prData.id,
|
|
677
|
+
title: prData.title,
|
|
678
|
+
body: prData.body,
|
|
679
|
+
state: prData.state,
|
|
680
|
+
author: prData.author.login,
|
|
681
|
+
labels: prData.labels.map((l) => l.name),
|
|
682
|
+
assignees: prData.assignees.map((a) => a.login),
|
|
683
|
+
commentsCount: prData.commentsCount,
|
|
684
|
+
headRef: prData.headRef,
|
|
685
|
+
baseRef: prData.baseRef,
|
|
686
|
+
merged: prData.merged,
|
|
687
|
+
mergedAt: prData.mergedAt || null,
|
|
688
|
+
mergeable: prData.mergeable,
|
|
689
|
+
draft: prData.draft,
|
|
690
|
+
lastSyncedAt: /* @__PURE__ */ new Date()
|
|
691
|
+
});
|
|
692
|
+
else {
|
|
693
|
+
pr.nodeId = prData.id;
|
|
694
|
+
pr.title = prData.title;
|
|
695
|
+
pr.body = prData.body;
|
|
696
|
+
pr.state = prData.state;
|
|
697
|
+
pr.author = prData.author.login;
|
|
698
|
+
pr.labels = prData.labels.map((l) => l.name);
|
|
699
|
+
pr.assignees = prData.assignees.map((a) => a.login);
|
|
700
|
+
pr.commentsCount = prData.commentsCount;
|
|
701
|
+
pr.headRef = prData.headRef;
|
|
702
|
+
pr.baseRef = prData.baseRef;
|
|
703
|
+
pr.merged = prData.merged;
|
|
704
|
+
pr.mergedAt = prData.mergedAt || null;
|
|
705
|
+
pr.mergeable = prData.mergeable;
|
|
706
|
+
pr.draft = prData.draft;
|
|
707
|
+
pr.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
708
|
+
}
|
|
709
|
+
await pr.save();
|
|
710
|
+
pullRequests.push(pr);
|
|
711
|
+
}
|
|
712
|
+
return pullRequests;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Find PRs by repository
|
|
716
|
+
*
|
|
717
|
+
* @param repositoryId - Repository ID
|
|
718
|
+
* @returns Array of PRs
|
|
719
|
+
*/
|
|
720
|
+
async findByRepository(repositoryId) {
|
|
721
|
+
return await this.list({ where: { repositoryId } });
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Find open PRs
|
|
725
|
+
*
|
|
726
|
+
* @param repositoryId - Optional repository filter
|
|
727
|
+
* @returns Array of open PRs
|
|
728
|
+
*/
|
|
729
|
+
async findOpen(repositoryId) {
|
|
730
|
+
const where = { state: "open" };
|
|
731
|
+
if (repositoryId) where.repositoryId = repositoryId;
|
|
732
|
+
return await this.list({ where });
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Find draft PRs
|
|
736
|
+
*
|
|
737
|
+
* @param repositoryId - Optional repository filter
|
|
738
|
+
* @returns Array of draft PRs
|
|
739
|
+
*/
|
|
740
|
+
async findDrafts(repositoryId) {
|
|
741
|
+
return (await this.findOpen(repositoryId)).filter((pr) => pr.draft);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Find PRs ready to merge
|
|
745
|
+
*
|
|
746
|
+
* @param repositoryId - Optional repository filter
|
|
747
|
+
* @returns Array of mergeable PRs
|
|
748
|
+
*/
|
|
749
|
+
async findReadyToMerge(repositoryId) {
|
|
750
|
+
return (await this.findOpen(repositoryId)).filter((pr) => !pr.draft && pr.mergeable);
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Find PRs by branch
|
|
754
|
+
*
|
|
755
|
+
* @param branch - Branch name (head or base)
|
|
756
|
+
* @param repositoryId - Optional repository filter
|
|
757
|
+
* @returns Array of PRs
|
|
758
|
+
*/
|
|
759
|
+
async findByBranch(branch, repositoryId) {
|
|
760
|
+
return (await this.list({ where: repositoryId ? { repositoryId } : {} })).filter((pr) => pr.headRef === branch || pr.baseRef === branch);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Find PR by number in a repository
|
|
764
|
+
*
|
|
765
|
+
* @param repositoryId - Repository ID
|
|
766
|
+
* @param number - PR number
|
|
767
|
+
* @returns PullRequest or null
|
|
768
|
+
*/
|
|
769
|
+
async findByNumber(repositoryId, number) {
|
|
770
|
+
return (await this.list({
|
|
771
|
+
where: {
|
|
772
|
+
repositoryId,
|
|
773
|
+
number
|
|
774
|
+
},
|
|
775
|
+
limit: 1
|
|
776
|
+
}))[0] || null;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Find PRs ready to merge (AI-powered)
|
|
780
|
+
*
|
|
781
|
+
* @param repositoryId - Optional repository filter
|
|
782
|
+
* @returns Array of PRs that are ready
|
|
783
|
+
*/
|
|
784
|
+
async findAIReadyToMerge(repositoryId) {
|
|
785
|
+
const openPRs = await this.findOpen(repositoryId);
|
|
786
|
+
const ready = [];
|
|
787
|
+
for (const pr of openPRs) if (await pr.isReadyToMerge()) ready.push(pr);
|
|
788
|
+
return ready;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Get PRs by change size
|
|
792
|
+
*
|
|
793
|
+
* @param size - Size classification
|
|
794
|
+
* @param repositoryId - Optional repository filter
|
|
795
|
+
* @returns Array of PRs
|
|
796
|
+
*/
|
|
797
|
+
async findBySize(size, repositoryId) {
|
|
798
|
+
return (await this.findOpen(repositoryId)).filter((pr) => pr.getChangeSize() === size);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Batch sync PRs from repository
|
|
802
|
+
*
|
|
803
|
+
* @param repository - Repository to sync from
|
|
804
|
+
* @param options - Sync options
|
|
805
|
+
* @returns Array of synced PRs
|
|
806
|
+
*/
|
|
807
|
+
async batchSync(repository, options = {}) {
|
|
808
|
+
const prs = await this.findByRepository(repository.id);
|
|
809
|
+
const synced = [];
|
|
810
|
+
for (const pr of prs) {
|
|
811
|
+
await pr.sync(options);
|
|
812
|
+
synced.push(pr);
|
|
813
|
+
}
|
|
814
|
+
return synced;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Find pull requests by tenant ID
|
|
818
|
+
*
|
|
819
|
+
* @param tenantId - Tenant ID to filter by
|
|
820
|
+
* @returns Array of pull requests for the tenant
|
|
821
|
+
*/
|
|
822
|
+
async findByTenant(tenantId) {
|
|
823
|
+
return this.list({ where: { tenantId } });
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Find all global pull requests (no tenant association).
|
|
827
|
+
*
|
|
828
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
829
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
830
|
+
* flagged as an isolation violation). (#1600)
|
|
831
|
+
*
|
|
832
|
+
* @returns Array of global pull requests
|
|
833
|
+
*/
|
|
834
|
+
async findGlobal() {
|
|
835
|
+
return queryGlobal(this);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Find pull requests for a tenant plus all global pull requests.
|
|
839
|
+
*
|
|
840
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
841
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
842
|
+
*
|
|
843
|
+
* @param tenantId - Tenant ID to filter by
|
|
844
|
+
* @returns Array of tenant and global pull requests
|
|
845
|
+
*/
|
|
846
|
+
async findWithGlobals(tenantId) {
|
|
847
|
+
return queryWithGlobals(this, tenantId, "PullRequest.findWithGlobals");
|
|
848
|
+
}
|
|
1498
849
|
};
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
smrt({
|
|
1502
|
-
api: { include: ["list", "get", "create", "update"] },
|
|
1503
|
-
mcp: { include: ["list", "get", "sync", "summarize", "merge"] },
|
|
1504
|
-
// sync/summarize/merge/markReady are operator commands invoked in-process
|
|
1505
|
-
// via the CLI; they intentionally aren't exposed over HTTP today.
|
|
1506
|
-
cli: {
|
|
1507
|
-
include: ["list", "get", "sync", "summarize", "merge", "markReady"],
|
|
1508
|
-
skipApiCheck: true
|
|
1509
|
-
}
|
|
1510
|
-
})
|
|
1511
|
-
], PullRequest);
|
|
1512
|
-
const PullRequest$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1513
|
-
__proto__: null,
|
|
1514
|
-
get PullRequest() {
|
|
1515
|
-
return PullRequest;
|
|
1516
|
-
}
|
|
1517
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
1518
|
-
const logger$2 = createLogger({ level: "info" });
|
|
1519
|
-
class PullRequestCollection extends SmrtCollection {
|
|
1520
|
-
static _itemClass = PullRequest;
|
|
1521
|
-
/**
|
|
1522
|
-
* Discover pull requests from a repository and sync to database
|
|
1523
|
-
*
|
|
1524
|
-
* @param options - Discovery options
|
|
1525
|
-
* @returns Array of PullRequest objects
|
|
1526
|
-
*/
|
|
1527
|
-
async discover(options) {
|
|
1528
|
-
const { repository, filters } = options;
|
|
1529
|
-
const repositoryId = repository.id ?? void 0;
|
|
1530
|
-
if (!repositoryId) {
|
|
1531
|
-
throw new Error(
|
|
1532
|
-
"Repository must be saved before discovering pull requests"
|
|
1533
|
-
);
|
|
1534
|
-
}
|
|
1535
|
-
const repoClient = await repository.getClient();
|
|
1536
|
-
const remoteItems = await repoClient.searchIssues("is:pr", filters);
|
|
1537
|
-
const pullRequests = [];
|
|
1538
|
-
for (const remote of remoteItems) {
|
|
1539
|
-
let prData;
|
|
1540
|
-
try {
|
|
1541
|
-
prData = await repoClient.getPullRequest(remote.number);
|
|
1542
|
-
} catch (error) {
|
|
1543
|
-
logger$2.warn(
|
|
1544
|
-
`Failed to fetch PR #${remote.number} from ${repository.owner}/${repository.name}`,
|
|
1545
|
-
{ error: error instanceof Error ? error.message : error }
|
|
1546
|
-
);
|
|
1547
|
-
continue;
|
|
1548
|
-
}
|
|
1549
|
-
let pr = await this.findOne({
|
|
1550
|
-
where: {
|
|
1551
|
-
repositoryId,
|
|
1552
|
-
number: remote.number
|
|
1553
|
-
}
|
|
1554
|
-
});
|
|
1555
|
-
if (!pr) {
|
|
1556
|
-
pr = await this.create({
|
|
1557
|
-
repositoryId,
|
|
1558
|
-
number: prData.number,
|
|
1559
|
-
nodeId: prData.id,
|
|
1560
|
-
title: prData.title,
|
|
1561
|
-
body: prData.body,
|
|
1562
|
-
state: prData.state,
|
|
1563
|
-
author: prData.author.login,
|
|
1564
|
-
labels: prData.labels.map((l) => l.name),
|
|
1565
|
-
assignees: prData.assignees.map((a) => a.login),
|
|
1566
|
-
commentsCount: prData.commentsCount,
|
|
1567
|
-
headRef: prData.headRef,
|
|
1568
|
-
baseRef: prData.baseRef,
|
|
1569
|
-
merged: prData.merged,
|
|
1570
|
-
mergedAt: prData.mergedAt || null,
|
|
1571
|
-
mergeable: prData.mergeable,
|
|
1572
|
-
draft: prData.draft,
|
|
1573
|
-
lastSyncedAt: /* @__PURE__ */ new Date()
|
|
1574
|
-
});
|
|
1575
|
-
} else {
|
|
1576
|
-
pr.nodeId = prData.id;
|
|
1577
|
-
pr.title = prData.title;
|
|
1578
|
-
pr.body = prData.body;
|
|
1579
|
-
pr.state = prData.state;
|
|
1580
|
-
pr.author = prData.author.login;
|
|
1581
|
-
pr.labels = prData.labels.map((l) => l.name);
|
|
1582
|
-
pr.assignees = prData.assignees.map((a) => a.login);
|
|
1583
|
-
pr.commentsCount = prData.commentsCount;
|
|
1584
|
-
pr.headRef = prData.headRef;
|
|
1585
|
-
pr.baseRef = prData.baseRef;
|
|
1586
|
-
pr.merged = prData.merged;
|
|
1587
|
-
pr.mergedAt = prData.mergedAt || null;
|
|
1588
|
-
pr.mergeable = prData.mergeable;
|
|
1589
|
-
pr.draft = prData.draft;
|
|
1590
|
-
pr.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
1591
|
-
}
|
|
1592
|
-
await pr.save();
|
|
1593
|
-
pullRequests.push(pr);
|
|
1594
|
-
}
|
|
1595
|
-
return pullRequests;
|
|
1596
|
-
}
|
|
1597
|
-
/**
|
|
1598
|
-
* Find PRs by repository
|
|
1599
|
-
*
|
|
1600
|
-
* @param repositoryId - Repository ID
|
|
1601
|
-
* @returns Array of PRs
|
|
1602
|
-
*/
|
|
1603
|
-
async findByRepository(repositoryId) {
|
|
1604
|
-
return await this.list({
|
|
1605
|
-
where: { repositoryId }
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
/**
|
|
1609
|
-
* Find open PRs
|
|
1610
|
-
*
|
|
1611
|
-
* @param repositoryId - Optional repository filter
|
|
1612
|
-
* @returns Array of open PRs
|
|
1613
|
-
*/
|
|
1614
|
-
async findOpen(repositoryId) {
|
|
1615
|
-
const where = { state: "open" };
|
|
1616
|
-
if (repositoryId) {
|
|
1617
|
-
where.repositoryId = repositoryId;
|
|
1618
|
-
}
|
|
1619
|
-
return await this.list({ where });
|
|
1620
|
-
}
|
|
1621
|
-
/**
|
|
1622
|
-
* Find draft PRs
|
|
1623
|
-
*
|
|
1624
|
-
* @param repositoryId - Optional repository filter
|
|
1625
|
-
* @returns Array of draft PRs
|
|
1626
|
-
*/
|
|
1627
|
-
async findDrafts(repositoryId) {
|
|
1628
|
-
const openPRs = await this.findOpen(repositoryId);
|
|
1629
|
-
return openPRs.filter((pr) => pr.draft);
|
|
1630
|
-
}
|
|
1631
|
-
/**
|
|
1632
|
-
* Find PRs ready to merge
|
|
1633
|
-
*
|
|
1634
|
-
* @param repositoryId - Optional repository filter
|
|
1635
|
-
* @returns Array of mergeable PRs
|
|
1636
|
-
*/
|
|
1637
|
-
async findReadyToMerge(repositoryId) {
|
|
1638
|
-
const openPRs = await this.findOpen(repositoryId);
|
|
1639
|
-
return openPRs.filter((pr) => !pr.draft && pr.mergeable);
|
|
1640
|
-
}
|
|
1641
|
-
/**
|
|
1642
|
-
* Find PRs by branch
|
|
1643
|
-
*
|
|
1644
|
-
* @param branch - Branch name (head or base)
|
|
1645
|
-
* @param repositoryId - Optional repository filter
|
|
1646
|
-
* @returns Array of PRs
|
|
1647
|
-
*/
|
|
1648
|
-
async findByBranch(branch, repositoryId) {
|
|
1649
|
-
const prs = await this.list({
|
|
1650
|
-
where: repositoryId ? { repositoryId } : {}
|
|
1651
|
-
});
|
|
1652
|
-
return prs.filter((pr) => pr.headRef === branch || pr.baseRef === branch);
|
|
1653
|
-
}
|
|
1654
|
-
/**
|
|
1655
|
-
* Find PR by number in a repository
|
|
1656
|
-
*
|
|
1657
|
-
* @param repositoryId - Repository ID
|
|
1658
|
-
* @param number - PR number
|
|
1659
|
-
* @returns PullRequest or null
|
|
1660
|
-
*/
|
|
1661
|
-
async findByNumber(repositoryId, number) {
|
|
1662
|
-
const results = await this.list({
|
|
1663
|
-
where: { repositoryId, number },
|
|
1664
|
-
limit: 1
|
|
1665
|
-
});
|
|
1666
|
-
return results[0] || null;
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Find PRs ready to merge (AI-powered)
|
|
1670
|
-
*
|
|
1671
|
-
* @param repositoryId - Optional repository filter
|
|
1672
|
-
* @returns Array of PRs that are ready
|
|
1673
|
-
*/
|
|
1674
|
-
async findAIReadyToMerge(repositoryId) {
|
|
1675
|
-
const openPRs = await this.findOpen(repositoryId);
|
|
1676
|
-
const ready = [];
|
|
1677
|
-
for (const pr of openPRs) {
|
|
1678
|
-
if (await pr.isReadyToMerge()) {
|
|
1679
|
-
ready.push(pr);
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
return ready;
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Get PRs by change size
|
|
1686
|
-
*
|
|
1687
|
-
* @param size - Size classification
|
|
1688
|
-
* @param repositoryId - Optional repository filter
|
|
1689
|
-
* @returns Array of PRs
|
|
1690
|
-
*/
|
|
1691
|
-
async findBySize(size, repositoryId) {
|
|
1692
|
-
const prs = await this.findOpen(repositoryId);
|
|
1693
|
-
return prs.filter((pr) => pr.getChangeSize() === size);
|
|
1694
|
-
}
|
|
1695
|
-
/**
|
|
1696
|
-
* Batch sync PRs from repository
|
|
1697
|
-
*
|
|
1698
|
-
* @param repository - Repository to sync from
|
|
1699
|
-
* @param options - Sync options
|
|
1700
|
-
* @returns Array of synced PRs
|
|
1701
|
-
*/
|
|
1702
|
-
async batchSync(repository, options = {}) {
|
|
1703
|
-
const prs = await this.findByRepository(repository.id);
|
|
1704
|
-
const synced = [];
|
|
1705
|
-
for (const pr of prs) {
|
|
1706
|
-
await pr.sync(options);
|
|
1707
|
-
synced.push(pr);
|
|
1708
|
-
}
|
|
1709
|
-
return synced;
|
|
1710
|
-
}
|
|
1711
|
-
/**
|
|
1712
|
-
* Find pull requests by tenant ID
|
|
1713
|
-
*
|
|
1714
|
-
* @param tenantId - Tenant ID to filter by
|
|
1715
|
-
* @returns Array of pull requests for the tenant
|
|
1716
|
-
*/
|
|
1717
|
-
async findByTenant(tenantId2) {
|
|
1718
|
-
return this.list({ where: { tenantId: tenantId2 } });
|
|
1719
|
-
}
|
|
1720
|
-
/**
|
|
1721
|
-
* Find all global pull requests (no tenant association).
|
|
1722
|
-
*
|
|
1723
|
-
* Routes through the shared tenant-global helper so it does not throw under
|
|
1724
|
-
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
1725
|
-
* flagged as an isolation violation). (#1600)
|
|
1726
|
-
*
|
|
1727
|
-
* @returns Array of global pull requests
|
|
1728
|
-
*/
|
|
1729
|
-
async findGlobal() {
|
|
1730
|
-
return queryGlobal(this);
|
|
1731
|
-
}
|
|
1732
|
-
/**
|
|
1733
|
-
* Find pull requests for a tenant plus all global pull requests.
|
|
1734
|
-
*
|
|
1735
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
1736
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1737
|
-
*
|
|
1738
|
-
* @param tenantId - Tenant ID to filter by
|
|
1739
|
-
* @returns Array of tenant and global pull requests
|
|
1740
|
-
*/
|
|
1741
|
-
async findWithGlobals(tenantId2) {
|
|
1742
|
-
return queryWithGlobals(
|
|
1743
|
-
this,
|
|
1744
|
-
tenantId2,
|
|
1745
|
-
"PullRequest.findWithGlobals"
|
|
1746
|
-
);
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
const PullRequests = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1750
|
-
__proto__: null,
|
|
1751
|
-
PullRequestCollection
|
|
1752
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
850
|
+
//#endregion
|
|
851
|
+
//#region src/models/Repository.ts
|
|
1753
852
|
var __defProp$2 = Object.defineProperty;
|
|
1754
853
|
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
1755
854
|
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
855
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
856
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
857
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
858
|
+
return result;
|
|
859
|
+
};
|
|
860
|
+
var Repository = class extends SmrtObject {
|
|
861
|
+
tenantId = null;
|
|
862
|
+
/**
|
|
863
|
+
* Repository owner (organization or user)
|
|
864
|
+
*/
|
|
865
|
+
owner = "";
|
|
866
|
+
/**
|
|
867
|
+
* Repository name
|
|
868
|
+
*/
|
|
869
|
+
name = "";
|
|
870
|
+
/**
|
|
871
|
+
* Full name in owner/repo format
|
|
872
|
+
*/
|
|
873
|
+
fullName = "";
|
|
874
|
+
/**
|
|
875
|
+
* Repository description
|
|
876
|
+
*/
|
|
877
|
+
description = "";
|
|
878
|
+
/**
|
|
879
|
+
* Default branch name
|
|
880
|
+
*/
|
|
881
|
+
defaultBranch = "main";
|
|
882
|
+
/**
|
|
883
|
+
* Whether repository is private
|
|
884
|
+
*/
|
|
885
|
+
isPrivate = false;
|
|
886
|
+
/**
|
|
887
|
+
* Repository provider type
|
|
888
|
+
*/
|
|
889
|
+
providerType = "github";
|
|
890
|
+
/**
|
|
891
|
+
* Base URL for self-hosted instances (GitHub Enterprise, GitLab self-hosted, etc.)
|
|
892
|
+
*/
|
|
893
|
+
baseUrl = "";
|
|
894
|
+
/**
|
|
895
|
+
* Environment variable name or config key for token resolution
|
|
896
|
+
* The token is NOT stored - only the key name is persisted
|
|
897
|
+
*/
|
|
898
|
+
tokenConfigKey = "GITHUB_TOKEN";
|
|
899
|
+
/**
|
|
900
|
+
* Last sync timestamp
|
|
901
|
+
*/
|
|
902
|
+
lastSyncedAt = null;
|
|
903
|
+
/**
|
|
904
|
+
* Transient: Cached repository client (not persisted)
|
|
905
|
+
*/
|
|
906
|
+
_client;
|
|
907
|
+
constructor(options = {}) {
|
|
908
|
+
super(options);
|
|
909
|
+
if (options.owner !== void 0) this.owner = options.owner;
|
|
910
|
+
if (options.name !== void 0) this.name = options.name;
|
|
911
|
+
if (options.fullName !== void 0) this.fullName = options.fullName;
|
|
912
|
+
if (options.description !== void 0) this.description = options.description;
|
|
913
|
+
if (options.defaultBranch !== void 0) this.defaultBranch = options.defaultBranch;
|
|
914
|
+
if (options.isPrivate !== void 0) this.isPrivate = options.isPrivate;
|
|
915
|
+
if (options.providerType !== void 0) this.providerType = options.providerType;
|
|
916
|
+
if (options.baseUrl !== void 0) this.baseUrl = options.baseUrl;
|
|
917
|
+
if (options.tokenConfigKey !== void 0) this.tokenConfigKey = options.tokenConfigKey;
|
|
918
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Get the repository client, resolving token from config
|
|
922
|
+
*
|
|
923
|
+
* Token resolution order:
|
|
924
|
+
* 1. Environment variable matching tokenConfigKey
|
|
925
|
+
* 2. Module config value matching tokenConfigKey
|
|
926
|
+
*
|
|
927
|
+
* @returns Repository client for API operations
|
|
928
|
+
* @throws Error if token cannot be resolved
|
|
929
|
+
*/
|
|
930
|
+
async getClient() {
|
|
931
|
+
if (this._client) return this._client;
|
|
932
|
+
const token = process.env[this.tokenConfigKey] || getModuleConfig("smrt-projects", {})[this.tokenConfigKey];
|
|
933
|
+
if (!token) throw new Error(`Token not found for key '${this.tokenConfigKey}'. Set the ${this.tokenConfigKey} environment variable or configure it in smrt.config.`);
|
|
934
|
+
this._client = await getRepository({
|
|
935
|
+
type: this.providerType,
|
|
936
|
+
owner: this.owner,
|
|
937
|
+
repo: this.name,
|
|
938
|
+
token,
|
|
939
|
+
baseUrl: this.baseUrl || void 0
|
|
940
|
+
});
|
|
941
|
+
return this._client;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Clear the cached client (useful after token refresh)
|
|
945
|
+
*/
|
|
946
|
+
clearClient() {
|
|
947
|
+
this._client = void 0;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Sync repository metadata from the provider
|
|
951
|
+
*
|
|
952
|
+
* @param options - Sync options
|
|
953
|
+
* @returns This repository with updated fields
|
|
954
|
+
*/
|
|
955
|
+
async sync(options = {}) {
|
|
956
|
+
if (!options.force && this.lastSyncedAt && Date.now() - this.lastSyncedAt.getTime() < 3e5) return this;
|
|
957
|
+
const repoData = await (await this.getClient()).getRepository();
|
|
958
|
+
this.owner = repoData.owner;
|
|
959
|
+
this.name = repoData.name;
|
|
960
|
+
this.fullName = `${repoData.owner}/${repoData.name}`;
|
|
961
|
+
this.description = repoData.description;
|
|
962
|
+
this.defaultBranch = repoData.defaultBranch;
|
|
963
|
+
this.isPrivate = repoData.isPrivate;
|
|
964
|
+
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
965
|
+
await this.save();
|
|
966
|
+
return this;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Get issues from this repository
|
|
970
|
+
*
|
|
971
|
+
* @param filters - Optional search filters
|
|
972
|
+
* @returns Array of Issue objects (SMRT models)
|
|
973
|
+
*/
|
|
974
|
+
async getIssues(filters) {
|
|
975
|
+
const { IssueCollection } = await Promise.resolve().then(() => Issues_exports);
|
|
976
|
+
return await (await IssueCollection.create(this.options)).discover({
|
|
977
|
+
repository: this,
|
|
978
|
+
filters
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Get pull requests from this repository
|
|
983
|
+
*
|
|
984
|
+
* @param filters - Optional search filters
|
|
985
|
+
* @returns Array of PullRequest objects (SMRT models)
|
|
986
|
+
*/
|
|
987
|
+
async getPullRequests(filters) {
|
|
988
|
+
const { PullRequestCollection } = await Promise.resolve().then(() => PullRequests_exports);
|
|
989
|
+
return await (await PullRequestCollection.create(this.options)).discover({
|
|
990
|
+
repository: this,
|
|
991
|
+
filters
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Create a new issue in this repository
|
|
996
|
+
*
|
|
997
|
+
* @param data - Issue creation data
|
|
998
|
+
* @returns Created Issue (SMRT model)
|
|
999
|
+
*/
|
|
1000
|
+
async createIssue(data) {
|
|
1001
|
+
const repositoryId = this.id ?? void 0;
|
|
1002
|
+
if (!repositoryId) throw new Error("Repository must be saved before creating issues");
|
|
1003
|
+
const created = await (await this.getClient()).createIssue(data);
|
|
1004
|
+
const { Issue } = await import("./chunks/Issue-DITLxBl7.js").then((n) => n.n);
|
|
1005
|
+
const issue = new Issue({
|
|
1006
|
+
...this.options,
|
|
1007
|
+
repositoryId,
|
|
1008
|
+
number: created.number,
|
|
1009
|
+
nodeId: created.id,
|
|
1010
|
+
title: created.title,
|
|
1011
|
+
body: created.body,
|
|
1012
|
+
state: created.state,
|
|
1013
|
+
author: created.author.login,
|
|
1014
|
+
labels: created.labels.map((l) => l.name),
|
|
1015
|
+
assignees: created.assignees.map((a) => a.login),
|
|
1016
|
+
commentsCount: created.commentsCount,
|
|
1017
|
+
lastSyncedAt: /* @__PURE__ */ new Date()
|
|
1018
|
+
});
|
|
1019
|
+
await issue.initialize();
|
|
1020
|
+
await issue.save();
|
|
1021
|
+
return issue;
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Create a new pull request in this repository
|
|
1025
|
+
*
|
|
1026
|
+
* @param data - PR creation data
|
|
1027
|
+
* @returns Created PullRequest (SMRT model)
|
|
1028
|
+
*/
|
|
1029
|
+
async createPullRequest(data) {
|
|
1030
|
+
const repositoryId = this.id ?? void 0;
|
|
1031
|
+
if (!repositoryId) throw new Error("Repository must be saved before creating pull requests");
|
|
1032
|
+
const created = await (await this.getClient()).createPullRequest(data);
|
|
1033
|
+
const { PullRequest } = await import("./chunks/PullRequest-C6mck19s.js").then((n) => n.n);
|
|
1034
|
+
const pr = new PullRequest({
|
|
1035
|
+
...this.options,
|
|
1036
|
+
repositoryId,
|
|
1037
|
+
number: created.number,
|
|
1038
|
+
nodeId: created.id,
|
|
1039
|
+
title: created.title,
|
|
1040
|
+
body: created.body,
|
|
1041
|
+
state: created.state,
|
|
1042
|
+
author: created.author.login,
|
|
1043
|
+
labels: created.labels.map((l) => l.name),
|
|
1044
|
+
assignees: created.assignees.map((a) => a.login),
|
|
1045
|
+
commentsCount: created.commentsCount,
|
|
1046
|
+
headRef: created.headRef,
|
|
1047
|
+
baseRef: created.baseRef,
|
|
1048
|
+
merged: created.merged,
|
|
1049
|
+
draft: created.draft,
|
|
1050
|
+
lastSyncedAt: /* @__PURE__ */ new Date()
|
|
1051
|
+
});
|
|
1052
|
+
await pr.initialize();
|
|
1053
|
+
await pr.save();
|
|
1054
|
+
return pr;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Check if this repository has any open issues matching criteria
|
|
1058
|
+
*
|
|
1059
|
+
* @param criteria - Natural language description of what to check
|
|
1060
|
+
* @returns True if matching issues exist
|
|
1061
|
+
*/
|
|
1062
|
+
async hasOpenIssuesMatching(criteria) {
|
|
1063
|
+
const issues = await this.getIssues({ state: "open" });
|
|
1064
|
+
if (issues.length === 0) return false;
|
|
1065
|
+
return await this.is(`This repository has open issues matching: ${criteria}. Current open issues: ${issues.map((i) => `#${i.number}: ${i.title}`).join(", ")}`);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Generate a summary of repository activity
|
|
1069
|
+
*
|
|
1070
|
+
* @returns AI-generated summary
|
|
1071
|
+
*/
|
|
1072
|
+
async summarizeActivity() {
|
|
1073
|
+
const issues = await this.getIssues({
|
|
1074
|
+
state: "open",
|
|
1075
|
+
limit: 10
|
|
1076
|
+
});
|
|
1077
|
+
const prs = await this.getPullRequests({
|
|
1078
|
+
state: "open",
|
|
1079
|
+
limit: 10
|
|
1080
|
+
});
|
|
1081
|
+
return await this.do(`Summarize the current activity in this repository. Open issues: ${issues.map((i) => `#${i.number}: ${i.title}`).join(", ")}. Open PRs: ${prs.map((p) => `#${p.number}: ${p.title}`).join(", ")}.`);
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Get repository by owner and name
|
|
1085
|
+
*
|
|
1086
|
+
* @param owner - Repository owner
|
|
1087
|
+
* @param name - Repository name
|
|
1088
|
+
* @param options - Additional options
|
|
1089
|
+
* @returns Repository or null if not found
|
|
1090
|
+
*/
|
|
1091
|
+
static async getByFullName(owner, name, options = {}) {
|
|
1092
|
+
const { RepositoryCollection } = await Promise.resolve().then(() => Repositories_exports);
|
|
1093
|
+
return await (await RepositoryCollection.create(options)).findByFullName(owner, name);
|
|
1094
|
+
}
|
|
1762
1095
|
};
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
throw new Error("Repository must be saved before creating issues");
|
|
1916
|
-
}
|
|
1917
|
-
const client = await this.getClient();
|
|
1918
|
-
const created = await client.createIssue(data);
|
|
1919
|
-
const { Issue: Issue2 } = await Promise.resolve().then(() => Issue$1);
|
|
1920
|
-
const issue = new Issue2({
|
|
1921
|
-
...this.options,
|
|
1922
|
-
repositoryId,
|
|
1923
|
-
number: created.number,
|
|
1924
|
-
nodeId: created.id,
|
|
1925
|
-
title: created.title,
|
|
1926
|
-
body: created.body,
|
|
1927
|
-
state: created.state,
|
|
1928
|
-
author: created.author.login,
|
|
1929
|
-
labels: created.labels.map((l) => l.name),
|
|
1930
|
-
assignees: created.assignees.map((a) => a.login),
|
|
1931
|
-
commentsCount: created.commentsCount,
|
|
1932
|
-
lastSyncedAt: /* @__PURE__ */ new Date()
|
|
1933
|
-
});
|
|
1934
|
-
await issue.initialize();
|
|
1935
|
-
await issue.save();
|
|
1936
|
-
return issue;
|
|
1937
|
-
}
|
|
1938
|
-
/**
|
|
1939
|
-
* Create a new pull request in this repository
|
|
1940
|
-
*
|
|
1941
|
-
* @param data - PR creation data
|
|
1942
|
-
* @returns Created PullRequest (SMRT model)
|
|
1943
|
-
*/
|
|
1944
|
-
async createPullRequest(data) {
|
|
1945
|
-
const repositoryId = this.id ?? void 0;
|
|
1946
|
-
if (!repositoryId) {
|
|
1947
|
-
throw new Error("Repository must be saved before creating pull requests");
|
|
1948
|
-
}
|
|
1949
|
-
const client = await this.getClient();
|
|
1950
|
-
const created = await client.createPullRequest(data);
|
|
1951
|
-
const { PullRequest: PullRequest2 } = await Promise.resolve().then(() => PullRequest$1);
|
|
1952
|
-
const pr = new PullRequest2({
|
|
1953
|
-
...this.options,
|
|
1954
|
-
repositoryId,
|
|
1955
|
-
number: created.number,
|
|
1956
|
-
nodeId: created.id,
|
|
1957
|
-
title: created.title,
|
|
1958
|
-
body: created.body,
|
|
1959
|
-
state: created.state,
|
|
1960
|
-
author: created.author.login,
|
|
1961
|
-
labels: created.labels.map((l) => l.name),
|
|
1962
|
-
assignees: created.assignees.map((a) => a.login),
|
|
1963
|
-
commentsCount: created.commentsCount,
|
|
1964
|
-
headRef: created.headRef,
|
|
1965
|
-
baseRef: created.baseRef,
|
|
1966
|
-
merged: created.merged,
|
|
1967
|
-
draft: created.draft,
|
|
1968
|
-
lastSyncedAt: /* @__PURE__ */ new Date()
|
|
1969
|
-
});
|
|
1970
|
-
await pr.initialize();
|
|
1971
|
-
await pr.save();
|
|
1972
|
-
return pr;
|
|
1973
|
-
}
|
|
1974
|
-
/**
|
|
1975
|
-
* Check if this repository has any open issues matching criteria
|
|
1976
|
-
*
|
|
1977
|
-
* @param criteria - Natural language description of what to check
|
|
1978
|
-
* @returns True if matching issues exist
|
|
1979
|
-
*/
|
|
1980
|
-
async hasOpenIssuesMatching(criteria) {
|
|
1981
|
-
const issues = await this.getIssues({ state: "open" });
|
|
1982
|
-
if (issues.length === 0) return false;
|
|
1983
|
-
return await this.is(
|
|
1984
|
-
`This repository has open issues matching: ${criteria}. Current open issues: ${issues.map((i) => `#${i.number}: ${i.title}`).join(", ")}`
|
|
1985
|
-
);
|
|
1986
|
-
}
|
|
1987
|
-
/**
|
|
1988
|
-
* Generate a summary of repository activity
|
|
1989
|
-
*
|
|
1990
|
-
* @returns AI-generated summary
|
|
1991
|
-
*/
|
|
1992
|
-
async summarizeActivity() {
|
|
1993
|
-
const issues = await this.getIssues({ state: "open", limit: 10 });
|
|
1994
|
-
const prs = await this.getPullRequests({ state: "open", limit: 10 });
|
|
1995
|
-
return await this.do(
|
|
1996
|
-
`Summarize the current activity in this repository. Open issues: ${issues.map((i) => `#${i.number}: ${i.title}`).join(", ")}. Open PRs: ${prs.map((p) => `#${p.number}: ${p.title}`).join(", ")}.`
|
|
1997
|
-
);
|
|
1998
|
-
}
|
|
1999
|
-
/**
|
|
2000
|
-
* Get repository by owner and name
|
|
2001
|
-
*
|
|
2002
|
-
* @param owner - Repository owner
|
|
2003
|
-
* @param name - Repository name
|
|
2004
|
-
* @param options - Additional options
|
|
2005
|
-
* @returns Repository or null if not found
|
|
2006
|
-
*/
|
|
2007
|
-
static async getByFullName(owner, name, options = {}) {
|
|
2008
|
-
const { RepositoryCollection: RepositoryCollection2 } = await Promise.resolve().then(() => Repositories);
|
|
2009
|
-
const collection = await RepositoryCollection2.create(options);
|
|
2010
|
-
return await collection.findByFullName(owner, name);
|
|
2011
|
-
}
|
|
1096
|
+
__decorateClass$2([tenantId({ nullable: true })], Repository.prototype, "tenantId", 2);
|
|
1097
|
+
Repository = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
|
|
1098
|
+
api: { include: [
|
|
1099
|
+
"list",
|
|
1100
|
+
"get",
|
|
1101
|
+
"create",
|
|
1102
|
+
"update"
|
|
1103
|
+
] },
|
|
1104
|
+
mcp: { include: [
|
|
1105
|
+
"list",
|
|
1106
|
+
"get",
|
|
1107
|
+
"sync"
|
|
1108
|
+
] },
|
|
1109
|
+
cli: {
|
|
1110
|
+
include: [
|
|
1111
|
+
"list",
|
|
1112
|
+
"get",
|
|
1113
|
+
"sync",
|
|
1114
|
+
"create"
|
|
1115
|
+
],
|
|
1116
|
+
skipApiCheck: true
|
|
1117
|
+
}
|
|
1118
|
+
})], Repository);
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region src/collections/Repositories.ts
|
|
1121
|
+
var Repositories_exports = /* @__PURE__ */ __exportAll({ RepositoryCollection: () => RepositoryCollection });
|
|
1122
|
+
var logger$1 = createLogger({ level: "info" });
|
|
1123
|
+
var RepositoryCollection = class extends SmrtCollection {
|
|
1124
|
+
static _itemClass = Repository;
|
|
1125
|
+
/**
|
|
1126
|
+
* Find a repository by owner and name
|
|
1127
|
+
*
|
|
1128
|
+
* @param owner - Repository owner
|
|
1129
|
+
* @param name - Repository name
|
|
1130
|
+
* @returns Repository or null
|
|
1131
|
+
*/
|
|
1132
|
+
async findByFullName(owner, name) {
|
|
1133
|
+
return (await this.list({
|
|
1134
|
+
where: {
|
|
1135
|
+
owner,
|
|
1136
|
+
name
|
|
1137
|
+
},
|
|
1138
|
+
limit: 1
|
|
1139
|
+
}))[0] || null;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Find repositories by owner
|
|
1143
|
+
*
|
|
1144
|
+
* @param owner - Repository owner
|
|
1145
|
+
* @returns Array of repositories
|
|
1146
|
+
*/
|
|
1147
|
+
async findByOwner(owner) {
|
|
1148
|
+
return await this.list({ where: { owner } });
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Find repositories by provider type
|
|
1152
|
+
*
|
|
1153
|
+
* @param providerType - Provider type
|
|
1154
|
+
* @returns Array of repositories
|
|
1155
|
+
*/
|
|
1156
|
+
async findByProvider(providerType) {
|
|
1157
|
+
return await this.list({ where: { providerType } });
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Get or create a repository by owner/name
|
|
1161
|
+
*
|
|
1162
|
+
* @param owner - Repository owner
|
|
1163
|
+
* @param name - Repository name
|
|
1164
|
+
* @param options - Additional options for creation
|
|
1165
|
+
* @returns Repository (existing or newly created)
|
|
1166
|
+
*/
|
|
1167
|
+
async getOrCreate(owner, name, options = {}) {
|
|
1168
|
+
let repo = await this.findByFullName(owner, name);
|
|
1169
|
+
if (!repo) {
|
|
1170
|
+
repo = await this.create({
|
|
1171
|
+
owner,
|
|
1172
|
+
name,
|
|
1173
|
+
fullName: `${owner}/${name}`,
|
|
1174
|
+
providerType: options.providerType || "github",
|
|
1175
|
+
tokenConfigKey: options.tokenConfigKey || "GITHUB_TOKEN",
|
|
1176
|
+
baseUrl: options.baseUrl || ""
|
|
1177
|
+
});
|
|
1178
|
+
await repo.save();
|
|
1179
|
+
}
|
|
1180
|
+
return repo;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Sync all repositories
|
|
1184
|
+
*
|
|
1185
|
+
* @param options - Sync options
|
|
1186
|
+
* @returns Array of synced repositories
|
|
1187
|
+
*/
|
|
1188
|
+
async syncAll(options = {}) {
|
|
1189
|
+
const repos = await this.list({});
|
|
1190
|
+
const synced = [];
|
|
1191
|
+
for (const repo of repos) {
|
|
1192
|
+
await repo.sync(options);
|
|
1193
|
+
synced.push(repo);
|
|
1194
|
+
}
|
|
1195
|
+
return synced;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Find repositories with open issues
|
|
1199
|
+
*
|
|
1200
|
+
* @returns Array of repositories with at least one open issue
|
|
1201
|
+
*/
|
|
1202
|
+
async findWithOpenIssues() {
|
|
1203
|
+
const repos = await this.list({});
|
|
1204
|
+
const withIssues = [];
|
|
1205
|
+
for (const repo of repos) try {
|
|
1206
|
+
if ((await repo.getIssues({
|
|
1207
|
+
state: "open",
|
|
1208
|
+
limit: 1
|
|
1209
|
+
})).length > 0) withIssues.push(repo);
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
logger$1.warn(`Error accessing issues for repository ${repo.owner}/${repo.name}`, { error: error instanceof Error ? error.message : error });
|
|
1212
|
+
}
|
|
1213
|
+
return withIssues;
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Find repositories by tenant ID
|
|
1217
|
+
*
|
|
1218
|
+
* @param tenantId - Tenant ID to filter by
|
|
1219
|
+
* @returns Array of repositories for the tenant
|
|
1220
|
+
*/
|
|
1221
|
+
async findByTenant(tenantId) {
|
|
1222
|
+
return this.list({ where: { tenantId } });
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Find all global repositories (no tenant association).
|
|
1226
|
+
*
|
|
1227
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
1228
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
1229
|
+
* flagged as an isolation violation). (#1600)
|
|
1230
|
+
*
|
|
1231
|
+
* @returns Array of global repositories
|
|
1232
|
+
*/
|
|
1233
|
+
async findGlobal() {
|
|
1234
|
+
return queryGlobal(this);
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Find repositories for a tenant plus all global repositories.
|
|
1238
|
+
*
|
|
1239
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
1240
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1241
|
+
*
|
|
1242
|
+
* @param tenantId - Tenant ID to filter by
|
|
1243
|
+
* @returns Array of tenant and global repositories
|
|
1244
|
+
*/
|
|
1245
|
+
async findWithGlobals(tenantId) {
|
|
1246
|
+
return queryWithGlobals(this, tenantId, "Repository.findWithGlobals");
|
|
1247
|
+
}
|
|
2012
1248
|
};
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
Repository = __decorateClass$2([
|
|
2017
|
-
TenantScoped({ mode: "optional" }),
|
|
2018
|
-
smrt({
|
|
2019
|
-
api: { include: ["list", "get", "create", "update"] },
|
|
2020
|
-
mcp: { include: ["list", "get", "sync"] },
|
|
2021
|
-
// sync is an operator command invoked in-process via the CLI;
|
|
2022
|
-
// it intentionally isn't exposed over HTTP today.
|
|
2023
|
-
cli: { include: ["list", "get", "sync", "create"], skipApiCheck: true }
|
|
2024
|
-
})
|
|
2025
|
-
], Repository);
|
|
2026
|
-
const logger$1 = createLogger({ level: "info" });
|
|
2027
|
-
class RepositoryCollection extends SmrtCollection {
|
|
2028
|
-
static _itemClass = Repository;
|
|
2029
|
-
/**
|
|
2030
|
-
* Find a repository by owner and name
|
|
2031
|
-
*
|
|
2032
|
-
* @param owner - Repository owner
|
|
2033
|
-
* @param name - Repository name
|
|
2034
|
-
* @returns Repository or null
|
|
2035
|
-
*/
|
|
2036
|
-
async findByFullName(owner, name) {
|
|
2037
|
-
const results = await this.list({
|
|
2038
|
-
where: { owner, name },
|
|
2039
|
-
limit: 1
|
|
2040
|
-
});
|
|
2041
|
-
return results[0] || null;
|
|
2042
|
-
}
|
|
2043
|
-
/**
|
|
2044
|
-
* Find repositories by owner
|
|
2045
|
-
*
|
|
2046
|
-
* @param owner - Repository owner
|
|
2047
|
-
* @returns Array of repositories
|
|
2048
|
-
*/
|
|
2049
|
-
async findByOwner(owner) {
|
|
2050
|
-
return await this.list({
|
|
2051
|
-
where: { owner }
|
|
2052
|
-
});
|
|
2053
|
-
}
|
|
2054
|
-
/**
|
|
2055
|
-
* Find repositories by provider type
|
|
2056
|
-
*
|
|
2057
|
-
* @param providerType - Provider type
|
|
2058
|
-
* @returns Array of repositories
|
|
2059
|
-
*/
|
|
2060
|
-
async findByProvider(providerType) {
|
|
2061
|
-
return await this.list({
|
|
2062
|
-
where: { providerType }
|
|
2063
|
-
});
|
|
2064
|
-
}
|
|
2065
|
-
/**
|
|
2066
|
-
* Get or create a repository by owner/name
|
|
2067
|
-
*
|
|
2068
|
-
* @param owner - Repository owner
|
|
2069
|
-
* @param name - Repository name
|
|
2070
|
-
* @param options - Additional options for creation
|
|
2071
|
-
* @returns Repository (existing or newly created)
|
|
2072
|
-
*/
|
|
2073
|
-
async getOrCreate(owner, name, options = {}) {
|
|
2074
|
-
let repo = await this.findByFullName(owner, name);
|
|
2075
|
-
if (!repo) {
|
|
2076
|
-
repo = await this.create({
|
|
2077
|
-
owner,
|
|
2078
|
-
name,
|
|
2079
|
-
fullName: `${owner}/${name}`,
|
|
2080
|
-
providerType: options.providerType || "github",
|
|
2081
|
-
tokenConfigKey: options.tokenConfigKey || "GITHUB_TOKEN",
|
|
2082
|
-
baseUrl: options.baseUrl || ""
|
|
2083
|
-
});
|
|
2084
|
-
await repo.save();
|
|
2085
|
-
}
|
|
2086
|
-
return repo;
|
|
2087
|
-
}
|
|
2088
|
-
/**
|
|
2089
|
-
* Sync all repositories
|
|
2090
|
-
*
|
|
2091
|
-
* @param options - Sync options
|
|
2092
|
-
* @returns Array of synced repositories
|
|
2093
|
-
*/
|
|
2094
|
-
async syncAll(options = {}) {
|
|
2095
|
-
const repos = await this.list({});
|
|
2096
|
-
const synced = [];
|
|
2097
|
-
for (const repo of repos) {
|
|
2098
|
-
await repo.sync(options);
|
|
2099
|
-
synced.push(repo);
|
|
2100
|
-
}
|
|
2101
|
-
return synced;
|
|
2102
|
-
}
|
|
2103
|
-
/**
|
|
2104
|
-
* Find repositories with open issues
|
|
2105
|
-
*
|
|
2106
|
-
* @returns Array of repositories with at least one open issue
|
|
2107
|
-
*/
|
|
2108
|
-
async findWithOpenIssues() {
|
|
2109
|
-
const repos = await this.list({});
|
|
2110
|
-
const withIssues = [];
|
|
2111
|
-
for (const repo of repos) {
|
|
2112
|
-
try {
|
|
2113
|
-
const issues = await repo.getIssues({ state: "open", limit: 1 });
|
|
2114
|
-
if (issues.length > 0) {
|
|
2115
|
-
withIssues.push(repo);
|
|
2116
|
-
}
|
|
2117
|
-
} catch (error) {
|
|
2118
|
-
logger$1.warn(
|
|
2119
|
-
`Error accessing issues for repository ${repo.owner}/${repo.name}`,
|
|
2120
|
-
{ error: error instanceof Error ? error.message : error }
|
|
2121
|
-
);
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
return withIssues;
|
|
2125
|
-
}
|
|
2126
|
-
/**
|
|
2127
|
-
* Find repositories by tenant ID
|
|
2128
|
-
*
|
|
2129
|
-
* @param tenantId - Tenant ID to filter by
|
|
2130
|
-
* @returns Array of repositories for the tenant
|
|
2131
|
-
*/
|
|
2132
|
-
async findByTenant(tenantId2) {
|
|
2133
|
-
return this.list({ where: { tenantId: tenantId2 } });
|
|
2134
|
-
}
|
|
2135
|
-
/**
|
|
2136
|
-
* Find all global repositories (no tenant association).
|
|
2137
|
-
*
|
|
2138
|
-
* Routes through the shared tenant-global helper so it does not throw under
|
|
2139
|
-
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
2140
|
-
* flagged as an isolation violation). (#1600)
|
|
2141
|
-
*
|
|
2142
|
-
* @returns Array of global repositories
|
|
2143
|
-
*/
|
|
2144
|
-
async findGlobal() {
|
|
2145
|
-
return queryGlobal(this);
|
|
2146
|
-
}
|
|
2147
|
-
/**
|
|
2148
|
-
* Find repositories for a tenant plus all global repositories.
|
|
2149
|
-
*
|
|
2150
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
2151
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
2152
|
-
*
|
|
2153
|
-
* @param tenantId - Tenant ID to filter by
|
|
2154
|
-
* @returns Array of tenant and global repositories
|
|
2155
|
-
*/
|
|
2156
|
-
async findWithGlobals(tenantId2) {
|
|
2157
|
-
return queryWithGlobals(
|
|
2158
|
-
this,
|
|
2159
|
-
tenantId2,
|
|
2160
|
-
"Repository.findWithGlobals"
|
|
2161
|
-
);
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
const Repositories = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2165
|
-
__proto__: null,
|
|
2166
|
-
RepositoryCollection
|
|
2167
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
1249
|
+
//#endregion
|
|
1250
|
+
//#region src/models/Comment.ts
|
|
1251
|
+
var Comment_exports = /* @__PURE__ */ __exportAll({ Comment: () => Comment });
|
|
2168
1252
|
var __defProp$1 = Object.defineProperty;
|
|
2169
1253
|
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
2170
1254
|
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
if (kind && result) __defProp$1(target, key, result);
|
|
2176
|
-
return result;
|
|
1255
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
1256
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1257
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
1258
|
+
return result;
|
|
2177
1259
|
};
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
* AI-powered: Extract action items from this comment
|
|
2243
|
-
*
|
|
2244
|
-
* @returns Array of action items
|
|
2245
|
-
*/
|
|
2246
|
-
async extractActionItems() {
|
|
2247
|
-
const result = await this.do(
|
|
2248
|
-
`Extract any action items, tasks, or requests from this comment.
|
|
1260
|
+
var logger = createLogger({ level: "info" });
|
|
1261
|
+
var Comment = class extends SmrtObject {
|
|
1262
|
+
tenantId = null;
|
|
1263
|
+
issueId;
|
|
1264
|
+
/**
|
|
1265
|
+
* Provider-specific comment ID
|
|
1266
|
+
*/
|
|
1267
|
+
commentId = "";
|
|
1268
|
+
/**
|
|
1269
|
+
* Comment body text
|
|
1270
|
+
*/
|
|
1271
|
+
body = "";
|
|
1272
|
+
/**
|
|
1273
|
+
* Comment author's login
|
|
1274
|
+
*/
|
|
1275
|
+
author = "";
|
|
1276
|
+
/**
|
|
1277
|
+
* When the comment was created
|
|
1278
|
+
*/
|
|
1279
|
+
createdAt = null;
|
|
1280
|
+
/**
|
|
1281
|
+
* When the comment was last updated
|
|
1282
|
+
*/
|
|
1283
|
+
updatedAt = null;
|
|
1284
|
+
/**
|
|
1285
|
+
* Comment URL
|
|
1286
|
+
*/
|
|
1287
|
+
url = "";
|
|
1288
|
+
constructor(options = {}) {
|
|
1289
|
+
super(options);
|
|
1290
|
+
if (options.issueId !== void 0) this.issueId = options.issueId;
|
|
1291
|
+
if (options.commentId !== void 0) this.commentId = options.commentId;
|
|
1292
|
+
if (options.body !== void 0) this.body = options.body;
|
|
1293
|
+
if (options.author !== void 0) this.author = options.author;
|
|
1294
|
+
if (options.createdAt !== void 0) this.createdAt = options.createdAt;
|
|
1295
|
+
if (options.updatedAt !== void 0) this.updatedAt = options.updatedAt;
|
|
1296
|
+
if (options.url !== void 0) this.url = options.url;
|
|
1297
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* AI-powered: Check if this comment contains a question
|
|
1301
|
+
*/
|
|
1302
|
+
async isQuestion() {
|
|
1303
|
+
return await this.is("This comment contains a question or request for clarification");
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* AI-powered: Check if this comment contains approval
|
|
1307
|
+
*/
|
|
1308
|
+
async isApproval() {
|
|
1309
|
+
return await this.is("This comment expresses approval, agreement, or a positive response (LGTM, +1, approved, etc.)");
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* AI-powered: Check if this comment requests changes
|
|
1313
|
+
*/
|
|
1314
|
+
async requestsChanges() {
|
|
1315
|
+
return await this.is("This comment requests changes, modifications, or improvements to the issue/PR");
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* AI-powered: Extract action items from this comment
|
|
1319
|
+
*
|
|
1320
|
+
* @returns Array of action items
|
|
1321
|
+
*/
|
|
1322
|
+
async extractActionItems() {
|
|
1323
|
+
const result = await this.do(`Extract any action items, tasks, or requests from this comment.
|
|
2249
1324
|
Return a JSON array of strings, each representing one action item.
|
|
2250
1325
|
If no action items, return an empty array [].
|
|
2251
1326
|
Only return the JSON array, nothing else.
|
|
2252
1327
|
|
|
2253
|
-
Comment: ${this.body}`,
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
/**
|
|
2281
|
-
* Get the sentiment of this comment
|
|
2282
|
-
*
|
|
2283
|
-
* @returns Sentiment classification
|
|
2284
|
-
*/
|
|
2285
|
-
async getSentiment() {
|
|
2286
|
-
const result = await this.do(
|
|
2287
|
-
`Classify the sentiment of this comment as exactly one of: positive, negative, neutral
|
|
1328
|
+
Comment: ${this.body}`, { includeData: false });
|
|
1329
|
+
try {
|
|
1330
|
+
return JSON.parse(result);
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
logger.warn("Failed to parse action items JSON", {
|
|
1333
|
+
error: error instanceof Error ? error.message : error,
|
|
1334
|
+
response: result
|
|
1335
|
+
});
|
|
1336
|
+
return [];
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* AI-powered: Summarize this comment
|
|
1341
|
+
*
|
|
1342
|
+
* @returns Brief summary
|
|
1343
|
+
*/
|
|
1344
|
+
async summarize() {
|
|
1345
|
+
return await this.do(`Summarize this comment in one sentence.
|
|
1346
|
+
Comment by ${this.author}: ${this.body}`, { includeData: false });
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Get the sentiment of this comment
|
|
1350
|
+
*
|
|
1351
|
+
* @returns Sentiment classification
|
|
1352
|
+
*/
|
|
1353
|
+
async getSentiment() {
|
|
1354
|
+
const normalized = (await this.do(`Classify the sentiment of this comment as exactly one of: positive, negative, neutral
|
|
2288
1355
|
Only return one word.
|
|
2289
|
-
Comment: ${this.body}`,
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
if (normalized.includes("positive")) return "positive";
|
|
2295
|
-
if (normalized.includes("negative")) return "negative";
|
|
2296
|
-
return "neutral";
|
|
2297
|
-
}
|
|
1356
|
+
Comment: ${this.body}`, { includeData: false })).toLowerCase().trim();
|
|
1357
|
+
if (normalized.includes("positive")) return "positive";
|
|
1358
|
+
if (normalized.includes("negative")) return "negative";
|
|
1359
|
+
return "neutral";
|
|
1360
|
+
}
|
|
2298
1361
|
};
|
|
2299
|
-
__decorateClass$1([
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
Comment
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
api: { include: ["list", "get"] },
|
|
2309
|
-
mcp: { include: ["list", "get"] },
|
|
2310
|
-
cli: { include: ["list", "get"] }
|
|
2311
|
-
})
|
|
2312
|
-
], Comment);
|
|
2313
|
-
const Comment$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2314
|
-
__proto__: null,
|
|
2315
|
-
get Comment() {
|
|
2316
|
-
return Comment;
|
|
2317
|
-
}
|
|
2318
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
1362
|
+
__decorateClass$1([tenantId({ nullable: true })], Comment.prototype, "tenantId", 2);
|
|
1363
|
+
__decorateClass$1([foreignKey("Issue")], Comment.prototype, "issueId", 2);
|
|
1364
|
+
Comment = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
|
|
1365
|
+
api: { include: ["list", "get"] },
|
|
1366
|
+
mcp: { include: ["list", "get"] },
|
|
1367
|
+
cli: { include: ["list", "get"] }
|
|
1368
|
+
})], Comment);
|
|
1369
|
+
//#endregion
|
|
1370
|
+
//#region src/models/Label.ts
|
|
2319
1371
|
var __defProp = Object.defineProperty;
|
|
2320
1372
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
2321
1373
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
if (kind && result) __defProp(target, key, result);
|
|
2327
|
-
return result;
|
|
1374
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
1375
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1376
|
+
if (kind && result) __defProp(target, key, result);
|
|
1377
|
+
return result;
|
|
2328
1378
|
};
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
*/
|
|
2453
|
-
async updateInRepository() {
|
|
2454
|
-
if (!this.repositoryId) {
|
|
2455
|
-
throw new Error("Label must have a repositoryId");
|
|
2456
|
-
}
|
|
2457
|
-
const { RepositoryCollection: RepositoryCollection2 } = await Promise.resolve().then(() => Repositories);
|
|
2458
|
-
const collection = await RepositoryCollection2.create(this.options);
|
|
2459
|
-
const repo = await collection.get({ id: this.repositoryId });
|
|
2460
|
-
if (!repo) {
|
|
2461
|
-
throw new Error(`Repository ${this.repositoryId} not found`);
|
|
2462
|
-
}
|
|
2463
|
-
const client = await repo.getClient();
|
|
2464
|
-
await client.updateLabel(this.name, {
|
|
2465
|
-
name: this.name,
|
|
2466
|
-
color: this.color,
|
|
2467
|
-
description: this.description
|
|
2468
|
-
});
|
|
2469
|
-
await this.save();
|
|
2470
|
-
}
|
|
1379
|
+
var Label = class extends SmrtObject {
|
|
1380
|
+
repositoryId;
|
|
1381
|
+
/**
|
|
1382
|
+
* Label name
|
|
1383
|
+
*/
|
|
1384
|
+
name = "";
|
|
1385
|
+
/**
|
|
1386
|
+
* Label color (hex without #)
|
|
1387
|
+
*/
|
|
1388
|
+
color = "";
|
|
1389
|
+
/**
|
|
1390
|
+
* Label description
|
|
1391
|
+
*/
|
|
1392
|
+
description = "";
|
|
1393
|
+
constructor(options = {}) {
|
|
1394
|
+
super(options);
|
|
1395
|
+
if (options.repositoryId !== void 0) this.repositoryId = options.repositoryId;
|
|
1396
|
+
if (options.name !== void 0) this.name = options.name;
|
|
1397
|
+
if (options.color !== void 0) this.color = options.color;
|
|
1398
|
+
if (options.description !== void 0) this.description = options.description;
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Check if this is a type label (bug, feature, etc.)
|
|
1402
|
+
*/
|
|
1403
|
+
isTypeLabel() {
|
|
1404
|
+
return [
|
|
1405
|
+
/^type:/i,
|
|
1406
|
+
/^kind:/i,
|
|
1407
|
+
/^bug$/i,
|
|
1408
|
+
/^feature$/i,
|
|
1409
|
+
/^enhancement$/i,
|
|
1410
|
+
/^docs$/i,
|
|
1411
|
+
/^chore$/i
|
|
1412
|
+
].some((p) => p.test(this.name));
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Check if this is a priority label
|
|
1416
|
+
*/
|
|
1417
|
+
isPriorityLabel() {
|
|
1418
|
+
return [
|
|
1419
|
+
/^p[0-4]$/i,
|
|
1420
|
+
/^priority:/i,
|
|
1421
|
+
/^critical$/i,
|
|
1422
|
+
/^high$/i,
|
|
1423
|
+
/^medium$/i,
|
|
1424
|
+
/^low$/i
|
|
1425
|
+
].some((p) => p.test(this.name));
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Check if this is a status label
|
|
1429
|
+
*/
|
|
1430
|
+
isStatusLabel() {
|
|
1431
|
+
return [
|
|
1432
|
+
/^status:/i,
|
|
1433
|
+
/^wip$/i,
|
|
1434
|
+
/^in.?progress$/i,
|
|
1435
|
+
/^blocked$/i,
|
|
1436
|
+
/^needs.?review$/i,
|
|
1437
|
+
/^ready$/i
|
|
1438
|
+
].some((p) => p.test(this.name));
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Get the label category
|
|
1442
|
+
*
|
|
1443
|
+
* @returns Category name
|
|
1444
|
+
*/
|
|
1445
|
+
getCategory() {
|
|
1446
|
+
if (this.isTypeLabel()) return "type";
|
|
1447
|
+
if (this.isPriorityLabel()) return "priority";
|
|
1448
|
+
if (this.isStatusLabel()) return "status";
|
|
1449
|
+
if (this.name.includes(":")) return "area";
|
|
1450
|
+
return "other";
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Parse priority level from label name
|
|
1454
|
+
*
|
|
1455
|
+
* @returns Priority level 0-4 or null
|
|
1456
|
+
*/
|
|
1457
|
+
getPriorityLevel() {
|
|
1458
|
+
const match = this.name.match(/p([0-4])/i);
|
|
1459
|
+
if (match) return parseInt(match[1], 10);
|
|
1460
|
+
if (/critical/i.test(this.name)) return 0;
|
|
1461
|
+
if (/high/i.test(this.name)) return 1;
|
|
1462
|
+
if (/medium/i.test(this.name)) return 2;
|
|
1463
|
+
if (/low/i.test(this.name)) return 3;
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Get label with # prefix for hex color
|
|
1468
|
+
*/
|
|
1469
|
+
getHexColor() {
|
|
1470
|
+
return this.color.startsWith("#") ? this.color : `#${this.color}`;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Create a label in the repository
|
|
1474
|
+
*/
|
|
1475
|
+
async createInRepository() {
|
|
1476
|
+
if (!this.repositoryId) throw new Error("Label must have a repositoryId");
|
|
1477
|
+
const { RepositoryCollection } = await Promise.resolve().then(() => Repositories_exports);
|
|
1478
|
+
const repo = await (await RepositoryCollection.create(this.options)).get({ id: this.repositoryId });
|
|
1479
|
+
if (!repo) throw new Error(`Repository ${this.repositoryId} not found`);
|
|
1480
|
+
await (await repo.getClient()).createLabel({
|
|
1481
|
+
name: this.name,
|
|
1482
|
+
color: this.color,
|
|
1483
|
+
description: this.description
|
|
1484
|
+
});
|
|
1485
|
+
await this.save();
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Update this label in the repository
|
|
1489
|
+
*/
|
|
1490
|
+
async updateInRepository() {
|
|
1491
|
+
if (!this.repositoryId) throw new Error("Label must have a repositoryId");
|
|
1492
|
+
const { RepositoryCollection } = await Promise.resolve().then(() => Repositories_exports);
|
|
1493
|
+
const repo = await (await RepositoryCollection.create(this.options)).get({ id: this.repositoryId });
|
|
1494
|
+
if (!repo) throw new Error(`Repository ${this.repositoryId} not found`);
|
|
1495
|
+
await (await repo.getClient()).updateLabel(this.name, {
|
|
1496
|
+
name: this.name,
|
|
1497
|
+
color: this.color,
|
|
1498
|
+
description: this.description
|
|
1499
|
+
});
|
|
1500
|
+
await this.save();
|
|
1501
|
+
}
|
|
2471
1502
|
};
|
|
2472
|
-
__decorateClass([
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
//# sourceMappingURL=index.js.map
|
|
1503
|
+
__decorateClass([foreignKey("Repository")], Label.prototype, "repositoryId", 2);
|
|
1504
|
+
Label = __decorateClass([smrt({
|
|
1505
|
+
api: { include: [
|
|
1506
|
+
"list",
|
|
1507
|
+
"get",
|
|
1508
|
+
"create",
|
|
1509
|
+
"update",
|
|
1510
|
+
"delete"
|
|
1511
|
+
] },
|
|
1512
|
+
mcp: { include: [
|
|
1513
|
+
"list",
|
|
1514
|
+
"get",
|
|
1515
|
+
"create"
|
|
1516
|
+
] },
|
|
1517
|
+
cli: { include: [
|
|
1518
|
+
"list",
|
|
1519
|
+
"get",
|
|
1520
|
+
"create",
|
|
1521
|
+
"update",
|
|
1522
|
+
"delete"
|
|
1523
|
+
] }
|
|
1524
|
+
})], Label);
|
|
1525
|
+
//#endregion
|
|
1526
|
+
export { Comment, Issue, IssueCollection, Label, PROJECTS_MODULE_META, PROJECTS_UI_SLOTS, Project, ProjectCollection, PullRequest, PullRequestCollection, Repository, RepositoryCollection, issueIncorporateFeedbackPrompt, Repositories_exports as n, Issues_exports as r, Comment_exports as t };
|
|
1527
|
+
|
|
1528
|
+
//# sourceMappingURL=index.js.map
|