dew/packages/kanban/lib/src/commands/get_command.dart
Chris Hendrickson 037f5fde28 Four polish fixes
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>
2026-04-23 22:10:12 -04:00

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();
}
}