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
83 changes: 83 additions & 0 deletions .github/scripts/__tests__/agents-pr-meta-keepalive.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,89 @@ test('keepalive detection accepts valid non-issue source context', async () => {
assert.ok(reactionCalls.includes('rocket'));
});

test('keepalive detection blocks no-automation source context', async () => {
const outputs = {};
const scopeBlock = [
'<!-- codex-keepalive-round: 3 -->',
'<!-- codex-keepalive-marker -->',
'<!-- codex-keepalive-trace: trace-local -->',
'@codex Continue working on the local request.',
].join('\n');

const github = {
rest: {
pulls: {
async get() {
return {
data: {
body: [
'## Workflow Source',
'',
'Started from:',
'- [x] Local Codex/user request',
'',
'Automation intent:',
'- [x] Human-only unless checks fail',
].join('\n'),
head: { ref: 'codex/source-context', repo: { fork: false, owner: { login: 'stranske' } } },
base: { ref: 'main', repo: { owner: { login: 'stranske' } } },
title: 'Add source context',
},
};
},
},
issues: {
async listComments() {
return { data: [] };
},
},
reactions: {
async listForIssueComment() {
return { data: [] };
},
async createForIssueComment() {
return { status: 201, data: { content: 'hooray' } };
},
},
},
async paginate(method) {
if (method === this.rest.issues.listComments) {
return [];
}
if (method === this.rest.reactions.listForIssueComment) {
return [];
}
return [];
},
};

await detectKeepalive({
core: createCore(outputs),
github,
context: {
repo: { owner: 'stranske', repo: 'Workflows' },
payload: {
comment: {
id: 200,
html_url: 'https://example.test/comment/200',
body: scopeBlock,
user: { login: 'stranske' },
},
issue: { number: 4002 },
},
},
env: {
ALLOWED_LOGINS: 'stranske',
KEEPALIVE_MARKER: '<!-- codex-keepalive-marker -->',
GATE_OK: 'true',
},
});

assert.equal(outputs.dispatch, 'false');
assert.equal(outputs.reason, 'no-automation-source-context');
assert.equal(outputs.source_type, 'local_request');
});

test('keepalive detection accepts sync campaign source context without linked issue', async () => {
const outputs = {};
const reactionCalls = [];
Expand Down
16 changes: 16 additions & 0 deletions .github/scripts/__tests__/agents-pr-meta-update-body.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
buildSourceContextRepairCommentBody,
buildSourceContextResolvedCommentBody,
resolveExplicitNonIssueWorkflowSourceContext,
resolveNonIssueWorkflowSourceContextForBodySync,
resolveSourceContextRepairComment,
resolveAgentType,
stripPrTemplateContent,
Expand Down Expand Up @@ -485,6 +486,21 @@ test('resolveExplicitNonIssueWorkflowSourceContext ignores source issue markers'
assert.equal(context, null);
});

test('resolveNonIssueWorkflowSourceContextForBodySync preserves issue-sourced sync precedence', () => {
const context = resolveNonIssueWorkflowSourceContextForBodySync(
{
body: [
'<!-- meta:issue:123 -->',
'<!-- workflow-source:local_request -->',
'<!-- workflow-source-ref:codex-thread-2026-04-26 -->',
].join('\n'),
},
123,
);

assert.equal(context, null);
});

test('resolveSourceContextRepairComment updates an existing warning once', async () => {
const calls = { update: 0, body: '' };
const github = {
Expand Down
15 changes: 15 additions & 0 deletions .github/scripts/__tests__/coverage-monitor-summary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@ test('treats PR source context coverage as an optional existing file input', ()
assert.equal(optionalExistingReportPath(` ${prSource} `), prSource);
});

test('treats stat errors as absent optional report inputs', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'coverage-monitor-'));
const prSource = writeJson(dir, 'pr-source.json', report('pass'));
const originalStatSync = fs.statSync;
fs.statSync = () => {
throw new Error('stat failed');
};

try {
assert.equal(optionalExistingReportPath(prSource), '');
} finally {
fs.statSync = originalStatSync;
}
});

test('does not include PR source context coverage by default', () => {
const options = parseArgs([]);

Expand Down
49 changes: 49 additions & 0 deletions .github/scripts/__tests__/source-context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
extractIssueNumberFromPull,
normalizeSourceType,
parseWorkflowSourceBlock,
hasNoAutomationWorkflowContext,
resolvePrSourceContext,
sourceTypeFromCheckedTemplate,
sourceTypeFromLabels,
Expand Down Expand Up @@ -146,6 +147,36 @@ Started from:
assert.equal(sourceTypeFromCheckedTemplate(body), SOURCE_TYPES.MANUAL_REMOTE);
});

test('sourceTypeFromCheckedTemplate accepts slash-separated local request wording', () => {
const body = `
## Workflow Source

Started from:
- [ ] GitHub issue: #
- [x] Local Codex/user request

Automation intent:
- [ ] Human-only unless checks fail
`;

assert.equal(sourceTypeFromCheckedTemplate(body), SOURCE_TYPES.LOCAL_REQUEST);
});

test('sourceTypeFromCheckedTemplate ignores automation intent choices', () => {
const body = `
## Workflow Source

Started from:
- [ ] GitHub issue: #
- [x] Local Codex/user request

Automation intent:
- [x] Human-only unless checks fail
`;

assert.equal(sourceTypeFromCheckedTemplate(body), SOURCE_TYPES.LOCAL_REQUEST);
});

