mcoda 0.1.18 → 0.1.19

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.
@@ -1 +1 @@
1
- {"version":3,"file":"BacklogCommands.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/BacklogCommands.ts"],"names":[],"mappings":"AAIA,UAAU,UAAU;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AA6BD,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,KAAG,UAuHjD,CAAC;AAgLF,qBAAa,eAAe;WACb,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA4DhD"}
1
+ {"version":3,"file":"BacklogCommands.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/BacklogCommands.ts"],"names":[],"mappings":"AAIA,UAAU,UAAU;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AA6BD,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,KAAG,UAuHjD,CAAC;AAqLF,qBAAa,eAAe;WACb,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA4DhD"}
@@ -177,13 +177,17 @@ const resolveStatuses = (parsed) => {
177
177
  };
178
178
  const pad = (value, width) => value.padEnd(width, " ");
179
179
  const formatTable = (headers, rows) => {
180
- const widths = headers.map((header, idx) => {
181
- return Math.max(header.length, ...rows.map((row) => (row[idx] ?? "").length));
182
- });
183
- const headerLine = headers.map((h, idx) => pad(h, widths[idx])).join(" | ");
184
- const sepLine = widths.map((w) => "-".repeat(w)).join("-+-");
185
- const body = rows.map((row) => row.map((cell, idx) => pad(cell ?? "", widths[idx])).join(" | ")).join("\n");
186
- return [headerLine, sepLine, body].filter(Boolean).join("\n");
180
+ const widths = headers.map((header, idx) => Math.max(header.length, ...rows.map((row) => (row[idx] ?? "").length)));
181
+ const border = (left, join, right) => `${left}${widths.map((width) => "─".repeat(width + 2)).join(join)}${right}`;
182
+ const headerLine = `│${headers.map((header, idx) => ` ${pad(header, widths[idx])} `).join("│")}│`;
183
+ const rowLines = rows.map((row) => `│${row.map((cell, idx) => ` ${pad(cell ?? "", widths[idx])} `).join("")}│`);
184
+ return [
185
+ border("╭", "", ""),
186
+ headerLine,
187
+ border("├", "┼", "┤"),
188
+ ...rowLines,
189
+ border("╰", "┴", "╯"),
190
+ ].join("\n");
187
191
  };
