@backlog-md/core 0.2.2 → 0.3.0

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.
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Milestone utilities for @backlog-md/core
3
+ *
4
+ * Functions for grouping tasks by milestone and calculating progress.
5
+ */
6
+ /** Key used for tasks without a milestone */
7
+ const NO_MILESTONE_KEY = "__none";
8
+ /**
9
+ * Normalize a milestone name by trimming whitespace
10
+ */
11
+ export function normalizeMilestoneName(name) {
12
+ return name.trim();
13
+ }
14
+ /**
15
+ * Get a lowercase key for milestone comparison
16
+ */
17
+ export function milestoneKey(name) {
18
+ return normalizeMilestoneName(name ?? "").toLowerCase();
19
+ }
20
+ /**
21
+ * Check if a status represents a "done" state
22
+ */
23
+ export function isDoneStatus(status) {
24
+ const normalized = (status ?? "").toLowerCase();
25
+ return normalized.includes("done") || normalized.includes("complete");
26
+ }
27
+ /**
28
+ * Get the display label for a milestone
29
+ * Uses the milestone entity title if available, otherwise returns the ID
30
+ */
31
+ export function getMilestoneLabel(milestoneId, milestoneEntities) {
32
+ if (!milestoneId) {
33
+ return "Tasks without milestone";
34
+ }
35
+ const entity = milestoneEntities.find((m) => milestoneKey(m.id) === milestoneKey(milestoneId));
36
+ return entity?.title || milestoneId;
37
+ }
38
+ /**
39
+ * Collect all unique milestone IDs from tasks and milestone entities
40
+ */
41
+ export function collectMilestoneIds(tasks, milestoneEntities) {
42
+ const merged = [];
43
+ const seen = new Set();
44
+ const addMilestone = (value) => {
45
+ const normalized = normalizeMilestoneName(value);
46
+ if (!normalized)
47
+ return;
48
+ const key = milestoneKey(normalized);
49
+ if (seen.has(key))
50
+ return;
51
+ seen.add(key);
52
+ merged.push(normalized);
53
+ };
54
+ // Add milestone entities first (they have priority for ordering)
55
+ for (const entity of milestoneEntities) {
56
+ addMilestone(entity.id);
57
+ }
58
+ // Then add any milestones from tasks that aren't in entities
59
+ for (const task of tasks) {
60
+ addMilestone(task.milestone ?? "");
61
+ }
62
+ return merged;
63
+ }
64
+ /**
65
+ * Collect milestones from tasks and config (legacy support)
66
+ */
67
+ export function collectMilestones(tasks, configMilestones) {
68
+ const merged = [];
69
+ const seen = new Set();
70
+ const addMilestone = (value) => {
71
+ const normalized = normalizeMilestoneName(value);
72
+ if (!normalized)
73
+ return;
74
+ const key = milestoneKey(normalized);
75
+ if (seen.has(key))
76
+ return;
77
+ seen.add(key);
78
+ merged.push(normalized);
79
+ };
80
+ for (const m of configMilestones) {
81
+ addMilestone(m);
82
+ }
83
+ for (const task of tasks) {
84
+ addMilestone(task.milestone ?? "");
85
+ }
86
+ return merged;
87
+ }
88
+ /**
89
+ * Create a milestone bucket for a given milestone
90
+ */
91
+ function createBucket(milestoneId, tasks, statuses, milestoneEntities, isNoMilestone) {
92
+ const bucketMilestoneKey = milestoneKey(milestoneId);
93
+ const bucketTasks = tasks.filter((task) => {
94
+ const taskMilestoneKey = milestoneKey(task.milestone);
95
+ return bucketMilestoneKey
96
+ ? taskMilestoneKey === bucketMilestoneKey
97
+ : !taskMilestoneKey;
98
+ });
99
+ const counts = {};
100
+ for (const status of statuses) {
101
+ counts[status] = 0;
102
+ }
103
+ for (const task of bucketTasks) {
104
+ const status = task.status ?? "";
105
+ counts[status] = (counts[status] ?? 0) + 1;
106
+ }
107
+ const doneCount = bucketTasks.filter((t) => isDoneStatus(t.status)).length;
108
+ const progress = bucketTasks.length > 0
109
+ ? Math.round((doneCount / bucketTasks.length) * 100)
110
+ : 0;
111
+ const key = bucketMilestoneKey ? bucketMilestoneKey : NO_MILESTONE_KEY;
112
+ const label = getMilestoneLabel(milestoneId, milestoneEntities);
113
+ return {
114
+ key,
115
+ label,
116
+ milestone: milestoneId,
117
+ isNoMilestone,
118
+ tasks: bucketTasks,
119
+ statusCounts: counts,
120
+ total: bucketTasks.length,
121
+ doneCount,
122
+ progress,
123
+ };
124
+ }
125
+ /**
126
+ * Build milestone buckets from tasks and milestone entities
127
+ *
128
+ * @param tasks - All tasks to group
129
+ * @param milestoneEntities - Milestone entities (for titles)
130
+ * @param statuses - Configured statuses (for status counts)
131
+ * @returns Array of milestone buckets
132
+ */
133
+ export function buildMilestoneBuckets(tasks, milestoneEntities, statuses) {
134
+ const allMilestoneIds = collectMilestoneIds(tasks, milestoneEntities);
135
+ const buckets = [
136
+ // "No milestone" bucket first
137
+ createBucket(undefined, tasks, statuses, milestoneEntities, true),
138
+ // Then each milestone bucket
139
+ ...allMilestoneIds.map((m) => createBucket(m, tasks, statuses, milestoneEntities, false)),
140
+ ];
141
+ return buckets;
142
+ }
143
+ /**
144
+ * Build milestone buckets using config milestone strings (legacy support)
145
+ *
146
+ * @deprecated Use buildMilestoneBuckets with Milestone entities instead
147
+ */
148
+ export function buildMilestoneBucketsFromConfig(tasks, configMilestones, statuses) {
149
+ // Convert config milestone strings to minimal Milestone entities
150
+ const milestoneEntities = configMilestones.map((id) => ({
151
+ id,
152
+ title: id,
153
+ description: "",
154
+ rawContent: "",
155
+ tasks: [],
156
+ }));
157
+ return buildMilestoneBuckets(tasks, milestoneEntities, statuses);
158
+ }
159
+ /**
160
+ * Build a complete milestone summary
161
+ *
162
+ * @param tasks - All tasks
163
+ * @param milestoneEntities - Milestone entities
164
+ * @param statuses - Configured statuses
165
+ * @returns MilestoneSummary with milestones and buckets
166
+ */
167
+ export function buildMilestoneSummary(tasks, milestoneEntities, statuses) {
168
+ const milestones = collectMilestoneIds(tasks, milestoneEntities);
169
+ const buckets = buildMilestoneBuckets(tasks, milestoneEntities, statuses);
170
+ return {
171
+ milestones,
172
+ buckets,
173
+ };
174
+ }
175
+ /**
176
+ * Group tasks by milestone (simple version using config milestones)
177
+ *
178
+ * @param tasks - Tasks to group
179
+ * @param configMilestones - Milestones from config
180
+ * @param statuses - Configured statuses
181
+ * @returns MilestoneSummary
182
+ */
183
+ export function groupTasksByMilestone(tasks, configMilestones, statuses) {
184
+ const milestones = collectMilestones(tasks, configMilestones);
185
+ const buckets = buildMilestoneBucketsFromConfig(tasks, configMilestones, statuses);
186
+ return {
187
+ milestones,
188
+ buckets,
189
+ };
190
+ }
191
+ //# sourceMappingURL=milestones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"milestones.js","sourceRoot":"","sources":["../../src/utils/milestones.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backlog-md/core",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Runtime-agnostic core package for Backlog.md task management",
5
5
  "author": "Backlog.md Team",
6
6
  "license": "MIT",