@happyvertical/smrt-projects 0.37.1 → 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
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import "./constants-BhVfX4Jn.js";
|
|
2
|
+
import { SmrtObject, config, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
3
|
+
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
4
|
+
import { getAI } from "@happyvertical/ai";
|
|
5
|
+
import { definePrompt, resolvePrompt } from "@happyvertical/smrt-prompts";
|
|
6
|
+
import { loadEnvConfig } from "@happyvertical/utils";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp$1 = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp$1(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp$1(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/prompts.ts
|
|
20
|
+
var issueIncorporateFeedbackPrompt = definePrompt({
|
|
21
|
+
key: "projects.issue.incorporateFeedback",
|
|
22
|
+
template: `You are updating a specification document based on team feedback.
|
|
23
|
+
|
|
24
|
+
Current specification:
|
|
25
|
+
{body}
|
|
26
|
+
|
|
27
|
+
Team comments and feedback:
|
|
28
|
+
{comments}
|
|
29
|
+
|
|
30
|
+
Instructions:
|
|
31
|
+
1. Analyze the comments for consensus, changes, and new requirements
|
|
32
|
+
2. Update the specification to reflect the agreed-upon changes
|
|
33
|
+
3. Maintain the original structure and formatting where possible
|
|
34
|
+
4. Mark any conflicting feedback that needs resolution
|
|
35
|
+
5. Return ONLY the updated specification text, no additional commentary`,
|
|
36
|
+
ai: { temperature: .2 },
|
|
37
|
+
editable: {
|
|
38
|
+
template: true,
|
|
39
|
+
profile: true,
|
|
40
|
+
model: true,
|
|
41
|
+
params: true
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/models/Issue.ts
|
|
46
|
+
var Issue_exports = /* @__PURE__ */ __exportAll({ Issue: () => Issue });
|
|
47
|
+
var __defProp = Object.defineProperty;
|
|
48
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
49
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
50
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
51
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
52
|
+
if (kind && result) __defProp(target, key, result);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
var Issue = class extends SmrtObject {
|
|
56
|
+
tenantId = null;
|
|
57
|
+
repositoryId;
|
|
58
|
+
/**
|
|
59
|
+
* Issue number (provider-specific)
|
|
60
|
+
*/
|
|
61
|
+
number = 0;
|
|
62
|
+
/**
|
|
63
|
+
* Node ID for GraphQL operations (GitHub Projects API)
|
|
64
|
+
*/
|
|
65
|
+
nodeId = "";
|
|
66
|
+
/**
|
|
67
|
+
* Issue title
|
|
68
|
+
*/
|
|
69
|
+
title = "";
|
|
70
|
+
/**
|
|
71
|
+
* Issue body/description
|
|
72
|
+
*/
|
|
73
|
+
body = "";
|
|
74
|
+
/**
|
|
75
|
+
* Issue state
|
|
76
|
+
*/
|
|
77
|
+
state = "open";
|
|
78
|
+
/**
|
|
79
|
+
* Author's login/username
|
|
80
|
+
*/
|
|
81
|
+
author = "";
|
|
82
|
+
/**
|
|
83
|
+
* Labels attached to the issue
|
|
84
|
+
*/
|
|
85
|
+
labels = [];
|
|
86
|
+
/**
|
|
87
|
+
* Assignee logins
|
|
88
|
+
*/
|
|
89
|
+
assignees = [];
|
|
90
|
+
/**
|
|
91
|
+
* Number of comments on the issue
|
|
92
|
+
*/
|
|
93
|
+
commentsCount = 0;
|
|
94
|
+
/**
|
|
95
|
+
* Last sync timestamp
|
|
96
|
+
*/
|
|
97
|
+
lastSyncedAt = null;
|
|
98
|
+
/**
|
|
99
|
+
* Original body before any AI synthesis (for rollback)
|
|
100
|
+
*/
|
|
101
|
+
originalBody = "";
|
|
102
|
+
/**
|
|
103
|
+
* Number of times feedback has been incorporated
|
|
104
|
+
*/
|
|
105
|
+
synthesisCount = 0;
|
|
106
|
+
/**
|
|
107
|
+
* Transient: Cached repository (not persisted)
|
|
108
|
+
* Protected so PullRequest can access it
|
|
109
|
+
*/
|
|
110
|
+
_repository;
|
|
111
|
+
/**
|
|
112
|
+
* Transient: Cached client (not persisted)
|
|
113
|
+
*/
|
|
114
|
+
_client;
|
|
115
|
+
constructor(options = {}) {
|
|
116
|
+
super(options);
|
|
117
|
+
if (options.repositoryId !== void 0) this.repositoryId = options.repositoryId;
|
|
118
|
+
if (options.number !== void 0) this.number = options.number;
|
|
119
|
+
if (options.nodeId !== void 0) this.nodeId = options.nodeId;
|
|
120
|
+
if (options.title !== void 0) this.title = options.title;
|
|
121
|
+
if (options.body !== void 0) this.body = options.body;
|
|
122
|
+
if (options.state !== void 0) this.state = options.state;
|
|
123
|
+
if (options.author !== void 0) this.author = options.author;
|
|
124
|
+
if (options.labels !== void 0) this.labels = options.labels;
|
|
125
|
+
if (options.assignees !== void 0) this.assignees = options.assignees;
|
|
126
|
+
if (options.commentsCount !== void 0) this.commentsCount = options.commentsCount;
|
|
127
|
+
if (options.lastSyncedAt !== void 0) this.lastSyncedAt = options.lastSyncedAt;
|
|
128
|
+
if (options.originalBody !== void 0) this.originalBody = options.originalBody;
|
|
129
|
+
if (options.synthesisCount !== void 0) this.synthesisCount = options.synthesisCount;
|
|
130
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get the repository this issue belongs to
|
|
134
|
+
*/
|
|
135
|
+
async getRepository() {
|
|
136
|
+
if (this._repository) return this._repository;
|
|
137
|
+
if (!this.repositoryId) throw new Error("Issue has no repositoryId set");
|
|
138
|
+
const { RepositoryCollection } = await import("../index.js").then((n) => n.n);
|
|
139
|
+
const repo = await (await RepositoryCollection.create(this.options)).get({ id: this.repositoryId });
|
|
140
|
+
if (!repo) throw new Error(`Repository ${this.repositoryId} not found`);
|
|
141
|
+
this._repository = repo;
|
|
142
|
+
return repo;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get the repository client for API operations
|
|
146
|
+
*/
|
|
147
|
+
async getClient() {
|
|
148
|
+
if (this._client) return this._client;
|
|
149
|
+
const repo = await this.getRepository();
|
|
150
|
+
this._client = await repo.getClient();
|
|
151
|
+
return this._client;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Clear cached repository and client
|
|
155
|
+
*/
|
|
156
|
+
clearCache() {
|
|
157
|
+
this._repository = void 0;
|
|
158
|
+
this._client = void 0;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Sync issue data from the provider
|
|
162
|
+
*
|
|
163
|
+
* @param options - Sync options
|
|
164
|
+
* @returns This issue with updated fields
|
|
165
|
+
*/
|
|
166
|
+
async sync(options = {}) {
|
|
167
|
+
if (!options.force && this.lastSyncedAt && Date.now() - this.lastSyncedAt.getTime() < 3e5) return this;
|
|
168
|
+
const issueData = await (await this.getClient()).getIssue(this.number);
|
|
169
|
+
this.nodeId = issueData.id;
|
|
170
|
+
this.title = issueData.title;
|
|
171
|
+
this.body = issueData.body;
|
|
172
|
+
this.state = issueData.state;
|
|
173
|
+
this.author = issueData.author.login;
|
|
174
|
+
this.labels = issueData.labels.map((l) => l.name);
|
|
175
|
+
this.assignees = issueData.assignees.map((a) => a.login);
|
|
176
|
+
this.commentsCount = issueData.commentsCount;
|
|
177
|
+
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
178
|
+
await this.save();
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get comments on this issue
|
|
183
|
+
*
|
|
184
|
+
* @returns Array of Comment objects (SMRT models)
|
|
185
|
+
*/
|
|
186
|
+
async getComments() {
|
|
187
|
+
const comments = await (await this.getClient()).listComments(this.number);
|
|
188
|
+
const { Comment: CommentClass } = await import("../index.js").then((n) => n.t);
|
|
189
|
+
return comments.map((c) => new CommentClass({
|
|
190
|
+
...this.options,
|
|
191
|
+
issueId: this.id ?? void 0,
|
|
192
|
+
commentId: c.id,
|
|
193
|
+
body: c.body,
|
|
194
|
+
author: c.author.login,
|
|
195
|
+
createdAt: c.createdAt,
|
|
196
|
+
updatedAt: c.updatedAt,
|
|
197
|
+
url: c.url
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Add a comment to this issue
|
|
202
|
+
*
|
|
203
|
+
* @param body - Comment body text
|
|
204
|
+
* @returns Created Comment (SMRT model)
|
|
205
|
+
*/
|
|
206
|
+
async addComment(body) {
|
|
207
|
+
const created = await (await this.getClient()).addComment(this.number, body);
|
|
208
|
+
const { Comment: CommentClass } = await import("../index.js").then((n) => n.t);
|
|
209
|
+
const comment = new CommentClass({
|
|
210
|
+
...this.options,
|
|
211
|
+
issueId: this.id ?? void 0,
|
|
212
|
+
commentId: created.id,
|
|
213
|
+
body: created.body,
|
|
214
|
+
author: created.author.login,
|
|
215
|
+
createdAt: created.createdAt,
|
|
216
|
+
updatedAt: created.updatedAt,
|
|
217
|
+
url: created.url
|
|
218
|
+
});
|
|
219
|
+
await comment.save();
|
|
220
|
+
this.commentsCount++;
|
|
221
|
+
await this.save();
|
|
222
|
+
return comment;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Incorporate feedback from comments into the issue body
|
|
226
|
+
*
|
|
227
|
+
* This is the core "Living Spec" functionality:
|
|
228
|
+
* 1. Reads all comments on the issue
|
|
229
|
+
* 2. Uses AI to synthesize comments with the current body
|
|
230
|
+
* 3. Optionally updates the issue with the synthesized content
|
|
231
|
+
*
|
|
232
|
+
* @param options - Feedback incorporation options
|
|
233
|
+
* @returns Result with synthesized content and status
|
|
234
|
+
*/
|
|
235
|
+
async incorporateFeedback(options = {}) {
|
|
236
|
+
const comments = await this.getComments();
|
|
237
|
+
let relevantComments = comments;
|
|
238
|
+
if (options.since) {
|
|
239
|
+
const sinceDate = options.since;
|
|
240
|
+
relevantComments = comments.filter((c) => c.createdAt && c.createdAt > sinceDate);
|
|
241
|
+
}
|
|
242
|
+
if (relevantComments.length === 0) return {
|
|
243
|
+
synthesized: this.body,
|
|
244
|
+
applied: false,
|
|
245
|
+
commentsAnalyzed: 0
|
|
246
|
+
};
|
|
247
|
+
const resolvedPrompt = await resolvePrompt(issueIncorporateFeedbackPrompt.key, {
|
|
248
|
+
db: this.options.db ?? this.options.persistence,
|
|
249
|
+
tenantId: this.tenantId,
|
|
250
|
+
variables: {
|
|
251
|
+
body: this.body,
|
|
252
|
+
comments: relevantComments.map((c) => `- ${c.author}: ${c.body}`).join("\n")
|
|
253
|
+
},
|
|
254
|
+
override: options.prompt ? { template: options.prompt } : void 0
|
|
255
|
+
});
|
|
256
|
+
const aiOptions = {
|
|
257
|
+
...resolvedPrompt.ai.params,
|
|
258
|
+
...resolvedPrompt.ai.model ? { model: resolvedPrompt.ai.model } : {}
|
|
259
|
+
};
|
|
260
|
+
let synthesized;
|
|
261
|
+
if (resolvedPrompt.ai.provider) synthesized = await this.runPromptWithResolvedAI(resolvedPrompt.text, resolvedPrompt.ai.provider, aiOptions);
|
|
262
|
+
else synthesized = await this.do(resolvedPrompt.text, {
|
|
263
|
+
...aiOptions,
|
|
264
|
+
includeData: false
|
|
265
|
+
});
|
|
266
|
+
const result = {
|
|
267
|
+
synthesized,
|
|
268
|
+
applied: false,
|
|
269
|
+
commentsAnalyzed: relevantComments.length,
|
|
270
|
+
previousBody: this.body
|
|
271
|
+
};
|
|
272
|
+
if (options.apply) {
|
|
273
|
+
if (this.synthesisCount === 0) this.originalBody = this.body;
|
|
274
|
+
await (await this.getClient()).updateIssue(this.number, { body: synthesized });
|
|
275
|
+
this.body = synthesized;
|
|
276
|
+
this.synthesisCount++;
|
|
277
|
+
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
278
|
+
await this.save();
|
|
279
|
+
result.applied = true;
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
async runPromptWithResolvedAI(instructions, provider, options) {
|
|
284
|
+
const aiOption = this.options.ai;
|
|
285
|
+
if (this.isExplicitAiClientOption(aiOption)) {
|
|
286
|
+
const ai2 = await this.getAiClient();
|
|
287
|
+
return this.sendPromptMessage(ai2, instructions, options);
|
|
288
|
+
}
|
|
289
|
+
const globalAiConfig = config.toJSON().ai || {};
|
|
290
|
+
const instanceAiConfig = aiOption ?? {};
|
|
291
|
+
const aiConfig = loadEnvConfig({
|
|
292
|
+
...globalAiConfig,
|
|
293
|
+
...instanceAiConfig
|
|
294
|
+
}, {
|
|
295
|
+
packageName: "ai",
|
|
296
|
+
prefix: "SMRT",
|
|
297
|
+
schema: {
|
|
298
|
+
provider: "string",
|
|
299
|
+
model: "string",
|
|
300
|
+
apiKey: "string",
|
|
301
|
+
timeout: "number",
|
|
302
|
+
maxRetries: "number",
|
|
303
|
+
temperature: "number",
|
|
304
|
+
maxTokens: "number"
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
const env = process.env;
|
|
308
|
+
const apiKey = aiConfig.apiKey || (provider === "anthropic" ? env.ANTHROPIC_API_KEY : provider === "gemini" ? env.GEMINI_API_KEY : env.OPENAI_API_KEY);
|
|
309
|
+
const ai = await getAI({
|
|
310
|
+
...aiConfig,
|
|
311
|
+
provider,
|
|
312
|
+
type: provider,
|
|
313
|
+
model: options.model ?? aiConfig.model,
|
|
314
|
+
defaultModel: options.model ?? aiConfig.model,
|
|
315
|
+
apiKey
|
|
316
|
+
});
|
|
317
|
+
return this.sendPromptMessage(ai, instructions, options);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Sends a fully-resolved instruction string to the given AI client.
|
|
321
|
+
*
|
|
322
|
+
* `incorporateFeedback()` curates the entire prompt (issue body + comments)
|
|
323
|
+
* via the resolved prompt template, so this path deliberately does NOT inject
|
|
324
|
+
* the object's own field data — that would duplicate the body. The `this.do()`
|
|
325
|
+
* fallback path passes `includeData: false` for the same reason, keeping both
|
|
326
|
+
* `incorporateFeedback()` paths consistent (#1567).
|
|
327
|
+
*/
|
|
328
|
+
async sendPromptMessage(ai, instructions, options) {
|
|
329
|
+
const prompt = `--- Beginning of instructions ---
|
|
330
|
+
${instructions}
|
|
331
|
+
--- End of instructions ---
|
|
332
|
+
Based on the content body, please follow the instructions and provide a response. Never make use of codeblocks.`;
|
|
333
|
+
const tools = this.getAvailableTools();
|
|
334
|
+
return await ai.message(prompt, {
|
|
335
|
+
...options,
|
|
336
|
+
tools: tools.length > 0 ? tools : void 0
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
isExplicitAiClientOption(aiOption) {
|
|
340
|
+
return !!(aiOption && typeof aiOption === "object" && typeof aiOption.embed === "function" && !aiOption.provider && !aiOption.type);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Rollback to the original body before AI synthesis
|
|
344
|
+
*
|
|
345
|
+
* @returns Result with success status
|
|
346
|
+
*/
|
|
347
|
+
async rollback() {
|
|
348
|
+
if (!this.originalBody) return {
|
|
349
|
+
success: false,
|
|
350
|
+
message: "No original body to rollback to"
|
|
351
|
+
};
|
|
352
|
+
if (this.synthesisCount === 0) return {
|
|
353
|
+
success: false,
|
|
354
|
+
message: "No synthesis has been applied"
|
|
355
|
+
};
|
|
356
|
+
await (await this.getClient()).updateIssue(this.number, { body: this.originalBody });
|
|
357
|
+
this.body = this.originalBody;
|
|
358
|
+
this.originalBody = "";
|
|
359
|
+
this.synthesisCount = 0;
|
|
360
|
+
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
361
|
+
await this.save();
|
|
362
|
+
return {
|
|
363
|
+
success: true,
|
|
364
|
+
message: "Successfully rolled back to original body"
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* AI-powered: Check if this issue needs review
|
|
369
|
+
*
|
|
370
|
+
* @returns True if the issue likely needs attention
|
|
371
|
+
*/
|
|
372
|
+
async needsReview() {
|
|
373
|
+
return await this.is(`This issue needs review because one or more of the following:
|
|
374
|
+
- It has been open for a long time without updates
|
|
375
|
+
- There are unresolved questions in the comments
|
|
376
|
+
- The requirements are unclear or incomplete
|
|
377
|
+
- There is conflicting feedback that needs resolution`);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* AI-powered: Check if the issue is a bug report
|
|
381
|
+
*/
|
|
382
|
+
async isBugReport() {
|
|
383
|
+
return await this.is("This issue describes a bug, defect, or unexpected behavior");
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* AI-powered: Check if the issue is a feature request
|
|
387
|
+
*/
|
|
388
|
+
async isFeatureRequest() {
|
|
389
|
+
return await this.is("This issue is a feature request or enhancement proposal");
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* AI-powered: Generate suggested labels based on content
|
|
393
|
+
*
|
|
394
|
+
* @returns Array of suggested label names
|
|
395
|
+
*/
|
|
396
|
+
async suggestLabels() {
|
|
397
|
+
return (await this.do(`Based on the issue title and body, suggest appropriate labels.
|
|
398
|
+
Consider:
|
|
399
|
+
- Type: bug, feature, docs, chore, test
|
|
400
|
+
- Priority: P0 (critical), P1 (high), P2 (medium), P3 (low)
|
|
401
|
+
- Area: specific code areas or components
|
|
402
|
+
|
|
403
|
+
Return only a comma-separated list of labels, nothing else.`)).split(",").map((l) => l.trim()).filter(Boolean);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Close this issue
|
|
407
|
+
*/
|
|
408
|
+
async close() {
|
|
409
|
+
await (await this.getClient()).closeIssue(this.number);
|
|
410
|
+
this.state = "closed";
|
|
411
|
+
this.lastSyncedAt = /* @__PURE__ */ new Date();
|
|
412
|
+
await this.save();
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Add labels to this issue
|
|
416
|
+
*
|
|
417
|
+
* @param labels - Label names to add
|
|
418
|
+
*/
|
|
419
|
+
async addLabels(labels) {
|
|
420
|
+
await (await this.getClient()).addLabels(this.number, labels);
|
|
421
|
+
this.labels = [.../* @__PURE__ */ new Set([...this.labels, ...labels])];
|
|
422
|
+
await this.save();
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Remove a label from this issue
|
|
426
|
+
*
|
|
427
|
+
* @param label - Label name to remove
|
|
428
|
+
*/
|
|
429
|
+
async removeLabel(label) {
|
|
430
|
+
await (await this.getClient()).removeLabel(this.number, label);
|
|
431
|
+
this.labels = this.labels.filter((l) => l !== label);
|
|
432
|
+
await this.save();
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Assign users to this issue
|
|
436
|
+
*
|
|
437
|
+
* @param assignees - User logins to assign
|
|
438
|
+
*/
|
|
439
|
+
async assign(assignees) {
|
|
440
|
+
await (await this.getClient()).assignIssue(this.number, assignees);
|
|
441
|
+
this.assignees = [.../* @__PURE__ */ new Set([...this.assignees, ...assignees])];
|
|
442
|
+
await this.save();
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Get issue URL
|
|
446
|
+
*/
|
|
447
|
+
getUrl() {
|
|
448
|
+
const repo = this._repository;
|
|
449
|
+
if (repo) return `https://github.com/${repo.owner}/${repo.name}/issues/${this.number}`;
|
|
450
|
+
return "";
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
__decorateClass([tenantId({ nullable: true })], Issue.prototype, "tenantId", 2);
|
|
454
|
+
__decorateClass([foreignKey("Repository", { required: true })], Issue.prototype, "repositoryId", 2);
|
|
455
|
+
Issue = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
456
|
+
tableStrategy: "sti",
|
|
457
|
+
api: { include: [
|
|
458
|
+
"list",
|
|
459
|
+
"get",
|
|
460
|
+
"create",
|
|
461
|
+
"update"
|
|
462
|
+
] },
|
|
463
|
+
mcp: { include: [
|
|
464
|
+
"list",
|
|
465
|
+
"get",
|
|
466
|
+
"sync",
|
|
467
|
+
"incorporateFeedback"
|
|
468
|
+
] },
|
|
469
|
+
cli: {
|
|
470
|
+
include: [
|
|
471
|
+
"list",
|
|
472
|
+
"get",
|
|
473
|
+
"sync",
|
|
474
|
+
"incorporateFeedback",
|
|
475
|
+
"rollback"
|
|
476
|
+
],
|
|
477
|
+
skipApiCheck: true
|
|
478
|
+
}
|
|
479
|
+
})], Issue);
|
|
480
|
+
//#endregion
|
|
481
|
+
export { __exportAll as i, Issue_exports as n, issueIncorporateFeedbackPrompt as r, Issue as t };
|
|
482
|
+
|
|
483
|
+
//# sourceMappingURL=Issue-DITLxBl7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Issue-DITLxBl7.js","names":["ai"],"sources":["../../src/prompts.ts","../../src/models/Issue.ts"],"sourcesContent":["import { definePrompt } from '@happyvertical/smrt-prompts';\n\nexport const issueIncorporateFeedbackPrompt = definePrompt({\n key: 'projects.issue.incorporateFeedback',\n template: `You are updating a specification document based on team feedback.\n\nCurrent specification:\n{body}\n\nTeam comments and feedback:\n{comments}\n\nInstructions:\n1. Analyze the comments for consensus, changes, and new requirements\n2. Update the specification to reflect the agreed-upon changes\n3. Maintain the original structure and formatting where possible\n4. Mark any conflicting feedback that needs resolution\n5. Return ONLY the updated specification text, no additional commentary`,\n ai: {\n temperature: 0.2,\n },\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n","/**\n * Issue model - SMRT wrapper for issue tracking\n *\n * Provides persistent issue tracking with AI-powered feedback incorporation.\n * Uses @happyvertical/repos SDK for actual API calls.\n */\n\nimport { type AIClientOptions, getAI } from '@happyvertical/ai';\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n config as smrtConfig,\n} from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { loadEnvConfig } from '@happyvertical/utils';\nimport { SYNC_THROTTLE_MS } from '../constants';\nimport { issueIncorporateFeedbackPrompt } from '../prompts';\nimport type {\n IncorporateFeedbackOptions,\n IncorporateFeedbackResult,\n IRepository,\n SDKComment,\n SyncOptions,\n} from '../types';\nimport type { Comment } from './Comment';\nimport type { Repository } from './Repository';\n\n/**\n * Resolved AI configuration produced by {@link loadEnvConfig} for the\n * `incorporateFeedback` synthesis path. Mirrors the schema passed to\n * `loadEnvConfig` (provider/model/apiKey plus tuning knobs).\n */\ninterface ResolvedAiConfig {\n provider?: string;\n model?: string;\n apiKey?: string;\n timeout?: number;\n maxRetries?: number;\n temperature?: number;\n maxTokens?: number;\n}\n\nexport interface IssueOptions extends SmrtObjectOptions {\n repositoryId?: string;\n number?: number;\n nodeId?: string;\n title?: string;\n body?: string;\n state?: 'open' | 'closed';\n author?: string;\n labels?: string[];\n assignees?: string[];\n commentsCount?: number;\n lastSyncedAt?: Date | null;\n originalBody?: string;\n synthesisCount?: number;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get', 'sync', 'incorporateFeedback'] },\n // sync/incorporateFeedback/rollback are operator commands invoked in-process\n // via the CLI; they intentionally aren't exposed over HTTP today.\n cli: {\n include: ['list', 'get', 'sync', 'incorporateFeedback', 'rollback'],\n skipApiCheck: true,\n },\n})\nexport class Issue extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Repository this issue belongs to\n */\n @foreignKey('Repository', { required: true })\n repositoryId?: string;\n\n /**\n * Issue number (provider-specific)\n */\n number: number = 0;\n\n /**\n * Node ID for GraphQL operations (GitHub Projects API)\n */\n nodeId: string = '';\n\n /**\n * Issue title\n */\n title: string = '';\n\n /**\n * Issue body/description\n */\n body: string = '';\n\n /**\n * Issue state\n */\n state: 'open' | 'closed' = 'open';\n\n /**\n * Author's login/username\n */\n author: string = '';\n\n /**\n * Labels attached to the issue\n */\n labels: string[] = [];\n\n /**\n * Assignee logins\n */\n assignees: string[] = [];\n\n /**\n * Number of comments on the issue\n */\n commentsCount: number = 0;\n\n /**\n * Last sync timestamp\n */\n lastSyncedAt: Date | null = null;\n\n /**\n * Original body before any AI synthesis (for rollback)\n */\n originalBody: string = '';\n\n /**\n * Number of times feedback has been incorporated\n */\n synthesisCount: number = 0;\n\n /**\n * Transient: Cached repository (not persisted)\n * Protected so PullRequest can access it\n */\n protected _repository?: Repository;\n\n /**\n * Transient: Cached client (not persisted)\n */\n protected _client?: IRepository;\n\n constructor(options: IssueOptions = {}) {\n super(options);\n if (options.repositoryId !== undefined)\n this.repositoryId = options.repositoryId;\n if (options.number !== undefined) this.number = options.number;\n if (options.nodeId !== undefined) this.nodeId = options.nodeId;\n if (options.title !== undefined) this.title = options.title;\n if (options.body !== undefined) this.body = options.body;\n if (options.state !== undefined) this.state = options.state;\n if (options.author !== undefined) this.author = options.author;\n if (options.labels !== undefined) this.labels = options.labels;\n if (options.assignees !== undefined) this.assignees = options.assignees;\n if (options.commentsCount !== undefined)\n this.commentsCount = options.commentsCount;\n if (options.lastSyncedAt !== undefined)\n this.lastSyncedAt = options.lastSyncedAt;\n if (options.originalBody !== undefined)\n this.originalBody = options.originalBody;\n if (options.synthesisCount !== undefined)\n this.synthesisCount = options.synthesisCount;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n }\n\n /**\n * Get the repository this issue belongs to\n */\n async getRepository(): Promise<Repository> {\n if (this._repository) {\n return this._repository;\n }\n\n if (!this.repositoryId) {\n throw new Error('Issue has no repositoryId set');\n }\n\n const { RepositoryCollection } = await import(\n '../collections/Repositories'\n );\n const collection = await RepositoryCollection.create(this.options);\n const repo = await collection.get({ id: this.repositoryId });\n\n if (!repo) {\n throw new Error(`Repository ${this.repositoryId} not found`);\n }\n\n this._repository = repo;\n return repo;\n }\n\n /**\n * Get the repository client for API operations\n */\n async getClient(): Promise<IRepository> {\n if (this._client) {\n return this._client;\n }\n\n const repo = await this.getRepository();\n this._client = await repo.getClient();\n return this._client;\n }\n\n /**\n * Clear cached repository and client\n */\n clearCache(): void {\n this._repository = undefined;\n this._client = undefined;\n }\n\n /**\n * Sync issue data from the provider\n *\n * @param options - Sync options\n * @returns This issue with updated fields\n */\n async sync(options: SyncOptions = {}): Promise<this> {\n // Check if we recently synced (within 5 minutes)\n if (\n !options.force &&\n this.lastSyncedAt &&\n Date.now() - this.lastSyncedAt.getTime() < SYNC_THROTTLE_MS\n ) {\n return this;\n }\n\n const client = await this.getClient();\n const issueData = await client.getIssue(this.number);\n\n // Update fields from remote\n this.nodeId = issueData.id;\n this.title = issueData.title;\n this.body = issueData.body;\n this.state = issueData.state;\n this.author = issueData.author.login;\n this.labels = issueData.labels.map((l) => l.name);\n this.assignees = issueData.assignees.map((a) => a.login);\n this.commentsCount = issueData.commentsCount;\n this.lastSyncedAt = new Date();\n\n await this.save();\n return this;\n }\n\n /**\n * Get comments on this issue\n *\n * @returns Array of Comment objects (SMRT models)\n */\n async getComments(): Promise<Comment[]> {\n const client = await this.getClient();\n const comments: SDKComment[] = await client.listComments(this.number);\n\n const { Comment: CommentClass } = await import('./Comment');\n return comments.map(\n (c) =>\n new CommentClass({\n ...this.options,\n issueId: this.id ?? undefined,\n commentId: c.id,\n body: c.body,\n author: c.author.login,\n createdAt: c.createdAt,\n updatedAt: c.updatedAt,\n url: c.url,\n }),\n );\n }\n\n /**\n * Add a comment to this issue\n *\n * @param body - Comment body text\n * @returns Created Comment (SMRT model)\n */\n async addComment(body: string): Promise<Comment> {\n const client = await this.getClient();\n const created = await client.addComment(this.number, body);\n\n const { Comment: CommentClass } = await import('./Comment');\n const comment = new CommentClass({\n ...this.options,\n issueId: this.id ?? undefined,\n commentId: created.id,\n body: created.body,\n author: created.author.login,\n createdAt: created.createdAt,\n updatedAt: created.updatedAt,\n url: created.url,\n });\n\n await comment.save();\n this.commentsCount++;\n await this.save();\n\n return comment;\n }\n\n /**\n * Incorporate feedback from comments into the issue body\n *\n * This is the core \"Living Spec\" functionality:\n * 1. Reads all comments on the issue\n * 2. Uses AI to synthesize comments with the current body\n * 3. Optionally updates the issue with the synthesized content\n *\n * @param options - Feedback incorporation options\n * @returns Result with synthesized content and status\n */\n async incorporateFeedback(\n options: IncorporateFeedbackOptions = {},\n ): Promise<IncorporateFeedbackResult> {\n const comments = await this.getComments();\n\n // Filter comments by date if specified\n let relevantComments = comments;\n if (options.since) {\n const sinceDate = options.since;\n relevantComments = comments.filter(\n (c) => c.createdAt && c.createdAt > sinceDate,\n );\n }\n\n if (relevantComments.length === 0) {\n return {\n synthesized: this.body,\n applied: false,\n commentsAnalyzed: 0,\n };\n }\n\n const resolvedPrompt = await resolvePrompt(\n issueIncorporateFeedbackPrompt.key,\n {\n db: this.options.db ?? this.options.persistence,\n tenantId: this.tenantId,\n variables: {\n body: this.body,\n comments: relevantComments\n .map((c) => `- ${c.author}: ${c.body}`)\n .join('\\n'),\n },\n override: options.prompt ? { template: options.prompt } : undefined,\n },\n );\n\n const aiOptions = {\n ...resolvedPrompt.ai.params,\n ...(resolvedPrompt.ai.model ? { model: resolvedPrompt.ai.model } : {}),\n };\n\n let synthesized: string;\n\n if (resolvedPrompt.ai.provider) {\n synthesized = await this.runPromptWithResolvedAI(\n resolvedPrompt.text,\n resolvedPrompt.ai.provider,\n aiOptions,\n );\n } else {\n // The resolved prompt template already curates the issue body + comments,\n // so opt out of do()'s object-data injection to avoid duplicating the body.\n synthesized = await this.do(resolvedPrompt.text, {\n ...aiOptions,\n includeData: false,\n });\n }\n\n const result: IncorporateFeedbackResult = {\n synthesized,\n applied: false,\n commentsAnalyzed: relevantComments.length,\n previousBody: this.body,\n };\n\n // Apply changes if requested\n if (options.apply) {\n // Store original for rollback (only if first synthesis)\n if (this.synthesisCount === 0) {\n this.originalBody = this.body;\n }\n\n // Update the issue body\n const client = await this.getClient();\n await client.updateIssue(this.number, { body: synthesized });\n\n // Update local state\n this.body = synthesized;\n this.synthesisCount++;\n this.lastSyncedAt = new Date();\n await this.save();\n\n result.applied = true;\n }\n\n return result;\n }\n\n private async runPromptWithResolvedAI(\n instructions: string,\n provider: string,\n options: Record<string, unknown>,\n ): Promise<string> {\n const aiOption = this.options.ai as Record<string, unknown> | undefined;\n if (this.isExplicitAiClientOption(aiOption)) {\n const ai = await this.getAiClient();\n return this.sendPromptMessage(ai, instructions, options);\n }\n\n const globalAiConfig = smrtConfig.toJSON().ai || {};\n const instanceAiConfig = aiOption ?? {};\n const aiConfig = loadEnvConfig<ResolvedAiConfig>(\n {\n ...globalAiConfig,\n ...instanceAiConfig,\n },\n {\n packageName: 'ai',\n prefix: 'SMRT',\n schema: {\n provider: 'string',\n model: 'string',\n apiKey: 'string',\n timeout: 'number',\n maxRetries: 'number',\n temperature: 'number',\n maxTokens: 'number',\n },\n },\n );\n\n const env = process.env;\n const apiKey =\n aiConfig.apiKey ||\n (provider === 'anthropic'\n ? env.ANTHROPIC_API_KEY\n : provider === 'gemini'\n ? env.GEMINI_API_KEY\n : env.OPENAI_API_KEY);\n\n const ai = await getAI({\n ...aiConfig,\n provider,\n type: provider,\n model: options.model ?? aiConfig.model,\n defaultModel: options.model ?? aiConfig.model,\n apiKey,\n } as AIClientOptions);\n\n return this.sendPromptMessage(ai, instructions, options);\n }\n\n /**\n * Sends a fully-resolved instruction string to the given AI client.\n *\n * `incorporateFeedback()` curates the entire prompt (issue body + comments)\n * via the resolved prompt template, so this path deliberately does NOT inject\n * the object's own field data — that would duplicate the body. The `this.do()`\n * fallback path passes `includeData: false` for the same reason, keeping both\n * `incorporateFeedback()` paths consistent (#1567).\n */\n private async sendPromptMessage(\n ai: {\n message: (\n prompt: string,\n options?: Record<string, unknown>,\n ) => Promise<string>;\n },\n instructions: string,\n options: Record<string, unknown>,\n ): Promise<string> {\n const prompt = `--- Beginning of instructions ---\\n${instructions}\\n--- End of instructions ---\\nBased on the content body, please follow the instructions and provide a response. Never make use of codeblocks.`;\n const tools = this.getAvailableTools();\n\n return await ai.message(prompt, {\n ...options,\n tools: tools.length > 0 ? tools : undefined,\n });\n }\n\n private isExplicitAiClientOption(\n aiOption: Record<string, unknown> | undefined,\n ): boolean {\n return !!(\n aiOption &&\n typeof aiOption === 'object' &&\n typeof aiOption.embed === 'function' &&\n !aiOption.provider &&\n !aiOption.type\n );\n }\n\n /**\n * Rollback to the original body before AI synthesis\n *\n * @returns Result with success status\n */\n async rollback(): Promise<{ success: boolean; message: string }> {\n if (!this.originalBody) {\n return {\n success: false,\n message: 'No original body to rollback to',\n };\n }\n\n if (this.synthesisCount === 0) {\n return {\n success: false,\n message: 'No synthesis has been applied',\n };\n }\n\n // Update the issue body on the provider\n const client = await this.getClient();\n await client.updateIssue(this.number, { body: this.originalBody });\n\n // Update local state\n this.body = this.originalBody;\n this.originalBody = '';\n this.synthesisCount = 0;\n this.lastSyncedAt = new Date();\n await this.save();\n\n return {\n success: true,\n message: 'Successfully rolled back to original body',\n };\n }\n\n /**\n * AI-powered: Check if this issue needs review\n *\n * @returns True if the issue likely needs attention\n */\n async needsReview(): Promise<boolean> {\n return await this.is(\n `This issue needs review because one or more of the following:\n - It has been open for a long time without updates\n - There are unresolved questions in the comments\n - The requirements are unclear or incomplete\n - There is conflicting feedback that needs resolution`,\n );\n }\n\n /**\n * AI-powered: Check if the issue is a bug report\n */\n async isBugReport(): Promise<boolean> {\n return await this.is(\n 'This issue describes a bug, defect, or unexpected behavior',\n );\n }\n\n /**\n * AI-powered: Check if the issue is a feature request\n */\n async isFeatureRequest(): Promise<boolean> {\n return await this.is(\n 'This issue is a feature request or enhancement proposal',\n );\n }\n\n /**\n * AI-powered: Generate suggested labels based on content\n *\n * @returns Array of suggested label names\n */\n async suggestLabels(): Promise<string[]> {\n const suggestion = await this.do(\n `Based on the issue title and body, suggest appropriate labels.\n Consider:\n - Type: bug, feature, docs, chore, test\n - Priority: P0 (critical), P1 (high), P2 (medium), P3 (low)\n - Area: specific code areas or components\n\n Return only a comma-separated list of labels, nothing else.`,\n );\n\n return suggestion\n .split(',')\n .map((l) => l.trim())\n .filter(Boolean);\n }\n\n /**\n * Close this issue\n */\n async close(): Promise<void> {\n const client = await this.getClient();\n await client.closeIssue(this.number);\n this.state = 'closed';\n this.lastSyncedAt = new Date();\n await this.save();\n }\n\n /**\n * Add labels to this issue\n *\n * @param labels - Label names to add\n */\n async addLabels(labels: string[]): Promise<void> {\n const client = await this.getClient();\n await client.addLabels(this.number, labels);\n this.labels = [...new Set([...this.labels, ...labels])];\n await this.save();\n }\n\n /**\n * Remove a label from this issue\n *\n * @param label - Label name to remove\n */\n async removeLabel(label: string): Promise<void> {\n const client = await this.getClient();\n await client.removeLabel(this.number, label);\n this.labels = this.labels.filter((l) => l !== label);\n await this.save();\n }\n\n /**\n * Assign users to this issue\n *\n * @param assignees - User logins to assign\n */\n async assign(assignees: string[]): Promise<void> {\n const client = await this.getClient();\n await client.assignIssue(this.number, assignees);\n this.assignees = [...new Set([...this.assignees, ...assignees])];\n await this.save();\n }\n\n /**\n * Get issue URL\n */\n getUrl(): string {\n const repo = this._repository;\n if (repo) {\n return `https://github.com/${repo.owner}/${repo.name}/issues/${this.number}`;\n }\n return '';\n }\n}\n"],"mappings":"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"}
|