ghagga-forge 3.1.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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/adapters/github/github-app-credential-provider.d.ts +102 -0
  4. package/dist/adapters/github/github-app-credential-provider.d.ts.map +1 -0
  5. package/dist/adapters/github/github-app-credential-provider.js +166 -0
  6. package/dist/adapters/github/github-app-credential-provider.js.map +1 -0
  7. package/dist/adapters/github/github-client-port.d.ts +92 -0
  8. package/dist/adapters/github/github-client-port.d.ts.map +1 -0
  9. package/dist/adapters/github/github-client-port.js +24 -0
  10. package/dist/adapters/github/github-client-port.js.map +1 -0
  11. package/dist/adapters/github/github-forge-adapter.d.ts +105 -0
  12. package/dist/adapters/github/github-forge-adapter.d.ts.map +1 -0
  13. package/dist/adapters/github/github-forge-adapter.js +225 -0
  14. package/dist/adapters/github/github-forge-adapter.js.map +1 -0
  15. package/dist/adapters/github/static-token-provider.d.ts +30 -0
  16. package/dist/adapters/github/static-token-provider.d.ts.map +1 -0
  17. package/dist/adapters/github/static-token-provider.js +35 -0
  18. package/dist/adapters/github/static-token-provider.js.map +1 -0
  19. package/dist/adapters/gitlab/gitlab-client-port.d.ts +82 -0
  20. package/dist/adapters/gitlab/gitlab-client-port.d.ts.map +1 -0
  21. package/dist/adapters/gitlab/gitlab-client-port.js +27 -0
  22. package/dist/adapters/gitlab/gitlab-client-port.js.map +1 -0
  23. package/dist/adapters/gitlab/gitlab-forge-adapter.d.ts +118 -0
  24. package/dist/adapters/gitlab/gitlab-forge-adapter.d.ts.map +1 -0
  25. package/dist/adapters/gitlab/gitlab-forge-adapter.js +238 -0
  26. package/dist/adapters/gitlab/gitlab-forge-adapter.js.map +1 -0
  27. package/dist/comment-id.d.ts +45 -0
  28. package/dist/comment-id.d.ts.map +1 -0
  29. package/dist/comment-id.js +48 -0
  30. package/dist/comment-id.js.map +1 -0
  31. package/dist/errors.d.ts +48 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +67 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/index.d.ts +35 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +34 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/lint-boundary.d.ts +36 -0
  40. package/dist/lint-boundary.d.ts.map +1 -0
  41. package/dist/lint-boundary.impl.d.mts +41 -0
  42. package/dist/lint-boundary.impl.mjs +400 -0
  43. package/dist/lint-boundary.js +35 -0
  44. package/dist/lint-boundary.js.map +1 -0
  45. package/dist/ports/ci-runner.d.ts +48 -0
  46. package/dist/ports/ci-runner.d.ts.map +1 -0
  47. package/dist/ports/ci-runner.js +10 -0
  48. package/dist/ports/ci-runner.js.map +1 -0
  49. package/dist/ports/credential-provider.d.ts +32 -0
  50. package/dist/ports/credential-provider.d.ts.map +1 -0
  51. package/dist/ports/credential-provider.js +10 -0
  52. package/dist/ports/credential-provider.js.map +1 -0
  53. package/dist/ports/forge-adapter.d.ts +174 -0
  54. package/dist/ports/forge-adapter.d.ts.map +1 -0
  55. package/dist/ports/forge-adapter.js +34 -0
  56. package/dist/ports/forge-adapter.js.map +1 -0
  57. package/dist/ports/webhook-codec.d.ts +41 -0
  58. package/dist/ports/webhook-codec.d.ts.map +1 -0
  59. package/dist/ports/webhook-codec.js +18 -0
  60. package/dist/ports/webhook-codec.js.map +1 -0
  61. package/dist/project.d.ts +32 -0
  62. package/dist/project.d.ts.map +1 -0
  63. package/dist/project.js +41 -0
  64. package/dist/project.js.map +1 -0
  65. package/dist/ref.d.ts +20 -0
  66. package/dist/ref.d.ts.map +1 -0
  67. package/dist/ref.js +21 -0
  68. package/dist/ref.js.map +1 -0
  69. package/dist/registry.d.ts +69 -0
  70. package/dist/registry.d.ts.map +1 -0
  71. package/dist/registry.js +68 -0
  72. package/dist/registry.js.map +1 -0
  73. package/dist/types.d.ts +310 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +50 -0
  76. package/dist/types.js.map +1 -0
  77. package/package.json +64 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * GitLabForgeAdapter — wraps a GitLab REST client behind the forge-agnostic
