forked from bkinnightskytw/code_work_spawner
fix(issue-assistant): poll only open issues
This commit is contained in:
parent
c00b30306c
commit
dd56ab4554
|
|
@ -22,6 +22,7 @@ class GitHubClient {
|
||||||
final queryParts = <String>[
|
final queryParts = <String>[
|
||||||
for (final repo in repoList) 'repo:${repo.fullName}',
|
for (final repo in repoList) 'repo:${repo.fullName}',
|
||||||
'is:issue',
|
'is:issue',
|
||||||
|
'state:open',
|
||||||
if (since != null) 'updated:>=${since.toUtc().toIso8601String()}',
|
if (since != null) 'updated:>=${since.toUtc().toIso8601String()}',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1261,16 +1261,19 @@ projects:
|
||||||
});
|
});
|
||||||
|
|
||||||
/// ```gherkin
|
/// ```gherkin
|
||||||
/// Scenario: Aggregate one search request across multiple enabled projects
|
/// Scenario: Aggregate one search request across multiple enabled projects while ignoring closed issues
|
||||||
/// Given two temporary project checkouts that can be used as local repo paths
|
/// Given two temporary project checkouts that can be used as local repo paths
|
||||||
/// And GitHub issue data containing an explicit assistant mention in each repository
|
/// And GitHub issue data containing an explicit assistant mention in each repository
|
||||||
|
/// And one of the search results is a closed issue
|
||||||
/// And a responder that emits a planning reply
|
/// And a responder that emits a planning reply
|
||||||
/// And an orchestrator-style config with both repositories enabled
|
/// And an orchestrator-style config with both repositories enabled
|
||||||
/// When the app processes one polling cycle
|
/// When the app processes one polling cycle
|
||||||
/// Then it fetches issue updates through one aggregate GitHub search request
|
/// Then it fetches issue updates through one aggregate GitHub search request for open issues only
|
||||||
/// And it still posts one reply in each repository
|
/// And it still posts one reply in each repository
|
||||||
/// ```
|
/// ```
|
||||||
test('Aggregate one search request across multiple enabled projects', () async {
|
test(
|
||||||
|
'Aggregate one search request across multiple enabled projects while ignoring closed issues',
|
||||||
|
() async {
|
||||||
// Given two temporary project checkouts that can be used as local repo paths.
|
// Given two temporary project checkouts that can be used as local repo paths.
|
||||||
final sandbox = await Directory.systemTemp.createTemp(
|
final sandbox = await Directory.systemTemp.createTemp(
|
||||||
'cws-aggregate-search-',
|
'cws-aggregate-search-',
|
||||||
|
|
@ -1315,6 +1318,16 @@ projects:
|
||||||
'user': {'login': 'reporter-two'},
|
'user': {'login': 'reporter-two'},
|
||||||
'labels': const [],
|
'labels': const [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'number': 23,
|
||||||
|
'title': 'Repo two closed mention',
|
||||||
|
'body': 'closed repo issue',
|
||||||
|
'state': 'closed',
|
||||||
|
'html_url': 'https://example.test/owner/repo-two/issues/23',
|
||||||
|
'updated_at': '2026-04-04T17:02:00Z',
|
||||||
|
'user': {'login': 'reporter-three'},
|
||||||
|
'labels': const [],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
commentResponsesByRepo: {
|
commentResponsesByRepo: {
|
||||||
|
|
@ -1343,6 +1356,17 @@ projects:
|
||||||
'user': {'login': 'reporter-two'},
|
'user': {'login': 'reporter-two'},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
23: [
|
||||||
|
{
|
||||||
|
'id': 230,
|
||||||
|
'body': '@helper please review closed repo two',
|
||||||
|
'html_url':
|
||||||
|
'https://example.test/owner/repo-two/issues/23#issuecomment-230',
|
||||||
|
'created_at': '2026-04-04T17:02:00Z',
|
||||||
|
'updated_at': '2026-04-04T17:02:00Z',
|
||||||
|
'user': {'login': 'reporter-three'},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
postCommentIssueNumbersByRepo: {
|
postCommentIssueNumbersByRepo: {
|
||||||
|
|
@ -1362,7 +1386,9 @@ projects:
|
||||||
});
|
});
|
||||||
|
|
||||||
// And an orchestrator-style config with both repositories enabled.
|
// And an orchestrator-style config with both repositories enabled.
|
||||||
final configFile = File(p.join(sandbox.path, 'agent-orchestrator.yaml'));
|
final configFile = File(
|
||||||
|
p.join(sandbox.path, 'agent-orchestrator.yaml'),
|
||||||
|
);
|
||||||
await configFile.writeAsString('''
|
await configFile.writeAsString('''
|
||||||
defaults:
|
defaults:
|
||||||
issueAssistant:
|
issueAssistant:
|
||||||
|
|
@ -1390,12 +1416,13 @@ projects:
|
||||||
await app.runOnce();
|
await app.runOnce();
|
||||||
await app.close();
|
await app.close();
|
||||||
|
|
||||||
// Then it fetches issue updates through one aggregate GitHub search request.
|
// Then it fetches issue updates through one aggregate GitHub search request for open issues only.
|
||||||
final logLines = await ghLog.readAsLines();
|
final logLines = await ghLog.readAsLines();
|
||||||
expect(
|
final searchLogs = logLines
|
||||||
logLines.where((line) => line.contains('search/issues')).length,
|
.where((line) => line.contains('search/issues'))
|
||||||
1,
|
.toList();
|
||||||
);
|
expect(searchLogs.length, 1);
|
||||||
|
expect(searchLogs.single, contains('state:open'));
|
||||||
|
|
||||||
// And it still posts one reply in each repository.
|
// And it still posts one reply in each repository.
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -1416,6 +1443,16 @@ projects:
|
||||||
.length,
|
.length,
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
});
|
expect(
|
||||||
|
logLines
|
||||||
|
.where(
|
||||||
|
(line) =>
|
||||||
|
line.contains('repos/owner/repo-two/issues/23/comments'),
|
||||||
|
)
|
||||||
|
.length,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,19 @@ Future<void> writeFakeGhScript({
|
||||||
final rightUpdatedAt = right['updated_at'] as String? ?? '';
|
final rightUpdatedAt = right['updated_at'] as String? ?? '';
|
||||||
return leftUpdatedAt.compareTo(rightUpdatedAt);
|
return leftUpdatedAt.compareTo(rightUpdatedAt);
|
||||||
});
|
});
|
||||||
|
final openSearchItems = searchItems
|
||||||
|
.where((item) => item['state'] == 'open')
|
||||||
|
.toList(growable: false);
|
||||||
|
final allSearchResponse = jsonEncode({
|
||||||
|
'total_count': searchItems.length,
|
||||||
|
'incomplete_results': false,
|
||||||
|
'items': searchItems,
|
||||||
|
});
|
||||||
|
final openSearchResponse = jsonEncode({
|
||||||
|
'total_count': openSearchItems.length,
|
||||||
|
'incomplete_results': false,
|
||||||
|
'items': openSearchItems,
|
||||||
|
});
|
||||||
|
|
||||||
final buffer = StringBuffer()
|
final buffer = StringBuffer()
|
||||||
..writeln('#!/usr/bin/env bash')
|
..writeln('#!/usr/bin/env bash')
|
||||||
|
|
@ -79,15 +92,21 @@ Future<void> writeFakeGhScript({
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||||
|
..writeln(r''' query=""''')
|
||||||
|
..writeln(r''' for arg in "$@"; do''')
|
||||||
|
..writeln(r''' if [[ "$arg" == q=* ]]; then''')
|
||||||
|
..writeln(r''' query="${arg#q=}"''')
|
||||||
|
..writeln(r''' fi''')
|
||||||
|
..writeln(r''' done''')
|
||||||
|
..writeln(r''' if [[ "$query" == *"state:open"* ]]; then''')
|
||||||
..writeln(" cat <<'JSON'")
|
..writeln(" cat <<'JSON'")
|
||||||
..writeln(
|
..writeln(openSearchResponse)
|
||||||
jsonEncode({
|
|
||||||
'total_count': searchItems.length,
|
|
||||||
'incomplete_results': false,
|
|
||||||
'items': searchItems,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
..writeln('JSON')
|
..writeln('JSON')
|
||||||
|
..writeln(r''' else''')
|
||||||
|
..writeln(" cat <<'JSON'")
|
||||||
|
..writeln(allSearchResponse)
|
||||||
|
..writeln('JSON')
|
||||||
|
..writeln(r''' fi''')
|
||||||
..writeln(' exit 0')
|
..writeln(' exit 0')
|
||||||
..writeln('fi');
|
..writeln('fi');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue