@task-mcp/shared 1.0.28 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/algorithms/index.d.ts +1 -1
  2. package/dist/algorithms/index.d.ts.map +1 -1
  3. package/dist/algorithms/index.js +1 -1
  4. package/dist/algorithms/index.js.map +1 -1
  5. package/dist/algorithms/topological-sort.d.ts +21 -1
  6. package/dist/algorithms/topological-sort.d.ts.map +1 -1
  7. package/dist/algorithms/topological-sort.js +12 -1
  8. package/dist/algorithms/topological-sort.js.map +1 -1
  9. package/dist/schemas/index.d.ts +1 -0
  10. package/dist/schemas/index.d.ts.map +1 -1
  11. package/dist/schemas/index.js +2 -0
  12. package/dist/schemas/index.js.map +1 -1
  13. package/dist/schemas/session.d.ts +521 -0
  14. package/dist/schemas/session.d.ts.map +1 -0
  15. package/dist/schemas/session.js +79 -0
  16. package/dist/schemas/session.js.map +1 -0
  17. package/dist/schemas/task.d.ts.map +1 -1
  18. package/dist/schemas/task.js +20 -6
  19. package/dist/schemas/task.js.map +1 -1
  20. package/dist/schemas/view.d.ts +18 -18
  21. package/dist/utils/hierarchy.d.ts.map +1 -1
  22. package/dist/utils/hierarchy.js +6 -3
  23. package/dist/utils/hierarchy.js.map +1 -1
  24. package/dist/utils/index.d.ts +2 -1
  25. package/dist/utils/index.d.ts.map +1 -1
  26. package/dist/utils/index.js +17 -1
  27. package/dist/utils/index.js.map +1 -1
  28. package/dist/utils/plan-parser.d.ts +57 -0
  29. package/dist/utils/plan-parser.d.ts.map +1 -0
  30. package/dist/utils/plan-parser.js +377 -0
  31. package/dist/utils/plan-parser.js.map +1 -0
  32. package/dist/utils/terminal-ui.d.ts +129 -0
  33. package/dist/utils/terminal-ui.d.ts.map +1 -1
  34. package/dist/utils/terminal-ui.js +191 -0
  35. package/dist/utils/terminal-ui.js.map +1 -1
  36. package/dist/utils/terminal-ui.test.js +236 -0
  37. package/dist/utils/terminal-ui.test.js.map +1 -1
  38. package/package.json +2 -2
  39. package/src/algorithms/index.ts +3 -0
  40. package/src/algorithms/topological-sort.ts +31 -1
  41. package/src/schemas/index.ts +11 -0
  42. package/src/schemas/session.ts +100 -0
  43. package/src/schemas/task.ts +30 -16
  44. package/src/utils/hierarchy.ts +8 -3
  45. package/src/utils/index.ts +31 -0
  46. package/src/utils/plan-parser.ts +478 -0
  47. package/src/utils/terminal-ui.test.ts +286 -0
  48. package/src/utils/terminal-ui.ts +315 -0
@@ -829,3 +829,289 @@ describe("BOX constants", () => {
829
829
  expect(BOX.dblVertical).toBe("║");
830
830
  });
831
831
  });
