Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions ast-grep/rules/no-hardcoded-bot-name-in-model-text.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
id: no-hardcoded-bot-name-in-model-text
language: TypeScript
severity: error
message: Do not write "Junior" in a description that the model reads. Each install configures the bot name. Use "you", "the runtime", or the configured name (botConfig.userName).
note: This rule checks tool, schema, and event descriptions. Pages and sign-in labels that people read are ignored.
files:
- packages/*/src/**/*.ts
ignores:
- packages/junior/src/api/acp/**
- packages/junior-google/src/setup-routes.ts
- packages/junior-memory/src/user-pages.ts
- packages/junior-testing/**
rule:
kind: string_fragment
regex: '\bJunior\b'
inside:
stopBy: end
any:
- kind: pair
has:
field: key
regex: "^(description|instructions)$"
# Only the arguments of `.describe()`, not strings in its receiver.
- kind: arguments
inside:
kind: call_expression
has:
field: function
kind: member_expression
has:
field: property
regex: "^describe$"
13 changes: 13 additions & 0 deletions ast-grep/tests/no-hardcoded-bot-name-in-model-text-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
id: no-hardcoded-bot-name-in-model-text
valid:
- 'const tool = { description: "Find open slots in your own calendar." };'
- 'const input = z.string().describe("A channel name you already know.");'
- 'throw new Error("Junior plugin manifest.name is required.");'
- 'const fields = { description: "Login of the junior-event bot." };'
- 'const input = z.object({ kind: z.literal("Junior") }).describe("The kind.");'
invalid:
- 'const tool = { description: "Read events that Junior''s account can see." };'
- "const tool = { description: `Find slots on Junior calendar ${zone}.` };"
- 'const input = z.string().describe("A channel name Junior already knows.");'
- 'const fields = { authors: { description: "bots such as Junior" } };'
- 'const plugin = { instructions: "Junior must ask first." };'
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const completedImplementation: HistoryToolCall[] = [
},
];

const eventAuthor = { userId: "UJRNEVENT", fullName: "Junior event" };
const eventAuthor = { userId: "UJRNEVENT", fullName: "System event" };

const maintenance = [
"[task]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ describe("Slack Thread Status and Title", () => {
expect(conversation.statuses[0]).not.toBe("");
expect(conversation.statuses.at(-1)).toBe("");
// Junior names a new assistant thread before the person writes.
expect(conversation.threadTitles).toEqual(["Junior", conversation.title]);
expect(conversation.threadTitles).toEqual([
expect.any(String),
conversation.title,
]);
expect(conversation.title).toMatch(/leak/i);

const next = await conversation.continue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ describe("Google Calendar", () => {
RubricJudge,
rubric({
pass: [
"The reply says that Junior's Google account is not connected and that a Junior admin must connect it.",
"The reply says that the Google account is not connected and that an admin must connect it.",
],
fail: [
"Do not offer meeting times as if calendars were checked.",
Expand Down
2 changes: 1 addition & 1 deletion packages/junior-github/src/credential-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export const GITHUB_GRAPHQL_RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
export const HTTP_READ_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
export const USER_TOKEN_GRANTS = new Set(["user-read", "user-write"]);
export const CREATE_TOOL_ROUTING_GUIDANCE =
"This is a Junior tool-routing denial, not a GitHub permission failure. Do not ask the user for GitHub permissions; retry with the required Junior tool.";
"This is a tool-routing denial, not a GitHub permission failure. Do not ask the user for GitHub permissions; retry with the required tool.";
export const USER_WRITE_REQUIREMENTS = [
"requesting GitHub user permission to perform this operation",
];
Expand Down
10 changes: 5 additions & 5 deletions packages/junior-github/src/egress-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,14 @@ const PULL_REQUEST_COMMENT_REACTIONS_PATH =

const GITHUB_BODY_WRITES = [
{
denialMessage: `GitHub issue creation must use the github_createIssue tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
denialMessage: `GitHub issue creation must use the github_createIssue tool, which owns idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
graphqlField: "createIssue",
method: "POST",
operation: "github.issue.create",
restPath: /^\/repos\/[^/]+\/[^/]+\/issues$/,
},
{
denialMessage: `GitHub pull request creation must use the github_createPullRequest tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
denialMessage: `GitHub pull request creation must use the github_createPullRequest tool, which owns idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
graphqlField: "createPullRequest",
method: "POST",
operation: "github.pull.create",
Expand All @@ -350,14 +350,14 @@ const GITHUB_BODY_WRITES = [
restPath: /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/reviews$/,
},
{
denialMessage: `GitHub issue updates must use the github_updateIssue tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
denialMessage: `GitHub issue updates must use the github_updateIssue tool, which owns requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
graphqlField: "updateIssue",
method: "PATCH",
operation: "github.issue.update",
restPath: /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+$/,
},
{
denialMessage: `GitHub pull request updates must use the github_updatePullRequest tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
denialMessage: `GitHub pull request updates must use the github_updatePullRequest tool, which owns requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
graphqlField: "updatePullRequest",
method: "PATCH",
operation: "github.pull.update",
Expand Down Expand Up @@ -698,7 +698,7 @@ export async function githubGrantForEgress(
const access = HTTP_READ_METHODS.has(method) ? "read" : "write";
if (access === "write") {
throw new EgressPolicyDenied(
"Junior does not support this GitHub write request. Use the matching GitHub tool or a supported REST endpoint.",
"This GitHub write request is not supported. Use the matching GitHub tool or a supported REST endpoint.",
);
}
return grantForAccess(access, "github.api-read", "installation-read");
Expand Down
2 changes: 1 addition & 1 deletion packages/junior-github/src/events/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const GITHUB_ISSUE_SUGGESTED_EVENTS = [
export const GITHUB_ISSUE_COMMENT_MATCH_FIELDS = eventMatchFieldsSchema.parse({
mentionsBot: {
kind: "boolean",
description: "true when the issue comment mentions the Junior GitHub App",
description: "true when the issue comment mentions this GitHub App",
events: ["issue.comment.created"],
},
senderId: {
Expand Down
6 changes: 3 additions & 3 deletions packages/junior-github/src/events/pull-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const GITHUB_PULL_REQUEST_MATCH_FIELDS = eventMatchFieldsSchema.parse({
kind: "string",
list: true,
description:
"GitHub logins of the commit authors and Co-authored-by people, including bots such as Junior's",
"GitHub logins of the commit authors and Co-authored-by people, including bots such as this GitHub App",
events: [...GITHUB_PULL_REQUEST_EVENTS],
},
headBranch: {
Expand All @@ -126,13 +126,13 @@ export const GITHUB_PULL_REQUEST_MATCH_FIELDS = eventMatchFieldsSchema.parse({
openerEmail: {
kind: "string",
description:
"opener email when GitHub sends it; missing on Junior's pull requests",
"opener email when GitHub sends it; missing on pull requests that this GitHub App opened",
events: [...GITHUB_PULL_REQUEST_EVENTS],
},
openerUsername: {
kind: "string",
description:
"login of who opened the pull request; on Junior's, the person who asked Junior to open it, missing until Junior saves them",
"login of who opened the pull request; on pull requests that this GitHub App opened, the person who asked for it, missing until that person is saved",
events: [...GITHUB_PULL_REQUEST_EVENTS],
},
openerRole: {
Expand Down
4 changes: 2 additions & 2 deletions packages/junior-github/src/git-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ if [ -z "$message_file" ]; then
fi

if [ -z "\${JUNIOR_GIT_AUTHOR_NAME:-}" ] || [ -z "\${JUNIOR_GIT_AUTHOR_EMAIL:-}" ]; then
echo "Junior GitHub plugin internal error: Junior author identity was not injected by the host runtime. Do not set Git author env vars manually; report this configuration error." >&2
echo "GitHub plugin internal error: the host runtime did not inject the Git author identity. Do not set Git author env vars manually; report this configuration error." >&2
exit 1
fi

if [ "\${GIT_AUTHOR_NAME:-}" != "$JUNIOR_GIT_AUTHOR_NAME" ] || [ "\${GIT_AUTHOR_EMAIL:-}" != "$JUNIOR_GIT_AUTHOR_EMAIL" ]; then
echo "Junior GitHub plugin internal error: Git author was not set to the Junior identity. Do not override Git author manually; report this configuration error." >&2
echo "GitHub plugin internal error: Git author was not set to the configured identity. Do not override Git author manually; report this configuration error." >&2
exit 1
fi

Expand Down
12 changes: 6 additions & 6 deletions packages/junior-github/src/pull-request-review-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function assertGitHubPullRequestApprovalDenied(input: {
if (!bodyText) {
if (isEventsPath) {
throw new EgressPolicyDenied(
"GitHub pull request review submissions must include a parseable non-APPROVE event so Junior can enforce the no-approve policy.",
"GitHub pull request review submissions must include a parseable non-APPROVE event so the no-approve policy can be enforced.",
);
}
return;
Expand All @@ -40,32 +40,32 @@ export function assertGitHubPullRequestApprovalDenied(input: {
body = JSON.parse(bodyText);
} catch {
throw new EgressPolicyDenied(
"GitHub pull request review requests must use JSON bodies so Junior can enforce the no-approve policy.",
"GitHub pull request review requests must use JSON bodies so the no-approve policy can be enforced.",
);
}
if (!isRecord(body)) {
throw new EgressPolicyDenied(
"GitHub pull request review requests must use JSON object bodies so Junior can enforce the no-approve policy.",
"GitHub pull request review requests must use JSON object bodies so the no-approve policy can be enforced.",
);
}

let event: string | undefined;
if ("event" in body) {
if (typeof body.event !== "string" || body.event.trim().length === 0) {
throw new EgressPolicyDenied(
"GitHub pull request review submissions must include a parseable non-APPROVE event so Junior can enforce the no-approve policy.",
"GitHub pull request review submissions must include a parseable non-APPROVE event so the no-approve policy can be enforced.",
);
}
event = body.event.trim().toUpperCase();
}
if (event === "APPROVE") {
throw new EgressPolicyDenied(
"Junior cannot approve GitHub pull requests. Request changes, leave a comment review, or dismiss Junior's own review instead.",
"You cannot approve GitHub pull requests. Request changes, leave a comment review, or dismiss your own review instead.",
);
}
if (isEventsPath && event === undefined) {
throw new EgressPolicyDenied(
"GitHub pull request review submissions must include a parseable non-APPROVE event so Junior can enforce the no-approve policy.",
"GitHub pull request review submissions must include a parseable non-APPROVE event so the no-approve policy can be enforced.",
);
}
}
4 changes: 2 additions & 2 deletions packages/junior-github/src/tools/footer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ export function githubConversationFooter(
const normalizedDashboardUrl = dashboardUrl?.trim();
const sentryUrl = sentryConversationUrl(id);
const sessionLinks = normalizedDashboardUrl
? `[View Junior Session](${normalizedDashboardUrl})${sentryUrl ? ` [[Sentry]](${sentryUrl})` : ""}`
? `[View Session](${normalizedDashboardUrl})${sentryUrl ? ` [[Sentry]](${sentryUrl})` : ""}`
: sentryUrl
? `[View Junior Session in Sentry](${sentryUrl})`
? `[View Session in Sentry](${sentryUrl})`
: undefined;
if (!sessionLinks) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const inputSchema = z
number: z.number().int().positive().describe("Pull request number."),
event: z
.enum(["COMMENT", "REQUEST_CHANGES"])
.describe("Review result. Junior cannot approve pull requests."),
.describe("Review result. You cannot approve pull requests."),
body: z.string().trim().min(1).describe("Review summary."),
comments: z
.array(reviewCommentSchema)
Expand Down Expand Up @@ -111,7 +111,7 @@ export function createGitHubSubmitPullRequestReviewTool(egress: PluginEgress) {
readOnlyHint: false,
},
description:
"Submit a GitHub pull request review as a comment or change request. Use this instead of `gh pr review`, GraphQL, or direct REST calls. Junior cannot approve pull requests.",
"Submit a GitHub pull request review as a comment or change request. Use this instead of `gh pr review`, GraphQL, or direct REST calls. You cannot approve pull requests.",
exposure: "direct",
inputSchema,
outputSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function createGitHubUpdatePullRequestFeedbackTool(
readOnlyHint: false,
},
description:
"Set Junior's outcome reaction on GitHub pull request feedback: addressed (+1) or declined (-1). Use the commentId and commentKind from the event after acting on the feedback. Replaces only Junior's prior status reaction on that comment.",
"Set your outcome reaction on GitHub pull request feedback: addressed (+1) or declined (-1). Use the commentId and commentKind from the event after acting on the feedback. Replaces only your prior status reaction on that comment.",
exposure: "direct",
inputSchema,
outputSchema,
Expand Down
6 changes: 3 additions & 3 deletions packages/junior-github/tests/footer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe("GitHub conversation footer", () => {
"https://junior.example.com/conversations/slack%3AC123%3A1712345.0001",
),
).toContain(
"[View Junior Session](https://junior.example.com/conversations/slack%3AC123%3A1712345.0001) [[Sentry]](https://acme.sentry.io/explore/conversations/slack%3AC123%3A1712345.0001/?project=12345)",
"[View Session](https://junior.example.com/conversations/slack%3AC123%3A1712345.0001) [[Sentry]](https://acme.sentry.io/explore/conversations/slack%3AC123%3A1712345.0001/?project=12345)",
);
});

Expand All @@ -39,7 +39,7 @@ describe("GitHub conversation footer", () => {
"https://junior.example.com/conversations/slack%3AC123%3A1712345.0001",
),
).toContain(
"[View Junior Session](https://junior.example.com/conversations/slack%3AC123%3A1712345.0001)",
"[View Session](https://junior.example.com/conversations/slack%3AC123%3A1712345.0001)",
);
});

Expand All @@ -48,7 +48,7 @@ describe("GitHub conversation footer", () => {
process.env.SENTRY_ORG_SLUG = "acme";

expect(appendGitHubFooter("PR body", conversationId)).toContain(
"[View Junior Session in Sentry](https://acme.sentry.io/explore/conversations/slack%3AC123%3A1712345.0001/?project=12345)",
"[View Session in Sentry](https://acme.sentry.io/explore/conversations/slack%3AC123%3A1712345.0001/?project=12345)",
);
});

Expand Down
2 changes: 1 addition & 1 deletion packages/junior-github/tests/get-pull-request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ via **Alex**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
[View Junior Session](https://example.com/session)
[View Session](https://example.com/session)
<!-- junior-session-footer:end -->
<!-- unfinished comment`,
user: { login: "alex" },
Expand Down
Loading
Loading