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
51 changes: 25 additions & 26 deletions sdk/src/backlog-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@

/** First bold top-level bullet: `- **Title** rest`. */
const ENTRY = /^- \*\*(.+?)\*\*\s*(.*(?:\n .*)*)/m;
const ACTION_TITLE =
/^(?:add|build|change|close|create|document|fix|implement|persist|refuse|release|remove|rename|replace|sharpen|update|validate|wire)\b/i;
const NOTES_TITLE = /^(?:notes?|release notes|upstream issues)\s*(?:\(|:|$)/i;
const CODE_REFERENCE = /`([^`]+)`/g;
// Outcomes include explicit changes and concrete defect statements. A list of
// identifiers or links alone carries neither, so it cannot qualify as work.
const ENGINEERING_OUTCOME =
/\b(?:add|asserts?|breaks?|build|cannot|capture|catches?|change|close|collapses?|cross-compile|delete|document|drift|duplicates?|fails?|fix|implement|invoke|leaks?|make|missing|must|needs?|no (?:end-to-end )?coverage|persist|refuse|register|replace|reserve|restore|run|scope|should|spawn|untested|update|use|validate|verified?|wire|wrong)\b/i;

export interface BacklogEntry {
title: string;
Expand Down Expand Up @@ -108,18 +110,27 @@ function isNonEmptyStringArray(value: unknown): value is string[] {
*/
export function packageFromEntry(entry: BacklogEntry): Record<string, unknown> {
const blob = `${entry.title} ${entry.body}`;
const files = scopeReferences(blob);
const references = [...entry.body.matchAll(CODE_REFERENCE)].map((match) => match[1] ?? '');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include title paths when deriving scope

For entries whose concrete path appears in the bold title, scanning only entry.body drops that scope even though the previous matcher scanned the combined title and body. The real backlog entry titled ``sdk/node_modules is absent, for example, now loses `sdk/node_modules` and falls back to `.` because its body contains only command/symbol references, broadening the package from the named target to the entire repository.

Useful? React with 👍 / 👎.

const files = [
...new Set(
references
.filter(
(candidate): candidate is string =>
/^[A-Za-z_][A-Za-z0-9._-]*(?:\/[A-Za-z0-9._-]*)+$/.test(candidate) &&
!/^\/|\/\//.test(candidate),
),
),
];
const hasEngineeringOutcome = ENGINEERING_OUTCOME.test(entry.body);
// A symbol or command locates work in this repository, but not necessarily
// one file. Preserve that honest breadth instead of discarding the signal.
if (files.length === 0 && references.length > 0 && hasEngineeringOutcome) {
files.push('.');
Comment on lines +127 to +128

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refuse unresolved references instead of using repository scope

When an entry contains only a stale, misspelled, or non-path backticked reference and any outcome keyword—for example, Update doesNotExistAnywhere—this fallback emits files_in_scope: ['.']. The consumer's nonexistent-file guard then always passes because . exists, so malformed entries become actionable despite having no identifiable target; resolve the reference to real scope or retain missing_scope instead of silently broadening it.

AGENTS.md reference: AGENTS.md:L16-L18

Useful? React with 👍 / 👎.

}
const gate = blob.match(/\bgate[ -]?(\d+)\b/i);
const explicitChecks = (entry.body.match(/`[^`]+`/g) || [])
.map((candidate) => candidate.slice(1, -1))
.filter((candidate) => /\s/.test(candidate));
const definitionOfDone = NOTES_TITLE.test(entry.title)
? []
: explicitChecks.length > 0
? explicitChecks
: ACTION_TITLE.test(entry.title)
? [entry.title.replace(/[.:]\s*$/, '')]
: [];
const definitionOfDone = hasEngineeringOutcome
? [entry.title.replace(/[.:]\s*$/, '')]
: [];
Comment on lines +131 to +133

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve explicit verification commands

When an actionable body includes a concrete check, such as run npm test --silent, this branch now replaces that check with the title. Since validation only requires a nonempty string, the package is accepted with a definition_of_done like "Validate packages"—or even a defect statement like "Half the drive runs complete but build nothing"—rather than the executable completion criterion supplied by the author. Preserve explicit checks when present and use inferred outcomes only as a fallback.

AGENTS.md reference: AGENTS.md:L16-L18

Useful? React with 👍 / 👎.

return {
title: entry.title,
description: entry.body,
Expand All @@ -128,15 +139,3 @@ export function packageFromEntry(entry: BacklogEntry): Record<string, unknown> {
definition_of_done: definitionOfDone,
};
}

/** Backticked paths, symbols, and command references are explicit scope. */
function scopeReferences(blob: string): string[] {
const references = [...blob.matchAll(/`([^`]+)`/g)]
.map((match) => match[1])
.filter((candidate): candidate is string => candidate !== undefined)
.filter((candidate) => {
if (/^\/|\/\//.test(candidate)) return false;
return !/\s/.test(candidate) || /--|<[^>]+>|\$[A-Za-z]/.test(candidate);
});
return [...new Set(references)];
}
30 changes: 26 additions & 4 deletions sdk/tests/backlog-picker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,35 @@ describe('backlog picker', () => {
});

describe('work package validation', () => {
it('uses a backticked code symbol as scope evidence', () => {
it('uses a referenced code symbol as evidence of repository scope', async () => {
const work = packageFromEntry({
title: 'Refuse malformed work packages',
body: 'Make `validateWorkPackage` reject an unterminated backtick.',
title: 'Refuse an entry with unterminated backticks',
body: 'Update `validateWorkPackage` to return a typed refusal for malformed input.',
});

expect(work['files_in_scope']).toEqual(['validateWorkPackage']);
expect(await validate(work)).toMatchObject({
accepted: true,
work: {
files_in_scope: ['.'],
definition_of_done: ['Refuse an entry with unterminated backticks'],
},
});
});

it('keeps at least twenty real backlog entries actionable', async () => {
const backlog = readFileSync(join(__dirname, '..', '..', 'ops', 'BACKLOG.md'), 'utf8');
const entries = [...backlog.matchAll(/^- \*\*(.+?)\*\*\s*(.*(?:\n .*)*)/gm)].map(
(match) => ({
title: match[1] ?? '',
body: (match[2] ?? '').replace(/\s+/g, ' ').trim(),
}),
);
const verdicts = await Promise.all(entries.map((entry) => validate(packageFromEntry(entry))));

const actionable = verdicts.filter(
(verdict) => (verdict as { accepted: boolean }).accepted,
).length;
expect(actionable).toBeGreaterThanOrEqual(20);
});

it('accepts an engineering task stated as an imperative outcome', async () => {
Expand Down