832
+
833
+ // =============================================================================
834
+ // Status Symbols Tests
835
+ // =============================================================================
836
+
837
+ import {
838
+ STATUS_SYMBOLS,
839
+ formatStatusSymbol,
840
+ PRIORITY_LEVELS,
841
+ formatPriorityBadge,
842
+ renderSection,
843
+ renderSections,
844
+ renderAlert,
845
+ renderStats,
846
+ formatDueDate,
847
+ formatTaskLine,
848
+ } from "./terminal-ui.js";
849
+
850
+ describe("STATUS_SYMBOLS", () => {
851
+ test("has checkbox-style symbols for all statuses", () => {
852
+ expect(STATUS_SYMBOLS.pending).toBe("[ ]");
853
+ expect(STATUS_SYMBOLS.in_progress).toBe("[~]");
854
+ expect(STATUS_SYMBOLS.completed).toBe("[x]");
855
+ expect(STATUS_SYMBOLS.blocked).toBe("[!]");
856
+ expect(STATUS_SYMBOLS.cancelled).toBe("[-]");
857
+ expect(STATUS_SYMBOLS.deferred).toBe("[.]");
858
+ });
859
+ });
860
+
861
+ describe("formatStatusSymbol", () => {
862
+ test("returns colored symbol for known status", () => {
863
+ const result = formatStatusSymbol("pending");
864
+ expect(stripAnsi(result)).toBe("[ ]");
865
+ });
866
+
867
+ test("returns colored symbol for blocked status", () => {
868
+ const result = formatStatusSymbol("blocked");
869
+ expect(stripAnsi(result)).toBe("[!]");
870
+ expect(result).toContain("\x1b[31m"); // red
871
+ });
872
+
873
+ test("returns unknown symbol for invalid status", () => {
874
+ const result = formatStatusSymbol("invalid");
875
+ expect(stripAnsi(result)).toBe("[?]");
876
+ });
877
+ });
878
+
879
+ // =============================================================================
880
+ // Priority Badge Tests
881
+ // =============================================================================
882
+
883
+ describe("PRIORITY_LEVELS", () => {
884
+ test("has P0-P3 labels", () => {
885
+ expect(PRIORITY_LEVELS.critical.label).toBe("P0");
886
+ expect(PRIORITY_LEVELS.high.label).toBe("P1");
887
+ expect(PRIORITY_LEVELS.medium.label).toBe("P2");
888
+ expect(PRIORITY_LEVELS.low.label).toBe("P3");
889
+ });
890
+
891
+ test("critical has background styling", () => {
892
+ expect(PRIORITY_LEVELS.critical.bg).toBe(true);
893
+ });
894
+ });
895
+
896
+ describe("formatPriorityBadge", () => {
897
+ test("formats critical with background", () => {
898
+ const result = formatPriorityBadge("critical");
899
+ expect(result).toContain("P0");
900
+ expect(result).toContain("\x1b[41m"); // red background
901
+ });
902
+
903
+ test("formats high priority", () => {
904
+ const result = formatPriorityBadge("high");
905
+ expect(stripAnsi(result)).toBe("P1");
906
+ });
907
+
908
+ test("returns dashes for unknown priority", () => {
909
+ const result = formatPriorityBadge("unknown");
910
+ expect(stripAnsi(result)).toBe("--");
911
+ });
912
+ });
913
+
914
+ // =============================================================================
915
+ // Section Renderer Tests
916
+ // =============================================================================
917
+
918
+ describe("renderSection", () => {
919
+ test("renders header and items", () => {
920
+ const items = [
921
+ { primary: "Task 1" },
922
+ { primary: "Task 2" },
923
+ ];
924
+ const lines = renderSection("Test Section", items);
925
+
926
+ expect(lines.length).toBeGreaterThan(0);
927
+ expect(stripAnsi(lines[0] ?? "")).toContain("Test Section");
928
+ expect(lines.some(l => l.includes("Task 1"))).toBe(true);
929
+ expect(lines.some(l => l.includes("Task 2"))).toBe(true);
930
+ });
931
+
932
+ test("renders with icon", () => {
933
+ const lines = renderSection("Tasks", [], { icon: "🎯" });
934
+ expect(stripAnsi(lines[0] ?? "")).toContain("🎯");
935
+ });
936
+
937
+ test("renders empty state message", () => {
938
+ const lines = renderSection("Empty", [], { emptyMessage: "No items" });
939
+ expect(lines.some(l => stripAnsi(l).includes("No items"))).toBe(true);
940
+ });
941
+
942
+ test("renders item details", () => {
943
+ const items = [
944
+ { primary: "Task", details: ["Detail 1", "Detail 2"] },
945
+ ];
946
+ const lines = renderSection("With Details", items);
947
+ expect(lines.some(l => l.includes("Detail 1"))).toBe(true);
948
+ expect(lines.some(l => l.includes("Detail 2"))).toBe(true);
949
+ });
950
+
951
+ test("renders prefix and suffix", () => {
952
+ const items = [
953
+ { primary: "Task", prefix: "[x]", suffix: "P1" },
954
+ ];
955
+ const lines = renderSection("Test", items);
956
+ expect(lines.some(l => l.includes("[x]"))).toBe(true);
957
+ expect(lines.some(l => l.includes("P1"))).toBe(true);
958
+ });
959
+ });
960
+
961
+ describe("renderSections", () => {
962
+ test("renders multiple sections with spacing", () => {
963
+ const sections = [
964
+ { title: "Section 1", items: [{ primary: "Item 1" }] },
965
+ { title: "Section 2", items: [{ primary: "Item 2" }] },
966
+ ];
967
+ const lines = renderSections(sections);
968
+
969
+ expect(lines.some(l => stripAnsi(l).includes("Section 1"))).toBe(true);
970
+ expect(lines.some(l => stripAnsi(l).includes("Section 2"))).toBe(true);
971
+ expect(lines.includes("")).toBe(true); // spacing between sections
972
+ });
973
+ });
974
+
975
+ // =============================================================================
976
+ // Alert Tests
977
+ // =============================================================================
978
+
979
+ describe("renderAlert", () => {
980
+ test("renders info alert", () => {
981
+ const lines = renderAlert("Test message", { severity: "info" });
982
+ expect(lines.length).toBe(3);
983
+ expect(lines[1]).toContain("Test message");
984
+ });
985
+
986
+ test("renders warning alert", () => {
987
+ const lines = renderAlert("Warning!", { severity: "warning" });
988
+ expect(lines[1]).toContain("Warning!");
989
+ expect(lines[1]).toContain("⚠");
990
+ });
991
+
992
+ test("renders with custom icon", () => {
993
+ const lines = renderAlert("Custom", { icon: "★" });
994
+ expect(lines[1]).toContain("★");
995
+ });
996
+ });
997
+
998
+ // =============================================================================
999
+ // Stats Tests
1000
+ // =============================================================================
1001
+
1002
+ describe("renderStats", () => {
1003
+ test("renders stat items", () => {
1004
+ const stats = [
1005
+ { label: "Total", value: 10 },
1006
+ { label: "Done", value: 5 },
1007
+ ];
1008
+ const result = renderStats(stats);
1009
+ expect(stripAnsi(result)).toContain("Total:");
1010
+ expect(stripAnsi(result)).toContain("10");
1011
+ expect(stripAnsi(result)).toContain("Done:");
1012
+ expect(stripAnsi(result)).toContain("5");
1013
+ });
1014
+
1015
+ test("applies color to values", () => {
1016
+ const stats = [
1017
+ { label: "Blocked", value: 3, color: "red" as const },
1018
+ ];
1019
+ const result = renderStats(stats);
1020
+ expect(result).toContain("\x1b[31m"); // red
1021
+ });
1022
+ });
1023
+
1024
+ // =============================================================================
1025
+ // Due Date Tests
1026
+ // =============================================================================
1027
+
1028
+ describe("formatDueDate", () => {
1029
+ test("returns dashes for null/undefined", () => {
1030
+ expect(stripAnsi(formatDueDate(null))).toBe("--");
1031
+ expect(stripAnsi(formatDueDate(undefined))).toBe("--");
1032
+ });
1033
+
1034
+ test("formats today", () => {
1035
+ const today = new Date().toISOString().split("T")[0];
1036
+ const result = formatDueDate(today);
1037
+ expect(stripAnsi(result)).toBe("today");
1038
+ expect(result).toContain("\x1b[33m"); // yellow
1039
+ });
1040
+
1041
+ test("formats tomorrow", () => {
1042
+ const tomorrow = new Date(Date.now() + 86400000).toISOString().split("T")[0];
1043
+ const result = formatDueDate(tomorrow);
1044
+ expect(stripAnsi(result)).toBe("tomorrow");
1045
+ });
1046
+
1047
+ test("formats overdue dates", () => {
1048
+ const yesterday = new Date(Date.now() - 86400000).toISOString().split("T")[0];
1049
+ const result = formatDueDate(yesterday);
1050
+ expect(stripAnsi(result)).toContain("overdue");
1051
+ expect(result).toContain("\x1b[31m"); // red
1052
+ });
1053
+
1054
+ test("formats dates within a week", () => {
1055
+ const inThreeDays = new Date(Date.now() + 3 * 86400000).toISOString().split("T")[0];
1056
+ const result = formatDueDate(inThreeDays);
1057
+ expect(stripAnsi(result)).toBe("3d");
1058
+ });
1059
+ });
1060
+
1061
+ // =============================================================================
1062
+ // Task Line Tests
1063
+ // =============================================================================
1064
+
1065
+ describe("formatTaskLine", () => {
1066
+ test("formats basic task", () => {
1067
+ const task = {
1068
+ id: "task_abc123def456",
1069
+ title: "Test task",
1070
+ status: "pending",
1071
+ };
1072
+ const result = formatTaskLine(task);
1073
+ expect(stripAnsi(result)).toContain("[ ]");
1074
+ expect(stripAnsi(result)).toContain("f456"); // last 4 chars of id
1075
+ expect(stripAnsi(result)).toContain("Test task");
1076
+ });
1077
+
1078
+ test("formats task with priority", () => {
1079
+ const task = {
1080
+ id: "task_123",
1081
+ title: "High priority task",
1082
+ status: "in_progress",
1083
+ priority: "high",
1084
+ };
1085
+ const result = formatTaskLine(task);
1086
+ expect(stripAnsi(result)).toContain("[~]");
1087
+ expect(stripAnsi(result)).toContain("P1");
1088
+ });
1089
+
1090
+ test("formats task with due date", () => {
1091
+ const today = new Date().toISOString().split("T")[0]!;
1092
+ const task = {
1093
+ id: "task_123",
1094
+ title: "Due today",
1095
+ status: "pending",
1096
+ dueDate: today,
1097
+ };
1098
+ const result = formatTaskLine(task);
1099
+ expect(stripAnsi(result)).toContain("today");
1100
+ });
1101
+
1102
+ test("respects options", () => {
1103
+ const task = {
1104
+ id: "task_123456789",
1105
+ title: "Test",
1106
+ status: "pending",
1107
+ priority: "low",
1108
+ };
1109
+ const result = formatTaskLine(task, {
1110
+ showId: false,
1111
+ showPriority: false,
1112
+ showDueDate: false,
1113
+ });
1114
+ expect(stripAnsi(result)).not.toContain("6789");
1115
+ expect(stripAnsi(result)).not.toContain("P3");
1116
+ });
1117
+ });
@@ -783,3 +783,318 @@ export function banner(text: string): string {
783
783
 
784
784
  return lines.map((l) => c.cyan(l)).join("\n");
785
785
  }
