@reneturcios/greenlight 0.17.1 → 0.19.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.
package/README.md CHANGED
@@ -38,12 +38,12 @@ Every verdict and every report cites its evidence: the quoted policy line, the c
38
38
  ## Who it is for
39
39
 
40
40
  * **Contributors (human).** Run `check` before you spend an evening on an issue. Run `scan` when you are picking what to work on. Run `ci-prep` before you push.
41
- * **AI agents and coding tools.** One command (`init --agent claude-code`, also cursor, copilot, windsurf, cline) installs the MCP config plus the one behavior rule: check Greenlight before opening a PR. Agents waste the most tokens fixing already-taken issues; 31 MCP tools make the whole engine available mid-run.
41
+ * **AI agents and coding tools.** One command (`init --agent claude-code`, also cursor, copilot, windsurf, cline) installs the MCP config plus the one behavior rule: check Greenlight before opening a PR. Agents waste the most tokens fixing already-taken issues; 32 MCP tools make the whole engine available mid-run.
42
42
  * **Maintainers.** `guard` on each incoming PR, `radar` across the whole open queue, `digest` for the weekly triage, and the roadmap below for the control room: stale-claim sweeps, policy enforcement, AI-PR filtering.
43
43
 
44
44
  ## Proof, stated plainly
45
45
 
46
- * 1039 tests, all passing, including a fixture corpus pinned to real campaign cases (a taken issue, a weak PR mention, claim-first gates, scoped claims, half-shipped issues, stale claims, paused-PR workflows).
46
+ * 1079 tests, all passing, including a fixture corpus pinned to real campaign cases (a taken issue, a weak PR mention, claim-first gates, scoped claims, half-shipped issues, stale claims, paused-PR workflows).
47
47
  * Dogfooded on real campaigns: the toolkit has authored 80+ pull requests across dozens of public open-source repos. Every feature exists because a real contribution died without it.
48
48
  * Distributed everywhere the audience already looks: npm (`@reneturcios/greenlight`), the MCP Registry, and GHCR (`ghcr.io/cloudcorprecords/greenlight`), plus Docker and direct MCP stdio.
49
49
  * Two living datasets ship with it: the Contribution Friction Index (`data/friction-index.json`) and the Agent Policy Index (505 repos, 127 gated, 19 agent bans, published at `docs/policy.html`), both built from real campaign field work, not scraped guesses.
@@ -103,7 +103,7 @@ One command sets it up:
103
103
  npx @reneturcios/greenlight init --agent claude-code
104
104
  ```
105
105
 
106
- (also `cursor`, `copilot`, `windsurf`, `cline`; repeat `--agent` for more than one). Init copies the MCP config into your project and installs the workflow instruction snippet that teaches your agent the one behavior rule: **check Greenlight before it opens a PR.** Prefer doing it by hand? The [`integrations/`](integrations/README.md) drop-in pack has ready-to-copy MCP configs plus the same snippets. Then verify with a "list your MCP tools" prompt; the greenlight server carries 31 tools (see the list below).
106
+ (also `cursor`, `copilot`, `windsurf`, `cline`; repeat `--agent` for more than one). Init copies the MCP config into your project and installs the workflow instruction snippet that teaches your agent the one behavior rule: **check Greenlight before it opens a PR.** Prefer doing it by hand? The [`integrations/`](integrations/README.md) drop-in pack has ready-to-copy MCP configs plus the same snippets. Then verify with a "list your MCP tools" prompt; the greenlight server carries 32 tools (see the list below).
107
107
 
108
108
  ## CLI
109
109
 
@@ -133,6 +133,13 @@ npx @reneturcios/greenlight scan PrefectHQ/fastmcp
133
133
  npx @reneturcios/greenlight xray usebruno/bruno --issues 8149,9319
134
134
  npx @reneturcios/greenlight scan usebruno/bruno --deep
135
135
 
136
+ # Scans stop on a wall-time budget (default 600s; override with --max-time <seconds>).
137
+ # A scan that hits the budget returns the results gathered so far, labeled partial:
138
+ # --json gains partial: true, partial_reason ("scan time budget 600s reached; evaluated
139
+ # K of M candidates"), evaluated, and candidates; the human output prints the results
140
+ # plus a "Partial scan:" line naming the budget. Partial scans still exit 0.
141
+ npx @reneturcios/greenlight scan PrefectHQ/prefect --max-time 300
142
+
136
143
  # Friction score for a repo
137
144
  npx @reneturcios/greenlight friction triggerdotdev/trigger.dev
138
145
 
@@ -283,7 +290,7 @@ The same engine is exposed over stdio for coding agents (Claude, Cursor, Cline).
283
290
  }
284
291
  ```
285
292
 
286
- Tools (31):
293
+ Tools (32):
287
294
 
288
295
  * `check_issue` - verdict for one issue URL
289
296
  * `scan_repo` - rank a repo's open issues, GO first (label-tagged starter issues, falling back to recent open unassigned issues; `deep: true` timeline-x-rays the candidates first)
@@ -293,6 +300,7 @@ Tools (31):
293
300
  * `verify` - honest base-vs-head test delta for a checked-out repo (red-first, never raw counts)
294
301
  * `shipcheck` - release-readiness: GitHub release, npm version, registry record, and GHCR tag, GO / NO-GO per surface
295
302
  * `passport` - contributor passport for a GitHub login: ledger profile, merged PR count, honor ratio
