mcoda 0.1.31 → 0.1.33

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.
@@ -13,7 +13,7 @@ interface ParsedArgs {
13
13
  spPerHourReview?: number;
14
14
  spPerHourQa?: number;
15
15
  velocityMode?: VelocitySource;
16
- velocityWindow?: 10 | 20 | 50;
16
+ velocityWindow?: number;
17
17
  json: boolean;
18
18
  debug: boolean;
19
19
  }
@@ -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;AAgEF,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAwBjE,CAAC;AA6NF,qBAAa,gBAAgB;WACd,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAkEhD"}
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,MAAM,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAmCD,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,EAAE,KAAG,UA4HlD,CAAC;AAqHF,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAwBjE,CAAC;AA2OF,qBAAa,gBAAgB;WACd,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAkEhD"}
@@ -11,7 +11,7 @@ const usage = `mcoda estimate \\
11
11
  [--sp-per-hour-review <FLOAT>] \\
12
12
  [--sp-per-hour-qa <FLOAT>] \\
13
13
  [--velocity-mode config|empirical|mixed] \\
14
- [--velocity-window 10|20|50] \\
14
+ [--velocity-window <POSITIVE_INT>] \\
15
15
  [--quiet] [--no-color] [--no-telemetry] \\
16
16
  [--json]`;
