@prmichaelsen/acp-visualizer 0.1.6 → 0.1.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/acp-visualizer",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "Browser-based dashboard for visualizing ACP progress.yaml data",
6
6
  "bin": {
@@ -186,6 +186,34 @@ milestones:
186
186
  expect(result.milestones[0].id).toBe('milestone_1')
187
187
  })
188
188
 
189
+ it('maps task keys to milestone IDs when formats differ', () => {
190
+ const yaml = `
191
+ milestones:
192
+ - id: M1
193
+ name: First
194
+ status: completed
195
+ - id: M2
196
+ name: Second
197
+ status: in_progress
198
+
199
+ tasks:
200
+ milestone_1:
201
+ - id: t1
202
+ name: Task A
203
+ status: completed
204
+ milestone_2:
205
+ - id: t2
206
+ name: Task B
207
+ status: in_progress
208
+ `
209
+ const result = parseProgressYaml(yaml)
210
+ // Tasks keyed as milestone_1 should map to milestone ID M1
211
+ expect(result.tasks['M1']).toHaveLength(1)
212
+ expect(result.tasks['M1'][0].name).toBe('Task A')
213
+ expect(result.tasks['M2']).toHaveLength(1)
214
+ expect(result.tasks['M2'][0].name).toBe('Task B')
215
+ })
216
+
189
217
  it('handles null values in dates', () => {
190
218
  const yaml = `
191
219
  milestones:
@@ -35,6 +35,7 @@ const TASK_ALIASES: Record<string, string> = {
35
35
  done_date: 'completed_date',
36
36
  filename: 'file',
37
37
  path: 'file',
38
+ document: 'file',
38
39
  milestone: 'milestone_id',
39
40
  }
40
41
 
@@ -134,7 +135,11 @@ function normalizeMilestone(raw: unknown, index: number): Milestone {
134
135
  id: safeString(known.id, `milestone_${index + 1}`),
135
136
  name: safeString(known.name, `Milestone ${index + 1}`),
136
137
  status: normalizeStatus(known.status),
137
- progress: safeNumber(known.progress),
138
+ progress: known.progress != null
139
+ ? safeNumber(known.progress)
140
+ : safeNumber(known.tasks_total) > 0
141
+ ? Math.round((safeNumber(known.tasks_completed) / safeNumber(known.tasks_total)) * 100)
142
+ : normalizeStatus(known.status) === 'completed' ? 100 : 0,
138
143
  started: known.started ? safeString(known.started) : null,
139
144
  completed: known.completed ? safeString(known.completed) : null,
140
145
  estimated_weeks: safeString(known.estimated_weeks, '0'),
@@ -171,12 +176,35 @@ function normalizeTask(raw: unknown, milestoneId: string, index: number): Task {
171
176
  }
172
177
  }
173
178
 
174
- function normalizeTasks(raw: unknown): Record<string, Task[]> {
179
+ function normalizeTasks(raw: unknown, milestones: Milestone[]): Record<string, Task[]> {
175
180
  const result: Record<string, Task[]> = {}
176
181
  const obj = asRecord(raw)
177
- for (const [milestoneId, tasks] of Object.entries(obj)) {
182
+
183
+ // Build a map from task key patterns to milestone IDs.
184
+ // Handles mismatch: tasks keyed as "milestone_1" but milestone.id = "M1"
185
+ const keyToMilestoneId = new Map<string, string>()
186
+ for (let i = 0; i < milestones.length; i++) {
187
+ const m = milestones[i]
188
+ // The task key might be the milestone ID itself, or "milestone_N"
189
+ keyToMilestoneId.set(m.id, m.id)
190
+ keyToMilestoneId.set(m.id.toLowerCase(), m.id)
191
+ keyToMilestoneId.set(`milestone_${i + 1}`, m.id)
192
+ // Also handle "milestone_N" where N matches the numeric part of "MN"
193
+ const numMatch = m.id.match(/(\d+)/)
194
+ if (numMatch) {
195
+ keyToMilestoneId.set(`milestone_${numMatch[1]}`, m.id)
196
+ }
197
+ }
198
+
199
+ for (const [rawKey, tasks] of Object.entries(obj)) {
178
200
  if (Array.isArray(tasks)) {
179
- result[milestoneId] = tasks.map((t, i) => normalizeTask(t, milestoneId, i))
201
+ // Resolve the key to a milestone ID, or keep as-is
202
+ const milestoneId = keyToMilestoneId.get(rawKey) || rawKey
203
+ const existing = result[milestoneId] || []
204
+ result[milestoneId] = [
205
+ ...existing,
206
+ ...tasks.map((t, i) => normalizeTask(t, milestoneId, existing.length + i)),
207
+ ]
180
208
  }
181
209
  }
182
210
  return result
@@ -248,10 +276,12 @@ export function parseProgressYaml(raw: string): ProgressData {
248
276
  }
249
277
  const d = doc as Record<string, unknown>
250
278
 
279
+ const milestones = normalizeMilestones(d.milestones)
280
+
251
281
  return {
252
282
  project: normalizeProject(d.project),
253
- milestones: normalizeMilestones(d.milestones),
254
- tasks: normalizeTasks(d.tasks),
283
+ milestones,
284
+ tasks: normalizeTasks(d.tasks, milestones),
255
285
  recent_work: normalizeWorkEntries(d.recent_work),
256
286
  next_steps: normalizeStringArray(d.next_steps),
257
287
  notes: normalizeStringArray(d.notes),