303
+ * `bounty_check` - bounty-liveness classification for one issue: live platform, unverifiable claim, or dead platform
296
304
  * `claim_issue` - verdict-gated claim comment (dry run unless `execute: true`)
297
305
  * `prep_issue` - fork and branch check plus a local PREP.md checklist with the repo's learned conventions (dry run unless `execute: true`)
298
306
  * `open_pr` - DCO-aware PR opening from a fork branch (dry run unless `execute: true`)
@@ -328,7 +336,7 @@ The verdict engine is deterministic (no LLM) and checks, in order:
328
336
  3. An open linked PR that **closes** the issue: **TAKEN**. A PR that merely mentions the issue number (like pdfcn PR #91, which closes #70 but lists sibling issues in its body) only adds a `weak-pr-mention` flag and downgrades an otherwise-GO verdict to **RISKY**. Marked a duplicate of an issue closed as completed: **RISKY** with the `duplicate-of-closed` flag (the work already shipped; a still-open duplicate target only adds a `duplicate-of-open` warning with its URL).
329
337
  4. Two or more strong claimants: **TAKEN** (claim race).
330
338
  5. One strong fresh whole-issue claim (under 45 days): **RISKY** with the `claimed` flag. A fresh claim scoped to part of the issue ("taking the remaining fallback ask"): **RISKY** with `claimed` + `partial-claim` flags and a reason naming the claimer and the scope, never TAKEN.
331
- 6. One strong claim older than 45 days with no closing PR: **RISKY** with the `stale_claim` flag. The claimant probably walked away, but nobody knows for sure. Stale scoped claims get the same treatment.
339
+ 6. One strong claim older than 45 days with no closing PR: **RISKY** with the `stale_claim` flag. The claimant probably walked away, but nobody knows for sure. Stale scoped claims get the same treatment. Claims older than 180 days with no closing PR are *decayed* instead: disclosed with the `stale-signal` flag and a down-weighting reason, but never blocking on their own (two ancient claims no longer form a claim race, and a single ancient claim no longer downgrades a clean issue).
332
340
  7. A hard gate (assignment, vouch, collaborators-only, paused unsolicited PRs, external PRs auto-closed by workflow) with nobody assigned: **GATED**. The last two come from CONTRIBUTING language ("paused unsolicited pull requests") and a scan of `.github/workflows` for close-external-PR automation.
333
341
  8. No activity for 180+ days: **STALE**.
334
342
  9. A soft claim-first gate (the repo asks contributors to comment before starting, like pdfcn): never **GATED**; it caps an otherwise-GO verdict at **RISKY** with a note, the same downgrade shape as a weak PR mention.
@@ -336,9 +344,13 @@ The verdict engine is deterministic (no LLM) and checks, in order:
336
344
  11. An open PR referenced in a non-author comment whose title/body mentions the issue number: downgrades an otherwise-GO verdict to **RISKY** with the `comment-mentioned-pr` flag ("may already cover part of this issue"). Neither can produce **TAKEN**; only closing PRs do that. Fix-announcement comments ("/attempt #N" commands or "Submitted fix for ... in <PR url>" narratives, often linking PRs in sibling repos) get the same downgrade under their own reason quoting the PR URL, with the `attempt-linked-pr` flag, even when the PR never names the issue. `/attempt` comments also register as strong claims.
337
345
  12. **Partially-addressed**: merged PRs that reference the issue number (comment-mentioned or linked), or maintainer comments signaling part of the work already shipped ("Half addressed by #905, leaving this open for half B", "Shipped in v3.1.0" next to the issue reference). A plain **GO** hides the scoping work this needs, so the flag `partially-addressed` is added, an otherwise-GO verdict is capped at **RISKY** with a reason like "part of this issue already shipped (PR #905, merged); scope your work to what remains". Never **TAKEN**.
338
346
 
339
- Friction score starts at 100 and subtracts gate weights (assignment -25, vouch -35, collaborators-only -50, paused unsolicited PRs -40, scoped pause -20, external PRs auto-closed -50, discussion-first -10, claim-first -5, CLA/DCO -5 each, detected behavioral gate -20), adjusts for external PR merge rate and review/response times when computable, and adds up to +10 for open good-first-issues. Unknown signals stay null and are skipped, never guessed. See `src/friction.ts` for the exact formula.
347
+ Friction score starts at 100 and subtracts gate weights (assignment -25, vouch -35, collaborators-only -50, paused unsolicited PRs -40, scoped pause -20, external PRs auto-closed -50, discussion-first -10, claim-first -5, CLA/DCO -5 each, detected behavioral gate -20), adjusts for external PR merge rate and review/response times when computable, and adds up to +10 for open good-first-issues. Unknown signals stay null and are skipped, never guessed. When the classic starter labels read exactly 0, friction reports a display-only candidate estimate (open, unassigned issues under a wider starter label set, e.g. "beginner friendly", "easy", "first-timers-only") next to the label count; the estimate is never scored and never merged into the label count. See `src/friction.ts` for the exact formula.
348
+
349
+ A behavioral gate is not a policy file: it is read from what maintainers do. `friction` walks the closed-PR list until it has the 15 most recent closed-unmerged PRs within a 12-month recency bound (older closures are excluded, with a "stale signal" note when the newest closure itself is older than the bound), then reads maintainer-side comments for AI-rejection evidence (AI attribution plus rejection language, explicit phrases like "AI slop" or an invoked "AI policy", with welcoming overrides like "AI-assisted PRs are welcome" suppressing the signal). Two independent PRs mark the repo as behaviorally gated; a single weak signal is carried as a warning, while a single explicit rejection phrase detects on its own. `friction --json` reports it as the top-level `behavioral_gate` field (`detected`, `strength`, `threshold`, `evidence` with PR numbers, quotes, and URLs, `notes`); that field is the canonical location, and `signals.behavioralGate` is kept as an identical mirror for index-pipeline compatibility. Index pipelines like the rebaseline read the canonical field to reclassify repos whose written policy is silent but whose practice is hostile. API cost is bounded at 48 calls: up to 3 list calls to collect 15 closed-unmerged PRs plus up to 3 evidence pages per PR (issue comments, reviews, review comments); skip it with `--no-behavioral`.
350
+
351
+ `friction --lite` is the canonical lightweight friction computation: policy gates ONLY. It fetches the policy sources (CONTRIBUTING variants, AGENTS.md, CLAUDE.md, and AI_POLICY.md at the root, under `.github/`, and under `docs/`, plus the org-level AI_POLICY.md), parses gates from them, applies the collaborators-only push from `detectRepoPolicy` (no API calls today), and scores with the exact documented formula: start at 100, add the gate weights with the same GATE_WEIGHTS, clamp to [0, 100]. It skips the behavioral gate pass, merge velocity (and with it the external PR merge-rate term), first-review and issue-response timing, and the good-first-issue bonus: all outcome signals stay null and are skipped, never guessed, `signals.note` reads `"lite"`, and the report carries `lite: true`. The `gates` and `score` fields have the same shape as full friction, so the Agent Policy Index scanner can adopt `--lite` as the single friction-lite implementation instead of maintaining its own.
340
352
 
341
- A behavioral gate is not a policy file: it is read from what maintainers do. `friction` walks the closed-PR list until it has the 15 most recent closed-unmerged PRs within a 12-month recency bound (older closures are excluded, with a "stale signal" note when the newest closure itself is older than the bound), then reads maintainer-side comments for AI-rejection evidence (AI attribution plus rejection language, explicit phrases like "AI slop" or an invoked "AI policy", with welcoming overrides like "AI-assisted PRs are welcome" suppressing the signal). Two independent PRs mark the repo as behaviorally gated; a single weak signal is carried as a warning, while a single explicit rejection phrase detects on its own. `friction --json` reports it as the top-level `behavioral_gate` field (`detected`, `strength`, `threshold`, `evidence` with PR numbers, quotes, and URLs, `notes`); that field is the canonical location, and `signals.behavioralGate` is kept as an identical mirror for index-pipeline compatibility. Index pipelines like the rebaseline read the canonical field to reclassify repos whose written policy is silent but whose practice is hostile. API cost is bounded at 18 calls: up to 3 list calls to collect 15 closed-unmerged PRs plus one comment page per PR; skip it with `--no-behavioral`.
353
+ Every friction report (lite and full) also carries `policy_provenance`: one entry per probed policy source saying whether it was read, was absent, or failed to fetch, so "no policy file" is never again confused with "policy fetch failed" (previously both looked identical). In human output this renders as one summary line, e.g. `policy sources: 2 read (CONTRIBUTING.md, AGENTS.md), 3 absent, 0 fetch errors`.
342
354
 
343
355
  `wave` turns the Agent Policy Index into a build queue in one command: pure-open repos with outsider merge share, star band, and starter issues are x-rayed with the existing xray engine (CLEAR / RISKY / TAKEN with competing PRs named), repos with detected behavioral gates are excluded, repos where you already have an open PR are skipped, and the run stops with marked partial results when the core API budget falls under the floor (default 800) or search throttling hits. Human and `--json` output; every stage reports the API calls it used. Candidates rank by winnability by default (outsider merge share, then friction, then stars); `--sort stars` restores the old order.
344
356
 
@@ -346,7 +358,7 @@ A scoped intake pause ("not accepting pull requests that add new formatting opti
346
358
 
347
359
  Autopilot keeps a standing branch-health board: per-PR mergeable, mergeable_state, base SHA, and behind-count persist in its state between runs, render in the report output, and ride along in the `autopilot_report` MCP tool (`branch_health`, `branch_board`).
348
360
 
349
- Three more CLI commands are agent-callable MCP tools: `verify` (honest base-vs-head test delta), `shipcheck` (release, npm, registry, and GHCR readiness), and `passport` (claim-ledger contributor profile).
361
+ Four more CLI commands are agent-callable MCP tools: `verify` (honest base-vs-head test delta), `shipcheck` (release, npm, registry, and GHCR readiness), `passport` (claim-ledger contributor profile), and `bounty` (bounty-liveness for one issue).
350
362
 
351
363
  ### Agent-policy gates and triage holds
352
364
 
@@ -4,12 +4,19 @@ export interface BehavioralClosedPR {
4
4
  author: string;
5
5
  closedAt: string;
6
6
  }
7
+ /**
8
+ * One maintainer-authored text blob on a closed-unmerged PR.
9
+ * v0.18.0 (item 5): maintainer rejections often live in review bodies
10
+ * and line comments, not issue comments, so all three surfaces feed the
11
+ * same matcher. `source` defaults to "comment" for older callers.
12
+ */
7
13
  export interface BehavioralMaintainerComment {
8
14
  pr: number;
9
15
  author: string;
10
16
  association: string;
11
17
  body: string;
12
18
  url: string;
19
+ source?: "comment" | "review" | "review_comment";
13
20
  }
14
21
  export interface BehavioralSignal {
15
22
  pr: number;
@@ -19,6 +26,8 @@ export interface BehavioralSignal {
19
26
  url: string;
20
27
  /** True for explicit phrases like "AI slop"; weaker signals can still count toward strength. */
21
28
  strong: boolean;
29
+ /** Which surface carried the evidence: "comment" | "review" | "review_comment". */
30
+ source?: "comment" | "review" | "review_comment";
22
31
  }
23
32
  export interface BehavioralGate {
24
33
  /** True when strength >= threshold: the maintainer rejects AI-built PRs in practice. */
@@ -53,9 +62,10 @@ export declare function partitionByRecency(prs: BehavioralClosedPR[], nowMs: num
53
62
  newestClosedAt: string | null;
54
63
  };
55
64
  /**
56
- * API cost contract: 1-3 calls to walk the closed-PR list (stops early
57
- * once 15 unmerged PRs are found) plus at most one comment page per
58
- * closed-unmerged PR.
65
+ * API cost contract (v0.18.0, item 5): 1-3 calls to walk the closed-PR
66
+ * list (stops early once 15 unmerged PRs are found) plus up to three
67
+ * evidence pages per closed-unmerged PR (issue comments, reviews,
68
+ * review comments).
59
69
  */
60
70
  export declare const BEHAVIORAL_MAX_API_CALLS: number;
61
71
  /** Pure: does one comment body read as maintainer AI-rejection evidence? */
package/dist/behavior.js CHANGED
@@ -17,9 +17,10 @@
17
17
  * itself is older than the bound). The closed-PR list is
18
18
  * walked (newest first, up to 3 pages of 30) until 15 unmerged PRs are
19
19
  * collected, because merged PRs carry no closure evidence and an
20
- * active repo's unmerged closures can sit pages deep. Comment fetches
21
- * (first page, per_page=100) run only for those unmerged PRs, so the
22
- * total cost is 1-3 list calls + up to 15 comment calls (18 max).
20
+ * active repo's unmerged closures can sit pages deep. Evidence fetches
21
+ * (first page, per_page=100) run only for those unmerged PRs, on all
22
+ * three surfaces (issue comments, reviews, review comments), so the
23
+ * total cost is 1-3 list calls + up to 3 pages x 15 PRs (48 max).
23
24
  * - Maintainer-side only: comment author_association must be OWNER,
24
25
  * MEMBER, or COLLABORATOR, and the comment author must not be the PR
25
26
  * author (a maintainer closing their own PR is not evidence against
@@ -80,11 +81,12 @@ export function partitionByRecency(prs, nowMs, months = BEHAVIORAL_RECENCY_MONTH
80
81
  return { fresh, staleCount, newestClosedAt };
81
82
  }
82
83
  /**
83
- * API cost contract: 1-3 calls to walk the closed-PR list (stops early
84
- * once 15 unmerged PRs are found) plus at most one comment page per
85
- * closed-unmerged PR.
84
+ * API cost contract (v0.18.0, item 5): 1-3 calls to walk the closed-PR
85
+ * list (stops early once 15 unmerged PRs are found) plus up to three
86
+ * evidence pages per closed-unmerged PR (issue comments, reviews,
87
+ * review comments).
86
88
  */
87
- export const BEHAVIORAL_MAX_API_CALLS = 3 + BEHAVIORAL_MAX_PRS;
89
+ export const BEHAVIORAL_MAX_API_CALLS = 3 + 3 * BEHAVIORAL_MAX_PRS;
88
90
  const AI_TERMS = /\b(ai|a\.i\.|llm|llms|chatgpt|copilot|Muse|gemini|gpt-?4|gpt-?3|generated|slop)\b/i;
89
91
  const REJECTION_TERMS = /\b(clos(?:e|ed|ing|ure)?|not\s+accept(?:ing|s)?|won['’]t|will\s+not|do\s+not|don['’]t|reject(?:ed|s|ion)?|declin(?:e|ed)|low[\s-]?effort|slop|refus(?:e|ed|ing)|spam|not\s+welcome|polic(?:y|ies))\b/i;
90
92
  /** Explicit phrases: unmistakable even without the two-term rule. */
@@ -128,7 +130,7 @@ export function detectBehavioralGateFromData(prs, comments) {
128
130
  const evidence = [];
129
131
  const prsWithSignal = new Set();
130
132
  const notes = [];
131
- notes.push(`scanned ${prs.length} closed-unmerged PRs, ${comments.length} maintainer comments on them`);
133
+ notes.push(`scanned ${prs.length} closed-unmerged PRs, ${comments.length} maintainer-authored texts on them (issue comments, reviews, review comments)`);
132
134
  for (const c of comments) {
133
135
  if (!MAINTAINER_ASSOCIATIONS.has(String(c.association ?? "")))
134
136
  continue;
@@ -147,6 +149,7 @@ export function detectBehavioralGateFromData(prs, comments) {
147
149
  quote: truncateQuote(c.body),
148
150
  url: c.url,
149
151
  strong,
152
+ source: c.source ?? "comment",
150
153
  });
151
154
  prsWithSignal.add(c.pr);
152
155
  }
@@ -200,16 +203,44 @@ export async function detectBehavioralGate(owner, repo, opts = {}) {
200
203
  // v0.17.1: drop stale closures before paying comment calls for them.
201
204
  const recency = partitionByRecency(closedUnmerged, nowMs);
202
205
  const live = recency.fresh;
206
+ // v0.18.0 (item 5): maintainer rejections live on three surfaces.
207
+ // One bounded page each: issue comments, reviews, review comments.
203
208
  const comments = [];
204
209
  for (const pr of live) {
205
- const list = (await apiGet(`/repos/${owner}/${repo}/issues/${pr.number}/comments?per_page=100`));
206
- for (const c of list ?? []) {
210
+ const issueComments = (await apiGet(`/repos/${owner}/${repo}/issues/${pr.number}/comments?per_page=100`));
211
+ for (const c of issueComments ?? []) {
207
212
  comments.push({
208
213
  pr: pr.number,
209
214
  author: String(c.user?.login ?? ""),
210
215
  association: String(c.author_association ?? ""),
211
216
  body: String(c.body ?? ""),
212
217
  url: c.html_url,
218
+ source: "comment",
219
+ });
220
+ }
221
+ const reviews = (await apiGet(`/repos/${owner}/${repo}/pulls/${pr.number}/reviews?per_page=100`));
222
+ for (const r of reviews ?? []) {
223
+ const body = String(r.body ?? "").trim();
224
+ if (!body)
225
+ continue; // bare APPROVE/REQUEST_CHANGES votes carry no text
226
+ comments.push({
227
+ pr: pr.number,
228
+ author: String(r.user?.login ?? ""),
229
+ association: String(r.author_association ?? ""),
230
+ body,
231
+ url: r.html_url,
232
+ source: "review",
233
+ });
234
+ }
235
+ const reviewComments = (await apiGet(`/repos/${owner}/${repo}/pulls/${pr.number}/comments?per_page=100`));
236
+ for (const c of reviewComments ?? []) {
237
+ comments.push({
238
+ pr: pr.number,
239
+ author: String(c.user?.login ?? ""),
240
+ association: String(c.author_association ?? ""),
241
+ body: String(c.body ?? ""),
242
+ url: c.html_url,
243
+ source: "review_comment",
213
244
  });
214
245
  }
215
246
  }
@@ -1 +1 @@
1
- {"version":3,"file":"behavior.js","sourceRoot":"","sources":["../src/behavior.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"behavior.js","sourceRoot":"","sources":["../src/behavior.ts"],"names":[],"mappings":"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"}
package/dist/bounty.d.ts CHANGED
@@ -19,6 +19,14 @@ export interface BountySignal {
19
19
  /** Evidence string for the flag, or "" when flag is null. */
20
20
  evidence: string;
21
21
  }
22
+ /** Live: fetch the issue and classify its bounty advertising (v0.18.0, item 23). */
23
+ export declare function detectBountyForIssue(owner: string, repo: string, number: number): Promise<{
24
+ issueUrl: string;
25
+ repo: string;
26
+ labels: string[];
27
+ title: string;
28
+ signal: BountySignal | null;
29
+ }>;
22
30
  export declare function detectBounty(input: {
23
31
  labels: string[];
24
32
  title: string;
package/dist/bounty.js CHANGED
@@ -11,6 +11,21 @@
11
11
  const DEAD_PLATFORMS = {
12
12
  algora: "algora.io 404s and its public API returns zero items (dead since 2025)",
13
13
  };
14
+ import { apiGet, resolveCanonicalRepo } from "./github.js";
15
+ /** Live: fetch the issue and classify its bounty advertising (v0.18.0, item 23). */
16
+ export async function detectBountyForIssue(owner, repo, number) {
17
+ const canon = await resolveCanonicalRepo(owner, repo);
18
+ const data = (await apiGet(`/repos/${canon.owner}/${canon.repo}/issues/${number}`));
19
+ const labels = (data.labels ?? []).map((l) => String(l.name ?? ""));
20
+ const title = String(data.title ?? "");
21
+ return {
22
+ issueUrl: String(data.html_url ?? ""),
23
+ repo: canon.fullName,
24
+ labels,
25
+ title,
26
+ signal: detectBounty({ labels, title, body: String(data.body ?? "") }),
27
+ };
28
+ }
14
29
  const PLATFORM_RE = /(algora|opire|issuehunt|polar|gitcoin|oss[-.]?hunter|bountysource)/i;
15
30
  function normalizePlatform(name) {
16
31
  return name.toLowerCase().replace(/[-.]/g, "");
@@ -1 +1 @@
1
- {"version":3,"file":"bounty.js","sourceRoot":"","sources":["../src/bounty.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAeH,MAAM,cAAc,GAA2B;IAC7C,MAAM,EAAE,wEAAwE;CACjF,CAAC;AAEF,MAAM,WAAW,GAAG,qEAAqE,CAAC;AAE1F,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAI5B;IACC,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,MAAM,aAAa,GACjB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QACf,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IAEhC,MAAM,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3D,MAAM,EAAE,GAAG,oCAAoC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7C,IAAI,QAAQ,IAAI,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,OAAO;YACL,QAAQ;YACR,MAAM;YACN,IAAI,EAAE,sBAAsB;YAC5B,QAAQ,EAAE,iBAAiB,QAAQ,qBAAqB,cAAc,CAAC,QAAQ,CAAC,EAAE;SACnF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,MAAM;YACN,IAAI,EAAE,mBAAmB;YACzB,QAAQ,EAAE,sEAAsE;SACjF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AACxD,CAAC"}
1
+ {"version":3,"file":"bounty.js","sourceRoot":"","sources":["../src/bounty.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -3,10 +3,10 @@
3
3
  * Greenlight CLI.
4
4
  *
5
5
  * greenlight check <issue-url> [--json] [--gates-file path]
6
- * greenlight scan <owner/repo> [--json] [--no-fallback] [--gates-file path]
6
+ * greenlight scan <owner/repo> [--json] [--no-fallback] [--gates-file path] [--deep] [--max-time seconds]
7
7
  * greenlight wave [--index url|path] [--repos N] [--min-share N] [--stars lo,hi] [--issues-per-repo N] [--min-budget N] [--json]
8
8
  * greenlight mcp
9
- * greenlight friction <owner/repo> [--json]
9
+ * greenlight friction <owner/repo> [--json] [--lite] [--no-behavioral]
10
10
  * greenlight ci <pr-url> [--json]
11
11
  * greenlight review <pr-url> [--json]
12
12
  * greenlight claim <issue-url> [--yes] [--force] [--json]
@@ -17,6 +17,7 @@
17
17
  * greenlight ci-prep <path> [--base <ref>] [--json] [--act]
18
18
  * greenlight babysit [--prs urls] [--mine --repos o/r] [--account] [--claims-only] [--json]
19
19
  * greenlight autopilot [--author login] [--prs urls] [--max-prs N] [--apply] [--max-fixes N] [--json]
20
+ * greenlight bounty <issue-url> [--json]
20
21
  * greenlight sync [--author login] [--tracker csv|json] [--stale-days N] [--write] [--json]
21
22
  * greenlight portfolio [--author login] [--json]
22
23
  * greenlight init [--agent <tool>...] [--dry-run] [--force]
@@ -28,7 +29,7 @@
28
29
  * The claim/prep/pr commands default to a DRY RUN: nothing is posted or
29
30
  * created without --yes, and writes require GITHUB_TOKEN (or GH_TOKEN).
30
31
  */
31
- import { parseIssueUrl, buildIssueSnapshot, scanCandidates, RateLimitError, fetchComments, apiCallsUsed, } from "./github.js";
32
+ import { parseIssueUrl, buildIssueSnapshot, scanCandidates, RateLimitError, fetchComments, apiCallsUsed, resolveCanonicalRepo, scanBudgetExceeded, DEFAULT_SCAN_MAX_TIME_SEC, } from "./github.js";
32
33
  import { xrayIssue, xrayIssues, sortXrayResults, applyXrayDemotion } from "./xray.js";
33
34
  import { CLAIM_MARKER } from "./protocol.js";
34
35
  import { llmEnabled, llmModelId } from "./llm-claims.js";
@@ -64,6 +65,7 @@ import { detectBehavioralGate } from "./behavior.js";
64
65
  import { viaApiClaim, viaApiRelease, viaApiHeartbeat, deviceFlowLogin, browserLoginFlow, defaultTokenStore, } from "./claim-api.js";
65
66
  import { buildEvidencePack } from "./evidence.js";
66
67
  import { buildPassport } from "./passport.js";
68
+ import { detectBountyForIssue } from "./bounty.js";
67
69
  import { renderLabelsDryRun } from "./guard.js";
68
70
  import { writeFileSync } from "node:fs";
69
71
  import { createRequire } from "node:module";
@@ -151,7 +153,14 @@ function loadExternalGates(args) {
151
153
  }
152
154
  }
153
155
  async function cmdCheck(issueUrl, asJson, args) {
154
- const { owner, repo, number } = parseIssueUrl(issueUrl);
156
+ const parsed = parseIssueUrl(issueUrl);
157
+ // v0.18.0 (item 15): resolve renames before any name-based fetch.
158
+ const canon = await resolveCanonicalRepo(parsed.owner, parsed.repo);
159
+ const { owner, repo } = canon;
160
+ const number = parsed.number;
161
+ const renameNote = canon.renamed
162
+ ? `repo renamed: ${parsed.owner}/${parsed.repo} -> ${canon.fullName}`
163
+ : null;
155
164
  // The check pipeline merges the legacy verdict with the Claim Protocol
156
165
  // state: a protocol claim verdict overrides legacy claim inference, but
157
166
  // never gates or cross-referenced PR evidence (see mergeProtocolVerdict).
@@ -194,9 +203,12 @@ async function cmdCheck(issueUrl, asJson, args) {
194
203
  llm_claims: merged.llm_claims ?? [],
195
204
  llm_status: merged.llm_status ?? null,
196
205
  suggestion,
206
+ renamedFrom: renameNote ? `${parsed.owner}/${parsed.repo}` : null,
197
207
  }, null, 2));
198
208
  return;
199
209
  }
210
+ if (renameNote)
211
+ console.log(renameNote);
200
212
  printVerdict(snapshot, result, false);
201
213
  for (const line of merged.protocolLines)
202
214
  console.log(` ${line}`);
@@ -247,7 +259,23 @@ async function cmdStatus(issueUrl, asJson) {
247
259
  }
248
260
  }
249
261
  async function cmdScan(repoRef, asJson, args) {
250
- const { owner, repo } = parseRepoRef(repoRef);
262
+ // v0.19.0 (item 17): wall-time bound (--max-time <seconds>, default
263
+ // DEFAULT_SCAN_MAX_TIME_SEC). Mega-backlog scans ran about 25 minutes;
264
+ // when the budget runs out the scan stops with marked partial results
265
+ // instead of running open-ended.
266
+ const startedAt = Date.now();
267
+ const maxTimeRaw = flagValue(args, "--max-time");
268
+ const maxTimeSec = maxTimeRaw !== undefined
269
+ ? Math.max(1, Math.trunc(Number(maxTimeRaw)) || DEFAULT_SCAN_MAX_TIME_SEC)
270
+ : DEFAULT_SCAN_MAX_TIME_SEC;
271
+ const parsed = parseRepoRef(repoRef);
272
+ // v0.18.0 (item 15): the old name 422s the candidate search; resolve
273
+ // first and say so in the output.
274
+ const canon = await resolveCanonicalRepo(parsed.owner, parsed.repo);
275
+ const { owner, repo } = canon;
276
+ const scanRenameNote = canon.renamed ? `repo renamed: ${parsed.owner}/${parsed.repo} -> ${canon.fullName}` : null;
277
+ if (scanRenameNote && !asJson)
278
+ console.log(scanRenameNote);
251
279
  const { issues, fallbackUsed } = await scanCandidates(owner, repo, {
252
280
  fallback: !args.includes("--no-fallback"),
253
281
  limit: 10,
@@ -275,7 +303,13 @@ async function cmdScan(repoRef, asJson, args) {
275
303
  // check pipeline's LLM layer was built to catch. The protocol state
276
304
  // costs extra API calls per issue; the scan's 10-issue cap and the
277
305
  // budget notes in help keep that visible.
306
+ let partial = false;
307
+ let partialDuringDeep = false;
278
308
  for (const issue of issues) {
309
+ if (scanBudgetExceeded(startedAt, maxTimeSec, Date.now())) {
310
+ partial = true;
311
+ break;
312
+ }
279
313
  try {
280
314
  const { merged } = await checkIssueWithProtocol(owner, repo, issue.number, { externalGates, behavioralGate });
281
315
  results.push({
@@ -310,6 +344,11 @@ async function cmdScan(repoRef, asJson, args) {
310
344
  let deepVerified = 0;
311
345
  if (args.includes("--deep") && results.length > 0) {
312
346
  for (const r of results) {
347
+ if (scanBudgetExceeded(startedAt, maxTimeSec, Date.now())) {
348
+ partial = true;
349
+ partialDuringDeep = true;
350
+ break;
351
+ }
313
352
  try {
314
353
  const xr = await xrayIssue(owner, repo, r.number);
315
354
  deepVerified++;
@@ -321,8 +360,28 @@ async function cmdScan(repoRef, asJson, args) {
321
360
  }
322
361
  results.sort((a, b) => (rank[a.verdict] ?? 9) - (rank[b.verdict] ?? 9));
323
362
  }
363
+ // v0.19.0 (item 17): a partial scan is valid, labeled output: same
364
+ // honest-partial contract as the wave command, still exit 0. When the
365
+ // budget is never hit these fields stay absent, byte-identical to
366
+ // before.
367
+ const partialReason = partial
368
+ ? `scan time budget ${maxTimeSec}s reached; evaluated ${results.length} of ${issues.length} candidates` +
369
+ (partialDuringDeep ? `; deep-verified ${deepVerified} of ${results.length} before stopping` : "")
370
+ : null;
324
371
  if (asJson) {
325
- console.log(JSON.stringify({ repo: `${owner}/${repo}`, fallback: fallbackUsed, results }, null, 2));
372
+ const out = {
373
+ repo: `${owner}/${repo}`,
374
+ renamedFrom: canon.renamed ? `${parsed.owner}/${parsed.repo}` : null,
375
+ fallback: fallbackUsed,
376
+ results,
377
+ };
378
+ if (partialReason) {
379
+ out.partial = true;
380
+ out.partial_reason = partialReason;
381
+ out.evaluated = results.length;
382
+ out.candidates = issues.length;
383
+ }
384
+ console.log(JSON.stringify(out, null, 2));
326
385
  return;
327
386
  }
328
387
  console.log(`Scan of ${owner}/${repo}: ${results.length} candidate issues, GO first`);
@@ -344,6 +403,9 @@ async function cmdScan(repoRef, asJson, args) {
344
403
  if (r.flags.length > 0)
345
404
  console.log(` flags: ${r.flags.join(", ")}`);
346
405
  }
406
+ if (partialReason) {
407
+ console.log(`\nPartial scan: ${partialReason}; results above cover the evaluated candidates only`);
408
+ }
347
409
  }
348
410
  async function cmdXray(repoRef, args) {
349
411
  const { owner, repo } = parseRepoRef(repoRef);
@@ -399,6 +461,20 @@ async function cmdXray(repoRef, args) {
399
461
  console.log(` flags: ${r.flags.join(", ")}`);
400
462
  }
401
463
  }
464
+ /**
465
+ * v0.19.0: one-line read provenance for a friction report, e.g.
466
+ * "policy sources: 2 read (CONTRIBUTING.md, AGENTS.md), 3 absent, 0 fetch errors".
467
+ * Fetch errors are counted separately so "no policy here" is never
468
+ * confused with "the policy fetch failed".
469
+ */
470
+ function formatPolicyProvenance(provenance) {
471
+ const prov = provenance ?? [];
472
+ const read = prov.filter((p) => p.status === "read");
473
+ const absent = prov.filter((p) => p.status === "absent").length;
474
+ const errors = prov.filter((p) => p.status === "error").length;
475
+ const readPart = read.length > 0 ? `${read.length} read (${read.map((p) => p.file).join(", ")})` : "0 read";
476
+ return `policy sources: ${readPart}, ${absent} absent, ${errors} fetch errors`;
477
+ }
402
478
  async function cmdFriction(repoRef, asJson, args = []) {
403
479
  const { owner, repo } = parseRepoRef(repoRef);
404
480
  // v0.9.3: one shared pipeline (fetchFrictionSummary) for the CLI, the
@@ -406,14 +482,22 @@ async function cmdFriction(repoRef, asJson, args = []) {
406
482
  // policy source set everywhere instead of a single CONTRIBUTING read.
407
483
  // v0.16.0: the behavioral gate pass (maintainer actions, not policy
408
484
  // text) is part of the report; --no-behavioral skips its API cost.
485
+ // v0.19.0: --lite computes policy-file gates only (the canonical
486
+ // lightweight friction); --json carries lite: true in that mode.
409
487
  const report = await fetchFrictionSummary(owner, repo, {
410
488
  behavioral: !args.includes("--no-behavioral"),
489
+ lite: args.includes("--lite"),
411
490
  });
412
491
  if (asJson) {
413
492
  console.log(JSON.stringify(report, null, 2));
414
493
  return;
415
494
  }
416
- console.log(`Friction for ${owner}/${repo}: ${report.score}/100 (100 = easiest)`);
495
+ console.log(`Friction for ${report.owner}/${report.repo}: ${report.score}/100 (100 = easiest)`);
496
+ if (report.lite)
497
+ console.log("mode: lite (policy gates only)");
498
+ console.log(` ${formatPolicyProvenance(report.policy_provenance)}`);
499
+ if (report.resolvedFrom)
500
+ console.log(` repo renamed: ${report.resolvedFrom} -> ${report.owner}/${report.repo}`);
417
501
  for (const reason of report.reasons)
418
502
  console.log(` - ${reason}`);
419
503
  if (report.reasons.length === 0)
@@ -429,7 +513,12 @@ async function cmdFriction(repoRef, asJson, args = []) {
429
513
  console.log(` behavioral note: ${report.behavioral_gate.note}`);
430
514
  }
431
515
  const starterCount = report.signals.openGoodFirstIssueCount ?? null;
432
- if (starterCount !== null)
516
+ const candidateEstimate = report.signals.starterCandidateEstimate ?? null;
517
+ if (starterCount !== null && candidateEstimate !== null) {
518
+ // v0.19.0 (item 16): both numbers shown, never merged.
519
+ console.log(` open starter issues: ${starterCount} by label; candidate estimate (expanded labels, unassigned): ~${candidateEstimate}`);
520
+ }
521
+ else if (starterCount !== null)
433
522
  console.log(` open starter issues: ${starterCount}`);
434
523
  if (report.mergeVelocity)
435
524
  console.log(` merge velocity: ${describeMergeVelocity(report.mergeVelocity)}`);
@@ -1281,6 +1370,38 @@ async function cmdPassport(login, args) {
1281
1370
  console.log(passport.markdown);
1282
1371
  }
1283
1372
  }
1373
+ /**
1374
+ * `greenlight bounty <issue-url>` (v0.18.0, item 23): is the bounty this
1375
+ * issue advertises actually alive? The classifier never raises a
1376
+ * verdict: bounties are context, not landability. What it kills is
1377
+ * chasing a dead platform (algora has been dead since 2025; thousands
1378
+ * of issues still wear its labels).
1379
+ */
1380
+ async function cmdBounty(issueUrl, args) {
1381
+ const { owner, repo, number } = parseIssueUrl(issueUrl);
1382
+ const result = await detectBountyForIssue(owner, repo, number);
1383
+ if (args.includes("--json")) {
1384
+ console.log(JSON.stringify(result, null, 2));
1385
+ return;
1386
+ }
1387
+ if (!result.signal) {
1388
+ console.log(`No bounty advertised on ${result.repo}#${number} ("${result.title}").`);
1389
+ return;
1390
+ }
1391
+ const s = result.signal;
1392
+ const head = s.amount ? `Bounty: ${s.amount}` : "Bounty advertised";
1393
+ const via = s.platform ? ` via ${s.platform}` : " (no platform named)";
1394
+ console.log(`${head}${via} on ${result.repo}#${number}`);
1395
+ if (s.flag === "bounty-platform-dead") {
1396
+ console.log(` DEAD PLATFORM: ${s.evidence}`);
1397
+ }
1398
+ else if (s.flag === "bounty-unverified") {
1399
+ console.log(` UNVERIFIED: ${s.evidence}`);
1400
+ }
1401
+ else {
1402
+ console.log(` platform is named and not known-dead; verify terms on the platform before counting on payment`);
1403
+ }
1404
+ }
1284
1405
  async function cmdDigest(repoRef, args) {
1285
1406
  const asJson = args.includes("--json");
1286
1407
  const limitRaw = flagValue(args, "--limit");
@@ -1587,6 +1708,8 @@ async function main() {
1587
1708
  return await cmdEvidence(target, args);
1588
1709
  if (command === "passport" && target)
1589
1710
  return await cmdPassport(target, args);
1711
+ if (command === "bounty" && target)
1712
+ return await cmdBounty(target, args);
1590
1713
  if (command && NEEDS_TARGET[command]) {
1591
1714
  // Known command with a missing argument: name the need and show its
1592
1715
  // help, instead of the generic overview.