188
192
  const truncate = (value, max = 100) => {
189
193
  if (!value)
@@ -1 +1 @@
1
- {"version":3,"file":"EstimateCommands.d.ts","sourceRoot":"","sources":["../../../src/commands/estimate/EstimateCommands.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,cAAc,EAGf,MAAM,aAAa,CAAC;AAErB,UAAU,UAAU;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,cAAc,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AA6BD,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,EAAE,KAAG,UA4HlD,CAAC;AAkBF,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAwBjE,CAAC;AA4HF,qBAAa,gBAAgB;WACd,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAgEhD"}
1
+ {"version":3,"file":"EstimateCommands.d.ts","sourceRoot":"","sources":["../../../src/commands/estimate/EstimateCommands.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,cAAc,EAGf,MAAM,aAAa,CAAC;AAErB,UAAU,UAAU;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,cAAc,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AA6BD,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,EAAE,KAAG,UA4HlD,CAAC;AA0DF,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAwBjE,CAAC;AA4NF,qBAAa,gBAAgB;WACd,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAkEhD"}
@@ -168,13 +168,43 @@ export const parseEstimateArgs = (argv) => {
168
168
  }
169
169
  return parsed;
170
170
  };
171
- const pad = (value, width) => value.padEnd(width, " ");
172
- const formatTable = (headers, rows) => {
173
- const widths = headers.map((header, idx) => Math.max(header.length, ...rows.map((row) => (row[idx] ?? "").length)));
174
- const headerLine = headers.map((h, idx) => pad(h, widths[idx])).join(" | ");
175
- const sepLine = widths.map((w) => "-".repeat(w)).join("-+-");
176
- const body = rows.map((row) => row.map((cell, idx) => pad(cell ?? "", widths[idx])).join(" | ")).join("\n");
177
- return [headerLine, sepLine, body].filter(Boolean).join("\n");
171
+ const ANSI_REGEX = /\x1b\[[0-9;]*m/g;
172
+ const stripAnsi = (value) => value.replace(ANSI_REGEX, "");
173
+ const visibleLength = (value) => stripAnsi(value).length;
174
+ const padVisible = (value, width) => {
175
+ const diff = width - visibleLength(value);
176
+ return diff > 0 ? `${value}${" ".repeat(diff)}` : value;
177
+ };
178
+ const colorize = (enabled, code, value) => enabled ? `\x1b[${code}m${value}\x1b[0m` : value;
179
+ const style = {
180
+ bold: (enabled, value) => colorize(enabled, 1, value),
181
+ dim: (enabled, value) => colorize(enabled, 2, value),
182
+ blue: (enabled, value) => colorize(enabled, 34, value),
183
+ cyan: (enabled, value) => colorize(enabled, 36, value),
184
+ green: (enabled, value) => colorize(enabled, 32, value),
185
+ yellow: (enabled, value) => colorize(enabled, 33, value),
186
+ magenta: (enabled, value) => colorize(enabled, 35, value),
187
+ red: (enabled, value) => colorize(enabled, 31, value),
188
+ };
189
+ const formatPanel = (lines) => {
190
+ const width = Math.max(0, ...lines.map((line) => visibleLength(line)));
191
+ const top = `╭${"─".repeat(width + 2)}╮`;
192
+ const body = lines.map((line) => `│ ${padVisible(line, width)} │`);
193
+ const bottom = `╰${"─".repeat(width + 2)}╯`;
194
+ return [top, ...body, bottom].join("\n");
195
+ };
196
+ const formatBoxTable = (headers, rows) => {
197
+ const widths = headers.map((header, idx) => Math.max(visibleLength(header), ...rows.map((row) => visibleLength(row[idx] ?? ""))));
198
+ const border = (left, join, right) => `${left}${widths.map((width) => "─".repeat(width + 2)).join(join)}${right}`;
199
+ const headerLine = `│${headers.map((header, idx) => ` ${padVisible(header, widths[idx])} `).join("│")}│`;
200
+ const rowLines = rows.map((row) => `│${row.map((cell, idx) => ` ${padVisible(cell ?? "", widths[idx])} `).join("│")}│`);
201
+ return [
202
+ border("╭", "┬", "╮"),
203
+ headerLine,
204
+ border("├", "┼", "┤"),
205
+ ...rowLines,
206
+ border("╰", "┴", "╯"),
207
+ ].join("\n");
178
208
  };
179
209
  const fmt = (value) => {
180
210
  if (value === null || value === undefined)
@@ -249,7 +279,57 @@ const formatEtaCell = (eta) => {
249
279
  const relative = formatRelativeDuration(date.getTime(), Date.now());
250
280
  return `${eta} (local ${local}, ${relative})`;
251
281
  };
252
- const renderResult = (result) => {
282
+ const createBar = (percentValue, theme) => {
283
+ const percent = Math.max(0, Math.min(100, Number.isFinite(percentValue) ? percentValue : 0));
284
+ const rounded = Math.round(percent);
285
+ const width = 10;
286
+ if (rounded <= 0) {
287
+ return theme.empty("░".repeat(width));
288
+ }
289
+ if (rounded >= 100) {
290
+ return theme.full("█".repeat(width));
291
+ }
292
+ const fullCount = Math.min(width - 1, Math.floor((rounded / 100) * width));
293
+ const partialCount = 1;
294
+ const emptyCount = Math.max(0, width - fullCount - partialCount);
295
+ return `${theme.full("█".repeat(fullCount))}${theme.partial("▒".repeat(partialCount))}${theme.empty("░".repeat(emptyCount))}`;
296
+ };
297
+ const renderProgressSection = (result, colorEnabled) => {
298
+ const work = result.completion.workOnTasks;
299
+ const qa = result.completion.readyToQa;
300
+ const done = result.completion.done;
301
+ const labels = [
302
+ "🛠️ Work on tasks",
303
+ "🧪 Ready to qa",
304
+ "✅ Done",
305
+ ];
306
+ const maxLabel = Math.max(...labels.map((label) => visibleLength(label)));
307
+ const formatLine = (label, metric, theme) => {
308
+ const bar = createBar(metric.percent, theme);
309
+ const percent = `${Math.round(metric.percent)}%`;
310
+ return `${padVisible(label, maxLabel)} : ${bar} ${percent} (${metric.done}/${metric.total})`;
311
+ };
312
+ return formatPanel([
313
+ style.bold(colorEnabled, "📊 Completion"),
314
+ formatLine("🛠️ Work on tasks", work, {
315
+ full: (value) => style.cyan(colorEnabled, value),
316
+ partial: (value) => style.blue(colorEnabled, value),
317
+ empty: (value) => style.dim(colorEnabled, value),
318
+ }),
319
+ formatLine("🧪 Ready to qa", qa, {
320
+ full: (value) => style.yellow(colorEnabled, value),
321
+ partial: (value) => style.magenta(colorEnabled, value),
322
+ empty: (value) => style.dim(colorEnabled, value),
323
+ }),
324
+ formatLine("✅ Done", done, {
325
+ full: (value) => style.green(colorEnabled, value),
326
+ partial: (value) => style.yellow(colorEnabled, value),
327
+ empty: (value) => style.dim(colorEnabled, value),
328
+ }),
329
+ ]);
330
+ };
331
+ const renderResult = (result, options) => {
332
+ const { colorEnabled } = options;
253
333
  const velocity = result.effectiveVelocity;
254
334
  const source = velocity.source;
255
335
  const spHeader = `SP/H (${source})`;
@@ -290,28 +370,58 @@ const renderResult = (result) => {
290
370
  ],
291
371
  ];
292
372
  // eslint-disable-next-line no-console
293
- console.log(formatTable(["LANE", "STORY_POINTS", spHeader, "TIME_LEFT"], rows));
373
+ console.log(formatPanel([
374
+ style.bold(colorEnabled, "🧮 Effort by Lane"),
375
+ formatBoxTable([
376
+ style.bold(colorEnabled, "LANE"),
377
+ style.bold(colorEnabled, "STORY POINTS"),
378
+ style.bold(colorEnabled, spHeader.toUpperCase()),
379
+ style.bold(colorEnabled, "TIME LEFT"),
380
+ ], rows),
381
+ ]));
382
+ const counts = result.statusCounts;
383
+ // eslint-disable-next-line no-console
384
+ console.log(formatPanel([
385
+ style.bold(colorEnabled, "📌 Task Status"),
386
+ `${style.bold(colorEnabled, "Total tasks")} : ${counts.total}`,
387
+ `${style.cyan(colorEnabled, "Ready to code review")} : ${counts.readyToCodeReview}`,
388
+ `${style.yellow(colorEnabled, "Ready to qa")} : ${counts.readyToQa}`,
389
+ `${style.blue(colorEnabled, "In progress")} : ${counts.inProgress}`,
390
+ `${style.red(colorEnabled, "Failed")} : ${counts.failed}`,
391
+ `${style.green(colorEnabled, "Completed")} : ${counts.completed}`,
392
+ ]));
393
+ // eslint-disable-next-line no-console
394
+ console.log(renderProgressSection(result, colorEnabled));
294
395
  const samples = velocity.samples ?? { implementation: 0, review: 0, qa: 0 };
295
396
  const windowLabel = velocity.windowTasks ? ` (window ${velocity.windowTasks})` : "";
296
397
  const fallbackNote = velocity.requestedMode && velocity.requestedMode !== velocity.source
297
398
  ? ` (requested ${velocity.requestedMode}; no empirical samples, using config)`
298
399
  : "";
299
400
  // eslint-disable-next-line no-console
300
- console.log(`\nVelocity source: ${velocity.source}${fallbackNote}`);
301
- // eslint-disable-next-line no-console
302
- console.log(`Velocity samples${windowLabel}: impl=${samples.implementation ?? 0}, review=${samples.review ?? 0}, qa=${samples.qa ?? 0}`);
303
- // eslint-disable-next-line no-console
304
- console.log("ETAs:");
401
+ console.log(formatPanel([
402
+ style.bold(colorEnabled, "📈 Velocity"),
403
+ `${style.bold(colorEnabled, "Velocity source")} : ${velocity.source}${fallbackNote}`,
404
+ `${style.bold(colorEnabled, "Samples")}${windowLabel} : impl=${samples.implementation ?? 0}, review=${samples.review ?? 0}, qa=${samples.qa ?? 0}`,
405
+ ]));
305
406
  // eslint-disable-next-line no-console
306
- console.log(formatTable(["READY_TO_REVIEW", "READY_TO_QA", "COMPLETE"], [
307
- [
308
- formatEtaCell(result.etas.readyToReviewEta),
309
- formatEtaCell(result.etas.readyToQaEta),
310
- formatEtaCell(result.etas.completeEta),
311
- ],
407
+ console.log(formatPanel([
408
+ style.bold(colorEnabled, "⏱️ ETAs"),
409
+ formatBoxTable([
410
+ style.bold(colorEnabled, "READY TO REVIEW"),
411
+ style.bold(colorEnabled, "READY TO QA"),
412
+ style.bold(colorEnabled, "COMPLETE"),
413
+ ], [
414
+ [
415
+ formatEtaCell(result.etas.readyToReviewEta),
416
+ formatEtaCell(result.etas.readyToQaEta),
417
+ formatEtaCell(result.etas.completeEta),
418
+ ],
419
+ ]),
312
420
  ]));
313
421
  // eslint-disable-next-line no-console
314
- console.log("\nAssumptions: lane work runs in parallel; total hours uses the longest lane.");
422
+ console.log(formatPanel([
423
+ `${style.bold(colorEnabled, "ℹ️ Assumptions")} : lane work runs in parallel; total hours uses the longest lane.`,
424
+ ]));
315
425
  };
316
426
  export class EstimateCommands {
317
427
  static async run(argv) {
@@ -346,17 +456,19 @@ export class EstimateCommands {
346
456
  console.log(JSON.stringify(result, null, 2));
347
457
  }
348
458
  else if (!parsed.quiet) {
459
+ const colorEnabled = !parsed.noColor;
349
460
  const totalTasks = result.backlogTotals.implementation.tasks +
350
461
  result.backlogTotals.review.tasks +
351
462
  result.backlogTotals.qa.tasks +
352
463
  result.backlogTotals.done.tasks;
464
+ const scopeText = `project=${parsed.project ?? "all"}${parsed.epic ? `, epic=${parsed.epic}` : ""}${parsed.story ? `, story=${parsed.story}` : ""}${parsed.assignee ? `, assignee=${parsed.assignee}` : ""}`;
353
465
  // eslint-disable-next-line no-console
354
- console.log(`Scope: project=${parsed.project ?? "all"}${parsed.epic ? `, epic=${parsed.epic}` : ""}${parsed.story ? `, story=${parsed.story}` : ""}${parsed.assignee ? `, assignee=${parsed.assignee}` : ""}`);
466
+ console.log(formatPanel([`${style.bold(colorEnabled, "🧭 Scope")} : ${scopeText}`]));
355
467
  if (totalTasks === 0) {
356
468
  // eslint-disable-next-line no-console
357
- console.log("No tasks found in the selected scope. Showing zeroed estimate.");
469
+ console.log(formatPanel([style.yellow(colorEnabled, "No tasks found in the selected scope. Showing zeroed estimate.")]));
358
470
  }
359
- renderResult(result);
471
+ renderResult(result, { colorEnabled });
360
472
  }
361
473
  }
362
474
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcoda",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Local-first CLI for planning, documentation, and execution workflows with agent assistance.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,12 +45,12 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "yaml": "^2.4.2",
48
- "@mcoda/core": "0.1.18",
49
- "@mcoda/shared": "0.1.18"
48
+ "@mcoda/core": "0.1.19",
49
+ "@mcoda/shared": "0.1.19"
50
50
  },
51
51
  "devDependencies": {
52
- "@mcoda/integrations": "0.1.18",
53
- "@mcoda/db": "0.1.18"
52
+ "@mcoda/db": "0.1.19",
53
+ "@mcoda/integrations": "0.1.19"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsc -p tsconfig.json",