@task-mcp/shared 1.0.22 → 1.0.24
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/dist/algorithms/critical-path.d.ts +47 -0
- package/dist/algorithms/critical-path.d.ts.map +1 -0
- package/dist/algorithms/critical-path.js +340 -0
- package/dist/algorithms/critical-path.js.map +1 -0
- package/dist/algorithms/critical-path.test.d.ts +2 -0
- package/dist/algorithms/critical-path.test.d.ts.map +1 -0
- package/dist/algorithms/critical-path.test.js +184 -0
- package/dist/algorithms/critical-path.test.js.map +1 -0
- package/dist/algorithms/dependency-integrity.d.ts +81 -0
- package/dist/algorithms/dependency-integrity.d.ts.map +1 -0
- package/dist/algorithms/dependency-integrity.js +209 -0
- package/dist/algorithms/dependency-integrity.js.map +1 -0
- package/dist/algorithms/dependency-integrity.test.d.ts +2 -0
- package/dist/algorithms/dependency-integrity.test.d.ts.map +1 -0
- package/dist/algorithms/dependency-integrity.test.js +296 -0
- package/dist/algorithms/dependency-integrity.test.js.map +1 -0
- package/dist/algorithms/index.d.ts +5 -0
- package/dist/algorithms/index.d.ts.map +1 -0
- package/dist/algorithms/index.js +5 -0
- package/dist/algorithms/index.js.map +1 -0
- package/dist/algorithms/tech-analysis.d.ts +106 -0
- package/dist/algorithms/tech-analysis.d.ts.map +1 -0
- package/dist/algorithms/tech-analysis.js +351 -0
- package/dist/algorithms/tech-analysis.js.map +1 -0
- package/dist/algorithms/tech-analysis.test.d.ts +2 -0
- package/dist/algorithms/tech-analysis.test.d.ts.map +1 -0
- package/dist/algorithms/tech-analysis.test.js +330 -0
- package/dist/algorithms/tech-analysis.test.js.map +1 -0
- package/dist/algorithms/topological-sort.d.ts +58 -0
- package/dist/algorithms/topological-sort.d.ts.map +1 -0
- package/dist/algorithms/topological-sort.js +201 -0
- package/dist/algorithms/topological-sort.js.map +1 -0
- package/dist/algorithms/topological-sort.test.d.ts +2 -0
- package/dist/algorithms/topological-sort.test.d.ts.map +1 -0
- package/dist/algorithms/topological-sort.test.js +154 -0
- package/dist/algorithms/topological-sort.test.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/inbox.d.ts +55 -0
- package/dist/schemas/inbox.d.ts.map +1 -0
- package/dist/schemas/inbox.js +25 -0
- package/dist/schemas/inbox.js.map +1 -0
- package/dist/schemas/index.d.ts +7 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +17 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/llm-guide.d.ts +147 -0
- package/dist/schemas/llm-guide.d.ts.map +1 -0
- package/dist/schemas/llm-guide.js +72 -0
- package/dist/schemas/llm-guide.js.map +1 -0
- package/dist/schemas/project.d.ts +177 -0
- package/dist/schemas/project.d.ts.map +1 -0
- package/dist/schemas/project.js +56 -0
- package/dist/schemas/project.js.map +1 -0
- package/dist/schemas/response-format.d.ts +148 -0
- package/dist/schemas/response-format.d.ts.map +1 -0
- package/dist/schemas/response-format.js +18 -0
- package/dist/schemas/response-format.js.map +1 -0
- package/dist/schemas/response-schema.d.ts +307 -0
- package/dist/schemas/response-schema.d.ts.map +1 -0
- package/dist/schemas/response-schema.js +78 -0
- package/dist/schemas/response-schema.js.map +1 -0
- package/dist/schemas/response-schema.test.d.ts +2 -0
- package/dist/schemas/response-schema.test.d.ts.map +1 -0
- package/dist/schemas/response-schema.test.js +256 -0
- package/dist/schemas/response-schema.test.js.map +1 -0
- package/dist/schemas/state.d.ts +17 -0
- package/dist/schemas/state.d.ts.map +1 -0
- package/dist/schemas/state.js +17 -0
- package/dist/schemas/state.js.map +1 -0
- package/dist/schemas/task.d.ts +881 -0
- package/dist/schemas/task.d.ts.map +1 -0
- package/dist/schemas/task.js +177 -0
- package/dist/schemas/task.js.map +1 -0
- package/dist/schemas/view.d.ts +143 -0
- package/dist/schemas/view.d.ts.map +1 -0
- package/dist/schemas/view.js +48 -0
- package/dist/schemas/view.js.map +1 -0
- package/dist/utils/dashboard-renderer.d.ts +93 -0
- package/dist/utils/dashboard-renderer.d.ts.map +1 -0
- package/dist/utils/dashboard-renderer.js +416 -0
- package/dist/utils/dashboard-renderer.js.map +1 -0
- package/dist/utils/dashboard-renderer.test.d.ts +2 -0
- package/dist/utils/dashboard-renderer.test.d.ts.map +1 -0
- package/dist/utils/dashboard-renderer.test.js +772 -0
- package/dist/utils/dashboard-renderer.test.js.map +1 -0
- package/dist/utils/date.d.ts +94 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +323 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/date.test.d.ts +2 -0
- package/dist/utils/date.test.d.ts.map +1 -0
- package/dist/utils/date.test.js +276 -0
- package/dist/utils/date.test.js.map +1 -0
- package/dist/utils/hierarchy.d.ts +102 -0
- package/dist/utils/hierarchy.d.ts.map +1 -0
- package/dist/utils/hierarchy.js +236 -0
- package/dist/utils/hierarchy.js.map +1 -0
- package/dist/utils/hierarchy.test.d.ts +2 -0
- package/dist/utils/hierarchy.test.d.ts.map +1 -0
- package/dist/utils/hierarchy.test.js +423 -0
- package/dist/utils/hierarchy.test.js.map +1 -0
- package/dist/utils/id.d.ts +60 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +118 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/id.test.d.ts +2 -0
- package/dist/utils/id.test.d.ts.map +1 -0
- package/dist/utils/id.test.js +193 -0
- package/dist/utils/id.test.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +34 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/natural-language.d.ts +111 -0
- package/dist/utils/natural-language.d.ts.map +1 -0
- package/dist/utils/natural-language.js +297 -0
- package/dist/utils/natural-language.js.map +1 -0
- package/dist/utils/natural-language.test.d.ts +2 -0
- package/dist/utils/natural-language.test.d.ts.map +1 -0
- package/dist/utils/natural-language.test.js +197 -0
- package/dist/utils/natural-language.test.js.map +1 -0
- package/dist/utils/priority-queue.d.ts +17 -0
- package/dist/utils/priority-queue.d.ts.map +1 -0
- package/dist/utils/priority-queue.js +62 -0
- package/dist/utils/priority-queue.js.map +1 -0
- package/dist/utils/priority-queue.test.d.ts +2 -0
- package/dist/utils/priority-queue.test.d.ts.map +1 -0
- package/dist/utils/priority-queue.test.js +82 -0
- package/dist/utils/priority-queue.test.js.map +1 -0
- package/dist/utils/projection.d.ts +65 -0
- package/dist/utils/projection.d.ts.map +1 -0
- package/dist/utils/projection.js +180 -0
- package/dist/utils/projection.js.map +1 -0
- package/dist/utils/projection.test.d.ts +2 -0
- package/dist/utils/projection.test.d.ts.map +1 -0
- package/dist/utils/projection.test.js +341 -0
- package/dist/utils/projection.test.js.map +1 -0
- package/dist/utils/terminal-ui.d.ts +208 -0
- package/dist/utils/terminal-ui.d.ts.map +1 -0
- package/dist/utils/terminal-ui.js +614 -0
- package/dist/utils/terminal-ui.js.map +1 -0
- package/dist/utils/terminal-ui.test.d.ts +2 -0
- package/dist/utils/terminal-ui.test.d.ts.map +1 -0
- package/dist/utils/terminal-ui.test.js +683 -0
- package/dist/utils/terminal-ui.test.js.map +1 -0
- package/dist/utils/workspace.d.ts +102 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +183 -0
- package/dist/utils/workspace.js.map +1 -0
- package/dist/utils/workspace.test.d.ts +2 -0
- package/dist/utils/workspace.test.d.ts.map +1 -0
- package/dist/utils/workspace.test.js +97 -0
- package/dist/utils/workspace.test.js.map +1 -0
- package/package.json +5 -1
- package/src/algorithms/critical-path.test.ts +227 -0
- package/src/algorithms/critical-path.ts +14 -34
- package/src/algorithms/dependency-integrity.test.ts +335 -0
- package/src/algorithms/dependency-integrity.ts +4 -13
- package/src/algorithms/tech-analysis.test.ts +405 -0
- package/src/algorithms/tech-analysis.ts +27 -27
- package/src/algorithms/topological-sort.test.ts +182 -0
- package/src/algorithms/topological-sort.ts +6 -10
- package/src/schemas/index.ts +2 -13
- package/src/schemas/response-format.ts +6 -6
- package/src/schemas/response-schema.test.ts +314 -0
- package/src/schemas/response-schema.ts +25 -20
- package/src/schemas/task.ts +4 -22
- package/src/utils/dashboard-renderer.test.ts +976 -0
- package/src/utils/dashboard-renderer.ts +27 -59
- package/src/utils/date.test.ts +329 -0
- package/src/utils/date.ts +2 -10
- package/src/utils/hierarchy.test.ts +488 -0
- package/src/utils/hierarchy.ts +4 -5
- package/src/utils/id.test.ts +235 -0
- package/src/utils/index.ts +7 -1
- package/src/utils/natural-language.test.ts +234 -0
- package/src/utils/priority-queue.test.ts +103 -0
- package/src/utils/projection.test.ts +430 -0
- package/src/utils/terminal-ui.test.ts +831 -0
- package/src/utils/terminal-ui.ts +53 -54
- package/src/utils/workspace.test.ts +125 -0
package/src/utils/terminal-ui.ts
CHANGED
|
@@ -185,11 +185,9 @@ export function isFullWidth(char: string): boolean {
|
|
|
185
185
|
(code >= 0xffe0 && code <= 0xffe6) || // Fullwidth Symbols
|
|
186
186
|
(code >= 0x20000 && code <= 0x2ffff) || // CJK Extension B, C, D, E, F
|
|
187
187
|
(code >= 0x30000 && code <= 0x3ffff) || // CJK Extension G, H, I
|
|
188
|
-
|
|
189
188
|
// Symbols (typically wide in terminals)
|
|
190
189
|
(code >= 0x2600 && code <= 0x26ff) || // Misc Symbols (sun, moon, stars, etc.)
|
|
191
190
|
(code >= 0x2700 && code <= 0x27bf) || // Dingbats
|
|
192
|
-
|
|
193
191
|
// Emoji ranges (comprehensive coverage)
|
|
194
192
|
(code >= 0x1f1e0 && code <= 0x1f1ff) || // Regional Indicator Symbols (flags)
|
|
195
193
|
(code >= 0x1f300 && code <= 0x1f5ff) || // Misc Symbols & Pictographs
|
|
@@ -202,7 +200,6 @@ export function isFullWidth(char: string): boolean {
|
|
|
202
200
|
(code >= 0x1fa00 && code <= 0x1fa6f) || // Chess Symbols
|
|
203
201
|
(code >= 0x1fa70 && code <= 0x1faff) || // Symbols & Pictographs Extended-A
|
|
204
202
|
(code >= 0x1fb00 && code <= 0x1fbff) || // Symbols for Legacy Computing
|
|
205
|
-
|
|
206
203
|
// Additional emoji-related
|
|
207
204
|
(code >= 0x231a && code <= 0x231b) || // Watch, Hourglass
|
|
208
205
|
(code >= 0x23e9 && code <= 0x23f3) || // Media control symbols
|
|
@@ -278,7 +275,11 @@ export const visibleLength = displayWidth;
|
|
|
278
275
|
/**
|
|
279
276
|
* Pad string to width (accounting for display width)
|
|
280
277
|
*/
|
|
281
|
-
export function pad(
|
|
278
|
+
export function pad(
|
|
279
|
+
str: string,
|
|
280
|
+
width: number,
|
|
281
|
+
align: "left" | "right" | "center" = "left"
|
|
282
|
+
): string {
|
|
282
283
|
const len = displayWidth(str);
|
|
283
284
|
const diff = width - len;
|
|
284
285
|
if (diff <= 0) return str;
|
|
@@ -359,7 +360,8 @@ export function progressBar(
|
|
|
359
360
|
const filledCount = Math.round((percent / 100) * width);
|
|
360
361
|
const emptyCount = width - filledCount;
|
|
361
362
|
|
|
362
|
-
const bar =
|
|
363
|
+
const bar =
|
|
364
|
+
color(filled.repeat(filledCount), filledColor) + color(empty.repeat(emptyCount), emptyColor);
|
|
363
365
|
|
|
364
366
|
return showPercent ? `${bar} ${percent}%` : bar;
|
|
365
367
|
}
|
|
@@ -383,7 +385,10 @@ export function box(content: string, options: BoxOptions = {}): string {
|
|
|
383
385
|
const { padding = 1, borderColor = "cyan", title, rounded = true } = options;
|
|
384
386
|
|
|
385
387
|
const lines = content.split("\n");
|
|
386
|
-
const maxLen = Math.max(
|
|
388
|
+
const maxLen = Math.max(
|
|
389
|
+
...lines.map((l) => displayWidth(stripAnsi(l))),
|
|
390
|
+
title ? title.length + 2 : 0
|
|
391
|
+
);
|
|
387
392
|
const innerWidth = options.width ? options.width - 2 - padding * 2 : maxLen + padding * 2;
|
|
388
393
|
|
|
389
394
|
const tl = rounded ? BOX.rTopLeft : BOX.topLeft;
|
|
@@ -402,7 +407,10 @@ export function box(content: string, options: BoxOptions = {}): string {
|
|
|
402
407
|
const remaining = innerWidth - titlePart.length;
|
|
403
408
|
const leftPad = Math.floor(remaining / 2);
|
|
404
409
|
const rightPad = remaining - leftPad;
|
|
405
|
-
top =
|
|
410
|
+
top =
|
|
411
|
+
applyBorder(tl + h.repeat(leftPad)) +
|
|
412
|
+
c.bold(titlePart) +
|
|
413
|
+
applyBorder(h.repeat(rightPad) + tr);
|
|
406
414
|
} else {
|
|
407
415
|
top = applyBorder(tl + h.repeat(innerWidth) + tr);
|
|
408
416
|
}
|
|
@@ -412,10 +420,16 @@ export function box(content: string, options: BoxOptions = {}): string {
|
|
|
412
420
|
const paddingLines = Array(padding).fill(padLine);
|
|
413
421
|
|
|
414
422
|
// Content lines
|
|
415
|
-
const contentLines = lines.map(line => {
|
|
423
|
+
const contentLines = lines.map((line) => {
|
|
416
424
|
const lineWidth = displayWidth(stripAnsi(line));
|
|
417
425
|
const padRight = innerWidth - lineWidth - padding;
|
|
418
|
-
return
|
|
426
|
+
return (
|
|
427
|
+
applyBorder(v) +
|
|
428
|
+
" ".repeat(padding) +
|
|
429
|
+
line +
|
|
430
|
+
" ".repeat(Math.max(0, padRight)) +
|
|
431
|
+
applyBorder(v)
|
|
432
|
+
);
|
|
419
433
|
});
|
|
420
434
|
|
|
421
435
|
// Bottom border
|
|
@@ -452,16 +466,16 @@ export function drawBox(lines: string[], options: BoxOptions = {}): string[] {
|
|
|
452
466
|
const rightBorder = remainingWidth - leftBorder;
|
|
453
467
|
result.push(
|
|
454
468
|
applyBorder(BOX.topLeft) +
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
469
|
+
applyBorder(BOX.horizontal.repeat(leftBorder)) +
|
|
470
|
+
c.label(titleStr) +
|
|
471
|
+
applyBorder(BOX.horizontal.repeat(rightBorder)) +
|
|
472
|
+
applyBorder(BOX.topRight)
|
|
459
473
|
);
|
|
460
474
|
} else {
|
|
461
475
|
result.push(
|
|
462
476
|
applyBorder(BOX.topLeft) +
|
|
463
|
-
|
|
464
|
-
|
|
477
|
+
applyBorder(BOX.horizontal.repeat(boxWidth - 2)) +
|
|
478
|
+
applyBorder(BOX.topRight)
|
|
465
479
|
);
|
|
466
480
|
}
|
|
467
481
|
|
|
@@ -469,18 +483,18 @@ export function drawBox(lines: string[], options: BoxOptions = {}): string[] {
|
|
|
469
483
|
for (const line of lines) {
|
|
470
484
|
result.push(
|
|
471
485
|
applyBorder(BOX.vertical) +
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
486
|
+
padStr +
|
|
487
|
+
padEnd(line, innerWidth) +
|
|
488
|
+
padStr +
|
|
489
|
+
applyBorder(BOX.vertical)
|
|
476
490
|
);
|
|
477
491
|
}
|
|
478
492
|
|
|
479
493
|
// Bottom border
|
|
480
494
|
result.push(
|
|
481
495
|
applyBorder(BOX.bottomLeft) +
|
|
482
|
-
|
|
483
|
-
|
|
496
|
+
applyBorder(BOX.horizontal.repeat(boxWidth - 2)) +
|
|
497
|
+
applyBorder(BOX.bottomRight)
|
|
484
498
|
);
|
|
485
499
|
|
|
486
500
|
return result;
|
|
@@ -494,9 +508,9 @@ export function drawBox(lines: string[], options: BoxOptions = {}): string[] {
|
|
|
494
508
|
* Place multiple boxes side by side (string input, string output)
|
|
495
509
|
*/
|
|
496
510
|
export function sideBySide(boxes: string[], gap: number = 2): string {
|
|
497
|
-
const boxLines = boxes.map(b => b.split("\n"));
|
|
498
|
-
const maxHeight = Math.max(...boxLines.map(lines => lines.length));
|
|
499
|
-
const boxWidths = boxLines.map(lines => Math.max(...lines.map(l => displayWidth(l))));
|
|
511
|
+
const boxLines = boxes.map((b) => b.split("\n"));
|
|
512
|
+
const maxHeight = Math.max(...boxLines.map((lines) => lines.length));
|
|
513
|
+
const boxWidths = boxLines.map((lines) => Math.max(...lines.map((l) => displayWidth(l))));
|
|
500
514
|
|
|
501
515
|
// Pad each box to max height
|
|
502
516
|
const paddedBoxLines = boxLines.map((lines, i) => {
|
|
@@ -504,7 +518,7 @@ export function sideBySide(boxes: string[], gap: number = 2): string {
|
|
|
504
518
|
while (lines.length < maxHeight) {
|
|
505
519
|
lines.push(" ".repeat(width));
|
|
506
520
|
}
|
|
507
|
-
return lines.map(line => {
|
|
521
|
+
return lines.map((line) => {
|
|
508
522
|
const lineWidth = displayWidth(line);
|
|
509
523
|
if (lineWidth < width) {
|
|
510
524
|
return line + " ".repeat(width - lineWidth);
|
|
@@ -518,7 +532,7 @@ export function sideBySide(boxes: string[], gap: number = 2): string {
|
|
|
518
532
|
const gapStr = " ".repeat(gap);
|
|
519
533
|
|
|
520
534
|
for (let i = 0; i < maxHeight; i++) {
|
|
521
|
-
const lineParts = paddedBoxLines.map(lines => lines[i] ?? "");
|
|
535
|
+
const lineParts = paddedBoxLines.map((lines) => lines[i] ?? "");
|
|
522
536
|
result.push(lineParts.join(gapStr));
|
|
523
537
|
}
|
|
524
538
|
|
|
@@ -529,14 +543,8 @@ export function sideBySide(boxes: string[], gap: number = 2): string {
|
|
|
529
543
|
* Merge two boxes side by side (array input, array output)
|
|
530
544
|
* For MCP server compatibility
|
|
531
545
|
*/
|
|
532
|
-
export function sideBySideArrays(
|
|
533
|
-
leftLines:
|
|
534
|
-
rightLines: string[],
|
|
535
|
-
gap = 2
|
|
536
|
-
): string[] {
|
|
537
|
-
const leftWidth = leftLines.length > 0
|
|
538
|
-
? Math.max(...leftLines.map(displayWidth))
|
|
539
|
-
: 0;
|
|
546
|
+
export function sideBySideArrays(leftLines: string[], rightLines: string[], gap = 2): string[] {
|
|
547
|
+
const leftWidth = leftLines.length > 0 ? Math.max(...leftLines.map(displayWidth)) : 0;
|
|
540
548
|
|
|
541
549
|
const maxLines = Math.max(leftLines.length, rightLines.length);
|
|
542
550
|
const result: string[] = [];
|
|
@@ -580,10 +588,10 @@ export function table<T extends Record<string, unknown>>(
|
|
|
580
588
|
const { headerColor = "cyan", borderColor = "gray" } = options;
|
|
581
589
|
|
|
582
590
|
// Calculate column widths
|
|
583
|
-
const widths = columns.map(col => {
|
|
591
|
+
const widths = columns.map((col) => {
|
|
584
592
|
const headerWidth = displayWidth(col.header);
|
|
585
593
|
const maxDataWidth = Math.max(
|
|
586
|
-
...data.map(row => {
|
|
594
|
+
...data.map((row) => {
|
|
587
595
|
const val = col.format ? col.format(row[col.key], row) : String(row[col.key] ?? "");
|
|
588
596
|
return displayWidth(val);
|
|
589
597
|
}),
|
|
@@ -601,10 +609,10 @@ export function table<T extends Record<string, unknown>>(
|
|
|
601
609
|
.join(` ${border} `);
|
|
602
610
|
|
|
603
611
|
// Separator
|
|
604
|
-
const separator = widths.map(w => hBorder.repeat(w)).join(color(`─${BOX.cross}─`, borderColor));
|
|
612
|
+
const separator = widths.map((w) => hBorder.repeat(w)).join(color(`─${BOX.cross}─`, borderColor));
|
|
605
613
|
|
|
606
614
|
// Data rows
|
|
607
|
-
const dataRows = data.map(row =>
|
|
615
|
+
const dataRows = data.map((row) =>
|
|
608
616
|
columns
|
|
609
617
|
.map((col, i) => {
|
|
610
618
|
const w = widths[i] ?? 0;
|
|
@@ -621,33 +629,24 @@ export function table<T extends Record<string, unknown>>(
|
|
|
621
629
|
* Render table with full borders (array output)
|
|
622
630
|
* For MCP server compatibility
|
|
623
631
|
*/
|
|
624
|
-
export function renderTable(
|
|
625
|
-
columns: TableColumn[],
|
|
626
|
-
rows: Record<string, unknown>[]
|
|
627
|
-
): string[] {
|
|
632
|
+
export function renderTable(columns: TableColumn[], rows: Record<string, unknown>[]): string[] {
|
|
628
633
|
const colWidths: number[] = columns.map((col) => {
|
|
629
634
|
const headerWidth = col.header.length;
|
|
630
|
-
const maxValueWidth = Math.max(
|
|
631
|
-
...rows.map((row) => String(row[col.key] ?? "").length)
|
|
632
|
-
);
|
|
635
|
+
const maxValueWidth = Math.max(...rows.map((row) => String(row[col.key] ?? "").length));
|
|
633
636
|
return col.width ?? Math.max(headerWidth, maxValueWidth);
|
|
634
637
|
});
|
|
635
638
|
|
|
636
639
|
const result: string[] = [];
|
|
637
640
|
|
|
638
641
|
// Header row
|
|
639
|
-
const headerCells = columns.map((col, i) =>
|
|
640
|
-
|
|
642
|
+
const headerCells = columns.map((col, i) => c.label(center(col.header, colWidths[i] ?? 0)));
|
|
643
|
+
result.push(
|
|
644
|
+
c.muted(BOX.vertical) + headerCells.join(c.muted(BOX.vertical)) + c.muted(BOX.vertical)
|
|
641
645
|
);
|
|
642
|
-
result.push(c.muted(BOX.vertical) + headerCells.join(c.muted(BOX.vertical)) + c.muted(BOX.vertical));
|
|
643
646
|
|
|
644
647
|
// Separator
|
|
645
648
|
const separator = columns.map((_, i) => BOX.horizontal.repeat(colWidths[i] ?? 0));
|
|
646
|
-
result.push(
|
|
647
|
-
c.muted(BOX.teeRight) +
|
|
648
|
-
c.muted(separator.join(BOX.cross)) +
|
|
649
|
-
c.muted(BOX.teeLeft)
|
|
650
|
-
);
|
|
649
|
+
result.push(c.muted(BOX.teeRight) + c.muted(separator.join(BOX.cross)) + c.muted(BOX.teeLeft));
|
|
651
650
|
|
|
652
651
|
// Data rows
|
|
653
652
|
for (const row of rows) {
|
|
@@ -782,5 +781,5 @@ export function banner(text: string): string {
|
|
|
782
781
|
}
|
|
783
782
|
}
|
|
784
783
|
|
|
785
|
-
return lines.map(l => c.cyan(l)).join("\n");
|
|
784
|
+
return lines.map((l) => c.cyan(l)).join("\n");
|
|
786
785
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
normalizeWorkspace,
|
|
4
|
+
detectWorkspaceSync,
|
|
5
|
+
detectWorkspace,
|
|
6
|
+
getWorkspaceFromPath,
|
|
7
|
+
getGitRepoRoot,
|
|
8
|
+
getGitRepoRootSync,
|
|
9
|
+
} from "./workspace";
|
|
10
|
+
|
|
11
|
+
describe("normalizeWorkspace", () => {
|
|
12
|
+
test("converts to lowercase", () => {
|
|
13
|
+
expect(normalizeWorkspace("MyProject")).toBe("myproject");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("replaces spaces with hyphens", () => {
|
|
17
|
+
expect(normalizeWorkspace("my project")).toBe("my-project");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("removes special characters", () => {
|
|
21
|
+
expect(normalizeWorkspace("my@project!")).toBe("myproject");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("handles empty string", () => {
|
|
25
|
+
expect(normalizeWorkspace("")).toBe("default");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("keeps hyphens and underscores", () => {
|
|
29
|
+
expect(normalizeWorkspace("my-project_name")).toBe("my-project_name");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("trims leading and trailing hyphens", () => {
|
|
33
|
+
expect(normalizeWorkspace("--my-project--")).toBe("my-project");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("handles multiple spaces", () => {
|
|
37
|
+
// Multiple spaces become multiple hyphens, then cleaned by special char removal
|
|
38
|
+
expect(normalizeWorkspace("my project")).toBe("my-project");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("handles mixed case with special chars", () => {
|
|
42
|
+
expect(normalizeWorkspace("My@Project#Name")).toBe("myprojectname");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("getWorkspaceFromPath", () => {
|
|
47
|
+
test("extracts workspace from path", () => {
|
|
48
|
+
expect(getWorkspaceFromPath("/home/user/projects/my-app")).toBe("my-app");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("handles path with .tasks suffix", () => {
|
|
52
|
+
expect(getWorkspaceFromPath("/projects/my-app/.tasks")).toBe("my-app");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("handles path with trailing slash", () => {
|
|
56
|
+
expect(getWorkspaceFromPath("/projects/my-app/.tasks/")).toBe("my-app");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("normalizes the extracted name", () => {
|
|
60
|
+
expect(getWorkspaceFromPath("/projects/My App")).toBe("my-app");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("getGitRepoRootSync", () => {
|
|
65
|
+
test("returns string or null for current directory", () => {
|
|
66
|
+
const result = getGitRepoRootSync();
|
|
67
|
+
expect(result === null || typeof result === "string").toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("returns null for non-git directory", () => {
|
|
71
|
+
const result = getGitRepoRootSync("/tmp");
|
|
72
|
+
expect(result).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("getGitRepoRoot", () => {
|
|
77
|
+
test("returns string or null for current directory", async () => {
|
|
78
|
+
const result = await getGitRepoRoot();
|
|
79
|
+
expect(result === null || typeof result === "string").toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("returns null for non-git directory", async () => {
|
|
83
|
+
const result = await getGitRepoRoot("/tmp");
|
|
84
|
+
expect(result).toBeNull();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("detectWorkspaceSync", () => {
|
|
89
|
+
test("returns a workspace name", () => {
|
|
90
|
+
const workspace = detectWorkspaceSync();
|
|
91
|
+
expect(typeof workspace).toBe("string");
|
|
92
|
+
expect(workspace.length).toBeGreaterThan(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("uses tasksDir path when provided and no git repo", () => {
|
|
96
|
+
// This tests the fallback to tasksDir when in a non-git directory
|
|
97
|
+
const workspace = detectWorkspaceSync("/some/path/my-project/.tasks");
|
|
98
|
+
expect(typeof workspace).toBe("string");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("returns normalized workspace name", () => {
|
|
102
|
+
const workspace = detectWorkspaceSync();
|
|
103
|
+
// Should be lowercase with no special characters except - and _
|
|
104
|
+
expect(workspace).toMatch(/^[a-z0-9_-]+$/);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("detectWorkspace", () => {
|
|
109
|
+
test("returns a workspace name asynchronously", async () => {
|
|
110
|
+
const workspace = await detectWorkspace();
|
|
111
|
+
expect(typeof workspace).toBe("string");
|
|
112
|
+
expect(workspace.length).toBeGreaterThan(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("uses tasksDir path when provided", async () => {
|
|
116
|
+
const workspace = await detectWorkspace("/some/path/test-project/.tasks");
|
|
117
|
+
expect(typeof workspace).toBe("string");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("returns normalized workspace name", async () => {
|
|
121
|
+
const workspace = await detectWorkspace();
|
|
122
|
+
// Should be lowercase with no special characters except - and _
|
|
123
|
+
expect(workspace).toMatch(/^[a-z0-9_-]+$/);
|
|
124
|
+
});
|
|
125
|
+
});
|