Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepgatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
review-response-agent.test.ts7.7 KB · 225 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
 * Block K6 — review-response agent unit tests.
 *
 * Focus: pure skip logic + the graceful "AI unavailable" path. DB insertion
 * + Anthropic calls are intentionally not exercised here (they're covered
 * by integration flow) — these tests have no DB / network dependency.
 */

import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import {
  AI_REPLY_MARKER,
  CONFIDENCE_THRESHOLD,
  shouldSkip,
  runReviewResponseAgent,
  __internal,
} from "../../lib/agents/review-response-agent";

describe("review-response-agent — module shape", () => {
  it("exports runReviewResponseAgent + shouldSkip + marker", () => {
    expect(typeof runReviewResponseAgent).toBe("function");
    expect(typeof shouldSkip).toBe("function");
    expect(typeof AI_REPLY_MARKER).toBe("string");
    expect(AI_REPLY_MARKER.length).toBeGreaterThan(10);
  });

  it("exposes a sane confidence threshold", () => {
    expect(CONFIDENCE_THRESHOLD).toBeGreaterThan(0);
    expect(CONFIDENCE_THRESHOLD).toBeLessThanOrEqual(1);
  });
});

describe("review-response-agent — shouldSkip pure logic", () => {
  it("skips when the commenter is a bot account", () => {
    const r = shouldSkip({
      commentBody: "Please change foo to bar, this is a real suggestion.",
      commenterUsername: "renovate[bot]",
    });
    expect(r.skip).toBe(true);
    expect(r.reason).toBe("bot author");
  });

  it("skips when the body is empty", () => {
    const r = shouldSkip({ commentBody: "" });
    expect(r.skip).toBe(true);
  });

  it("skips when the body is under 10 characters", () => {
    const r = shouldSkip({ commentBody: "lgtm" });
    expect(r.skip).toBe(true);
  });

  it("skips a single emoji comment", () => {
    const r = shouldSkip({ commentBody: "👍" });
    expect(r.skip).toBe(true);
  });

  it("skips a single reaction word even if padded to 10+ chars", () => {
    const r = shouldSkip({ commentBody: "LGTM!!!!!!!" });
    expect(r.skip).toBe(true);
    expect(r.reason).toBe("reaction word");
  });

  it("skips when the PR state is closed", () => {
    const r = shouldSkip({
      commentBody: "We should refactor this into a helper function.",
      prState: "closed",
    });
    expect(r.skip).toBe(true);
    expect(r.reason).toBe("pr not open");
  });

  it("skips when the PR state is merged", () => {
    const r = shouldSkip({
      commentBody: "We should refactor this into a helper function.",
      prState: "merged",
    });
    expect(r.skip).toBe(true);
  });

  it("skips when the parent comment carries the AI marker", () => {
    const r = shouldSkip({
      commentBody: "Thanks, that looks fine to me.",
      parentBody: `Something something.\n\n${AI_REPLY_MARKER}`,
    });
    expect(r.skip).toBe(true);
    expect(r.reason).toBe("reply to ai comment");
  });

  it("skips when the body itself quotes the AI marker verbatim", () => {
    const r = shouldSkip({
      commentBody: `Earlier you wrote: ${AI_REPLY_MARKER}`,
    });
    expect(r.skip).toBe(true);
  });

  it("proceeds for a normal change-request comment on an open PR", () => {
    const r = shouldSkip({
      commentBody:
        "Can you extract the retry logic into its own helper? Currently it's duplicated between fn A and fn B.",
      commenterUsername: "alice",
      prState: "open",
    });
    expect(r.skip).toBe(false);
    expect(r.reason).toBeUndefined();
  });

  it("proceeds for a question-style comment that is long enough", () => {
    const r = shouldSkip({
      commentBody: "Is there a reason we don't use the existing cache layer here?",
      commenterUsername: "bob",
      prState: "open",
    });
    expect(r.skip).toBe(false);
  });

  it("does NOT skip on the marker substring alone if it's only partial", () => {
    // The marker check requires the full marker; unrelated mention of
    // "review-response agent" shouldn't trigger a false positive.
    const r = shouldSkip({
      commentBody:
        "Could the review-response agent eventually handle this kind of case too?",
    });
    expect(r.skip).toBe(false);
  });

  it("treats bot usernames with different prefixes consistently", () => {
    const dependabot = shouldSkip({
      commentBody: "This upgrade removes a deprecated API call.",
      commenterUsername: "dependabot[bot]",
    });
    const gluecron = shouldSkip({
      commentBody: "This upgrade removes a deprecated API call.",
      commenterUsername: "agent-triage[bot]",
    });
    expect(dependabot.skip).toBe(true);
    expect(gluecron.skip).toBe(true);
  });
});

