1. get shows milestones/labels: GetCommand._format() now shows Milestones:/Labels: lines when non-empty, between Created: and Links: 2. unarchive command: kanban unarchive --id <id> [--column <col>] restores a ticket from archive/ back to a column (default: first configured column); registered as 'kanban_unarchive_ticket' MCP tool (15 tools total) 3. Test isolation: add dart_test.yaml (concurrency: 1) — Directory.current is a process-global OS chdir(); concurrent test files in the same process would race. Now dart test packages/core packages/kanban passes cleanly. 4. update empty multi-option fix: --milestone '' / --label '' with empty strings now filters them out (treats as 'clear to empty') rather than writing spurious empty-string YAML list entries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
68 lines
1.9 KiB
Dart
68 lines
1.9 KiB
Dart
import 'package:dew_core/dew_core.dart';
|
|
import '../kanban_config.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import '../ticket.dart';
|
|
import '../ticket_store.dart';
|
|
|
|
class GetCommand extends DewCommand with DewToolCommand {
|
|
GetCommand() {
|
|
argParser.addOption(
|
|
'id',
|
|
abbr: 'i',
|
|
mandatory: true,
|
|
help: 'Ticket ID (e.g. DEW-0001).',
|
|
);
|
|
}
|
|
|
|
@override
|
|
final String name = 'get';
|
|
|
|
@override
|
|
final String description = 'Get a kanban ticket by ID.';
|
|
|
|
@override
|
|
final String toolName = 'kanban_get_ticket';
|
|
|
|
@override
|
|
Future<String> callAsTool(Map<String, dynamic> args) async {
|
|
final id = (args['id'] as String).toUpperCase();
|
|
final context = await ProjectContext.find();
|
|
final store = TicketStore(
|
|
kanbanDir: p.join(context.root, '.project', 'kanban'),
|
|
prefix: context.config.kanban.prefix,
|
|
);
|
|
final ticket = await store.findById(id);
|
|
if (ticket == null) throw ArgumentError('Ticket $id not found.');
|
|
return _format(ticket);
|
|
}
|
|
|
|
String _format(Ticket t) {
|
|
final buf = StringBuffer();
|
|
buf.writeln('[${t.id}] (${t.type}) [${t.column}] ${t.title}');
|
|
buf.writeln('Created: ${t.created.toLocal().toString().split('.').first}');
|
|
if (t.milestones.isNotEmpty) {
|
|
buf.writeln('Milestones: ${t.milestones.join(', ')}');
|
|
}
|
|
if (t.labels.isNotEmpty) {
|
|
buf.writeln('Labels: ${t.labels.join(', ')}');
|
|
}
|
|
if (t.links.isNotEmpty) {
|
|
buf.writeln();
|
|
buf.writeln('Links:');
|
|
for (final link in t.links) {
|
|
buf.writeln(' ${link.type.replaceAll('_', ' ')}: ${link.targetId}');
|
|
}
|
|
}
|
|
if (t.body.isNotEmpty) {
|
|
buf.writeln();
|
|
buf.writeln(t.body);
|
|
}
|
|
for (final (i, comment) in t.comments.indexed) {
|
|
buf.writeln();
|
|
buf.writeln('── Comment ${i + 1} ${'─' * 20}');
|
|
buf.write(comment);
|
|
}
|
|
return buf.toString().trimRight();
|
|
}
|
|
}
|