17
17
  const parseNumber = (value) => {
@@ -20,6 +20,12 @@ const parseNumber = (value) => {
20
20
  const parsed = Number(value);
21
21
  return Number.isFinite(parsed) ? parsed : undefined;
22
22
  };
23
+ const parsePositiveInteger = (value) => {
24
+ const parsed = parseNumber(value);
25
+ if (parsed === undefined || !Number.isInteger(parsed) || parsed <= 0)
26
+ return undefined;
27
+ return parsed;
28
+ };
23
29
  const parseVelocityMode = (value) => {
24
30
  if (!value)
25
31
  return undefined;
@@ -99,8 +105,8 @@ export const parseEstimateArgs = (argv) => {
99
105
  break;
100
106
  case "--velocity-window":
101
107
  case "--window": {
102
- const value = parseNumber(argv[i + 1]);
103
- if (value === 10 || value === 20 || value === 50) {
108
+ const value = parsePositiveInteger(argv[i + 1]);
109
+ if (value !== undefined) {
104
110
  parsed.velocityWindow = value;
105
111
  }
106
112
  i += 1;
@@ -146,8 +152,8 @@ export const parseEstimateArgs = (argv) => {
146
152
  }
147
153
  }
148
154
  else if (arg.startsWith("--velocity-window=") || arg.startsWith("--window=")) {
149
- const value = parseNumber(arg.split("=")[1]);
150
- if (value === 10 || value === 20 || value === 50) {
155
+ const value = parsePositiveInteger(arg.split("=")[1]);
156
+ if (value !== undefined) {
151
157
  parsed.velocityWindow = value;
152
158
  }
153
159
  }
@@ -170,11 +176,73 @@ export const parseEstimateArgs = (argv) => {
170
176
  };
171
177
  const ANSI_REGEX = /\x1b\[[0-9;]*m/g;
172
178
  const stripAnsi = (value) => value.replace(ANSI_REGEX, "");
173
- const visibleLength = (value) => stripAnsi(value).length;
179
+ // Mirrors common wcwidth full-width ranges so CJK/emoji cells align in terminal output.
180
+ const isFullWidthCodePoint = (codePoint) => {
181
+ if (codePoint >= 0x1100 && codePoint <= 0x115f)
182
+ return true;
183
+ if (codePoint >= 0x2329 && codePoint <= 0x232a)
184
+ return true;
185
+ if (codePoint >= 0x2e80 && codePoint <= 0x303e)
186
+ return true;
187
+ if (codePoint >= 0x3040 && codePoint <= 0xa4cf)
188
+ return true;
189
+ if (codePoint >= 0xac00 && codePoint <= 0xd7a3)
190
+ return true;
191
+ if (codePoint >= 0xf900 && codePoint <= 0xfaff)
192
+ return true;
193
+ if (codePoint >= 0xfe10 && codePoint <= 0xfe19)
194
+ return true;
195
+ if (codePoint >= 0xfe30 && codePoint <= 0xfe6f)
196
+ return true;
197
+ if (codePoint >= 0xff00 && codePoint <= 0xff60)
198
+ return true;
199
+ if (codePoint >= 0xffe0 && codePoint <= 0xffe6)
200
+ return true;
201
+ if (codePoint >= 0x1f300 && codePoint <= 0x1f64f)
202
+ return true;
203
+ if (codePoint >= 0x1f900 && codePoint <= 0x1f9ff)
204
+ return true;
205
+ if (codePoint >= 0x20000 && codePoint <= 0x3fffd)
206
+ return true;
207
+ return false;
208
+ };
209
+ const isCombiningCodePoint = (codePoint) => {
210
+ if (codePoint >= 0x0300 && codePoint <= 0x036f)
211
+ return true;
212
+ if (codePoint >= 0x1ab0 && codePoint <= 0x1aff)
213
+ return true;
214
+ if (codePoint >= 0x1dc0 && codePoint <= 0x1dff)
215
+ return true;
216
+ if (codePoint >= 0x20d0 && codePoint <= 0x20ff)
217
+ return true;
218
+ if (codePoint >= 0xfe20 && codePoint <= 0xfe2f)
219
+ return true;
220
+ return false;
221
+ };
222
+ const visibleLength = (value) => {
223
+ const plain = stripAnsi(value);
224
+ if (!plain)
225
+ return 0;
226
+ let width = 0;
227
+ for (const ch of plain) {
228
+ // Zero-width shaping/selectors.
229
+ if (ch === "\u200d" || ch === "\ufe0e" || ch === "\ufe0f")
230
+ continue;
231
+ const codePoint = ch.codePointAt(0);
232
+ if (codePoint === undefined || isCombiningCodePoint(codePoint))
233
+ continue;
234
+ width += isFullWidthCodePoint(codePoint) ? 2 : 1;
235
+ }
236
+ return width;
237
+ };
174
238
  const padVisible = (value, width) => {
175
239
  const diff = width - visibleLength(value);
176
240
  return diff > 0 ? `${value}${" ".repeat(diff)}` : value;
177
241
  };
242
+ const padVisibleStart = (value, width) => {
243
+ const diff = width - visibleLength(value);
244
+ return diff > 0 ? `${" ".repeat(diff)}${value}` : value;
245
+ };
178
246
  const colorize = (enabled, code, value) => enabled ? `\x1b[${code}m${value}\x1b[0m` : value;
179
247
  const style = {
180
248
  bold: (enabled, value) => colorize(enabled, 1, value),
@@ -196,10 +264,20 @@ const formatPanel = (lines) => {
196
264
  const formatBoxTable = (headers, rows, options) => {
197
265
  const widths = headers.map((header, idx) => Math.max(visibleLength(header), ...rows.map((row) => visibleLength(row[idx] ?? ""))));
198
266
  const lineStyle = options?.lineStyle ?? ((value) => value);
267
+ const align = options?.align ?? [];
199
268
  const border = (left, join, right) => lineStyle(`${left}${widths.map((width) => "─".repeat(width + 2)).join(join)}${right}`);
200
269
  const verticalLine = lineStyle("│");
201
270
  const headerLine = `${verticalLine}${headers.map((header, idx) => ` ${padVisible(header, widths[idx])} `).join(verticalLine)}${verticalLine}`;
202
- const rowLines = rows.map((row) => `${verticalLine}${row.map((cell, idx) => ` ${padVisible(cell ?? "", widths[idx])} `).join(verticalLine)}${verticalLine}`);
271
+ const rowLines = rows.map((row) => {
272
+ const cells = row.map((cell, idx) => {
273
+ const value = cell ?? "";
274
+ if (align[idx] === "right") {
275
+ return ` ${padVisibleStart(value, widths[idx])} `;
276
+ }
277
+ return ` ${padVisible(value, widths[idx])} `;
278
+ });
279
+ return `${verticalLine}${cells.join(verticalLine)}${verticalLine}`;
280
+ });
203
281
  return [
204
282
  border("╭", "┬", "╮"),
205
283
  headerLine,
@@ -302,7 +380,7 @@ const renderProgressSection = (result, colorEnabled) => {
302
380
  const done = result.completion.done;
303
381
  const labels = [
304
382
  "🛠️ Work on tasks",
305
- "🧪 Ready to qa",
383
+ "🧪 Ready to QA",
306
384
  "✅ Done",
307
385
  ];
308
386
  const maxLabel = Math.max(...labels.map((label) => visibleLength(label)));
@@ -318,7 +396,7 @@ const renderProgressSection = (result, colorEnabled) => {
318
396
  partial: (value) => style.blue(colorEnabled, value),
319
397
  empty: (value) => style.dim(colorEnabled, value),
320
398
  }),
321
- formatLine("🧪 Ready to qa", qa, {
399
+ formatLine("🧪 Ready to QA", qa, {
322
400
  full: (value) => style.yellow(colorEnabled, value),
323
401
  partial: (value) => style.magenta(colorEnabled, value),
324
402
  empty: (value) => style.dim(colorEnabled, value),
@@ -330,6 +408,10 @@ const renderProgressSection = (result, colorEnabled) => {
330
408
  }),
331
409
  ]);
332
410
  };
411
+ const formatKeyValueLines = (entries) => {
412
+ const maxLabel = Math.max(0, ...entries.map((entry) => visibleLength(entry.label)));
413
+ return entries.map((entry) => `${padVisible(entry.label, maxLabel)} : ${entry.value}`);
414
+ };
333
415
  const renderResult = (result, options) => {
334
416
  const { colorEnabled } = options;
335
417
  const purpleTableLines = (value) => style.magenta(colorEnabled, value);
@@ -380,17 +462,20 @@ const renderResult = (result, options) => {
380
462
  style.bold(colorEnabled, "STORY POINTS"),
381
463
  style.bold(colorEnabled, spHeader.toUpperCase()),
382
464
  style.bold(colorEnabled, "TIME LEFT"),
383
- ], rows, { lineStyle: purpleTableLines }));
465
+ ], rows, { lineStyle: purpleTableLines, align: ["left", "right", "right", "right"] }));
384
466
  const counts = result.statusCounts;
467
+ const statusLines = formatKeyValueLines([
468
+ { label: style.bold(colorEnabled, "Total tasks"), value: `${counts.total}` },
469
+ { label: style.cyan(colorEnabled, "Ready to code review"), value: `${counts.readyToCodeReview}` },
470
+ { label: style.yellow(colorEnabled, "Ready to QA"), value: `${counts.readyToQa}` },
471
+ { label: style.blue(colorEnabled, "In progress"), value: `${counts.inProgress}` },
472
+ { label: style.red(colorEnabled, "Failed"), value: `${counts.failed}` },
473
+ { label: style.green(colorEnabled, "Completed"), value: `${counts.completed}` },
474
+ ]);
385
475
  // eslint-disable-next-line no-console
386
476
  console.log(formatPanel([
387
477
  style.bold(colorEnabled, "📌 Task Status"),
388
- `${style.bold(colorEnabled, "Total tasks")} : ${counts.total}`,
389
- `${style.cyan(colorEnabled, "Ready to code review")} : ${counts.readyToCodeReview}`,
390
- `${style.yellow(colorEnabled, "Ready to qa")} : ${counts.readyToQa}`,
391
- `${style.blue(colorEnabled, "In progress")} : ${counts.inProgress}`,
392
- `${style.red(colorEnabled, "Failed")} : ${counts.failed}`,
393
- `${style.green(colorEnabled, "Completed")} : ${counts.completed}`,
478
+ ...statusLines,
394
479
  ]));
395
480
  // eslint-disable-next-line no-console
396
481
  console.log(renderProgressSection(result, colorEnabled));
@@ -399,11 +484,17 @@ const renderResult = (result, options) => {
399
484
  const fallbackNote = velocity.requestedMode && velocity.requestedMode !== velocity.source
400
485
  ? ` (requested ${velocity.requestedMode}; no empirical samples, using config)`
401
486
  : "";
487
+ const velocityLines = formatKeyValueLines([
488
+ { label: style.bold(colorEnabled, "Velocity source"), value: `${velocity.source}${fallbackNote}` },
489
+ {
490
+ label: `${style.bold(colorEnabled, "Samples")}${windowLabel}`,
491
+ value: `impl=${samples.implementation ?? 0}, review=${samples.review ?? 0}, qa=${samples.qa ?? 0}`,
492
+ },
493
+ ]);
402
494
  // eslint-disable-next-line no-console
403
495
  console.log(formatPanel([
404
496
  style.bold(colorEnabled, "📈 Velocity"),
405
- `${style.bold(colorEnabled, "Velocity source")} : ${velocity.source}${fallbackNote}`,
406
- `${style.bold(colorEnabled, "Samples")}${windowLabel} : impl=${samples.implementation ?? 0}, review=${samples.review ?? 0}, qa=${samples.qa ?? 0}`,
497
+ ...velocityLines,
407
498
  ]));
408
499
  // eslint-disable-next-line no-console
409
500
  console.log(style.bold(colorEnabled, style.magenta(colorEnabled, "⏱️ ETAs")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcoda",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
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.31",
49
- "@mcoda/shared": "0.1.31"
48
+ "@mcoda/core": "0.1.33",
49
+ "@mcoda/shared": "0.1.33"
50
50
  },
51
51
  "devDependencies": {
52
- "@mcoda/db": "0.1.31",
53
- "@mcoda/integrations": "0.1.31"
52
+ "@mcoda/db": "0.1.33",
53
+ "@mcoda/integrations": "0.1.33"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsc -p tsconfig.json",