test('sourceTypeFromCheckedTemplate treats human-only PRs as manual remote work', () => {
const body = `
## Workflow Source
Expand All @@ -161,6 +192,24 @@ Automation intent:
assert.equal(sourceTypeFromCheckedTemplate(body), SOURCE_TYPES.MANUAL_REMOTE);
});

test('resolvePrSourceContext marks no-automation sources without changing source type', () => {
const context = resolvePrSourceContext({
body: `
## Workflow Source

Started from:
- [x] Direct PR / remote GitHub work

Automation intent:
- [x] Human-only unless checks fail
`,
});

assert.equal(context.sourceType, SOURCE_TYPES.MANUAL_REMOTE);
assert.equal(context.noAutomation, true);
assert.equal(hasNoAutomationWorkflowContext({ labels: [{ name: 'workflow:no-automation' }] }), true);
});

test('sourceTypeFromCheckedTemplate rejects ambiguous checked source choices', () => {
const body = `
## Workflow Source
Expand Down
9 changes: 9 additions & 0 deletions .github/scripts/agents_pr_meta_keepalive.js
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,15 @@ async function detectKeepalive({ core, github, context, env = process.env }) {
return finalise();
}

if (sourceContext.noAutomation) {
outputs.reason = 'no-automation-source-context';
outputs.dispatch = 'false';
core.info(
`Keepalive dispatch skipped: PR source context opts out of automation (${formatSourceContextForLog(sourceContext)}).`,
);
return finalise();
}

if (!issueNumber) {
if (sourceContext.isValid && !sourceContext.requiresIssue) {
core.info(
Expand Down
30 changes: 27 additions & 3 deletions .github/scripts/agents_pr_meta_update_body.js
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,10 @@ function resolveExplicitNonIssueWorkflowSourceContext(pr = {}) {
};
}

function resolveNonIssueWorkflowSourceContextForBodySync(pr = {}, issueNumber = null) {
return issueNumber ? null : resolveExplicitNonIssueWorkflowSourceContext(pr);
}

async function resolveSourceContextRepairComment({
github,
owner,
Expand Down Expand Up @@ -1356,7 +1360,9 @@ async function run({github: rawGithub, context, core, inputs}) {
return;
}

const explicitNonIssueSourceContext = resolveExplicitNonIssueWorkflowSourceContext(pr);
const issueNumber = extractIssueNumberFromPull(pr);
const sourceContext = resolvePrSourceContext(pr);
const explicitNonIssueSourceContext = resolveNonIssueWorkflowSourceContextForBodySync(pr, issueNumber);
if (explicitNonIssueSourceContext) {
Comment on lines +1363 to 1366

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use strict issue detection before skipping non-issue sync

This new gate now discards explicit non-issue workflow markers whenever issueNumber is truthy, but in run() that value comes from extractIssueNumberFromPull (from the keepalive script), which accepts broad matches like #123 in titles/branches. That means a non-issue PR with <!-- workflow-source:local_request --> can now be misclassified as issue-sourced just because its title contains an incidental #N, and the body-sync flow will fetch and apply content from an unrelated issue. Please key this decision off a stricter issue signal (for example the source-context resolver’s issue determination) before suppressing non-issue context.

Useful? React with 👍 / 👎.

core.info(
`PR #${pr.number} has explicit non-issue workflow source context (${formatSourceContextForLog(explicitNonIssueSourceContext)}); skipping issue-sourced body sync.`,
Expand All @@ -1382,8 +1388,6 @@ async function run({github: rawGithub, context, core, inputs}) {
return;
}

const issueNumber = extractIssueNumberFromPull(pr);
const sourceContext = resolvePrSourceContext(pr);
if (!issueNumber) {
if (sourceContext.isValid && !sourceContext.requiresIssue) {
core.info(
Expand Down Expand Up @@ -1454,6 +1458,25 @@ async function run({github: rawGithub, context, core, inputs}) {
return;
}

try {
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: pr.number,
});
await resolveSourceContextRepairComment({
github,
owner,
repo,
prNumber: pr.number,
comments,
sourceContext,
core,
});
Comment on lines +1471 to +1475

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveSourceContextRepairComment always rewrites the warning using buildSourceContextResolvedCommentBody, which currently ends with “No linked GitHub issue is required for this PR.”. In this new block it runs even when issueNumber is present, so the resolved comment becomes misleading for issue-sourced PRs. Consider either (a) branching the resolved body based on sourceContext.requiresIssue / issueNumber (e.g., “Linked issue #… detected”), or (b) passing an explicit non-issue context here only when you want the “no issue required” wording.

Copilot uses AI. Check for mistakes.
} catch (error) {
core.warning(`Failed to resolve workflow source repair comment: ${error.message}`);
}

core.info(`Fetching content from issue #${issueNumber} for PR #${pr.number}`);
const issueResponse = await withRetries(
() => github.rest.issues.get({owner, repo, issue_number: issueNumber}),
Expand Down Expand Up @@ -1629,6 +1652,7 @@ module.exports = {
buildSourceContextRepairCommentBody,
buildSourceContextResolvedCommentBody,
resolveExplicitNonIssueWorkflowSourceContext,
resolveNonIssueWorkflowSourceContextForBodySync,
resolveSourceContextRepairComment,
isCampaignIssue,
buildStatusBlock,
Expand Down
8 changes: 6 additions & 2 deletions .github/scripts/coverage_monitor_summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ function optionalExistingReportPath(filePath) {
const cleanedPath = cleanString(filePath);
if (!cleanedPath) return '';
if (!fs.existsSync(cleanedPath)) return '';
if (!fs.statSync(cleanedPath).isFile()) return '';
return cleanedPath;
try {
if (!fs.statSync(cleanedPath).isFile()) return '';
return cleanedPath;
} catch (_error) {
return '';
}
}

function buildCoverageMonitorSummary(options = {}) {
Expand Down
Loading
Loading