mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 22:26:38 +03:00
Keeps the label a deterministic mirror of the body's disclosure state, per Greptile's P2 review finding on #518. AI-assisted: written with an AI agent under maintainer direction. Co-authored-by: Cursor <cursoragent@cursor.com>
374 lines
12 KiB
JavaScript
374 lines
12 KiB
JavaScript
import { describe, it, before } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
|
|
import {
|
|
AI_LABEL,
|
|
GATE_LABEL,
|
|
GATE_MARKER,
|
|
REJECT_MARKER,
|
|
evaluateComment,
|
|
evaluateIssue,
|
|
loadTemplates,
|
|
parseArgs,
|
|
proseWordCount,
|
|
} from '../scripts/github/issue-gate.mjs';
|
|
|
|
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
|
let templates;
|
|
before(() => {
|
|
templates = loadTemplates(join(REPO_ROOT, '.github', 'ISSUE_TEMPLATE'));
|
|
});
|
|
|
|
const FILLED_BUG_BODY = `## What happened?
|
|
|
|
Running the detect command against a directory crashes with a TypeError.
|
|
|
|
## Steps to reproduce
|
|
|
|
1. Run \`npx impeccable detect src/\`
|
|
2. Watch it crash
|
|
|
|
## Expected behavior
|
|
|
|
A findings report.
|
|
|
|
## How did you run impeccable?
|
|
|
|
Via \`npx impeccable detect\` as documented.
|
|
|
|
## Provider & environment
|
|
|
|
- **Provider** (Cursor / Claude Code / Gemini CLI / Codex / Copilot / Kiro / OpenCode): Cursor
|
|
- **Provider version**: 2.4.0
|
|
- **OS**: macOS 15
|
|
|
|
## Additional context
|
|
|
|
None.
|
|
`;
|
|
|
|
function issue(overrides = {}) {
|
|
return {
|
|
number: 42,
|
|
title: '[Bug] detect crashes on directories',
|
|
body: FILLED_BUG_BODY,
|
|
state: 'open',
|
|
createdAt: '2026-08-01T00:00:00Z',
|
|
authorLogin: 'outside-reporter',
|
|
authorAssociation: 'NONE',
|
|
labels: [],
|
|
comments: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function comment(authorLogin, createdAt, body) {
|
|
return { authorLogin, createdAt, body };
|
|
}
|
|
|
|
function words(count) {
|
|
return Array.from({ length: count }, (_, i) => `word${i}`).join(' ');
|
|
}
|
|
|
|
describe('issue gate templates', () => {
|
|
it('derives required sections from the real templates', () => {
|
|
const bug = templates.find((template) => template.file === 'bug_report.md');
|
|
const feature = templates.find((template) => template.file === 'feature_request.md');
|
|
assert.deepEqual(bug.requiredSections, [
|
|
'what happened',
|
|
'steps to reproduce',
|
|
'how did you run impeccable',
|
|
'provider & environment',
|
|
]);
|
|
assert.deepEqual(feature.requiredSections, ['what problem does this solve', 'proposed solution']);
|
|
});
|
|
});
|
|
|
|
describe('issue body gate', () => {
|
|
it('passes a properly filled bug report', () => {
|
|
const plan = evaluateIssue(issue(), { templates });
|
|
assert.equal(plan.verdict, 'pass');
|
|
assert.deepEqual(plan.reasons, []);
|
|
assert.equal(plan.shouldComment, false);
|
|
assert.equal(plan.shouldClose, false);
|
|
});
|
|
|
|
it('passes a properly filled feature request', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: [
|
|
'## What problem does this solve?',
|
|
'',
|
|
'The audit command has no way to scope to one route.',
|
|
'',
|
|
'## Proposed solution',
|
|
'',
|
|
'Accept a path argument that narrows the audit to matching files.',
|
|
].join('\n'),
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'pass');
|
|
});
|
|
|
|
it('rejects and closes a body with no template sections at all', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: 'hey the tool is broken please fix asap. also add dark mode.',
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'reject');
|
|
assert.deepEqual(plan.labelsToAdd, [GATE_LABEL]);
|
|
assert.equal(plan.shouldClose, true);
|
|
assert.equal(plan.shouldComment, true);
|
|
assert.match(plan.comment, /none of the template sections/);
|
|
assert.ok(plan.comment.includes(REJECT_MARKER));
|
|
});
|
|
|
|
it('accepts headings without judging the content under them', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: [
|
|
'## What happened?',
|
|
'',
|
|
'<!-- A clear description of the bug. -->',
|
|
'',
|
|
'## Steps to reproduce',
|
|
'',
|
|
'1. ',
|
|
'2. ',
|
|
'3. ',
|
|
'',
|
|
'## How did you run impeccable?',
|
|
'',
|
|
'## Provider & environment',
|
|
'',
|
|
'- **Provider** (Cursor / Claude Code / Gemini CLI / Codex / Copilot / Kiro / OpenCode):',
|
|
'- **Provider version**: ',
|
|
'- **OS**: ',
|
|
].join('\n'),
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'pass');
|
|
});
|
|
|
|
it('names missing required sections', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: [
|
|
'## What happened?',
|
|
'',
|
|
'The build fails.',
|
|
'',
|
|
'## How did you run impeccable?',
|
|
'',
|
|
'As documented, via /impeccable audit.',
|
|
'',
|
|
'## Provider & environment',
|
|
'',
|
|
'- **OS**: macOS 15 and provider Cursor 2.4.0',
|
|
].join('\n'),
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'needs-work');
|
|
assert.deepEqual(plan.reasons, ['missing section "steps to reproduce" from the bug report template']);
|
|
});
|
|
|
|
it('flags an oversized prose body even when the structure passes', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: FILLED_BUG_BODY + '\n' + words(700),
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'needs-work');
|
|
assert.equal(plan.reasons.length, 1);
|
|
assert.match(plan.reasons[0], /the limit is 600/);
|
|
});
|
|
|
|
it('does not count fenced code blocks or details blocks against the budget', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: `${FILLED_BUG_BODY}\n\`\`\`\n${words(700)}\n\`\`\`\n<details><summary>log</summary>\n${words(700)}\n</details>\n`,
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'pass');
|
|
});
|
|
|
|
it('ignores author sub-headings between template sections', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: [
|
|
'## What happened?',
|
|
'',
|
|
'Install fails for the grok provider.',
|
|
'',
|
|
'## Steps to reproduce',
|
|
'',
|
|
'### A. Clean project',
|
|
'',
|
|
'1. Run `npx impeccable install --providers=grok`',
|
|
'',
|
|
'### B. Another provider already installed',
|
|
'',
|
|
'1. Seed a Claude install first, then run the same command.',
|
|
'',
|
|
'## How did you run impeccable?',
|
|
'',
|
|
'CLI, as documented.',
|
|
'',
|
|
'## Provider & environment',
|
|
'',
|
|
'- **OS**: macOS 15, CLI 4.0.4',
|
|
].join('\n'),
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'pass');
|
|
});
|
|
|
|
it('labels disclosed AI assistance without failing the issue', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: `AI-assisted: yes\n\n${FILLED_BUG_BODY}`,
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'pass');
|
|
assert.equal(plan.aiAssisted, true);
|
|
assert.deepEqual(plan.labelsToAdd, [AI_LABEL]);
|
|
});
|
|
|
|
it('removes the AI label when an edit drops the disclosure line', () => {
|
|
const plan = evaluateIssue(issue({
|
|
labels: [AI_LABEL],
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'pass');
|
|
assert.equal(plan.aiAssisted, false);
|
|
assert.deepEqual(plan.labelsToRemove, [AI_LABEL]);
|
|
});
|
|
|
|
it('exempts maintainers and repo members', () => {
|
|
const maintainer = evaluateIssue(issue({ authorLogin: 'pbakaus', body: 'quick note' }), { templates });
|
|
assert.equal(maintainer.exempt, true);
|
|
const member = evaluateIssue(issue({ authorAssociation: 'COLLABORATOR', body: 'quick note' }), { templates });
|
|
assert.equal(member.exempt, true);
|
|
});
|
|
|
|
it('warns a needs-work issue only once', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: FILLED_BUG_BODY + '\n' + words(700),
|
|
labels: [GATE_LABEL],
|
|
comments: [comment('github-actions[bot]', '2026-08-01T01:00:00Z', GATE_MARKER)],
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'needs-work');
|
|
assert.equal(plan.shouldComment, false);
|
|
assert.deepEqual(plan.labelsToAdd, []);
|
|
});
|
|
|
|
it('comments only once per issue', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: 'no structure here',
|
|
labels: [GATE_LABEL],
|
|
comments: [comment('github-actions[bot]', '2026-08-01T01:00:00Z', REJECT_MARKER)],
|
|
state: 'closed',
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'reject');
|
|
assert.equal(plan.shouldComment, false);
|
|
assert.equal(plan.shouldClose, false);
|
|
assert.deepEqual(plan.labelsToAdd, []);
|
|
});
|
|
|
|
it('ignores gate markers posted by untrusted commenters', () => {
|
|
const plan = evaluateIssue(issue({
|
|
body: 'no structure here',
|
|
comments: [comment('drive-by', '2026-08-01T01:00:00Z', REJECT_MARKER)],
|
|
}), { templates });
|
|
assert.equal(plan.shouldComment, true);
|
|
});
|
|
|
|
it('clears the label and reopens a gate-closed issue once it passes', () => {
|
|
const plan = evaluateIssue(issue({
|
|
state: 'closed',
|
|
labels: [GATE_LABEL],
|
|
comments: [comment('github-actions[bot]', '2026-08-01T01:00:00Z', REJECT_MARKER)],
|
|
}), { templates });
|
|
assert.equal(plan.verdict, 'pass');
|
|
assert.deepEqual(plan.labelsToRemove, [GATE_LABEL]);
|
|
assert.equal(plan.shouldReopen, true);
|
|
});
|
|
|
|
it('does not reopen issues the gate did not close', () => {
|
|
const plan = evaluateIssue(issue({
|
|
state: 'closed',
|
|
labels: [GATE_LABEL],
|
|
}), { templates });
|
|
assert.equal(plan.shouldReopen, false);
|
|
});
|
|
});
|
|
|
|
describe('comment gate', () => {
|
|
const base = {
|
|
commentAuthorLogin: 'outside-reporter',
|
|
commentAuthorAssociation: 'NONE',
|
|
issueAuthorLogin: 'outside-reporter',
|
|
issueCreatedAt: '2026-08-05T10:00:00Z',
|
|
commentCreatedAt: '2026-08-05T10:10:00Z',
|
|
};
|
|
|
|
it('minimizes an oversized early self-reply', () => {
|
|
const result = evaluateComment({ ...base, commentBody: words(400) });
|
|
assert.equal(result.shouldMinimize, true);
|
|
assert.match(result.reasons[0], /400 words of prose/);
|
|
});
|
|
|
|
it('leaves short self-replies alone', () => {
|
|
const result = evaluateComment({ ...base, commentBody: 'Forgot to say: version 2.4.0.' });
|
|
assert.equal(result.shouldMinimize, false);
|
|
});
|
|
|
|
it('leaves long comments from other participants alone', () => {
|
|
const result = evaluateComment({
|
|
...base,
|
|
commentAuthorLogin: 'helpful-stranger',
|
|
commentBody: words(400),
|
|
});
|
|
assert.equal(result.shouldMinimize, false);
|
|
});
|
|
|
|
it('leaves late self-replies alone', () => {
|
|
const result = evaluateComment({
|
|
...base,
|
|
commentCreatedAt: '2026-08-05T12:30:00Z',
|
|
commentBody: words(400),
|
|
});
|
|
assert.equal(result.shouldMinimize, false);
|
|
});
|
|
|
|
it('does not count pasted logs against the comment budget', () => {
|
|
const result = evaluateComment({
|
|
...base,
|
|
commentBody: `Full log below.\n\`\`\`\n${words(900)}\n\`\`\``,
|
|
});
|
|
assert.equal(result.shouldMinimize, false);
|
|
});
|
|
|
|
it('exempts maintainers and members', () => {
|
|
const maintainer = evaluateComment({
|
|
...base,
|
|
commentAuthorLogin: 'abdulwahabone',
|
|
issueAuthorLogin: 'abdulwahabone',
|
|
commentBody: words(400),
|
|
});
|
|
assert.equal(maintainer.shouldMinimize, false);
|
|
const member = evaluateComment({
|
|
...base,
|
|
commentAuthorAssociation: 'MEMBER',
|
|
commentBody: words(400),
|
|
});
|
|
assert.equal(member.shouldMinimize, false);
|
|
});
|
|
});
|
|
|
|
describe('helpers', () => {
|
|
it('counts prose words outside code and details blocks', () => {
|
|
assert.equal(proseWordCount('one two three'), 3);
|
|
assert.equal(proseWordCount('one\n```\nskip these words\n```\ntwo'), 2);
|
|
assert.equal(proseWordCount('one <details>skip skip</details> two'), 2);
|
|
assert.equal(proseWordCount('<!-- skip --> one'), 1);
|
|
});
|
|
|
|
it('parses workflow arguments', () => {
|
|
const options = parseArgs(['--apply', '--repo', 'pbakaus/impeccable', '--issue', '42', '--comment-id', '99']);
|
|
assert.equal(options.apply, true);
|
|
assert.equal(options.issue, 42);
|
|
assert.equal(options.commentId, '99');
|
|
|
|
assert.throws(() => parseArgs(['--max-words', '0']), /positive/);
|
|
assert.throws(() => parseArgs(['--unknown']), /Unknown argument/);
|
|
});
|
|
});
|