kiro-memory 1.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +5 -1
  2. package/package.json +5 -5
  3. package/plugin/dist/cli/contextkit.js +2611 -345
  4. package/plugin/dist/hooks/agentSpawn.js +853 -223
  5. package/plugin/dist/hooks/kiro-hooks.js +841 -211
  6. package/plugin/dist/hooks/postToolUse.js +853 -222
  7. package/plugin/dist/hooks/stop.js +850 -220
  8. package/plugin/dist/hooks/userPromptSubmit.js +848 -216
  9. package/plugin/dist/index.js +843 -340
  10. package/plugin/dist/plugins/github/github-client.js +152 -0
  11. package/plugin/dist/plugins/github/index.js +412 -0
  12. package/plugin/dist/plugins/github/issue-parser.js +54 -0
  13. package/plugin/dist/plugins/slack/formatter.js +90 -0
  14. package/plugin/dist/plugins/slack/index.js +215 -0
  15. package/plugin/dist/sdk/index.js +841 -215
  16. package/plugin/dist/servers/mcp-server.js +4461 -397
  17. package/plugin/dist/services/search/EmbeddingService.js +146 -37
  18. package/plugin/dist/services/search/HybridSearch.js +564 -116
  19. package/plugin/dist/services/search/VectorSearch.js +187 -60
  20. package/plugin/dist/services/search/index.js +565 -254
  21. package/plugin/dist/services/sqlite/Backup.js +416 -0
  22. package/plugin/dist/services/sqlite/Database.js +126 -153
  23. package/plugin/dist/services/sqlite/ImportExport.js +452 -0
  24. package/plugin/dist/services/sqlite/Observations.js +314 -19
  25. package/plugin/dist/services/sqlite/Prompts.js +1 -1
  26. package/plugin/dist/services/sqlite/Search.js +41 -29
  27. package/plugin/dist/services/sqlite/Summaries.js +4 -4
  28. package/plugin/dist/services/sqlite/index.js +1428 -208
  29. package/plugin/dist/viewer.css +1 -0
  30. package/plugin/dist/viewer.html +2 -179
  31. package/plugin/dist/viewer.js +23 -24942
  32. package/plugin/dist/viewer.js.map +7 -0
  33. package/plugin/dist/worker-service.js +427 -5569
  34. package/plugin/dist/worker-service.js.map +7 -0