786
+
787
+ // =============================================================================
788
+ // Status Symbols (Checkbox Style)
789
+ // =============================================================================
790
+
791
+ /**
792
+ * Checkbox-style status symbols for task display
793
+ * Designed for quick visual scanning
794
+ */
795
+ export const STATUS_SYMBOLS = {
796
+ pending: "[ ]",
797
+ in_progress: "[~]",
798
+ completed: "[x]",
799
+ blocked: "[!]",
800
+ cancelled: "[-]",
801
+ deferred: "[.]",
802
+ } as const;
803
+
804
+ /**
805
+ * Format status with checkbox symbol and color
806
+ */
807
+ export function formatStatusSymbol(status: string): string {
808
+ const symbol = STATUS_SYMBOLS[status as keyof typeof STATUS_SYMBOLS] ?? "[?]";
809
+ const colorFn = statusColors[status] ?? c.white;
810
+ return colorFn(symbol);
811
+ }
812
+
813
+ // =============================================================================
814
+ // Priority Badge (P0-P3 Style)
815
+ // =============================================================================
816
+
817
+ /**
818
+ * Priority levels with P0-P3 notation
819
+ */
820
+ export const PRIORITY_LEVELS = {
821
+ critical: { label: "P0", color: "brightRed" as const, bg: true },
822
+ high: { label: "P1", color: "red" as const, bg: false },
823
+ medium: { label: "P2", color: "yellow" as const, bg: false },
824
+ low: { label: "P3", color: "gray" as const, bg: false },
825
+ } as const;
826
+
827
+ /**
828
+ * Format priority as P0-P3 badge with appropriate styling
829
+ */
830
+ export function formatPriorityBadge(priority: string): string {
831
+ const level = PRIORITY_LEVELS[priority as keyof typeof PRIORITY_LEVELS];
832
+ if (!level) return c.gray("--");
833
+
834
+ if (level.bg && priority === "critical") {
835
+ // P0 gets inverse (background) styling for maximum visibility
836
+ return `\x1b[41m\x1b[37m ${level.label} \x1b[0m`;
837
+ }
838
+
839
+ return color(level.label, level.color);
840
+ }
841
+
842
+ // =============================================================================
843
+ // Section Renderer (GitHub CLI Style)
844
+ // =============================================================================
845
+
846
+ export interface SectionItem {
847
+ /** Primary text (e.g., task title) */
848
+ primary: string;
849
+ /** Secondary text (e.g., status, metadata) */
850
+ secondary?: string;
851
+ /** Detail lines shown below the item */
852
+ details?: string[];
853
+ /** Item prefix (e.g., status symbol) */
854
+ prefix?: string;
855
+ /** Item suffix (e.g., priority badge, due date) */
856
+ suffix?: string;
857
+ }
858
+
859
+ export interface SectionOptions {
860
+ /** Section icon/emoji */
861
+ icon?: string;
862
+ /** Indent level for items (default: 2) */
863
+ indent?: number;
864
+ /** Show item count in header */
865
+ showCount?: boolean;
866
+ /** Header color */
867
+ headerColor?: ColorName;
868
+ /** Empty state message */
869
+ emptyMessage?: string;
870
+ }
871
+
872
+ /**
873
+ * Render a section with header and indented items (GitHub CLI style)
874
+ *
875
+ * Example output:
876
+ * ```
877
+ * 🎯 Next Action
878
+ * [!] a1b2 Implement auth API P0 due:today
879
+ * → Unblocks 3 tasks (critical path)
880
+ * ```
881
+ */
882
+ export function renderSection(
883
+ title: string,
884
+ items: SectionItem[],
885
+ options: SectionOptions = {}
886
+ ): string[] {
887
+ const {
888
+ icon,
889
+ indent = 2,
890
+ showCount = false,
891
+ headerColor = "cyan",
892
+ emptyMessage = "None",
893
+ } = options;
894
+
895
+ const lines: string[] = [];
896
+ const indentStr = " ".repeat(indent);
897
+ const detailIndent = " ".repeat(indent + 4);
898
+
899
+ // Header
900
+ const countStr = showCount ? c.gray(` (${items.length})`) : "";
901
+ const iconStr = icon ? `${icon} ` : "";
902
+ lines.push(color(`${iconStr}${title}${countStr}`, headerColor));
903
+
904
+ // Empty state
905
+ if (items.length === 0) {
906
+ lines.push(indentStr + c.gray(emptyMessage));
907
+ return lines;
908
+ }
909
+
910
+ // Items
911
+ for (const item of items) {
912
+ const prefix = item.prefix ? `${item.prefix} ` : "";
913
+ const suffix = item.suffix ? ` ${item.suffix}` : "";
914
+ const secondary = item.secondary ? ` ${c.gray(item.secondary)}` : "";
915
+
916
+ lines.push(`${indentStr}${prefix}${item.primary}${secondary}${suffix}`);
917
+
918
+ // Detail lines
919
+ if (item.details && item.details.length > 0) {
920
+ for (const detail of item.details) {
921
+ lines.push(`${detailIndent}${c.gray("→")} ${c.gray(detail)}`);
922
+ }
923
+ }
924
+ }
925
+
926
+ return lines;
927
+ }
928
+
929
+ /**
930
+ * Render multiple sections with spacing
931
+ */
932
+ export function renderSections(
933
+ sections: Array<{ title: string; items: SectionItem[]; options?: SectionOptions }>
934
+ ): string[] {
935
+ const lines: string[] = [];
936
+
937
+ for (let i = 0; i < sections.length; i++) {
938
+ const section = sections[i];
939
+ if (!section) continue;
940
+
941
+ if (i > 0) {
942
+ lines.push(""); // Add spacing between sections
943
+ }
944
+
945
+ lines.push(...renderSection(section.title, section.items, section.options));
946
+ }
947
+
948
+ return lines;
949
+ }
950
+
951
+ // =============================================================================
952
+ // Alert Box
953
+ // =============================================================================
954
+
955
+ export type AlertSeverity = "critical" | "warning" | "info" | "success";
956
+
957
+ export interface AlertOptions {
958
+ severity?: AlertSeverity;
959
+ icon?: string;
960
+ }
961
+
962
+ const ALERT_STYLES: Record<AlertSeverity, { color: ColorName; icon: string; border: string }> = {
963
+ critical: { color: "red", icon: "✗", border: BOX.dblHorizontal },
964
+ warning: { color: "yellow", icon: "⚠", border: BOX.horizontal },
965
+ info: { color: "cyan", icon: "ℹ", border: BOX.horizontal },
966
+ success: { color: "green", icon: "✓", border: BOX.horizontal },
967
+ };
968
+
969
+ /**
970
+ * Render an alert box with severity styling
971
+ */
972
+ export function renderAlert(message: string, options: AlertOptions = {}): string[] {
973
+ const { severity = "info", icon } = options;
974
+ const style = ALERT_STYLES[severity];
975
+ const displayIcon = icon ?? style.icon;
976
+
977
+ const content = `${displayIcon} ${message}`;
978
+ const width = displayWidth(content) + 4;
979
+
980
+ const topBorder = color(style.border.repeat(width), style.color);
981
+ const bottomBorder = topBorder;
982
+
983
+ return [topBorder, ` ${color(content, style.color)} `, bottomBorder];
984
+ }
985
+
986
+ // =============================================================================
987
+ // Compact Stats Row
988
+ // =============================================================================
989
+
990
+ export interface StatItem {
991
+ label: string;
992
+ value: string | number;
993
+ color?: ColorName;
994
+ }
995
+
996
+ /**
997
+ * Render a compact stats row
998
+ * Example: "Total: 24 Done: 8 (33%) Blocked: 4"
999
+ */
1000
+ export function renderStats(stats: StatItem[], separator = " "): string {
1001
+ return stats
1002
+ .map((stat) => {
1003
+ const valueStr = String(stat.value);
1004
+ const coloredValue = stat.color ? color(valueStr, stat.color) : valueStr;
1005
+ return `${c.gray(stat.label + ":")} ${coloredValue}`;
1006
+ })
1007
+ .join(separator);
1008
+ }
1009
+
1010
+ // =============================================================================
1011
+ // Due Date Formatter
1012
+ // =============================================================================
1013
+
1014
+ /**
1015
+ * Format due date with relative time and appropriate coloring
1016
+ */
1017
+ export function formatDueDate(dueDate: string | undefined | null): string {
1018
+ if (!dueDate) return c.gray("--");
1019
+
1020
+ const today = new Date();
1021
+ today.setHours(0, 0, 0, 0);
1022
+
1023
+ const due = new Date(dueDate);
1024
+ due.setHours(0, 0, 0, 0);
1025
+
1026
+ const diffDays = Math.floor((due.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
1027
+
1028
+ if (diffDays < 0) {
1029
+ const overdueDays = Math.abs(diffDays);
1030
+ return c.red(`${overdueDays}d overdue`);
1031
+ } else if (diffDays === 0) {
1032
+ return c.yellow("today");
1033
+ } else if (diffDays === 1) {
1034
+ return c.yellow("tomorrow");
1035
+ } else if (diffDays <= 7) {
1036
+ return c.cyan(`${diffDays}d`);
1037
+ } else {
1038
+ return c.gray(dueDate);
1039
+ }
1040
+ }
1041
+
1042
+ // =============================================================================
1043
+ // Task Line Formatter
1044
+ // =============================================================================
1045
+
1046
+ export interface TaskLineOptions {
1047
+ showId?: boolean;
1048
+ idLength?: number;
1049
+ maxTitleWidth?: number;
1050
+ showPriority?: boolean;
1051
+ showDueDate?: boolean;
1052
+ }
1053
+
1054
+ /**
1055
+ * Format a single task line for list display
1056
+ * Example: "[~] a1b2 Implement auth API P1 today"
1057
+ */
1058
+ export function formatTaskLine(
1059
+ task: {
1060
+ id: string;
1061
+ title: string;
1062
+ status: string;
1063
+ priority?: string;
1064
+ dueDate?: string | null;
1065
+ },
1066
+ options: TaskLineOptions = {}
1067
+ ): string {
1068
+ const {
1069
+ showId = true,
1070
+ idLength = 4,
1071
+ maxTitleWidth = 40,
1072
+ showPriority = true,
1073
+ showDueDate = true,
1074
+ } = options;
1075
+
1076
+ const parts: string[] = [];
1077
+
1078
+ // Status symbol
1079
+ parts.push(formatStatusSymbol(task.status));
1080
+
1081
+ // Task ID
1082
+ if (showId) {
1083
+ parts.push(c.gray(task.id.slice(-idLength)));
1084
+ }
1085
+
1086
+ // Title (truncated)
1087
+ parts.push(padEnd(truncateStr(task.title, maxTitleWidth), maxTitleWidth));
1088
+
1089
+ // Priority badge
1090
+ if (showPriority && task.priority) {
1091
+ parts.push(formatPriorityBadge(task.priority));
1092
+ }
1093
+
1094
+ // Due date
1095
+ if (showDueDate && task.dueDate) {
1096
+ parts.push(formatDueDate(task.dueDate));
1097
+ }
1098
+
1099
+ return parts.join(" ");
1100
+ }