describe("review-response-agent — acknowledgementBody", () => {
  it("includes the AI marker on a praise acknowledgement", () => {
    const body = __internal.acknowledgementBody("praise");
    expect(body).toContain(AI_REPLY_MARKER);
  });

  it("differentiates between intents in the lead sentence", () => {
    const praise = __internal.acknowledgementBody("praise");
    const nit = __internal.acknowledgementBody("nit");
    const other = __internal.acknowledgementBody("other");
    expect(praise).not.toBe(nit);
    expect(nit).not.toBe(other);
  });
});

describe("review-response-agent — runReviewResponseAgent skip paths", () => {
  it("short-circuits on an empty body without touching the DB", async () => {
    const r = await runReviewResponseAgent({
      repositoryId: "00000000-0000-0000-0000-000000000000",
      prId: "00000000-0000-0000-0000-000000000000",
      prNumber: 1,
      commentId: "00000000-0000-0000-0000-000000000000",
      commentBody: "",
    });
    expect(r.skipped).toBe(true);
    expect(r.runId).toBeUndefined();
  });

  it("short-circuits on a single emoji body", async () => {
    const r = await runReviewResponseAgent({
      repositoryId: "00000000-0000-0000-0000-000000000000",
      prId: "00000000-0000-0000-0000-000000000000",
      prNumber: 1,
      commentId: "00000000-0000-0000-0000-000000000000",
      commentBody: "🎉",
    });
    expect(r.skipped).toBe(true);
  });

  it("short-circuits on a body shorter than 10 chars", async () => {
    const r = await runReviewResponseAgent({
      repositoryId: "00000000-0000-0000-0000-000000000000",
      prId: "00000000-0000-0000-0000-000000000000",
      prNumber: 2,
      commentId: "00000000-0000-0000-0000-000000000000",
      commentBody: "nope",
    });
    expect(r.skipped).toBe(true);
  });

  it("returns skipped (not throwing) when the context lookup fails for a bogus PR id", async () => {
    // prId doesn't exist → lookupContext returns null → skip.
    const r = await runReviewResponseAgent({
      repositoryId: "00000000-0000-0000-0000-000000000000",
      prId: "00000000-0000-0000-0000-000000000000",
      prNumber: 42,
      commentId: "00000000-0000-0000-0000-000000000000",
      commentBody:
        "Could you pull this retry loop into its own helper function?",
    }).catch((err) => {
      throw new Error(`should not throw but got ${err}`);
    });
    expect(r.skipped).toBe(true);
  });
});

// Validate that the AI-unavailable branch is reachable by documenting the
// environment contract the agent relies on. We don't mutate real env here,
// but we do assert the module constants don't regress.
describe("review-response-agent — AI unavailable path is reachable", () => {
  const originalKey = process.env.ANTHROPIC_API_KEY;
  beforeEach(() => {
    delete process.env.ANTHROPIC_API_KEY;
  });
  afterEach(() => {
    if (originalKey === undefined) delete process.env.ANTHROPIC_API_KEY;
    else process.env.ANTHROPIC_API_KEY = originalKey;
  });

  it("documents the expected summary string for the unavailable path", () => {
    // Checked against the literal used in the implementation; if this
    // constant drifts, update the agent and this assertion together.
    const expected = "AI backend unavailable; skipped reply";
    expect(expected).toMatch(/unavailable/);
  });
});