@@ -0,0 +1,152 @@
1
+ import { createRequire } from 'module';const require = createRequire(import.meta.url);
2
+
3
+ // src/plugins/github/github-client.ts
4
+ var GitHubClient = class {
5
+ token;
6
+ baseUrl;
7
+ maxRetries;
8
+ cacheTtlMs;
9
+ logger;
10
+ /** Cache in-memory per le issue (chiave: owner/repo#number) */
11
+ issueCache = /* @__PURE__ */ new Map();
12
+ /** Ultimo stato rate limit noto */
13
+ rateLimit = { remaining: 5e3, resetAt: 0 };
14
+ constructor(config, logger) {
15
+ this.token = config.token;
16
+ this.baseUrl = (config.baseUrl || "https://api.github.com").replace(/\/$/, "");
17
+ this.maxRetries = config.maxRetries ?? 3;
18
+ this.cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1e3;
19
+ this.logger = logger;
20
+ }
21
+ // ── Metodi pubblici ──────────────────────────────────────────────────────
22
+ /**
23
+ * Recupera i dati di una issue specifica.
24
+ * Usa cache in-memory per evitare chiamate ripetute.
25
+ */
26
+ async getIssue(owner, repo, issueNumber) {
27
+ const cacheKey = `${owner}/${repo}#${issueNumber}`;
28
+ const cached = this.issueCache.get(cacheKey);
29
+ if (cached && cached.expiresAt > Date.now()) {
30
+ this.logger.info(`Cache hit per issue ${cacheKey}`);
31
+ return cached.data;
32
+ }
33
+ const url = `${this.baseUrl}/repos/${owner}/${repo}/issues/${issueNumber}`;
34
+ const data = await this.request(url);
35
+ this.issueCache.set(cacheKey, {
36
+ data,
37
+ expiresAt: Date.now() + this.cacheTtlMs
38
+ });
39
+ return data;
40
+ }
41
+ /**
42
+ * Aggiunge un commento a una issue.
43
+ * Non usa cache (operazione di scrittura).
44
+ */
45
+ async addComment(owner, repo, issueNumber, body) {
46
+ const url = `${this.baseUrl}/repos/${owner}/${repo}/issues/${issueNumber}/comments`;
47
+ return this.request(url, {
48
+ method: "POST",
49
+ body: JSON.stringify({ body })
50
+ });
51
+ }
52
+ /**
53
+ * Restituisce le informazioni correnti sul rate limit.
54
+ */
55
+ getRateLimit() {
56
+ return { ...this.rateLimit };
57
+ }
58
+ /**
59
+ * Svuota la cache delle issue.
60
+ */
61
+ clearCache() {
62
+ this.issueCache.clear();
63
+ this.logger.info("Cache issue svuotata");
64
+ }
65
+ // ── Request engine con retry ─────────────────────────────────────────────
66
+ /**
67
+ * Esegue una richiesta HTTP verso le GitHub API.
68
+ * Gestisce automaticamente:
69
+ * - Headers di autenticazione
70
+ * - Parsing della risposta JSON
71
+ * - Aggiornamento rate limit
72
+ * - Retry su 403/429 con backoff esponenziale
73
+ */
74
+ async request(url, options = {}) {
75
+ const headers = {
76
+ "Authorization": `token ${this.token}`,
77
+ "Accept": "application/vnd.github.v3+json",
78
+ "Content-Type": "application/json",
79
+ "User-Agent": "kiro-memory-plugin-github/1.0.0"
80
+ };
81
+ let lastError = null;
82
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
83
+ if (this.rateLimit.remaining === 0) {
84
+ const waitMs = this.rateLimit.resetAt * 1e3 - Date.now();
85
+ if (waitMs > 0 && waitMs < 6e4) {
86
+ this.logger.warn(`Rate limit esaurito. Attendo ${Math.ceil(waitMs / 1e3)}s prima del reset.`);
87
+ await this.sleep(waitMs);
88
+ }
89
+ }
90
+ try {
91
+ const response = await fetch(url, {
92
+ ...options,
93
+ headers: { ...headers, ...options.headers || {} }
94
+ });
95
+ this.updateRateLimit(response);
96
+ if (response.ok) {
97
+ return await response.json();
98
+ }
99
+ if (response.status === 404) {
100
+ throw new Error(`Risorsa non trovata: ${url}`);
101
+ }
102
+ if (response.status === 401) {
103
+ throw new Error("Token GitHub non valido o scaduto");
104
+ }
105
+ if ((response.status === 403 || response.status === 429) && attempt < this.maxRetries) {
106
+ const retryAfter = response.headers.get("retry-after");
107
+ const backoffMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 3e4);
108
+ this.logger.warn(`Rate limit (${response.status}). Retry ${attempt + 1}/${this.maxRetries} tra ${Math.ceil(backoffMs / 1e3)}s`);
109
+ await this.sleep(backoffMs);
110
+ continue;
111
+ }
112
+ const errorBody = await response.text().catch(() => "corpo non leggibile");
113
+ throw new Error(`GitHub API errore ${response.status}: ${errorBody}`);
114
+ } catch (err) {
115
+ lastError = err instanceof Error ? err : new Error(String(err));
116
+ if (lastError.message.includes("non trovata") || lastError.message.includes("non valido") || lastError.message.includes("scaduto")) {
117
+ throw lastError;
118
+ }
119
+ if (attempt < this.maxRetries) {
120
+ const backoffMs = Math.min(1e3 * Math.pow(2, attempt), 3e4);
121
+ this.logger.warn(`Errore di rete, retry ${attempt + 1}/${this.maxRetries}: ${lastError.message}`);
122
+ await this.sleep(backoffMs);
123
+ continue;
124
+ }
125
+ }
126
+ }
127
+ throw lastError || new Error(`Richiesta fallita dopo ${this.maxRetries} tentativi`);
128
+ }
129
+ // ── Helpers ──────────────────────────────────────────────────────────────
130
+ /**
131
+ * Aggiorna le informazioni di rate limit dalla risposta HTTP.
132
+ */
133
+ updateRateLimit(response) {
134
+ const remaining = response.headers.get("x-ratelimit-remaining");
135
+ const reset = response.headers.get("x-ratelimit-reset");
136
+ if (remaining !== null) {
137
+ this.rateLimit.remaining = parseInt(remaining, 10);
138
+ }
139
+ if (reset !== null) {
140
+ this.rateLimit.resetAt = parseInt(reset, 10);
141
+ }
142
+ }
143
+ /**
144
+ * Utility per await su un delay (usato per backoff e attesa rate limit).
145
+ */
146
+ sleep(ms) {
147
+ return new Promise((resolve) => setTimeout(resolve, ms));
148
+ }
149
+ };
150
+ export {
151
+ GitHubClient
152
+ };
@@ -0,0 +1,412 @@
1
+ import { createRequire } from 'module';const require = createRequire(import.meta.url);
2
+
3
+ // src/plugins/github/github-client.ts
4
+ var GitHubClient = class {
5
+ token;
6
+ baseUrl;
7
+ maxRetries;
8
+ cacheTtlMs;
9
+ logger;
10
+ /** Cache in-memory per le issue (chiave: owner/repo#number) */
11
+ issueCache = /* @__PURE__ */ new Map();
12
+ /** Ultimo stato rate limit noto */
13
+ rateLimit = { remaining: 5e3, resetAt: 0 };
14
+ constructor(config, logger) {
15
+ this.token = config.token;
16
+ this.baseUrl = (config.baseUrl || "https://api.github.com").replace(/\/$/, "");
17
+ this.maxRetries = config.maxRetries ?? 3;
18
+ this.cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1e3;
19
+ this.logger = logger;
20
+ }
21
+ // ── Metodi pubblici ──────────────────────────────────────────────────────
22
+ /**
23
+ * Recupera i dati di una issue specifica.
24
+ * Usa cache in-memory per evitare chiamate ripetute.
25
+ */
26
+ async getIssue(owner, repo, issueNumber) {
27
+ const cacheKey = `${owner}/${repo}#${issueNumber}`;
28
+ const cached = this.issueCache.get(cacheKey);
29
+ if (cached && cached.expiresAt > Date.now()) {
30
+ this.logger.info(`Cache hit per issue ${cacheKey}`);
31
+ return cached.data;
32
+ }
33
+ const url = `${this.baseUrl}/repos/${owner}/${repo}/issues/${issueNumber}`;
34
+ const data = await this.request(url);
35
+ this.issueCache.set(cacheKey, {
36
+ data,
37
+ expiresAt: Date.now() + this.cacheTtlMs
38
+ });
39
+ return data;
40
+ }
41
+ /**
42
+ * Aggiunge un commento a una issue.
43
+ * Non usa cache (operazione di scrittura).
44
+ */
45
+ async addComment(owner, repo, issueNumber, body) {
46
+ const url = `${this.baseUrl}/repos/${owner}/${repo}/issues/${issueNumber}/comments`;
47
+ return this.request(url, {
48
+ method: "POST",
49
+ body: JSON.stringify({ body })
50
+ });
51
+ }
52
+ /**
53
+ * Restituisce le informazioni correnti sul rate limit.
54
+ */
55
+ getRateLimit() {
56
+ return { ...this.rateLimit };
57
+ }
58
+ /**
59
+ * Svuota la cache delle issue.
60
+ */
61
+ clearCache() {
62
+ this.issueCache.clear();
63
+ this.logger.info("Cache issue svuotata");
64
+ }
65
+ // ── Request engine con retry ─────────────────────────────────────────────
66
+ /**
67
+ * Esegue una richiesta HTTP verso le GitHub API.
68
+ * Gestisce automaticamente:
69
+ * - Headers di autenticazione
70
+ * - Parsing della risposta JSON
71
+ * - Aggiornamento rate limit
72
+ * - Retry su 403/429 con backoff esponenziale
73
+ */
74
+ async request(url, options = {}) {
75
+ const headers = {
76
+ "Authorization": `token ${this.token}`,
77
+ "Accept": "application/vnd.github.v3+json",
78
+ "Content-Type": "application/json",
79
+ "User-Agent": "kiro-memory-plugin-github/1.0.0"
80
+ };
81
+ let lastError = null;
82
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
83
+ if (this.rateLimit.remaining === 0) {
84
+ const waitMs = this.rateLimit.resetAt * 1e3 - Date.now();
85
+ if (waitMs > 0 && waitMs < 6e4) {
86
+ this.logger.warn(`Rate limit esaurito. Attendo ${Math.ceil(waitMs / 1e3)}s prima del reset.`);
87
+ await this.sleep(waitMs);
88
+ }
89
+ }
90
+ try {
91
+ const response = await fetch(url, {
92
+ ...options,
93
+ headers: { ...headers, ...options.headers || {} }
94
+ });
95
+ this.updateRateLimit(response);
96
+ if (response.ok) {
97
+ return await response.json();
98
+ }
99
+ if (response.status === 404) {
100
+ throw new Error(`Risorsa non trovata: ${url}`);
101
+ }
102
+ if (response.status === 401) {
103
+ throw new Error("Token GitHub non valido o scaduto");
104
+ }
105
+ if ((response.status === 403 || response.status === 429) && attempt < this.maxRetries) {
106
+ const retryAfter = response.headers.get("retry-after");
107
+ const backoffMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 3e4);
108
+ this.logger.warn(`Rate limit (${response.status}). Retry ${attempt + 1}/${this.maxRetries} tra ${Math.ceil(backoffMs / 1e3)}s`);
109
+ await this.sleep(backoffMs);
110
+ continue;
111
+ }
112
+ const errorBody = await response.text().catch(() => "corpo non leggibile");
113
+ throw new Error(`GitHub API errore ${response.status}: ${errorBody}`);
114
+ } catch (err) {
115
+ lastError = err instanceof Error ? err : new Error(String(err));
116
+ if (lastError.message.includes("non trovata") || lastError.message.includes("non valido") || lastError.message.includes("scaduto")) {
117
+ throw lastError;
118
+ }
119
+ if (attempt < this.maxRetries) {
120
+ const backoffMs = Math.min(1e3 * Math.pow(2, attempt), 3e4);
121
+ this.logger.warn(`Errore di rete, retry ${attempt + 1}/${this.maxRetries}: ${lastError.message}`);
122
+ await this.sleep(backoffMs);
123
+ continue;
124
+ }
125
+ }
126
+ }
127
+ throw lastError || new Error(`Richiesta fallita dopo ${this.maxRetries} tentativi`);
128
+ }
129
+ // ── Helpers ──────────────────────────────────────────────────────────────
130
+ /**
131
+ * Aggiorna le informazioni di rate limit dalla risposta HTTP.
132
+ */
133
+ updateRateLimit(response) {
134
+ const remaining = response.headers.get("x-ratelimit-remaining");
135
+ const reset = response.headers.get("x-ratelimit-reset");
136
+ if (remaining !== null) {
137
+ this.rateLimit.remaining = parseInt(remaining, 10);
138
+ }
139
+ if (reset !== null) {
140
+ this.rateLimit.resetAt = parseInt(reset, 10);
141
+ }
142
+ }
143
+ /**
144
+ * Utility per await su un delay (usato per backoff e attesa rate limit).
145
+ */
146
+ sleep(ms) {
147
+ return new Promise((resolve) => setTimeout(resolve, ms));
148
+ }
149
+ };
150
+
151
+ // src/plugins/github/issue-parser.ts
152
+ var KEYWORD_PATTERN = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(?:([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+))?#(\d+)\b/gi;
153
+ var FULL_REF_PATTERN = /(?:^|[\s,(])([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)#(\d+)\b/g;
154
+ var STANDALONE_PATTERN = /(?:^|[\s,(:])#(\d+)\b/g;
155
+ function parseIssueReferences(text) {
156
+ if (!text || typeof text !== "string") return [];
157
+ const refs = /* @__PURE__ */ new Map();
158
+ let match;
159
+ KEYWORD_PATTERN.lastIndex = 0;
160
+ while ((match = KEYWORD_PATTERN.exec(text)) !== null) {
161
+ const owner = match[1] || void 0;
162
+ const repo = match[2] || void 0;
163
+ const number = parseInt(match[3], 10);
164
+ const keyword = match[0].split(/\s/)[0].toLowerCase();
165
+ const key = makeKey(owner, repo, number);
166
+ refs.set(key, { number, owner, repo, keyword });
167
+ }
168
+ FULL_REF_PATTERN.lastIndex = 0;
169
+ while ((match = FULL_REF_PATTERN.exec(text)) !== null) {
170
+ const owner = match[1];
171
+ const repo = match[2];
172
+ const number = parseInt(match[3], 10);
173
+ const key = makeKey(owner, repo, number);
174
+ if (!refs.has(key)) {
175
+ refs.set(key, { number, owner, repo });
176
+ }
177
+ }
178
+ STANDALONE_PATTERN.lastIndex = 0;
179
+ while ((match = STANDALONE_PATTERN.exec(text)) !== null) {
180
+ const number = parseInt(match[1], 10);
181
+ const key = makeKey(void 0, void 0, number);
182
+ if (!refs.has(key) && !hasRefWithNumber(refs, number)) {
183
+ refs.set(key, { number });
184
+ }
185
+ }
186
+ return Array.from(refs.values());
187
+ }
188
+ function makeKey(owner, repo, number) {
189
+ if (owner && repo) {
190
+ return `${owner}/${repo}#${number}`;
191
+ }
192
+ return `#${number}`;
193
+ }
194
+ function hasRefWithNumber(refs, number) {
195
+ for (const ref of refs.values()) {
196
+ if (ref.number === number) return true;
197
+ }
198
+ return false;
199
+ }
200
+
201
+ // src/plugins/github/index.ts
202
+ var GitHubPlugin = class {
203
+ name = "kiro-memory-plugin-github";
204
+ version = "1.0.0";
205
+ description = "Integrazione GitHub: rileva issue references e commenta a fine sessione";
206
+ minKiroVersion = "2.0.0";
207
+ /** Client HTTP per le GitHub API */
208
+ client = null;
209
+ /** Logger iniettato dal registry */
210
+ logger = null;
211
+ /** Configurazione validata */
212
+ config = null;
213
+ /** Mappa issue linkate nella sessione corrente (chiave: "owner/repo#number") */
214
+ linkedIssues = /* @__PURE__ */ new Map();
215
+ /** Hook esposti al registry */
216
+ hooks = {
217
+ onObservation: async (obs) => this.handleObservation(obs),
218
+ onSessionEnd: async (session) => this.handleSessionEnd(session)
219
+ };
220
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
221
+ async init(context) {
222
+ this.logger = context.logger;
223
+ this.config = this.parseConfig(context.config);
224
+ if (!this.config.token) {
225
+ throw new Error('Configurazione mancante: "token" \xE8 obbligatorio per il plugin GitHub');
226
+ }
227
+ this.client = new GitHubClient(
228
+ {
229
+ token: this.config.token,
230
+ baseUrl: this.config.baseUrl
231
+ },
232
+ this.logger
233
+ );
234
+ this.logger.info("Plugin GitHub inizializzato");
235
+ if (this.config.repo) {
236
+ this.logger.info(`Repository default: ${this.config.repo}`);
237
+ }
238
+ }
239
+ async destroy() {
240
+ this.client?.clearCache();
241
+ this.linkedIssues.clear();
242
+ this.client = null;
243
+ this.config = null;
244
+ this.logger?.info("Plugin GitHub distrutto");
245
+ this.logger = null;
246
+ }
247
+ // ── Hook: onObservation ────────────────────────────────────────────────────
248
+ /**
249
+ * Rileva riferimenti issue nel titolo dell'osservazione.
250
+ * Accumula le issue trovate per il commento a fine sessione.
251
+ */
252
+ async handleObservation(obs) {
253
+ if (!this.client || !this.config) return;
254
+ const refs = parseIssueReferences(obs.title);
255
+ if (refs.length === 0) return;
256
+ this.logger?.info(`Trovati ${refs.length} riferimenti issue in osservazione "${obs.title}"`);
257
+ for (const ref of refs) {
258
+ this.trackIssue(ref, obs.title);
259
+ }
260
+ }
261
+ // ── Hook: onSessionEnd ─────────────────────────────────────────────────────
262
+ /**
263
+ * Commenta automaticamente sulle issue linkate con un riepilogo della sessione.
264
+ * Chiamato alla chiusura di ogni sessione.
265
+ */
266
+ async handleSessionEnd(session) {
267
+ if (!this.client || !this.config) return;
268
+ if (this.config.autoComment === false) return;
269
+ if (this.linkedIssues.size === 0) return;
270
+ if (!session.summary) {
271
+ this.logger?.info("Nessun summary di sessione, skip commento su issue");
272
+ return;
273
+ }
274
+ this.logger?.info(`Fine sessione: commento su ${this.linkedIssues.size} issue linkate`);
275
+ const issues = Array.from(this.linkedIssues.values());
276
+ const results = await Promise.allSettled(
277
+ issues.map((issue) => this.commentOnIssue(issue, session))
278
+ );
279
+ let successi = 0;
280
+ let errori = 0;
281
+ for (const result of results) {
282
+ if (result.status === "fulfilled") {
283
+ successi++;
284
+ } else {
285
+ errori++;
286
+ this.logger?.warn(`Errore commento su issue: ${result.reason}`);
287
+ }
288
+ }
289
+ this.logger?.info(`Commenti: ${successi} riusciti, ${errori} falliti`);
290
+ this.linkedIssues.clear();
291
+ }
292
+ // ── Metodi privati ─────────────────────────────────────────────────────────
293
+ /**
294
+ * Traccia una issue reference trovata in un'osservazione.
295
+ * Se la issue è già tracciata, aggiunge il titolo dell'osservazione.
296
+ */
297
+ trackIssue(ref, observationTitle) {
298
+ const { owner, repo } = this.resolveOwnerRepo(ref);
299
+ if (!owner || !repo) {
300
+ this.logger?.warn(`Impossibile risolvere owner/repo per issue #${ref.number} \u2014 configura "repo" nel plugin`);
301
+ return;
302
+ }
303
+ const key = `${owner}/${repo}#${ref.number}`;
304
+ const existing = this.linkedIssues.get(key);
305
+ if (existing) {
306
+ existing.observationTitles.push(observationTitle);
307
+ if (ref.keyword) existing.keywords.add(ref.keyword);
308
+ } else {
309
+ this.linkedIssues.set(key, {
310
+ number: ref.number,
311
+ owner,
312
+ repo,
313
+ observationTitles: [observationTitle],
314
+ keywords: new Set(ref.keyword ? [ref.keyword] : [])
315
+ });
316
+ }
317
+ }
318
+ /**
319
+ * Risolve owner e repo da un IssueReference.
320
+ * Usa il reference se specifico, altrimenti il default dalla configurazione.
321
+ */
322
+ resolveOwnerRepo(ref) {
323
+ if (ref.owner && ref.repo) {
324
+ return { owner: ref.owner, repo: ref.repo };
325
+ }
326
+ if (this.config?.repo) {
327
+ const parts = this.config.repo.split("/");
328
+ if (parts.length === 2) {
329
+ return { owner: parts[0], repo: parts[1] };
330
+ }
331
+ }
332
+ return {};
333
+ }
334
+ /**
335
+ * Genera il corpo del commento e lo posta sulla issue.
336
+ */
337
+ async commentOnIssue(issue, session) {
338
+ if (!this.client || !session.summary) return;
339
+ const body = this.formatComment(issue, session);
340
+ await this.client.addComment(
341
+ issue.owner,
342
+ issue.repo,
343
+ issue.number,
344
+ body
345
+ );
346
+ this.logger?.info(`Commento postato su ${issue.owner}/${issue.repo}#${issue.number}`);
347
+ }
348
+ /**
349
+ * Formatta il corpo del commento per una issue.
350
+ */
351
+ formatComment(issue, session) {
352
+ const lines = [];
353
+ lines.push(`### \u{1F9E0} Kiro Memory \u2014 Sessione \`${session.id}\``);
354
+ lines.push("");
355
+ lines.push(`**Progetto:** ${session.project}`);
356
+ lines.push("");
357
+ if (issue.keywords.size > 0) {
358
+ const keywords = Array.from(issue.keywords).join(", ");
359
+ lines.push(`**Azioni:** ${keywords}`);
360
+ lines.push("");
361
+ }
362
+ if (issue.observationTitles.length > 0) {
363
+ lines.push("**Osservazioni correlate:**");
364
+ for (const title of issue.observationTitles.slice(0, 10)) {
365
+ lines.push(`- ${title}`);
366
+ }
367
+ if (issue.observationTitles.length > 10) {
368
+ lines.push(`- _...e altre ${issue.observationTitles.length - 10}_`);
369
+ }
370
+ lines.push("");
371
+ }
372
+ lines.push("**Riepilogo sessione:**");
373
+ lines.push(session.summary || "_Nessun riepilogo disponibile_");
374
+ lines.push("");
375
+ lines.push("---");
376
+ lines.push("_Commento generato automaticamente da [kiro-memory](https://github.com/Auriti-Labs/kiro-memory)_");
377
+ return lines.join("\n");
378
+ }
379
+ /**
380
+ * Parsa la configurazione grezza del plugin.
381
+ * Valida i campi e restituisce un oggetto tipizzato.
382
+ */
383
+ parseConfig(raw) {
384
+ return {
385
+ token: typeof raw["token"] === "string" ? raw["token"] : "",
386
+ repo: typeof raw["repo"] === "string" ? raw["repo"] : void 0,
387
+ baseUrl: typeof raw["baseUrl"] === "string" ? raw["baseUrl"] : void 0,
388
+ autoComment: raw["autoComment"] !== false
389
+ // default: true
390
+ };
391
+ }
392
+ // ── Metodi esposti per testing ─────────────────────────────────────────────
393
+ /**
394
+ * Restituisce le issue attualmente tracciate nella sessione.
395
+ * @internal Usato solo nei test.
396
+ */
397
+ _getLinkedIssues() {
398
+ return this.linkedIssues;
399
+ }
400
+ /**
401
+ * Restituisce il client HTTP interno.
402
+ * @internal Usato solo nei test.
403
+ */
404
+ _getClient() {
405
+ return this.client;
406
+ }
407
+ };
408
+ var index_default = new GitHubPlugin();
409
+ export {
410
+ GitHubPlugin,
411
+ index_default as default
412
+ };
@@ -0,0 +1,54 @@
1
+ import { createRequire } from 'module';const require = createRequire(import.meta.url);
2
+
3
+ // src/plugins/github/issue-parser.ts
4
+ var KEYWORD_PATTERN = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(?:([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+))?#(\d+)\b/gi;
5
+ var FULL_REF_PATTERN = /(?:^|[\s,(])([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)#(\d+)\b/g;
6
+ var STANDALONE_PATTERN = /(?:^|[\s,(:])#(\d+)\b/g;
7
+ function parseIssueReferences(text) {
8
+ if (!text || typeof text !== "string") return [];
9
+ const refs = /* @__PURE__ */ new Map();
10
+ let match;
11
+ KEYWORD_PATTERN.lastIndex = 0;
12
+ while ((match = KEYWORD_PATTERN.exec(text)) !== null) {
13
+ const owner = match[1] || void 0;
14
+ const repo = match[2] || void 0;
15
+ const number = parseInt(match[3], 10);
16
+ const keyword = match[0].split(/\s/)[0].toLowerCase();
17
+ const key = makeKey(owner, repo, number);
18
+ refs.set(key, { number, owner, repo, keyword });
19
+ }
20
+ FULL_REF_PATTERN.lastIndex = 0;
21
+ while ((match = FULL_REF_PATTERN.exec(text)) !== null) {
22
+ const owner = match[1];
23
+ const repo = match[2];
24
+ const number = parseInt(match[3], 10);
25
+ const key = makeKey(owner, repo, number);
26
+ if (!refs.has(key)) {
27
+ refs.set(key, { number, owner, repo });
28
+ }
29
+ }
30
+ STANDALONE_PATTERN.lastIndex = 0;
31
+ while ((match = STANDALONE_PATTERN.exec(text)) !== null) {
32
+ const number = parseInt(match[1], 10);
33
+ const key = makeKey(void 0, void 0, number);
34
+ if (!refs.has(key) && !hasRefWithNumber(refs, number)) {
35
+ refs.set(key, { number });
36
+ }
37
+ }
38
+ return Array.from(refs.values());
39
+ }
40
+ function makeKey(owner, repo, number) {
41
+ if (owner && repo) {
42
+ return `${owner}/${repo}#${number}`;
43
+ }
44
+ return `#${number}`;
45
+ }
46
+ function hasRefWithNumber(refs, number) {
47
+ for (const ref of refs.values()) {
48
+ if (ref.number === number) return true;
49
+ }
50
+ return false;
51
+ }
52
+ export {
53
+ parseIssueReferences
54
+ };