@reneturcios/greenlight 0.0.0-stage → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,213 @@
1
+ import type { IssueSnapshot, LinkedPR, CommentSignal, GateKind, Gate, MentionedPR, DuplicateTarget } from "./types.js";
2
+ import { type WorkflowFile } from "./gates.js";
3
+ export declare class RateLimitError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ /** Thrown by apiGet/apiPost for HTTP 404, so callers can treat "missing" as data. */
7
+ export declare class ApiNotFoundError extends Error {
8
+ constructor(path: string);
9
+ }
10
+ export declare function githubToken(): string | undefined;
11
+ export declare function apiGet(path: string): Promise<unknown>;
12
+ /** POST helper for write operations (automation commands). Read paths use apiGet. */
13
+ export declare function apiPost(path: string, body: unknown): Promise<unknown>;
14
+ export interface IssueURL {
15
+ owner: string;
16
+ repo: string;
17
+ number: number;
18
+ }
19
+ export declare function parseIssueUrl(url: string): IssueURL;
20
+ /** Fetch issue comments for claim detection. */
21
+ export declare function fetchComments(owner: string, repo: string, number: number): Promise<CommentSignal[]>;
22
+ /**
23
+ * Find PRs that reference an issue. Timeline cross-references and a text
24
+ * search for the issue number are discovery only: every candidate PR is
25
+ * then fetched individually and scored with classifyLinkStrength, so a PR
26
+ * that merely lists a sibling issue (e.g. pdfcn #91 closing #70 while
27
+ * mentioning #61) is stored as a "mention", not a "closing" PR. See issue #1.
28
+ */
29
+ export declare function fetchLinkedPRs(owner: string, repo: string, number: number): Promise<LinkedPR[]>;
30
+ /** Minimal PR detail needed for the comment-mention cross-check. */
31
+ export interface PullRefDetail {
32
+ number: number;
33
+ state: "open" | "closed";
34
+ merged: boolean;
35
+ title: string;
36
+ body: string;
37
+ url: string;
38
+ }
39
+ export interface MentionedPRDeps {
40
+ fetchPull?: (owner: string, repo: string, number: number) => Promise<PullRefDetail | null>;
41
+ }
42
+ /**
43
+ * Policy files fetched for gate extraction. Binding contribution rules
44
+ * increasingly live in house-rules files that CONTRIBUTING only links to
45
+ * (reticle keeps its claim-first, DCO, .changes and `pnpm verify` rules in
46
+ * AGENTS.md), so CONTRIBUTING variants are fetched together with AGENTS.md
47
+ * and CLAUDE.md at the root and under .github/. fastmcp moved its rules
48
+ * again in 2026, into docs/development/contributing.mdx behind a pointer
49
+ * CONTRIBUTING.md, which is why the development-guide paths are here too.
50
+ */
51
+ export declare const HOUSE_RULE_FILES: readonly ["CONTRIBUTING.md", "contributing.md", ".github/CONTRIBUTING.md", "docs/development/contributing.mdx", "docs/development/contributing.md", "AGENTS.md", "CLAUDE.md", ".github/AGENTS.md", ".github/CLAUDE.md"];
52
+ export interface HouseRuleDeps {
53
+ /** Test seam: fetch a repo-root file's raw text, or undefined when absent. */
54
+ fetchRaw?: (file: string) => Promise<string | undefined>;
55
+ }
56
+ /** Fetch all existing house-rules files for a repo (best-effort, cached). */
57
+ export declare function fetchHouseRules(owner: string, repo: string, deps?: HouseRuleDeps): Promise<{
58
+ file: string;
59
+ text: string;
60
+ }[]>;
61
+ /** Fetch CONTRIBUTING.md raw text; returns undefined when the repo has none. */
62
+ export declare function fetchContributing(owner: string, repo: string): Promise<string | undefined>;
63
+ /**
64
+ * Extract PR references from comment bodies: bare #NNN mentions and full
65
+ * github.com/<owner>/<repo>/pull/NNN URLs. Returns deduplicated numbers in
66
+ * order of first appearance.
67
+ */
68
+ export declare function extractPRReferences(comments: CommentSignal[], owner: string, repo: string): number[];
69
+ /**
70
+ * PR references per citing comment author: number -> first comment author
71
+ * that cited it. Used to tag MentionedPRs with fromAuthor.
72
+ */
73
+ export declare function extractPRReferenceAuthors(comments: CommentSignal[], owner: string, repo: string): Map<number, string>;
74
+ /**
75
+ * Cross-check PR references found in comments against the PR API.
76
+ *
77
+ * Motivation: reticlehq/reticle#691 got a clean GO verdict while its comments
78
+ * contained a partial claim ("Taking the remaining fallback ask") and a
79
+ * heads-up that PR #861 covers that work. Neither signal existed in the
80
+ * timeline or the issue-number search, so both were missed.
81
+ *
82
+ * All comments are scanned (PR references are cheap to extract), but each
83
+ * result records whether the citing comment came from the issue author.
84
+ * The open-PR downgrade (rule 11) only trusts non-author heads-ups; the
85
+ * partially-addressed rule (rule 12) also reads the author's, because the
86
+ * maintainer narrating what already shipped is usually the issue author.
87
+ * References already found via the timeline/search, and the issue's own
88
+ * number, are skipped. Bounded at 5 PR fetches per call; the walk is
89
+ * best-effort and cached like the rest of the issue data.
90
+ */
91
+ export declare function fetchCommentMentionedPRs(owner: string, repo: string, issueNumber: number, comments: CommentSignal[], knownPRNumbers: number[], deps?: MentionedPRDeps & {
92
+ issueAuthor?: string;
93
+ }): Promise<MentionedPR[]>;
94
+ export interface WorkflowDeps {
95
+ /** Test seam: list .github/workflows file names, or undefined when the directory is absent. */
96
+ listFiles?: () => Promise<string[] | undefined>;
97
+ /** Test seam: fetch one workflow file's raw text, or undefined when absent. */
98
+ fetchRaw?: (name: string) => Promise<string | undefined>;
99
+ }
100
+ /**
101
+ * List the repo's .github/workflows files and fetch their contents
102
+ * (best-effort, cached). Contents fetch uses raw.githubusercontent, so it
103
+ * does not consume the api.github.com rate limit. Capped at 12 files: a
104
+ * repo with more workflows still shows the signal on the suspiciously
105
+ * named ones first (they are not reordered; name matching catches them).
106
+ */
107
+ export declare function listWorkflows(owner: string, repo: string, deps?: WorkflowDeps): Promise<WorkflowFile[]>;
108
+ /** Scan a repo's workflows for auto-close automation; returns detected-only gates. */
109
+ export declare function scanWorkflowGates(owner: string, repo: string, deps?: WorkflowDeps): Promise<Gate[]>;
110
+ /**
111
+ * Extract duplicate target issue numbers from comment bodies ("duplicate
112
+ * of #N", GitHub's "This issue has been marked as a duplicate of #N").
113
+ * Pure; tests drive it directly. The last capture group always holds the
114
+ * number.
115
+ */
116
+ export declare function extractDuplicateTargets(comments: CommentSignal[]): number[];
117
+ export interface DuplicateTargetDeps {
118
+ /** Test seam: issue timeline events, used instead of the live call. */
119
+ timelineEvents?: Array<Record<string, unknown>>;
120
+ /** Test seam: fetch a referenced issue's live state. */
121
+ fetchIssue?: (owner: string, repo: string, number: number) => Promise<{
122
+ state: string;
123
+ stateReason?: string;
124
+ } | null>;
125
+ }
126
+ /**
127
+ * Find other issues this one is marked a duplicate of. Signals: "duplicate
128
+ * of #N" comments (n8n's bot posts "marked as a duplicate of #N"), and
129
+ * marked_as_duplicate timeline events when the API surfaces them. Each
130
+ * unique target is fetched for its current state (bounded at 3), because
131
+ * only a target closed as completed means the work already shipped and a
132
+ * build here is wasted.
133
+ */
134
+ export declare function fetchDuplicateTargets(owner: string, repo: string, number: number, comments: CommentSignal[], deps?: DuplicateTargetDeps): Promise<DuplicateTarget[]>;
135
+ /** A PR URL found in a fix-announcement comment, possibly in another repo of the same org. */
136
+ export interface AttemptPRLink {
137
+ author: string;
138
+ owner: string;
139
+ repo: string;
140
+ number: number;
141
+ url: string;
142
+ }
143
+ /**
144
+ * Extract PR links from fix-announcement comments. Comments from bots and
145
+ * from the issue author are skipped. The URLs routinely point at sibling
146
+ * repos in the same org (tscircuit/tscircuit#5563's fixes live in
147
+ * tscircuit/core and tscircuit/checks), so they are not limited to the
148
+ * issue's own repo. Pure; tests drive it directly. Bounded at 5 links.
149
+ */
150
+ export declare function extractAttemptLinkedPRs(comments: CommentSignal[], issueAuthor: string): AttemptPRLink[];
151
+ /** Count open issues labelled good-first-issue or help-wanted. */
152
+ export declare function countStarterIssues(owner: string, repo: string): Promise<number | null>;
153
+ /** Detect a collaborators-only PR creation policy via the community metrics endpoint. */
154
+ export declare function detectRepoPolicy(owner: string, repo: string): Promise<{
155
+ collaboratorsOnly: boolean;
156
+ }>;
157
+ /** Build a full IssueSnapshot from the live API. */
158
+ export declare function buildIssueSnapshot(owner: string, repo: string, number: number): Promise<IssueSnapshot>;
159
+ export interface ScanCandidate {
160
+ number: number;
161
+ title: string;
162
+ url: string;
163
+ labels: string[];
164
+ updatedAt: string;
165
+ }
166
+ /** List open good-first-issue / help-wanted issues for scan mode. */
167
+ export declare function listStarterIssues(owner: string, repo: string, limit?: number): Promise<ScanCandidate[]>;
168
+ /** Raw issue-list item shape from GET /repos/:o/:r/issues. */
169
+ interface IssueListItem {
170
+ number: number;
171
+ title: string;
172
+ html_url: string;
173
+ labels: Array<{
174
+ name: string;
175
+ }>;
176
+ updated_at: string;
177
+ pull_request?: unknown;
178
+ assignees?: Array<{
179
+ login: string;
180
+ }>;
181
+ }
182
+ /**
183
+ * Filter raw issue-list items down to scan candidates: real issues (not
184
+ * pull requests) with no assignees, newest first. Pure so tests can drive
185
+ * it with fixtures.
186
+ */
187
+ export declare function toScanCandidates(items: IssueListItem[]): ScanCandidate[];
188
+ /**
189
+ * Fallback candidate list for scan mode: the most recent open unassigned
190
+ * issues in the repo, for repos whose landable issues do not carry
191
+ * good-first-issue / help-wanted labels. Lists broadly (up to `cap`) so
192
+ * the caller can take the newest slice.
193
+ */
194
+ export declare function listRecentUnassignedIssues(owner: string, repo: string, cap?: number): Promise<ScanCandidate[]>;
195
+ export interface ScanCandidateList {
196
+ issues: ScanCandidate[];
197
+ /** True when the label pass came back empty and the fallback list was used. */
198
+ fallbackUsed: boolean;
199
+ }
200
+ export interface ScanDeps {
201
+ starter?: (owner: string, repo: string, limit: number) => Promise<ScanCandidate[]>;
202
+ recent?: (owner: string, repo: string, cap: number) => Promise<ScanCandidate[]>;
203
+ }
204
+ /**
205
+ * Scan candidates for a repo: the label-tagged starter list first; when it
206
+ * is empty, fall back to recent open unassigned issues (opt out with
207
+ * fallback: false). Injectable deps keep tests offline.
208
+ */
209
+ export declare function scanCandidates(owner: string, repo: string, opts?: {
210
+ fallback?: boolean;
211
+ limit?: number;
212
+ }, deps?: ScanDeps): Promise<ScanCandidateList>;
213
+ export type { GateKind };