hippo-memory 1.2.1 → 1.3.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 (97) hide show
  1. package/README.md +9 -0
  2. package/dist/cli.js +10 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/connectors/github/backfill.d.ts +48 -0
  5. package/dist/connectors/github/backfill.d.ts.map +1 -0
  6. package/dist/connectors/github/backfill.js +257 -0
  7. package/dist/connectors/github/backfill.js.map +1 -0
  8. package/dist/connectors/github/cli-impl.d.ts +24 -0
  9. package/dist/connectors/github/cli-impl.d.ts.map +1 -0
  10. package/dist/connectors/github/cli-impl.js +152 -0
  11. package/dist/connectors/github/cli-impl.js.map +1 -0
  12. package/dist/connectors/github/deletion.d.ts +38 -0
  13. package/dist/connectors/github/deletion.d.ts.map +1 -0
  14. package/dist/connectors/github/deletion.js +78 -0
  15. package/dist/connectors/github/deletion.js.map +1 -0
  16. package/dist/connectors/github/dlq.d.ts +101 -0
  17. package/dist/connectors/github/dlq.d.ts.map +1 -0
  18. package/dist/connectors/github/dlq.js +181 -0
  19. package/dist/connectors/github/dlq.js.map +1 -0
  20. package/dist/connectors/github/idempotency.d.ts +19 -0
  21. package/dist/connectors/github/idempotency.d.ts.map +1 -0
  22. package/dist/connectors/github/idempotency.js +25 -0
  23. package/dist/connectors/github/idempotency.js.map +1 -0
  24. package/dist/connectors/github/ingest.d.ts +67 -0
  25. package/dist/connectors/github/ingest.d.ts.map +1 -0
  26. package/dist/connectors/github/ingest.js +107 -0
  27. package/dist/connectors/github/ingest.js.map +1 -0
  28. package/dist/connectors/github/octokit-client.d.ts +36 -0
  29. package/dist/connectors/github/octokit-client.d.ts.map +1 -0
  30. package/dist/connectors/github/octokit-client.js +65 -0
  31. package/dist/connectors/github/octokit-client.js.map +1 -0
  32. package/dist/connectors/github/ratelimit.d.ts +20 -0
  33. package/dist/connectors/github/ratelimit.d.ts.map +1 -0
  34. package/dist/connectors/github/ratelimit.js +31 -0
  35. package/dist/connectors/github/ratelimit.js.map +1 -0
  36. package/dist/connectors/github/scope.d.ts +8 -0
  37. package/dist/connectors/github/scope.d.ts.map +1 -0
  38. package/dist/connectors/github/scope.js +13 -0
  39. package/dist/connectors/github/scope.js.map +1 -0
  40. package/dist/connectors/github/signature.d.ts +24 -0
  41. package/dist/connectors/github/signature.d.ts.map +1 -0
  42. package/dist/connectors/github/signature.js +35 -0
  43. package/dist/connectors/github/signature.js.map +1 -0
  44. package/dist/connectors/github/tenant-routing.d.ts +33 -0
  45. package/dist/connectors/github/tenant-routing.d.ts.map +1 -0
  46. package/dist/connectors/github/tenant-routing.js +61 -0
  47. package/dist/connectors/github/tenant-routing.js.map +1 -0
  48. package/dist/connectors/github/transform.d.ts +7 -0
  49. package/dist/connectors/github/transform.d.ts.map +1 -0
  50. package/dist/connectors/github/transform.js +103 -0
  51. package/dist/connectors/github/transform.js.map +1 -0
  52. package/dist/connectors/github/types.d.ts +87 -0
  53. package/dist/connectors/github/types.d.ts.map +1 -0
  54. package/dist/connectors/github/types.js +94 -0
  55. package/dist/connectors/github/types.js.map +1 -0
  56. package/dist/db.d.ts.map +1 -1
  57. package/dist/db.js +72 -1
  58. package/dist/db.js.map +1 -1
  59. package/dist/server.d.ts.map +1 -1
  60. package/dist/server.js +310 -1
  61. package/dist/server.js.map +1 -1
  62. package/dist/src/cli.js +10 -0
  63. package/dist/src/cli.js.map +1 -1
  64. package/dist/src/connectors/github/backfill.js +257 -0
  65. package/dist/src/connectors/github/backfill.js.map +1 -0
  66. package/dist/src/connectors/github/cli-impl.js +152 -0
  67. package/dist/src/connectors/github/cli-impl.js.map +1 -0
  68. package/dist/src/connectors/github/deletion.js +78 -0
  69. package/dist/src/connectors/github/deletion.js.map +1 -0
  70. package/dist/src/connectors/github/dlq.js +181 -0
  71. package/dist/src/connectors/github/dlq.js.map +1 -0
  72. package/dist/src/connectors/github/idempotency.js +25 -0
  73. package/dist/src/connectors/github/idempotency.js.map +1 -0
  74. package/dist/src/connectors/github/ingest.js +107 -0
  75. package/dist/src/connectors/github/ingest.js.map +1 -0
  76. package/dist/src/connectors/github/octokit-client.js +65 -0
  77. package/dist/src/connectors/github/octokit-client.js.map +1 -0
  78. package/dist/src/connectors/github/ratelimit.js +31 -0
  79. package/dist/src/connectors/github/ratelimit.js.map +1 -0
  80. package/dist/src/connectors/github/scope.js +13 -0
  81. package/dist/src/connectors/github/scope.js.map +1 -0
  82. package/dist/src/connectors/github/signature.js +35 -0
  83. package/dist/src/connectors/github/signature.js.map +1 -0
  84. package/dist/src/connectors/github/tenant-routing.js +61 -0
  85. package/dist/src/connectors/github/tenant-routing.js.map +1 -0
  86. package/dist/src/connectors/github/transform.js +103 -0
  87. package/dist/src/connectors/github/transform.js.map +1 -0
  88. package/dist/src/connectors/github/types.js +94 -0
  89. package/dist/src/connectors/github/types.js.map +1 -0
  90. package/dist/src/db.js +72 -1
  91. package/dist/src/db.js.map +1 -1
  92. package/dist/src/server.js +310 -1
  93. package/dist/src/server.js.map +1 -1
  94. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  95. package/extensions/openclaw-plugin/package.json +1 -1
  96. package/openclaw.plugin.json +1 -1
  97. package/package.json +1 -1
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Paginated backfill of three GitHub REST streams with per-stream
3
+ * high-water marks (HWMs):
4
+ * - /repos/{repo}/issues -> issues_hwm
5
+ * - /repos/{repo}/issues/comments -> issue_comments_hwm
6
+ * - /repos/{repo}/pulls/comments -> pr_review_comments_hwm
7
+ *
8
+ * Crash safety (codex P1 #3): each stream's HWM is persisted ONLY after
9
+ * the stream fully drains. If stream 2 throws mid-flight, stream 1's HWM
10
+ * is committed and stream 2's stays NULL (or its prior value). Rerun
11
+ * picks up from stream 1's saved HWM and re-fetches stream 2 from its
12
+ * last committed point.
13
+ *
14
+ * Codex P1 #2: the /issues endpoint returns BOTH issues and PRs (a PR is
15
+ * an issue with a `pull_request` field). We skip PRs here so they don't
16
+ * get ingested under the issues schema. PRs are handled via webhook in
17
+ * V1 (no /pulls backfill stream — review comments cover the discussion
18
+ * surface).
19
+ *
20
+ * Privacy (V1 limitation): the REST list endpoints don't reliably set
21
+ * `repository.private`, and resolving it requires an extra API call per
22
+ * repo. Backfill leaves the field undefined so scopeFromRepository falls
23
+ * through to private. Callers who know the repo is public can re-tag
24
+ * downstream; the fail-safe default protects private orgs.
25
+ */
26
+ import type { Context } from '../../api.js';
27
+ import type { GitHubFetcher } from './octokit-client.js';
28
+ export interface BackfillOpts {
29
+ /** e.g. 'acme/repo'. */
30
+ repoFullName: string;
31
+ fetcher: GitHubFetcher;
32
+ token: string;
33
+ /** Optional cap on items per stream. Useful for tests. */
34
+ maxPerStream?: number;
35
+ /** sleep ms — injectable so tests don't actually wait. */
36
+ sleepMs?: (ms: number) => Promise<void>;
37
+ }
38
+ export interface BackfillStreamCounts {
39
+ issues: number;
40
+ issueComments: number;
41
+ prReviewComments: number;
42
+ }
43
+ export interface BackfillResult {
44
+ ingested: BackfillStreamCounts;
45
+ pages: BackfillStreamCounts;
46
+ }
47
+ export declare function backfillRepo(ctx: Context, opts: BackfillOpts): Promise<BackfillResult>;
48
+ //# sourceMappingURL=backfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backfill.d.ts","sourceRoot":"","sources":["../../../src/connectors/github/backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5C,OAAO,KAAK,EAAE,aAAa,EAAsB,MAAM,qBAAqB,CAAC;AAW7E,MAAM,WAAW,YAAY;IAC3B,wBAAwB;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,KAAK,EAAE,oBAAoB,CAAC;CAC7B;AA6LD,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,cAAc,CAAC,CA6IzB"}
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Paginated backfill of three GitHub REST streams with per-stream
3
+ * high-water marks (HWMs):
4
+ * - /repos/{repo}/issues -> issues_hwm
5
+ * - /repos/{repo}/issues/comments -> issue_comments_hwm
6
+ * - /repos/{repo}/pulls/comments -> pr_review_comments_hwm
7
+ *
8
+ * Crash safety (codex P1 #3): each stream's HWM is persisted ONLY after
9
+ * the stream fully drains. If stream 2 throws mid-flight, stream 1's HWM
10
+ * is committed and stream 2's stays NULL (or its prior value). Rerun
11
+ * picks up from stream 1's saved HWM and re-fetches stream 2 from its
12
+ * last committed point.
13
+ *
14
+ * Codex P1 #2: the /issues endpoint returns BOTH issues and PRs (a PR is
15
+ * an issue with a `pull_request` field). We skip PRs here so they don't
16
+ * get ingested under the issues schema. PRs are handled via webhook in
17
+ * V1 (no /pulls backfill stream — review comments cover the discussion
18
+ * surface).
19
+ *
20
+ * Privacy (V1 limitation): the REST list endpoints don't reliably set
21
+ * `repository.private`, and resolving it requires an extra API call per
22
+ * repo. Backfill leaves the field undefined so scopeFromRepository falls
23
+ * through to private. Callers who know the repo is public can re-tag
24
+ * downstream; the fail-safe default protects private orgs.
25
+ */
26
+ import { openHippoDb, closeHippoDb } from '../../db.js';
27
+ import { ingestEvent } from './ingest.js';
28
+ const API = 'https://api.github.com';
29
+ function readCursor(root, tenantId, repo) {
30
+ const db = openHippoDb(root);
31
+ try {
32
+ const row = db
33
+ .prepare(`SELECT issues_hwm, issue_comments_hwm, pr_review_comments_hwm
34
+ FROM github_cursors WHERE tenant_id = ? AND repo_full_name = ?`)
35
+ .get(tenantId, repo);
36
+ return {
37
+ issues: row?.issues_hwm ?? null,
38
+ issueComments: row?.issue_comments_hwm ?? null,
39
+ prReviewComments: row?.pr_review_comments_hwm ?? null,
40
+ };
41
+ }
42
+ finally {
43
+ closeHippoDb(db);
44
+ }
45
+ }
46
+ function writeOneHwm(root, tenantId, repo, column, value) {
47
+ const db = openHippoDb(root);
48
+ try {
49
+ db.prepare(`INSERT INTO github_cursors (tenant_id, repo_full_name, ${column}, updated_at)
50
+ VALUES (?, ?, ?, ?)
51
+ ON CONFLICT(tenant_id, repo_full_name)
52
+ DO UPDATE SET ${column} = excluded.${column}, updated_at = excluded.updated_at`).run(tenantId, repo, value, new Date().toISOString());
53
+ }
54
+ finally {
55
+ closeHippoDb(db);
56
+ }
57
+ }
58
+ /**
59
+ * Build a synthetic `repository` object from the known repo full name.
60
+ * REST list endpoints often omit the full repository object on each item;
61
+ * we already know the repo since the caller passed it. `private` left
62
+ * undefined so scopeFromRepository falls through to private.
63
+ */
64
+ function syntheticRepository(repoFullName) {
65
+ const [owner, name] = repoFullName.split('/');
66
+ return {
67
+ full_name: repoFullName,
68
+ name: name ?? repoFullName,
69
+ owner: { login: owner ?? repoFullName },
70
+ // private intentionally omitted — fail-safe to private scope.
71
+ };
72
+ }
73
+ /**
74
+ * Parse the trailing issue/PR number from a REST API URL.
75
+ * e.g. https://api.github.com/repos/o/r/issues/42 -> 42
76
+ * https://api.github.com/repos/o/r/pulls/7 -> 7
77
+ */
78
+ function parseTrailingNumber(url) {
79
+ if (!url)
80
+ return null;
81
+ const m = url.match(/\/(\d+)(?:\?.*)?$/);
82
+ if (!m)
83
+ return null;
84
+ const n = Number(m[1]);
85
+ return Number.isFinite(n) ? n : null;
86
+ }
87
+ function isIssuesItem(x) {
88
+ if (!x || typeof x !== 'object')
89
+ return false;
90
+ const o = x;
91
+ if (typeof o.number !== 'number')
92
+ return false;
93
+ if (typeof o.title !== 'string')
94
+ return false;
95
+ const u = o.user;
96
+ if (!u || typeof u.login !== 'string' || typeof u.id !== 'number')
97
+ return false;
98
+ return true;
99
+ }
100
+ function isCommentItem(x) {
101
+ if (!x || typeof x !== 'object')
102
+ return false;
103
+ const o = x;
104
+ if (typeof o.id !== 'number')
105
+ return false;
106
+ const u = o.user;
107
+ if (!u || typeof u.login !== 'string' || typeof u.id !== 'number')
108
+ return false;
109
+ return true;
110
+ }
111
+ /**
112
+ * Drain one stream end-to-end. Pauses and retries on rate-limit. Throws
113
+ * on any other fetch error so the caller leaves the HWM unchanged
114
+ * (codex P1 #3 crash safety). Returns max(updated_at) seen so the caller
115
+ * can advance the HWM only if the whole stream drained.
116
+ */
117
+ async function drainStream(ctx, url0, toIngestEvent, fetcher, token, sleep, maxItems) {
118
+ let url = url0;
119
+ let ingested = 0;
120
+ let pages = 0;
121
+ let maxUpdatedAt = null;
122
+ while (url) {
123
+ // Fetch with rate-limit retry loop.
124
+ let page;
125
+ while (true) {
126
+ page = await fetcher({ url, token });
127
+ if (page.rateLimit.reason !== 'none') {
128
+ await sleep(page.rateLimit.sleepSeconds * 1000);
129
+ continue;
130
+ }
131
+ break;
132
+ }
133
+ pages++;
134
+ for (const item of page.items) {
135
+ const evt = toIngestEvent(item);
136
+ if (!evt)
137
+ continue; // PRs returned by /issues, malformed shape, etc.
138
+ const updatedAt = item && typeof item === 'object'
139
+ ? (item.updated_at ?? null)
140
+ : null;
141
+ const r = ingestEvent(ctx, {
142
+ event: evt,
143
+ rawBody: JSON.stringify(item),
144
+ deliveryId: `backfill:${ctx.tenantId}:${updatedAt ?? ''}`,
145
+ });
146
+ // Count anything we successfully processed (new ingest OR known dup).
147
+ if (r.status === 'ingested' || r.status === 'skipped')
148
+ ingested++;
149
+ if (updatedAt && (!maxUpdatedAt || updatedAt > maxUpdatedAt)) {
150
+ maxUpdatedAt = updatedAt;
151
+ }
152
+ if (maxItems && ingested >= maxItems) {
153
+ return { ingested, pages, maxUpdatedAt };
154
+ }
155
+ }
156
+ url = page.next;
157
+ }
158
+ return { ingested, pages, maxUpdatedAt };
159
+ }
160
+ export async function backfillRepo(ctx, opts) {
161
+ const sleep = opts.sleepMs ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
162
+ const cursors = readCursor(ctx.hippoRoot, ctx.tenantId, opts.repoFullName);
163
+ const repository = syntheticRepository(opts.repoFullName);
164
+ const result = {
165
+ ingested: { issues: 0, issueComments: 0, prReviewComments: 0 },
166
+ pages: { issues: 0, issueComments: 0, prReviewComments: 0 },
167
+ };
168
+ // ---------- Stream 1: issues (skip PRs) ----------
169
+ const issuesUrl = `${API}/repos/${opts.repoFullName}/issues?state=all&per_page=100` +
170
+ (cursors.issues ? `&since=${encodeURIComponent(cursors.issues)}` : '');
171
+ const issuesRes = await drainStream(ctx, issuesUrl, (item) => {
172
+ if (!isIssuesItem(item))
173
+ return null;
174
+ // Codex P1 #2: /issues returns PRs too — skip them.
175
+ if (item.pull_request)
176
+ return null;
177
+ const payload = {
178
+ action: 'opened',
179
+ repository,
180
+ issue: {
181
+ number: item.number,
182
+ title: item.title,
183
+ body: item.body ?? null,
184
+ user: { login: item.user.login, id: item.user.id },
185
+ updated_at: item.updated_at,
186
+ },
187
+ };
188
+ return { eventName: 'issues', payload };
189
+ }, opts.fetcher, opts.token, sleep, opts.maxPerStream);
190
+ result.ingested.issues = issuesRes.ingested;
191
+ result.pages.issues = issuesRes.pages;
192
+ if (issuesRes.maxUpdatedAt) {
193
+ writeOneHwm(ctx.hippoRoot, ctx.tenantId, opts.repoFullName, 'issues_hwm', issuesRes.maxUpdatedAt);
194
+ }
195
+ // ---------- Stream 2: repo-level issue comments ----------
196
+ const commentsUrl = `${API}/repos/${opts.repoFullName}/issues/comments?per_page=100` +
197
+ (cursors.issueComments
198
+ ? `&since=${encodeURIComponent(cursors.issueComments)}`
199
+ : '');
200
+ const commentsRes = await drainStream(ctx, commentsUrl, (item) => {
201
+ if (!isCommentItem(item))
202
+ return null;
203
+ const c = item;
204
+ const issueNumber = parseTrailingNumber(c.issue_url);
205
+ if (issueNumber === null)
206
+ return null;
207
+ const payload = {
208
+ action: 'created',
209
+ repository,
210
+ issue: { number: issueNumber },
211
+ comment: {
212
+ id: c.id,
213
+ body: c.body ?? null,
214
+ user: { login: c.user.login, id: c.user.id },
215
+ updated_at: c.updated_at,
216
+ },
217
+ };
218
+ return { eventName: 'issue_comment', payload };
219
+ }, opts.fetcher, opts.token, sleep, opts.maxPerStream);
220
+ result.ingested.issueComments = commentsRes.ingested;
221
+ result.pages.issueComments = commentsRes.pages;
222
+ if (commentsRes.maxUpdatedAt) {
223
+ writeOneHwm(ctx.hippoRoot, ctx.tenantId, opts.repoFullName, 'issue_comments_hwm', commentsRes.maxUpdatedAt);
224
+ }
225
+ // ---------- Stream 3: repo-level PR review comments ----------
226
+ const prCommentsUrl = `${API}/repos/${opts.repoFullName}/pulls/comments?per_page=100` +
227
+ (cursors.prReviewComments
228
+ ? `&since=${encodeURIComponent(cursors.prReviewComments)}`
229
+ : '');
230
+ const prCommentsRes = await drainStream(ctx, prCommentsUrl, (item) => {
231
+ if (!isCommentItem(item))
232
+ return null;
233
+ const c = item;
234
+ const prNumber = parseTrailingNumber(c.pull_request_url);
235
+ if (prNumber === null)
236
+ return null;
237
+ const payload = {
238
+ action: 'created',
239
+ repository,
240
+ pull_request: { number: prNumber },
241
+ comment: {
242
+ id: c.id,
243
+ body: c.body ?? null,
244
+ user: { login: c.user.login, id: c.user.id },
245
+ updated_at: c.updated_at,
246
+ },
247
+ };
248
+ return { eventName: 'pull_request_review_comment', payload };
249
+ }, opts.fetcher, opts.token, sleep, opts.maxPerStream);
250
+ result.ingested.prReviewComments = prCommentsRes.ingested;
251
+ result.pages.prReviewComments = prCommentsRes.pages;
252
+ if (prCommentsRes.maxUpdatedAt) {
253
+ writeOneHwm(ctx.hippoRoot, ctx.tenantId, opts.repoFullName, 'pr_review_comments_hwm', prCommentsRes.maxUpdatedAt);
254
+ }
255
+ return result;
256
+ }
257
+ //# sourceMappingURL=backfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backfill.js","sourceRoot":"","sources":["../../../src/connectors/github/backfill.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Implementation of `hippo github` CLI subcommands. Extracted from the main
3
+ * cli.ts so unit tests can import these functions directly without triggering
4
+ * the cli.ts main() side effects. The cli.ts dispatcher re-exports the
5
+ * top-level cmdGithub.
6
+ *
7
+ * Subcommands mirror the Slack connector shape (cli.ts §Slack subcommands):
8
+ * - hippo github backfill --repo <owner/name> [--since ISO] [--max <N>]
9
+ * - hippo github dlq list
10
+ * - hippo github dlq replay <id> [--force]
11
+ */
12
+ import { type GitHubFetcher } from './octokit-client.js';
13
+ type Flags = Record<string, string | boolean | string[]>;
14
+ export declare function printGithubBackfillUsage(): void;
15
+ /**
16
+ * `hippo github backfill`. The fetcher is injectable so tests can drive the
17
+ * code path without hitting the network. Defaults to `realGitHubFetcher`.
18
+ */
19
+ export declare function cmdGithubBackfill(hippoRoot: string, flags: Flags, fetcher?: GitHubFetcher): Promise<void>;
20
+ export declare function cmdGithubDlqList(hippoRoot: string, _flags: Flags): void;
21
+ export declare function cmdGithubDlqReplay(hippoRoot: string, args: string[], flags: Flags): Promise<void>;
22
+ export declare function cmdGithub(hippoRoot: string, args: string[], flags: Flags): Promise<void>;
23
+ export {};
24
+ //# sourceMappingURL=cli-impl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-impl.d.ts","sourceRoot":"","sources":["../../../src/connectors/github/cli-impl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG5E,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;AAEzD,wBAAgB,wBAAwB,IAAI,IAAI,CAM/C;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,KAAK,EACZ,OAAO,GAAE,aAAiC,GACzC,OAAO,CAAC,IAAI,CAAC,CAkEf;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG,IAAI,CAiBvE;AAED,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,wBAAsB,SAAS,CAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,IAAI,CAAC,CAgBf"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Implementation of `hippo github` CLI subcommands. Extracted from the main
3
+ * cli.ts so unit tests can import these functions directly without triggering
4
+ * the cli.ts main() side effects. The cli.ts dispatcher re-exports the
5
+ * top-level cmdGithub.
6
+ *
7
+ * Subcommands mirror the Slack connector shape (cli.ts §Slack subcommands):
8
+ * - hippo github backfill --repo <owner/name> [--since ISO] [--max <N>]
9
+ * - hippo github dlq list
10
+ * - hippo github dlq replay <id> [--force]
11
+ */
12
+ import { openHippoDb, closeHippoDb } from '../../db.js';
13
+ import { resolveTenantId } from '../../tenant.js';
14
+ import { backfillRepo } from './backfill.js';
15
+ import { realGitHubFetcher } from './octokit-client.js';
16
+ import { listDlq, replayDlqEntry } from './dlq.js';
17
+ export function printGithubBackfillUsage() {
18
+ console.log('hippo github backfill --repo <owner/name> [--since ISO] [--max <N>]');
19
+ console.log(' --repo GitHub repository in owner/name format (required, e.g. acme/widgets)');
20
+ console.log(' --since Initial high-water-mark for first run (optional, ISO 8601)');
21
+ console.log(' --max Cap items per stream (optional, integer)');
22
+ console.log(' Requires GITHUB_TOKEN env var with repo read scope.');
23
+ }
24
+ /**
25
+ * `hippo github backfill`. The fetcher is injectable so tests can drive the
26
+ * code path without hitting the network. Defaults to `realGitHubFetcher`.
27
+ */
28
+ export async function cmdGithubBackfill(hippoRoot, flags, fetcher = realGitHubFetcher) {
29
+ if (flags['help']) {
30
+ printGithubBackfillUsage();
31
+ return;
32
+ }
33
+ const repo = flags['repo'];
34
+ if (typeof repo !== 'string' || !repo.includes('/')) {
35
+ printGithubBackfillUsage();
36
+ process.exit(2);
37
+ }
38
+ const token = process.env.GITHUB_TOKEN;
39
+ if (!token) {
40
+ console.error('GITHUB_TOKEN is not set. Backfill requires a personal access token with repo read scope.');
41
+ process.exit(2);
42
+ }
43
+ const maxRaw = flags['max'];
44
+ let maxPerStream;
45
+ if (typeof maxRaw === 'string' || typeof maxRaw === 'number') {
46
+ const parsed = Number(maxRaw);
47
+ if (Number.isFinite(parsed) && parsed > 0) {
48
+ maxPerStream = Math.floor(parsed);
49
+ }
50
+ }
51
+ const sinceIso = typeof flags['since'] === 'string' ? flags['since'] : undefined;
52
+ const tenantId = resolveTenantId({});
53
+ // Seed the github_cursors row so all 3 streams use --since on a fresh run.
54
+ // COALESCE preserves any existing HWM (subsequent runs ignore --since for
55
+ // streams that already drained at least once — same idempotency story as
56
+ // Slack's slack_cursors).
57
+ if (sinceIso) {
58
+ const db = openHippoDb(hippoRoot);
59
+ try {
60
+ db.prepare(`INSERT INTO github_cursors (tenant_id, repo_full_name, issues_hwm, issue_comments_hwm, pr_review_comments_hwm, updated_at)
61
+ VALUES (?, ?, ?, ?, ?, ?)
62
+ ON CONFLICT(tenant_id, repo_full_name) DO UPDATE SET
63
+ issues_hwm = COALESCE(github_cursors.issues_hwm, excluded.issues_hwm),
64
+ issue_comments_hwm = COALESCE(github_cursors.issue_comments_hwm, excluded.issue_comments_hwm),
65
+ pr_review_comments_hwm = COALESCE(github_cursors.pr_review_comments_hwm, excluded.pr_review_comments_hwm),
66
+ updated_at = excluded.updated_at`).run(tenantId, repo, sinceIso, sinceIso, sinceIso, new Date().toISOString());
67
+ }
68
+ finally {
69
+ closeHippoDb(db);
70
+ }
71
+ }
72
+ const ctx = {
73
+ hippoRoot,
74
+ tenantId,
75
+ actor: 'cli:github-backfill',
76
+ };
77
+ try {
78
+ const result = await backfillRepo(ctx, {
79
+ repoFullName: repo,
80
+ fetcher,
81
+ token: token,
82
+ maxPerStream,
83
+ });
84
+ console.log(JSON.stringify(result, null, 2));
85
+ }
86
+ catch (e) {
87
+ console.error('backfill failed:', e.message);
88
+ process.exit(3);
89
+ }
90
+ }
91
+ export function cmdGithubDlqList(hippoRoot, _flags) {
92
+ const db = openHippoDb(hippoRoot);
93
+ try {
94
+ const tenantId = resolveTenantId({});
95
+ const items = listDlq(db, { tenantId });
96
+ if (items.length === 0) {
97
+ console.log('no entries');
98
+ return;
99
+ }
100
+ for (const it of items) {
101
+ console.log(`${it.id}\t${it.bucket}\t${it.tenantId}\t${it.eventName ?? '-'}\t${it.receivedAt}\t${it.error}`);
102
+ }
103
+ }
104
+ finally {
105
+ closeHippoDb(db);
106
+ }
107
+ }
108
+ export async function cmdGithubDlqReplay(hippoRoot, args, flags) {
109
+ const idArg = args[0];
110
+ if (!idArg) {
111
+ console.error('Usage: hippo github dlq replay <id> [--force]');
112
+ process.exit(1);
113
+ }
114
+ const id = Number(idArg);
115
+ if (!Number.isFinite(id) || !Number.isInteger(id) || id < 1) {
116
+ console.error(`replay: invalid id ${idArg}`);
117
+ process.exit(1);
118
+ }
119
+ const force = flags['force'] === true;
120
+ const ctx = {
121
+ hippoRoot,
122
+ tenantId: resolveTenantId({}),
123
+ actor: 'cli:github-dlq-replay',
124
+ };
125
+ const result = await replayDlqEntry(ctx, id, {
126
+ force,
127
+ webhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
128
+ });
129
+ if (!result.ok) {
130
+ console.error(`replay failed: status=${result.status} retry_count=${result.retryCount}${result.reason ? ` reason=${result.reason}` : ''}`);
131
+ process.exit(1);
132
+ }
133
+ console.log(`replay ok: status=${result.status} memory_id=${result.memoryId ?? '(none)'} retry_count=${result.retryCount}`);
134
+ }
135
+ export async function cmdGithub(hippoRoot, args, flags) {
136
+ const sub = args[0];
137
+ if (sub === 'backfill') {
138
+ await cmdGithubBackfill(hippoRoot, flags);
139
+ return;
140
+ }
141
+ if (sub === 'dlq' && args[1] === 'list') {
142
+ cmdGithubDlqList(hippoRoot, flags);
143
+ return;
144
+ }
145
+ if (sub === 'dlq' && args[1] === 'replay') {
146
+ await cmdGithubDlqReplay(hippoRoot, args.slice(2), flags);
147
+ return;
148
+ }
149
+ console.error('Usage: hippo github <backfill|dlq list|dlq replay <id> [--force]> [...]');
150
+ process.exit(1);
151
+ }
152
+ //# sourceMappingURL=cli-impl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-impl.js","sourceRoot":"","sources":["../../../src/connectors/github/cli-impl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { type Context } from '../../api.js';
2
+ export interface DeletionInput {
3
+ /** artifact_ref of the comment, e.g.,
4
+ * 'github://acme/repo/issue/42/comment/123' or
5
+ * 'github://acme/repo/pull/7/review_comment/456'. */
6
+ artifactRef: string;
7
+ /** Source idempotency key for this delete event (sha256 of eventName+body). */
8
+ idempotencyKey: string;
9
+ /** X-GitHub-Delivery header for audit log. */
10
+ deliveryId: string;
11
+ /** X-GitHub-Event header value: 'issue_comment' or 'pull_request_review_comment'. */
12
+ eventName: string;
13
+ }
14
+ export type DeletionStatus = 'archived' | 'archive_skipped_not_found' | 'duplicate';
15
+ export interface DeletionResult {
16
+ status: DeletionStatus;
17
+ archivedCount: number;
18
+ }
19
+ /**
20
+ * Handle GitHub `issue_comment.deleted` and `pull_request_review_comment.deleted`.
21
+ *
22
+ * Codex P0 #5: filter by tenant_id + kind='raw'. Multi-row archive: GitHub edits
23
+ * keep the same artifact_ref, so multiple active raw rows can match a single
24
+ * deletion event (each edit produces a fresh raw memory id sharing the same
25
+ * artifact_ref). Archive ALL of them, not just the most recent.
26
+ *
27
+ * Crash-safety: the afterArchive hook on archiveRaw runs inside the SAVEPOINT,
28
+ * so the idempotency mark commits with the FIRST archive — a crash mid-archive
29
+ * cannot leave the deletion event un-acked. A retry sees the key as 'seen' and
30
+ * returns 'duplicate' instead of attempting a partial re-archive.
31
+ *
32
+ * Tenant scope is load-bearing: without `tenant_id = ?` a deletion event from
33
+ * tenant A could archive tenant B's raw row sharing the same artifact_ref. The
34
+ * `kind = 'raw'` filter prevents accidentally targeting distilled rows that
35
+ * may share artifact_ref via downstream extraction.
36
+ */
37
+ export declare function handleCommentDeleted(ctx: Context, input: DeletionInput): DeletionResult;
38
+ //# sourceMappingURL=deletion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deletion.d.ts","sourceRoot":"","sources":["../../../src/connectors/github/deletion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAIxD,MAAM,WAAW,aAAa;IAC5B;;0DAEsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,2BAA2B,GAAG,WAAW,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,GAAG,cAAc,CA8DvF"}
@@ -0,0 +1,78 @@
1
+ import { archiveRaw } from '../../api.js';
2
+ import { openHippoDb, closeHippoDb } from '../../db.js';
3
+ import { hasSeenKey, markKeySeen } from './idempotency.js';
4
+ /**
5
+ * Handle GitHub `issue_comment.deleted` and `pull_request_review_comment.deleted`.
6
+ *
7
+ * Codex P0 #5: filter by tenant_id + kind='raw'. Multi-row archive: GitHub edits
8
+ * keep the same artifact_ref, so multiple active raw rows can match a single
9
+ * deletion event (each edit produces a fresh raw memory id sharing the same
10
+ * artifact_ref). Archive ALL of them, not just the most recent.
11
+ *
12
+ * Crash-safety: the afterArchive hook on archiveRaw runs inside the SAVEPOINT,
13
+ * so the idempotency mark commits with the FIRST archive — a crash mid-archive
14
+ * cannot leave the deletion event un-acked. A retry sees the key as 'seen' and
15
+ * returns 'duplicate' instead of attempting a partial re-archive.
16
+ *
17
+ * Tenant scope is load-bearing: without `tenant_id = ?` a deletion event from
18
+ * tenant A could archive tenant B's raw row sharing the same artifact_ref. The
19
+ * `kind = 'raw'` filter prevents accidentally targeting distilled rows that
20
+ * may share artifact_ref via downstream extraction.
21
+ */
22
+ export function handleCommentDeleted(ctx, input) {
23
+ // Fast-path duplicate check + look up all matching raw rows.
24
+ const dbCheck = openHippoDb(ctx.hippoRoot);
25
+ let memoryIds = [];
26
+ try {
27
+ if (hasSeenKey(dbCheck, input.idempotencyKey)) {
28
+ return { status: 'duplicate', archivedCount: 0 };
29
+ }
30
+ const rows = dbCheck
31
+ .prepare(`SELECT id FROM memories WHERE artifact_ref = ? AND tenant_id = ? AND kind = 'raw'`)
32
+ .all(input.artifactRef, ctx.tenantId);
33
+ memoryIds = rows.map((r) => r.id);
34
+ }
35
+ finally {
36
+ closeHippoDb(dbCheck);
37
+ }
38
+ if (memoryIds.length === 0) {
39
+ // Nothing to archive — but still mark idempotency so a retry returns 'duplicate'.
40
+ // No row to roll back, so the second-handle pattern is safe here.
41
+ const dbMark = openHippoDb(ctx.hippoRoot);
42
+ try {
43
+ markKeySeen(dbMark, {
44
+ idempotencyKey: input.idempotencyKey,
45
+ deliveryId: input.deliveryId,
46
+ eventName: input.eventName,
47
+ memoryId: null,
48
+ });
49
+ }
50
+ finally {
51
+ closeHippoDb(dbMark);
52
+ }
53
+ return { status: 'archive_skipped_not_found', archivedCount: 0 };
54
+ }
55
+ // Archive each matching row. Mark idempotency on the first archive's
56
+ // afterArchive hook so the mark lands inside the same SAVEPOINT as the first
57
+ // archive (crash-safe). markKeySeen is INSERT OR IGNORE so subsequent calls
58
+ // are no-ops, but we only need the first one to commit-or-rollback with the
59
+ // first archive — that's enough to keep the source-of-truth lock-step.
60
+ let firstMarked = false;
61
+ for (const id of memoryIds) {
62
+ archiveRaw(ctx, id, `source_deleted:github:${input.eventName}:${input.deliveryId}`, {
63
+ afterArchive: (sameDb, archivedId) => {
64
+ if (!firstMarked) {
65
+ markKeySeen(sameDb, {
66
+ idempotencyKey: input.idempotencyKey,
67
+ deliveryId: input.deliveryId,
68
+ eventName: input.eventName,
69
+ memoryId: archivedId,
70
+ });
71
+ firstMarked = true;
72
+ }
73
+ },
74
+ });
75
+ }
76
+ return { status: 'archived', archivedCount: memoryIds.length };
77
+ }
78
+ //# sourceMappingURL=deletion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deletion.js","sourceRoot":"","sources":["../../../src/connectors/github/deletion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAsB3D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY,EAAE,KAAoB;IACrE,6DAA6D;IAC7D,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3C,IAAI,SAAS,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;YAC9C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;QACnD,CAAC;QACD,MAAM,IAAI,GAAG,OAAO;aACjB,OAAO,CACN,mFAAmF,CACpF;aACA,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC,QAAQ,CAA0B,CAAC;QACjE,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,kFAAkF;QAClF,kEAAkE;QAClE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC;YACH,WAAW,CAAC,MAAM,EAAE;gBAClB,cAAc,EAAE,KAAK,CAAC,cAAc;gBACpC,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,2BAA2B,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IACnE,CAAC;IAED,qEAAqE;IACrE,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,uEAAuE;IACvE,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,UAAU,CACR,GAAG,EACH,EAAE,EACF,yBAAyB,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,UAAU,EAAE,EAC9D;YACE,YAAY,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE;gBACnC,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,WAAW,CAAC,MAAM,EAAE;wBAClB,cAAc,EAAE,KAAK,CAAC,cAAc;wBACpC,UAAU,EAAE,KAAK,CAAC,UAAU;wBAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,QAAQ,EAAE,UAAU;qBACrB,CAAC,CAAC;oBACH,WAAW,GAAG,IAAI,CAAC;gBACrB,CAAC;YACH,CAAC;SACF,CACF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;AACjE,CAAC"}