Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bd6c371
chore(codex): bootstrap PR for issue #1385
github-actions[bot] Feb 8, 2026
f66e5f3
feat: filter .agents ledger files from pr context
Feb 8, 2026
5bbf3f7
chore: sync template scripts
github-actions[bot] Feb 8, 2026
11c00aa
feat: record ignored pr files in context
Feb 8, 2026
73c25be
chore: sync template scripts
github-actions[bot] Feb 8, 2026
0c2480c
test: cover ignored path patterns in pr context
Feb 8, 2026
a30314c
test: lock bot comment handler ignores
Feb 8, 2026
c10a936
chore(autofix): formatting/lint
github-actions[bot] Feb 8, 2026
8fe4994
test: add connector exclusion smoke helper
Feb 8, 2026
886b7f8
chore: sync template scripts
github-actions[bot] Feb 8, 2026
f4cfb08
feat: auto-dismiss ignored bot reviews in template
Feb 8, 2026
908f4ca
chore(codex-keepalive): apply updates (PR #1387)
github-actions[bot] Feb 8, 2026
479dde3
Add bot comment dismiss helper and Copilot ignores
codex Feb 8, 2026
dcf700f
feat: add bot comment dismissal helper
Feb 8, 2026
d1f3d3a
chore: sync template scripts
github-actions[bot] Feb 8, 2026
ec8a37a
Add max-age filtering for bot comment dismissal
Feb 8, 2026
da50d73
chore: sync template scripts
github-actions[bot] Feb 8, 2026
28ae570
feat: default bot comment dismiss max age
Feb 8, 2026
d93b4ed
chore: sync template scripts
github-actions[bot] Feb 8, 2026
72fb89d
feat: handle GraphQL timestamps for bot comment dismiss
Feb 8, 2026
087e052
feat: add auto-dismiss helper for bot review comments
Feb 8, 2026
c9e181d
fix: Add API wrapper documentation to bot-comment-dismiss.js
stranske Feb 8, 2026
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
8 changes: 8 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ After reading CLAUDE.md, these are the key architectural points:

If a user says "read X" or "check Y", do it **immediately** as your next action. Not later. Not after you've started something else. Immediately.

## Ignore Ledger Tracking Files

Do not review or comment on ledger tracking files used for agent automation:
- `.agents/issue-*-ledger.yml`
- Any file under `.agents/` that is not application code

These files are internal task trackers and should never receive Copilot review comments or suggestions.

## Template Changes

Any change to workflows that consumers use must be reflected in BOTH:
Expand Down
260 changes: 260 additions & 0 deletions .github/scripts/__tests__/bot-comment-dismiss.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
'use strict';

const { describe, it } = require('node:test');
const assert = require('node:assert');

const {
autoDismissReviewComments,
collectDismissable,
dismissReviewComments,
formatDismissLog,
runCli,
} = require('../bot-comment-dismiss');

describe('bot-comment-dismiss', () => {
it('collects bot comments in ignored paths', () => {
const comments = [
{ id: 1, path: '.agents/issue-1-ledger.yml', user: { login: 'copilot[bot]' } },
{ id: 2, path: 'src/app.js', user: { login: 'copilot[bot]' } },
{ id: 3, path: '.agents/notes.txt', user: { login: 'coderabbitai[bot]' } },
{ id: 4, path: '.agents/issue-2-ledger.yml', user: { login: 'octocat' } },
];

const dismissable = collectDismissable(comments, {
ignoredPaths: ['.agents/'],
botAuthors: ['copilot[bot]', 'coderabbitai[bot]'],
});

assert.deepStrictEqual(dismissable, [
{ id: 1, path: '.agents/issue-1-ledger.yml', author: 'copilot[bot]' },
{ id: 3, path: '.agents/notes.txt', author: 'coderabbitai[bot]' },
]);
});

it('formats log lines with author and path', () => {
const log = formatDismissLog({ id: 42, path: '.agents/issue-9-ledger.yml', author: 'copilot[bot]' });
assert.equal(log, 'Auto-dismissed review comment 42 by copilot[bot] in .agents/issue-9-ledger.yml');
});

it('runs cli with env overrides', () => {
const env = {
COMMENTS_JSON: JSON.stringify([
{
id: 9,
path: '.agents/issue-9-ledger.yml',
user: { login: 'copilot[bot]' },
created_at: '2026-02-08T12:00:00.000Z',
},
{
id: 10,
path: 'src/app.js',
user: { login: 'copilot[bot]' },
created_at: '2026-02-08T12:00:00.000Z',
},
]),
IGNORED_PATHS: '.agents/',
BOT_AUTHORS: 'copilot[bot]',
MAX_AGE_SECONDS: '30',
NOW_EPOCH_MS: String(Date.parse('2026-02-08T12:00:20.000Z')),
};

const result = runCli(env);

assert.deepStrictEqual(result.dismissable, [
{ id: 9, path: '.agents/issue-9-ledger.yml', author: 'copilot[bot]' },
]);
assert.deepStrictEqual(result.logs, [
'Auto-dismissed review comment 9 by copilot[bot] in .agents/issue-9-ledger.yml',
]);
});

it('uses default max age of 30 seconds when not provided', () => {
const env = {
COMMENTS_JSON: JSON.stringify([
{
id: 21,
path: '.agents/issue-21-ledger.yml',
user: { login: 'copilot[bot]' },
created_at: '2026-02-08T12:00:00.000Z',
},
{
id: 22,
path: '.agents/issue-22-ledger.yml',
user: { login: 'copilot[bot]' },
created_at: '2026-02-08T12:00:40.000Z',
},
]),
IGNORED_PATHS: '.agents/',
BOT_AUTHORS: 'copilot[bot]',
NOW_EPOCH_MS: String(Date.parse('2026-02-08T12:01:00.000Z')),
};

const result = runCli(env);

assert.deepStrictEqual(result.dismissable, [
{ id: 22, path: '.agents/issue-22-ledger.yml', author: 'copilot[bot]' },
]);
});

it('filters out ignored-path comments older than max age', () => {
const dismissable = collectDismissable(
[
{
id: 11,
path: '.agents/issue-11-ledger.yml',
user: { login: 'copilot[bot]' },
created_at: '2026-02-08T12:00:00.000Z',
},
{
id: 12,
path: '.agents/issue-12-ledger.yml',
user: { login: 'copilot[bot]' },
created_at: '2026-02-08T12:00:40.000Z',
},
],
{
ignoredPaths: ['.agents/'],
botAuthors: ['copilot[bot]'],
maxAgeSeconds: 30,
now: Date.parse('2026-02-08T12:01:00.000Z'),
}
);

assert.deepStrictEqual(dismissable, [
{ id: 12, path: '.agents/issue-12-ledger.yml', author: 'copilot[bot]' },
]);
});

it('accepts GraphQL-style createdAt timestamps', () => {
const dismissable = collectDismissable(
[
{
id: 31,
path: '.agents/issue-31-ledger.yml',
user: { login: 'copilot[bot]' },
createdAt: '2026-02-08T12:00:50.000Z',
},
],
{
ignoredPaths: ['.agents/'],
botAuthors: ['copilot[bot]'],
maxAgeSeconds: 30,
now: Date.parse('2026-02-08T12:01:10.000Z'),
}
);

assert.deepStrictEqual(dismissable, [
{ id: 31, path: '.agents/issue-31-ledger.yml', author: 'copilot[bot]' },
]);
});

it('dismisses comments and logs each dismissal', async () => {
const deleted = [];
const github = {
rest: {
pulls: {
deleteReviewComment: async ({ comment_id }) => {
deleted.push(comment_id);
},
},
},
};
const logs = [];
const logger = {
info: (line) => logs.push(line),
};

const result = await dismissReviewComments({
github,
owner: 'octo',
repo: 'repo',
dismissable: [
{ id: 1, path: '.agents/issue-1-ledger.yml', author: 'copilot[bot]' },
{ id: 2, path: '.agents/issue-2-ledger.yml', author: 'coderabbitai[bot]' },
],
logger,
});

assert.deepStrictEqual(deleted, [1, 2]);
assert.deepStrictEqual(result.dismissed, [
{ id: 1, path: '.agents/issue-1-ledger.yml', author: 'copilot[bot]' },
{ id: 2, path: '.agents/issue-2-ledger.yml', author: 'coderabbitai[bot]' },
]);
assert.deepStrictEqual(result.logs, [
'Auto-dismissed review comment 1 by copilot[bot] in .agents/issue-1-ledger.yml',
'Auto-dismissed review comment 2 by coderabbitai[bot] in .agents/issue-2-ledger.yml',
]);
assert.deepStrictEqual(logs, result.logs);
});

it('lists review comments and auto-dismisses ignored-path bot comments', async () => {
const deleted = [];
const github = {
rest: {
pulls: {
listReviewComments: async () => ({
data: [
{
id: 7,
path: '.agents/issue-7-ledger.yml',
user: { login: 'copilot[bot]' },
created_at: '2026-02-08T12:00:10.000Z',
},
{
id: 8,
path: 'src/app.js',
user: { login: 'copilot[bot]' },
created_at: '2026-02-08T12:00:10.000Z',
},
],
}),
deleteReviewComment: async ({ comment_id }) => {
deleted.push(comment_id);
},
},
},
};

const result = await autoDismissReviewComments({
github,
owner: 'octo',
repo: 'repo',
pullNumber: 123,
ignoredPaths: ['.agents/'],
botAuthors: ['copilot[bot]'],
maxAgeSeconds: 30,
now: Date.parse('2026-02-08T12:00:20.000Z'),
});

assert.deepStrictEqual(result.dismissable, [
{ id: 7, path: '.agents/issue-7-ledger.yml', author: 'copilot[bot]' },
]);
assert.deepStrictEqual(deleted, [7]);
});

it('tracks failures when dismissal fails', async () => {
const github = {
rest: {
pulls: {
deleteReviewComment: async () => {
throw new Error('nope');
},
},
},
};

const result = await dismissReviewComments({
github,
owner: 'octo',
repo: 'repo',
dismissable: [
{ id: 99, path: '.agents/issue-99-ledger.yml', author: 'copilot[bot]' },
],
logger: { warn: () => {} },
});

assert.deepStrictEqual(result.dismissed, []);
assert.equal(result.failed.length, 1);
assert.equal(result.failed[0].id, 99);
});
});
24 changes: 24 additions & 0 deletions .github/scripts/__tests__/connector-exclusion-smoke.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const { describe, it } = require('node:test');
const assert = require('node:assert');

const { filterPaths } = require('../connector-exclusion-smoke');

describe('connector-exclusion-smoke', () => {
it('filters .agents ledger files using default ignore rules', () => {
const input = [
'.agents/issue-1234-ledger.yml',
'.agents/notes.txt',
'src/index.js'
];

const result = filterPaths(input);

assert.deepStrictEqual(result.ignored, [
'.agents/issue-1234-ledger.yml',
'.agents/notes.txt'
]);
assert.deepStrictEqual(result.kept, ['src/index.js']);
});
});
Loading
Loading