3
+ * {@link ForgeAdapterBase} + {@link InlineCapable} surfaces.
4
+ *
5
+ * DEPENDENCY INVERSION (boundary rule R-AGNOSTIC):
6
+ * `packages/forge` MUST NOT import a concrete HTTP client. This adapter depends
7
+ * on the injected {@link GitLabClientPort} (declared inside the forge package);
8
+ * the CLI (P4) constructs the adapter passing a native-fetch implementation:
9
+ *
10
+ * new GitLabForgeAdapter({ client, token, projectId })
11
+ *
12
+ * IDENTITY (R-GITLAB):
13
+ * - {@link RepoRef.nativeId} is the GitLab NUMERIC project id (the group/project
14
+ * `path` is mutable → the id is canonical). The adapter is constructed with the
15
+ * numeric `projectId` and routes EVERY REST call through it — never the path.
16
+ * - {@link ChangeRequestRef.iid} is the MR iid (project-scoped). The GitLab MR
17
+ * API path is `/projects/:id/merge_requests/:iid/...`.
18
+ *
19
+ * CAPABILITIES: reactions ❌ (GitLab reaction-award is not modelled — no
20
+ * `addReaction`), inlineComments ✅ (publishInline present), graphRead ❌ (no
21
+ * graph methods). The `capabilities` field is a HINT only (R-CAPABILITY): callers
22
+ * guard optional methods by method-presence, never by these flags.
23
+ *
24
+ * SCOPE — v1 deliverable is the SUMMARY COMMENT (upsertSummaryComment). The
25
+ * adapter also implements {@link InlineCapable.publishInline} to satisfy
26
+ * R-LEAK-PUBLISH: it posts N INDEPENDENT inline posts, returning a
27
+ * {@link PublishReport} where partial failure is FIRST-CLASS (never swallowed).
28
+ * INLINE POSITIONING: when an {@link InlineComment} carries `position` and the
29
+ * injected client supports `createMrDiscussion`, v1 posts a TRUE diff-anchored
30
+ * discussion (`position[position_type]=text` + the three SHAs + old/new line +
31
+ * old/new path). When `position` is absent (or the client lacks discussion
32
+ * support) it degrades to a plain MR note carrying the `path:line` prefix in the
33
+ * body. The public {@link InlineComment} type carries `position` + `oldPath`/
34
+ * `newPath` regardless, so broadening positioned coverage needs NO API change.
35
+ *
36
+ * COMMENT IDs: `upsertSummaryComment` returns PLAIN GitLab-native numeric ids
37
+ * (boxing happens caller-LOCAL via {@link gitlabCommentId}). `publishInline`
38
+ * boxes per the {@link PublishReport} contract (CommentId) since the report is
39
+ * the canonical cross-forge shape.
40
+ */
41
+ import { gitlabCommentId } from '../../comment-id.js';
42
+ import { ForgeAuthError, getErrorStatus, isForgeAuthError } from '../../errors.js';
43
+ /**
44
+ * GitLab implementation of the forge adapter.
45
+ *
46
+ * Implements the mandatory base (summary-comment + the read methods, the latter
47
+ * unused by the CLI v1 path and intentionally absent from the client port) plus
48
+ * {@link InlineCapable}. Deliberately does NOT implement reactions or graph read.
49
+ */
50
+ export class GitLabForgeAdapter {
51
+ capabilities = {
52
+ reactions: false,
53
+ inlineComments: true,
54
+ graphRead: false,
55
+ };
56
+ #client;
57
+ #token;
58
+ #projectId;
59
+ constructor(deps) {
60
+ this.#client = deps.client;
61
+ this.#token = deps.token;
62
+ this.#projectId = deps.projectId;
63
+ }
64
+ /**
65
+ * Run a client call, reclassifying a 401/403 failure as a {@link ForgeAuthError}
66
+ * (mirrors the GitHub adapter). With a static PAT a 401 is terminal — there is
67
+ * nothing to re-mint — but the typed signal keeps the CLI's error reporting
68
+ * consistent across forges. NON-auth failures rethrow UNCHANGED.
69
+ */
70
+ async #mapAuth(call) {
71
+ try {
72
+ return await call();
73
+ }
74
+ catch (error) {
75
+ const status = getErrorStatus(error);
76
+ if (status === 401 || status === 403) {
77
+ throw new ForgeAuthError(status, error instanceof Error ? error.message : `Forge auth failure (HTTP ${status})`, { cause: error });
78
+ }
79
+ throw error;
80
+ }
81
+ }
82
+ // ─── Base: reads ───────────────────────────────────────────────
83
+ //
84
+ // The GitLab CLI v1 deliverable is the summary comment; the diff/details/
85
+ // file-list/commits are sourced LOCALLY by the CLI (same posture as the GitHub
86
+ // CLI port). These methods are part of the mandatory base surface but are NOT
87
+ // exercised by the `--mr` flow, so they fail loudly rather than fabricate data.
88
+ fetchDiff(_ref) {
89
+ return Promise.reject(new Error('GitLabForgeAdapter.fetchDiff is not supported in the CLI summary-comment path — ' +
90
+ 'the diff is sourced locally. (GitLab read methods are a v2 deliverable.)'));
91
+ }
92
+ fetchChangeRequest(_ref) {
93
+ return Promise.reject(new Error('GitLabForgeAdapter.fetchChangeRequest is not supported (v2 deliverable).'));
94
+ }
95
+ fetchFileList(_ref) {
96
+ return Promise.reject(new Error('GitLabForgeAdapter.fetchFileList is not supported (v2 deliverable).'));
97
+ }
98
+ fetchCommits(_ref) {
99
+ return Promise.reject(new Error('GitLabForgeAdapter.fetchCommits is not supported (v2 deliverable).'));
100
+ }
101
+ // ─── Base: upsert summary comment (fold list/delete/create → 1) ──
102
+ /**
103
+ * Idempotently upsert the single GHAGGA summary note on an MR.
104
+ *
105
+ * Behavior (mirrors the GitHub adapter's delete-all-stale + repost-at-bottom):
106
+ * 1. list MR notes; match the GHAGGA-owned ones by `marker.html` substring.
107
+ * 2. delete ALL matches in `[latest, ...stale]` order — BEST-EFFORT: each
108
+ * delete is per-note try/catch so a 404 (already gone) OR any other failure
109
+ * is tolerated and does NOT block the repost. Only ids that actually deleted
110
+ * (no throw) are reported in `deleted`.
111
+ * 3. create a FRESH note at the bottom — this is the ONLY failure that
112
+ * propagates (a failed create means no summary exists, which is fatal).
113
+ *
114
+ * "latest" = the LAST marker-matching note in chronological order (GitLab list
115
+ * returns notes oldest-first), matching the GitHub adapter's convention.
116
+ *
117
+ * Returns GitLab-native numeric ids (boxing happens caller-local via
118
+ * {@link gitlabCommentId}).
119
+ */
120
+ async upsertSummaryComment(ref, body, marker) {
121
+ const notes = await this.#mapAuth(() => this.#client.listMrNotes(this.#projectId, ref.iid, this.#token));
122
+ const matchIds = notes.filter((n) => n.body.includes(marker.html)).map((n) => n.id);
123
+ const deleted = [];
124
+ if (matchIds.length > 0) {
125
+ // latest = last marker note; the rest are stale. Delete latest FIRST then
126
+ // stale, mirroring the GitHub adapter's baseline order. (Guaranteed
127
+ // non-empty inside this `matchIds.length > 0` branch.)
128
+ const latestId = matchIds[matchIds.length - 1];
129
+ const staleIds = matchIds.slice(0, -1);
130
+ const ordered = [latestId, ...staleIds];
131
+ for (const noteId of ordered) {
132
+ try {
133
+ await this.#client.deleteMrNote(this.#projectId, ref.iid, noteId, this.#token);
134
+ deleted.push(noteId);
135
+ }
136
+ catch {
137
+ // Best-effort: tolerate any delete failure (404 or non-404). A stale
138
+ // note that cannot be deleted must NOT block the fresh repost.
139
+ }
140
+ }
141
+ }
142
+ // Always create a fresh note at the bottom. This is the ONLY error that
143
+ // propagates. A 401/403 here is reclassified to ForgeAuthError.
144
+ const created = await this.#mapAuth(() => this.#client.createMrNote(this.#projectId, ref.iid, body, this.#token));
145
+ if (created?.id == null) {
146
+ throw new TypeError('GitLabForgeAdapter.upsertSummaryComment: createMrNote returned no id (expected { id: number })');
147
+ }
148
+ return { created: created.id, deleted };
149
+ }
150
+ // ─── InlineCapable ─────────────────────────────────────────────
151
+ /**
152
+ * Publish a batch of inline comments as N INDEPENDENT posts (R-LEAK-PUBLISH).
153
+ *
154
+ * PARTIAL FAILURE IS FIRST-CLASS: GitLab posts each comment independently, so
155
+ * each create is wrapped in its own try/catch. A failure records
156
+ * `{index, error, status?, authFailure?}` into `failed` and the loop CONTINUES
157
+ * — failures are NEVER swallowed and never abort the remaining posts. The
158
+ * `status`/`authFailure` tags let a caller tell an AUTH failure (401/403, a
159
+ * static PAT cannot recover) from a transient one without string-parsing the
160
+ * message. Successes are boxed into `CommentId` (kind:'gitlab') and collected
161
+ * into `posted`.
162
+ *
163
+ * POSITION HANDLING (v1):
164
+ * - When a comment carries `position` AND the injected client implements
165
+ * `createMrDiscussion`, the adapter posts a TRUE diff-anchored discussion
166
+ * (`position[position_type]=text` with the three SHAs + old/new line +
167
+ * old/new path — renames are honored via `oldPath`/`newPath`, defaulting to
168
+ * `path`). The boxed id is the discussion's first-note id.
169
+ * - Otherwise (no `position`, or a client without discussion support) it
170
+ * degrades to a plain MR note carrying a `path:line` body prefix so the
171
+ * anchor info is not lost. Either way the partial-failure REPORT SHAPE +
172
+ * independent-post semantics — the load-bearing R-LEAK-PUBLISH contract —
173
+ * hold. The public {@link InlineComment} type carries `position` regardless,
174
+ * so no future API change is needed to broaden positioned coverage.
175
+ */
176
+ async publishInline(ref, comments) {
177
+ const posted = [];
178
+ const failed = [];
179
+ for (let index = 0; index < comments.length; index++) {
180
+ const comment = comments[index];
181
+ if (comment == null)
182
+ continue;
183
+ try {
184
+ const created = await this.#postInline(ref, comment);
185
+ if (created?.id == null) {
186
+ throw new TypeError('inline post returned no id (expected { id: number })');
187
+ }
188
+ posted.push(gitlabCommentId(created.id));
189
+ }
190
+ catch (error) {
191
+ // First-class partial failure: record and CONTINUE (never swallow, never
192
+ // abort the rest). No #mapAuth reclassification here — a per-note failure
193
+ // is reported, not thrown, so the batch outcome is observable.
194
+ //
195
+ // TAG the failure so a caller can distinguish an AUTH failure (401/403 —
196
+ // a static PAT cannot recover, surface "fix your token") from a transient
197
+ // one WITHOUT string-parsing `error`. `#postInline` does not run through
198
+ // `#mapAuth`, so the raw client error still carries its numeric `status`;
199
+ // `isForgeAuthError` ALSO catches an already-reclassified ForgeAuthError
200
+ // for robustness.
201
+ const status = getErrorStatus(error);
202
+ const authFailure = isForgeAuthError(error) || status === 401 || status === 403;
203
+ failed.push({
204
+ index,
205
+ error: error instanceof Error ? error.message : String(error),
206
+ ...(status != null ? { status } : {}),
207
+ ...(authFailure ? { authFailure: true } : {}),
208
+ });
209
+ }
210
+ }
211
+ return { posted, failed };
212
+ }
213
+ /**
214
+ * Post a single inline comment: a TRUE diff-anchored discussion when the
215
+ * comment carries `position` AND the client supports it, else a degraded
216
+ * `path:line` plain note. Throws on a missing id (the caller records it as a
217
+ * per-comment failure).
218
+ */
219
+ #postInline(ref, comment) {
220
+ const createDiscussion = this.#client.createMrDiscussion;
221
+ if (comment.position && typeof createDiscussion === 'function') {
222
+ return createDiscussion.call(this.#client, this.#projectId, ref.iid, comment.body, {
223
+ baseSha: comment.position.baseSha,
224
+ headSha: comment.position.headSha,
225
+ startSha: comment.position.startSha,
226
+ // GitLab requires BOTH paths for a text diff note; default to `path`.
227
+ oldPath: comment.oldPath ?? comment.path,
228
+ newPath: comment.newPath ?? comment.path,
229
+ ...(comment.position.oldLine != null ? { oldLine: comment.position.oldLine } : {}),
230
+ ...(comment.position.newLine != null ? { newLine: comment.position.newLine } : {}),
231
+ }, this.#token);
232
+ }
233
+ // Degrade: encode the anchor in the body as a plain note.
234
+ const noteBody = `\`${comment.path}:${comment.line}\`\n\n${comment.body}`;
235
+ return this.#client.createMrNote(this.#projectId, ref.iid, noteBody, this.#token);
236
+ }
237
+ }
238
+ //# sourceMappingURL=gitlab-forge-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab-forge-adapter.js","sourceRoot":"","sources":["../../../src/adapters/gitlab/gitlab-forge-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Sanctioned comment-id boxing helpers (R-COMMENTID).
3
+ *
4
+ * The forge adapters RETURN forge-native primitives (e.g. GitHub numeric
5
+ * comment ids) — see {@link UpsertSummaryResult}. Boxing those primitives into
6
+ * the canonical {@link CommentId} ({kind, raw}) happens caller-LOCAL, AFTER the
7
+ * adapter returns. This module is the ONE blessed place that boxing lives, so
8
+ * both the server worker (apps/server review.ts) AND the future P3 CLI can reuse
9
+ * it WITHOUT importing side-effectful modules (review.ts constructs Redis/BullMQ
10
+ * queue state at import time — unusable from a CLI / from the helper's own test).
11
+ *
12
+ * This is a PURE helper using the forge {@link CommentId} type. It does NOT
13
+ * brand the adapter's return type (the adapter still returns numbers); it is the
14
+ * sanctioned boxing step the caller applies at the seam.
15
+ *
16
+ * Boundary note (R-AGNOSTIC): this file imports only the forge-local
17
+ * {@link CommentId} type — no `apps/server`, no `ghagga-core` value import — so
18
+ * it does not breach any package boundary.
19
+ */
20
+ import type { CommentId } from './types.js';
21
+ /**
22
+ * BOX a GitHub-native numeric comment id into the canonical {@link CommentId}.
23
+ *
24
+ * The `kind: 'github'` tag prevents a GitHub comment id from being
25
+ * cross-assigned as a GitLab note id — the same numeric value across forges
26
+ * never collides because the discriminator differs.
27
+ *
28
+ * @param raw the GitHub-native numeric comment id.
29
+ * @returns the boxed `{ kind: 'github', raw: String(raw) }`.
30
+ */
31
+ export declare function githubCommentId(raw: number): CommentId;
32
+ /**
33
+ * BOX a GitLab-native numeric note id into the canonical {@link CommentId}.
34
+ *
35
+ * GitLab note ids are NUMERIC but MR-scoped — the `kind: 'gitlab'` tag is what
36
+ * disambiguates them from a GitHub comment id (R-COMMENTID). The SAME numeric
37
+ * value boxed as `'github'` vs `'gitlab'` never collides because the
38
+ * discriminator differs, so a GitHub comment id can never be cross-assigned as a
39
+ * GitLab note id (and vice-versa).
40
+ *
41
+ * @param raw the GitLab-native numeric note id.
42
+ * @returns the boxed `{ kind: 'gitlab', raw: String(raw) }`.
43
+ */
44
+ export declare function gitlabCommentId(raw: number): CommentId;
45
+ //# sourceMappingURL=comment-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-id.d.ts","sourceRoot":"","sources":["../src/comment-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAEtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAEtD"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Sanctioned comment-id boxing helpers (R-COMMENTID).
3
+ *
4
+ * The forge adapters RETURN forge-native primitives (e.g. GitHub numeric
5
+ * comment ids) — see {@link UpsertSummaryResult}. Boxing those primitives into
6
+ * the canonical {@link CommentId} ({kind, raw}) happens caller-LOCAL, AFTER the
7
+ * adapter returns. This module is the ONE blessed place that boxing lives, so
8
+ * both the server worker (apps/server review.ts) AND the future P3 CLI can reuse
9
+ * it WITHOUT importing side-effectful modules (review.ts constructs Redis/BullMQ
10
+ * queue state at import time — unusable from a CLI / from the helper's own test).
11
+ *
12
+ * This is a PURE helper using the forge {@link CommentId} type. It does NOT
13
+ * brand the adapter's return type (the adapter still returns numbers); it is the
14
+ * sanctioned boxing step the caller applies at the seam.
15
+ *
16
+ * Boundary note (R-AGNOSTIC): this file imports only the forge-local
17
+ * {@link CommentId} type — no `apps/server`, no `ghagga-core` value import — so
18
+ * it does not breach any package boundary.
19
+ */
20
+ /**
21
+ * BOX a GitHub-native numeric comment id into the canonical {@link CommentId}.
22
+ *
23
+ * The `kind: 'github'` tag prevents a GitHub comment id from being
24
+ * cross-assigned as a GitLab note id — the same numeric value across forges
25
+ * never collides because the discriminator differs.
26
+ *
27
+ * @param raw the GitHub-native numeric comment id.
28
+ * @returns the boxed `{ kind: 'github', raw: String(raw) }`.
29
+ */
30
+ export function githubCommentId(raw) {
31
+ return { kind: 'github', raw: String(raw) };
32
+ }
33
+ /**
34
+ * BOX a GitLab-native numeric note id into the canonical {@link CommentId}.
35
+ *
36
+ * GitLab note ids are NUMERIC but MR-scoped — the `kind: 'gitlab'` tag is what
37
+ * disambiguates them from a GitHub comment id (R-COMMENTID). The SAME numeric
38
+ * value boxed as `'github'` vs `'gitlab'` never collides because the
39
+ * discriminator differs, so a GitHub comment id can never be cross-assigned as a
40
+ * GitLab note id (and vice-versa).
41
+ *
42
+ * @param raw the GitLab-native numeric note id.
43
+ * @returns the boxed `{ kind: 'gitlab', raw: String(raw) }`.
44
+ */
45
+ export function gitlabCommentId(raw) {
46
+ return { kind: 'gitlab', raw: String(raw) };
47
+ }
48
+ //# sourceMappingURL=comment-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-id.js","sourceRoot":"","sources":["../src/comment-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Forge error taxonomy (SDD forge-agnostic — P2 401-recovery fix).
3
+ *
4
+ * The forge boundary throws plain `Error`s on most failures. But the in-job
5
+ * 401-recovery seam (review.ts postback) needs to distinguish an AUTH failure
6
+ * (HTTP 401/403 — the cached installation token was revoked/rotated server-side)
7
+ * from any other failure, GENERICALLY, without sniffing error message strings.
8
+ *
9
+ * {@link ForgeAuthError} is that typed signal. The adapter raises it when an
10
+ * underlying forge call fails with a 401/403 status; the worker catches it via
11
+ * {@link isForgeAuthError}, invalidates the credential provider's cache, re-mints,
12
+ * and retries the call ONCE. Non-auth failures stay plain `Error`s (NOT
13
+ * reclassified) so retry logic only fires for genuine auth failures.
14
+ */
15
+ /** HTTP statuses that mean "the token is no longer accepted" → re-mint + retry. */
16
+ export declare const FORGE_AUTH_STATUSES: readonly [401, 403];
17
+ /**
18
+ * A forge call failed because the access token was rejected (HTTP 401/403).
19
+ *
20
+ * Carries the originating HTTP `status` and (optionally) the underlying `cause`
21
+ * so callers can log the real error while reacting to the auth class generically.
22
+ */
23
+ export declare class ForgeAuthError extends Error {
24
+ /** The HTTP status that triggered this auth error (401 or 403). */
25
+ readonly status: number;
26
+ constructor(status: number, message?: string, options?: {
27
+ cause?: unknown;
28
+ });
29
+ }
30
+ /**
31
+ * Type guard: is `e` a {@link ForgeAuthError}?
32
+ *
33
+ * Robust to cross-realm / multiple-bundle-copy situations (where `instanceof`
34
+ * can fail) by ALSO accepting any error shaped like a ForgeAuthError (name +
35
+ * numeric auth status). Non-auth errors return `false` so they are never
36
+ * mistaken for a retry-able auth failure.
37
+ */
38
+ export declare function isForgeAuthError(e: unknown): e is ForgeAuthError;
39
+ /**
40
+ * Extract an HTTP status from an arbitrary thrown value, if it carries one.
41
+ *
42
+ * The `apps/server` GitHub client tags its thrown errors with a numeric
43
+ * `status` field (see client.ts). The adapter uses this to decide whether a
44
+ * client failure is an auth failure (401/403) it should reclassify as a
45
+ * {@link ForgeAuthError}. Returns `undefined` when no usable status is present.
46
+ */
47
+ export declare function getErrorStatus(e: unknown): number | undefined;
48
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,qBAAsB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAQ5E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,cAAc,CAShE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI7D"}
package/dist/errors.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Forge error taxonomy (SDD forge-agnostic — P2 401-recovery fix).
3
+ *
4
+ * The forge boundary throws plain `Error`s on most failures. But the in-job
5
+ * 401-recovery seam (review.ts postback) needs to distinguish an AUTH failure
6
+ * (HTTP 401/403 — the cached installation token was revoked/rotated server-side)
7
+ * from any other failure, GENERICALLY, without sniffing error message strings.
8
+ *
9
+ * {@link ForgeAuthError} is that typed signal. The adapter raises it when an
10
+ * underlying forge call fails with a 401/403 status; the worker catches it via
11
+ * {@link isForgeAuthError}, invalidates the credential provider's cache, re-mints,
12
+ * and retries the call ONCE. Non-auth failures stay plain `Error`s (NOT
13
+ * reclassified) so retry logic only fires for genuine auth failures.
14
+ */
15
+ /** HTTP statuses that mean "the token is no longer accepted" → re-mint + retry. */
16
+ export const FORGE_AUTH_STATUSES = [401, 403];
17
+ /**
18
+ * A forge call failed because the access token was rejected (HTTP 401/403).
19
+ *
20
+ * Carries the originating HTTP `status` and (optionally) the underlying `cause`
21
+ * so callers can log the real error while reacting to the auth class generically.
22
+ */
23
+ export class ForgeAuthError extends Error {
24
+ /** The HTTP status that triggered this auth error (401 or 403). */
25
+ status;
26
+ constructor(status, message, options) {
27
+ super(message ?? `Forge auth failure (HTTP ${status})`, options);
28
+ this.name = 'ForgeAuthError';
29
+ this.status = status;
30
+ // Preserve prototype chain across the TS-to-ES5/ES2015 transpile boundary so
31
+ // `instanceof` keeps working even if a downstream target down-levels classes.
32
+ Object.setPrototypeOf(this, ForgeAuthError.prototype);
33
+ }
34
+ }
35
+ /**
36
+ * Type guard: is `e` a {@link ForgeAuthError}?
37
+ *
38
+ * Robust to cross-realm / multiple-bundle-copy situations (where `instanceof`
39
+ * can fail) by ALSO accepting any error shaped like a ForgeAuthError (name +
40
+ * numeric auth status). Non-auth errors return `false` so they are never
41
+ * mistaken for a retry-able auth failure.
42
+ */
43
+ export function isForgeAuthError(e) {
44
+ if (e instanceof ForgeAuthError)
45
+ return true;
46
+ if (typeof e !== 'object' || e === null)
47
+ return false;
48
+ const candidate = e;
49
+ return (candidate.name === 'ForgeAuthError' &&
50
+ typeof candidate.status === 'number' &&
51
+ FORGE_AUTH_STATUSES.includes(candidate.status));
52
+ }
53
+ /**
54
+ * Extract an HTTP status from an arbitrary thrown value, if it carries one.
55
+ *
56
+ * The `apps/server` GitHub client tags its thrown errors with a numeric
57
+ * `status` field (see client.ts). The adapter uses this to decide whether a
58
+ * client failure is an auth failure (401/403) it should reclassify as a
59
+ * {@link ForgeAuthError}. Returns `undefined` when no usable status is present.
60
+ */
61
+ export function getErrorStatus(e) {
62
+ if (typeof e !== 'object' || e === null)
63
+ return undefined;
64
+ const status = e.status;
65
+ return typeof status === 'number' ? status : undefined;
66
+ }
67
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,mFAAmF;AACnF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,CAAU,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,mEAAmE;IAC1D,MAAM,CAAS;IAExB,YAAY,MAAc,EAAE,OAAgB,EAAE,OAA6B;QACzE,KAAK,CAAC,OAAO,IAAI,4BAA4B,MAAM,GAAG,EAAE,OAAO,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,6EAA6E;QAC7E,8EAA8E;QAC9E,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACxD,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAU;IACzC,IAAI,CAAC,YAAY,cAAc;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,SAAS,GAAG,CAAyC,CAAC;IAC5D,OAAO,CACL,SAAS,CAAC,IAAI,KAAK,gBAAgB;QACnC,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ;QACnC,mBAAyC,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CACtE,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,CAAU;IACvC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC1D,MAAM,MAAM,GAAI,CAA0B,CAAC,MAAM,CAAC;IAClD,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ghagga-forge — Public API
3
+ *
4
+ * Forge-agnostic ports and canonical domain types. The core review engine and
5
+ * the server talk to these abstractions instead of any concrete forge SDK.
6
+ *
7
+ * Architecture invariants (see individual files for detail):
8
+ * - R-AGNOSTIC: `ghagga-core` MUST NOT import this package (value OR type).
9
+ * This package MAY import core in TYPE position only.
10
+ * - R-PROJECTION / R-COMMENTID / R-CAPABILITY / R-RESOLVE / R-GRAPH are pinned
11
+ * by the types and the test suite.
12
+ */
13
+ export type { Actor, ActorKind, AuthorAssociation, ChangedFile, ChangeKind, ChangeRequest, ChangeRequestRef, CommentId, CommentMarker, Commit, DiffRefs, ForgeCapabilities, ForgeEvent, ForgeKind, NormalizedComment, PublishFailure, PublishReport, RepoRef, TenantHint, UnifiedDiff, UpsertSummaryResult, } from './types.js';
14
+ export { ACTOR_KIND, AUTHOR_ASSOCIATION, CHANGE_KIND, FORGE_KIND, } from './types.js';
15
+ export type { ForgeAdapter, ForgeAdapterBase, GraphReadCapable, InlineCapable, InlineComment, MarkerExtractable, ReactionCapable, ReactionKind, } from './ports/forge-adapter.js';
16
+ export { REACTION_KIND } from './ports/forge-adapter.js';
17
+ export type { CiDispatchRequest, CiDispatchResult, CiRunner, EnsureWorkflowResult, } from './ports/ci-runner.js';
18
+ export { FORGE_AUTH_STATUSES, ForgeAuthError, getErrorStatus, isForgeAuthError } from './errors.js';
19
+ export type { ForgeCredentialProvider } from './ports/credential-provider.js';
20
+ export type { ForgeWebhookCodec } from './ports/webhook-codec.js';
21
+ export type { ForgeRegistry, UnknownForgeErrorContext } from './registry.js';
22
+ export { MapForgeRegistry, UnknownForgeError } from './registry.js';
23
+ export type { GitHubAppCredentialProviderDeps } from './adapters/github/github-app-credential-provider.js';
24
+ export { BUDGET_SECONDS, GitHubAppCredentialProvider, SKEW_SECONDS, } from './adapters/github/github-app-credential-provider.js';
25
+ export type { GitHubClientPort, GitHubInstallationTokenMintWithExpiry, GitHubReactionContent, MintedInstallationToken, } from './adapters/github/github-client-port.js';
26
+ export type { GitHubForgeAdapterDeps } from './adapters/github/github-forge-adapter.js';
27
+ export { GitHubForgeAdapter } from './adapters/github/github-forge-adapter.js';
28
+ export { StaticTokenProvider } from './adapters/github/static-token-provider.js';
29
+ export type { GitLabClientPort, GitLabDiffPosition, GitLabNote, } from './adapters/gitlab/gitlab-client-port.js';
30
+ export type { GitLabForgeAdapterDeps } from './adapters/gitlab/gitlab-forge-adapter.js';
31
+ export { GitLabForgeAdapter } from './adapters/gitlab/gitlab-forge-adapter.js';
32
+ export { githubCommentId, gitlabCommentId } from './comment-id.js';
33
+ export { repoRefEquals } from './ref.js';
34
+ export { toCommitMessages, toFileList, toReviewContext } from './project.js';
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,YAAY,EACV,KAAK,EACL,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,OAAO,EACP,UAAU,EACV,WAAW,EACX,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC;AAIpB,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,YAAY,GACb,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAIzD,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EACR,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpG,YAAY,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAIlE,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AASpE,YAAY,EAAE,+BAA+B,EAAE,MAAM,qDAAqD,CAAC;AAC3G,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,YAAY,GACb,MAAM,qDAAqD,CAAC;AAC7D,YAAY,EACV,gBAAgB,EAChB,qCAAqC,EACrC,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,yCAAyC,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AAOjF,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,GACX,MAAM,yCAAyC,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAQ/E,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ghagga-forge — Public API
3
+ *
4
+ * Forge-agnostic ports and canonical domain types. The core review engine and
5
+ * the server talk to these abstractions instead of any concrete forge SDK.
6
+ *
7
+ * Architecture invariants (see individual files for detail):
8
+ * - R-AGNOSTIC: `ghagga-core` MUST NOT import this package (value OR type).
9
+ * This package MAY import core in TYPE position only.
10
+ * - R-PROJECTION / R-COMMENTID / R-CAPABILITY / R-RESOLVE / R-GRAPH are pinned
11
+ * by the types and the test suite.
12
+ */
13
+ export { ACTOR_KIND, AUTHOR_ASSOCIATION, CHANGE_KIND, FORGE_KIND, } from './types.js';
14
+ export { REACTION_KIND } from './ports/forge-adapter.js';
15
+ // ─── Ports: credentials & webhooks ──────────────────────────────
16
+ // ─── Errors ─────────────────────────────────────────────────────
17
+ // ForgeAuthError + isForgeAuthError: the typed 401/403 signal the worker catches
18
+ // to drive the in-job credential re-mint + retry (P2 401-recovery seam).
19
+ export { FORGE_AUTH_STATUSES, ForgeAuthError, getErrorStatus, isForgeAuthError } from './errors.js';
20
+ export { MapForgeRegistry, UnknownForgeError } from './registry.js';
21
+ export { BUDGET_SECONDS, GitHubAppCredentialProvider, SKEW_SECONDS, } from './adapters/github/github-app-credential-provider.js';
22
+ export { GitHubForgeAdapter } from './adapters/github/github-forge-adapter.js';
23
+ export { StaticTokenProvider } from './adapters/github/static-token-provider.js';
24
+ export { GitLabForgeAdapter } from './adapters/gitlab/gitlab-forge-adapter.js';
25
+ // ─── Identity helpers ───────────────────────────────────────────
26
+ // ─── Comment-id boxing (R-COMMENTID) ────────────────────────────
27
+ // Sanctioned, side-effect-free boxing helper reusable by both the server
28
+ // worker and the P3 CLI (the adapter still RETURNS native numbers; this is the
29
+ // caller-local boxing step, NOT a branding of the adapter return type).
30
+ export { githubCommentId, gitlabCommentId } from './comment-id.js';
31
+ export { repoRefEquals } from './ref.js';
32
+ // ─── Projection helpers ─────────────────────────────────────────
33
+ export { toCommitMessages, toFileList, toReviewContext } from './project.js';
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA2BH,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC;AAcpB,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAWzD,mEAAmE;AAEnE,mEAAmE;AACnE,iFAAiF;AACjF,yEAAyE;AACzE,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAOpG,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAUpE,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,YAAY,GACb,MAAM,qDAAqD,CAAC;AAQ7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AAajF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAE/E,mEAAmE;AAEnE,mEAAmE;AACnE,yEAAyE;AACzE,+EAA+E;AAC/E,wEAAwE;AACxE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,mEAAmE;AAEnE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Forge-internal boundary checker (task 0.8).
3
+ *
4
+ * Enforces the R-AGNOSTIC import rules that Biome 2.5 cannot express on its own,
5
+ * specifically the TYPE-vs-VALUE distinction for forge→core imports:
6
+ *
7
+ * - `import type { X } from 'ghagga-core'` ✅ allowed (type position)
8
+ * - `import { type X } from 'ghagga-core'` ✅ allowed (all specifiers inline-type)
9
+ * - `import { X } from 'ghagga-core'` ❌ forbidden (value position)
10
+ * - `export { X } from 'ghagga-core'` ❌ forbidden (re-export = value escape)
11
+ * - `import('ghagga-core')` / `require('ghagga-core')` ❌ forbidden (dynamic = value)
12
+ * - `ghagga-core/<subpath>` / `@ghagga/core` ❌ same rules (subpath & scoped alias)
13
+ * - any import of `apps/server` / `ghagga-server` ❌ forbidden outright
14
+ *
15
+ * Biome's `noRestrictedImports` is a blunt path ban — it would reject the LEGAL
16
+ * `import type` forge→core case (false positive). This checker closes that gap
17
+ * so the boundary test (`lint-boundary.test.ts`) can pin both directions.
18
+ *
19
+ * It is deliberately a small, dependency-free scanner over source text rather
20
+ * than a full AST pass: the boundary rules only care about import-statement
21
+ * forms, which are matched reliably with focused regexes. The Biome overrides in
22
+ * `biome.json` cover the forge↛server and core↛forge path bans; this module
23
+ * adds the type-position nuance for forge→core.
24
+ *
25
+ * IMPLEMENTATION LIVES IN A `.mjs` SIBLING (P0 fix F2 hardening):
26
+ * The actual scanner logic is in `lint-boundary.impl.mjs` (plain JS). The
27
+ * lint:boundary RUNNER (`scripts/lint-boundary.mjs`) imports that `.mjs`
28
+ * DIRECTLY, so the gate never imports a `.ts` file at runtime and therefore does
29
+ * NOT depend on Node's experimental TS type-stripping (Node >= 22.18 / unflagged)
30
+ * — eliminating the `ERR_UNKNOWN_FILE_EXTENSION` Node-version fragility. This
31
+ * module re-exports the function with a precise TS type and owns the
32
+ * {@link BoundaryViolation} interface so the tests keep a fully typed import.
33
+ */
34
+ export type { BoundaryViolation } from './lint-boundary.impl.mjs';
35
+ export { BANNED_CLIENT_FORGE_FNS, checkForgeBoundary, checkServerForgeClientBoundary, } from './lint-boundary.impl.mjs';
36
+ //# sourceMappingURL=lint-boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint-boundary.d.ts","sourceRoot":"","sources":["../src/lint-boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Type surface for the plain-JS boundary checker implementation
3
+ * ({@link lint-boundary.impl.mjs}). Lets `lint-boundary.ts` import the runtime
4
+ * function with a precise type WITHOUT enabling `allowJs` for the package.
5
+ */
6
+
7
+ /** A single boundary violation. */
8
+ export interface BoundaryViolation {
9
+ /** The offending import source/module specifier. */
10
+ module: string;
11
+ /** Why it violates the boundary. */
12
+ reason: string;
13
+ }
14
+
15
+ /**
16
+ * Scan a single source file's text for forge-boundary violations.
17
+ *
18
+ * @param rawSource the file contents to scan.
19
+ * @returns the list of violations (empty when the file is clean).
20
+ */
21
+ export function checkForgeBoundary(rawSource: string): BoundaryViolation[];
22
+
23
+ /** The 11 @internal client.ts forge-adapter fns banned outside the factory. */
24
+ export const BANNED_CLIENT_FORGE_FNS: readonly string[];
25
+
26
+ /**
27
+ * Scan an apps/server source file for direct use of the 11 @internal client.ts
28
+ * forge-adapter fns. Catches NAMED imports, the NAMESPACE-alias member-access
29
+ * bypass, dynamic `import()`/`require()` member access, re-export laundering
30
+ * (`export { ... } from` / `export * from`), and destructuring off a namespace
31
+ * or dynamic-import alias — all the forms Biome's `noRestrictedImports` cannot
32
+ * see.
33
+ *
34
+ * @param filePath the file path (used only in the violation message).
35
+ * @param rawSource the file contents to scan.
36
+ * @returns the list of violations (empty when the file is clean).
37
+ */
38
+ export function checkServerForgeClientBoundary(
39
+ filePath: string,
40
+ rawSource: string,
41
+ ): BoundaryViolation[];