esedre 0.1.10 → 0.1.12

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/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to Esedre are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.12] - 2026-09-26
9
+
10
+ ### Fixed
11
+ - **UTF-8 En-Dash Mojibake Normalization**: Added `normalizeDashesAndMojibake` and `normalizeTicketFields` to transparently sanitize Windows-1252 mojibake (`–`), double-encoded UTF-8, and non-ASCII Unicode dashes (`\u2013`, `\u2014`, `\u2015`, `\u2012`, `\u2212`) into clean standard ASCII hyphens (`-`) across CLI tables, JSON projections, storage reads, and ticket mutations.
12
+
13
+ ## [0.1.11] - 2026-09-26
14
+
15
+ ### Fixed
16
+ - **Prevent Project Query Leak in Embedded Mode**: Guarded `window.history.replaceState` behind `!isEmbedded` in `PlannedWorkView` when switching project filters, added an explicit `isEmbedded` prop to `PlannedWorkViewProps`, and introduced `detectIsEmbedded` and `updateProjectUrlSearchParam` helpers to ensure embedded planners never mutate the host window's URL search parameters.
17
+ - **Published Version Collision Guard**: Added upfront registry version pre-check to `scripts/publish.js` to immediately detect and reject attempts to republish already-published versions before running the full build/test lifecycle.
18
+
8
19
  ## [0.1.10] - 2026-09-26
9
20
 
10
21
  ### Added
package/dist/esedre.mjs CHANGED
@@ -9,7 +9,7 @@ import fs3 from "node:fs";
9
9
  import path3 from "node:path";
10
10
 
11
11
  // src/types.ts
12
- var CURRENT_ESEDRE_VERSION = "0.1.10";
12
+ var CURRENT_ESEDRE_VERSION = "0.1.12";
13
13
  var EsedreConflictError = class extends Error {
14
14
  constructor(ticketId, currentHash, lastHash) {
15
15
  super(
@@ -538,6 +538,162 @@ async function generateProjectSnapshot(storage, projectCode, outputDir) {
538
538
  return snapshot;
539
539
  }
540
540
 
541
+ // src/utils/formatter.ts
542
+ var isColorSupported = !process.env.NO_COLOR && (process.stdout.isTTY || process.env.FORCE_COLOR);
543
+ var colors = {
544
+ reset: isColorSupported ? "\x1B[0m" : "",
545
+ bold: isColorSupported ? "\x1B[1m" : "",
546
+ dim: isColorSupported ? "\x1B[2m" : "",
547
+ cyan: isColorSupported ? "\x1B[36m" : "",
548
+ green: isColorSupported ? "\x1B[32m" : "",
549
+ yellow: isColorSupported ? "\x1B[33m" : "",
550
+ magenta: isColorSupported ? "\x1B[35m" : "",
551
+ red: isColorSupported ? "\x1B[31m" : "",
552
+ blue: isColorSupported ? "\x1B[34m" : ""
553
+ };
554
+ function normalizeDashesAndMojibake(val) {
555
+ if (typeof val !== "string") return val;
556
+ return val.replace(/–|‗/g, "-").replace(/â€[˜™]/g, "'").replace(/â€[œ]/g, '"').replace(/\u00e2\u20ac\u201c|\u00e2\u0080\u0093|–/g, "-").replace(/\u00e2\u20ac\u201d|\u00e2\u0080\u0094|—/g, "-").replace(/\u00e2\u20ac\u02dc|\u00e2\u20ac\u2122|‘|’/g, "'").replace(/\u00e2\u20ac\u0153|\u00e2\u0080\u009d|“|”/g, '"').replace(/\u00e2\u20ac\u00a6|…/g, "...").replace(/[\u2012\u2013\u2014\u2015\u2212]/g, "-");
557
+ }
558
+ function normalizeTicketFields(ticket) {
559
+ if (!ticket) return ticket;
560
+ if (ticket.meta) {
561
+ if (ticket.meta.title) ticket.meta.title = normalizeDashesAndMojibake(ticket.meta.title);
562
+ if (ticket.meta.estimatedEffort) ticket.meta.estimatedEffort = normalizeDashesAndMojibake(ticket.meta.estimatedEffort);
563
+ if (ticket.meta.complexity) ticket.meta.complexity = normalizeDashesAndMojibake(ticket.meta.complexity);
564
+ }
565
+ if (ticket.detail) {
566
+ if (ticket.detail.title) ticket.detail.title = normalizeDashesAndMojibake(ticket.detail.title);
567
+ if (ticket.detail.estimatedEffort) ticket.detail.estimatedEffort = normalizeDashesAndMojibake(ticket.detail.estimatedEffort);
568
+ if (ticket.detail.summary) ticket.detail.summary = normalizeDashesAndMojibake(ticket.detail.summary);
569
+ if (ticket.detail.breakdown) ticket.detail.breakdown = ticket.detail.breakdown.map((b) => normalizeDashesAndMojibake(b));
570
+ if (ticket.detail.technicalDetails) ticket.detail.technicalDetails = ticket.detail.technicalDetails.map((t) => normalizeDashesAndMojibake(t));
571
+ if (ticket.detail.openQuestions) ticket.detail.openQuestions = ticket.detail.openQuestions.map((q) => normalizeDashesAndMojibake(q));
572
+ if (ticket.detail.raw) ticket.detail.raw = normalizeDashesAndMojibake(ticket.detail.raw);
573
+ }
574
+ if (ticket.planMarkdown) {
575
+ ticket.planMarkdown = normalizeDashesAndMojibake(ticket.planMarkdown);
576
+ }
577
+ if (ticket.comments) {
578
+ for (const c of ticket.comments) {
579
+ if (c.text) c.text = normalizeDashesAndMojibake(c.text);
580
+ if (c.author) c.author = normalizeDashesAndMojibake(c.author);
581
+ }
582
+ }
583
+ return ticket;
584
+ }
585
+ function formatTicketListTable(tickets) {
586
+ if (tickets.length === 0) {
587
+ return `${colors.dim}No tickets found matching criteria.${colors.reset}`;
588
+ }
589
+ const rows = tickets.map((t) => {
590
+ const id = t.projectDescriptor?.code ? `${t.projectDescriptor.code}-${t.meta.id}` : `#${t.meta.id}`;
591
+ const project = t.projectDescriptor?.code || "CORE";
592
+ const type = t.meta.type || t.meta.category || "Feature";
593
+ const status = t.meta.status;
594
+ const title = normalizeDashesAndMojibake(t.meta.title);
595
+ return { id, project, type, status, title };
596
+ });
597
+ const idWidth = Math.max(4, ...rows.map((r) => r.id.length));
598
+ const projWidth = Math.max(7, ...rows.map((r) => r.project.length));
599
+ const typeWidth = Math.max(8, ...rows.map((r) => r.type.length));
600
+ const statusWidth = Math.max(14, ...rows.map((r) => r.status.length));
601
+ const header = `${colors.bold}${pad("ID", idWidth)} ${pad("Project", projWidth)} ${pad("Type", typeWidth)} ${pad("Status", statusWidth)} Title${colors.reset}`;
602
+ const divider = `${colors.dim}${"-".repeat(idWidth)} ${"-".repeat(projWidth)} ${"-".repeat(typeWidth)} ${"-".repeat(statusWidth)} ${"-".repeat(40)}${colors.reset}`;
603
+ const formattedRows = rows.map((r) => {
604
+ const statusColored = colorStatus(r.status);
605
+ const typeColored = colorType(r.type);
606
+ const projColored = `${colors.magenta}${r.project}${colors.reset}`;
607
+ return `${colors.bold}${pad(r.id, idWidth)}${colors.reset} ${pad(projColored, projWidth + (isColorSupported ? colors.magenta.length + colors.reset.length : 0))} ${pad(typeColored, typeWidth + (isColorSupported ? 9 : 0))} ${pad(statusColored, statusWidth + (isColorSupported ? 9 : 0))} ${r.title}`;
608
+ });
609
+ return [header, divider, ...formattedRows].join("\n");
610
+ }
611
+ function formatTicketDetail(ticket) {
612
+ const { meta, detail, comments, planMarkdown, projectDescriptor } = ticket;
613
+ const lines = [];
614
+ lines.push(`${colors.bold}${colors.cyan}Ticket #${meta.project ? `${meta.project}-${meta.id}` : meta.id}: ${normalizeDashesAndMojibake(meta.title)}${colors.reset}`);
615
+ lines.push(`${colors.dim}${"=".repeat(60)}${colors.reset}`);
616
+ lines.push(`${colors.bold}Project:${colors.reset} ${projectDescriptor ? `${projectDescriptor.code} - ${projectDescriptor.name}` : "Default"}`);
617
+ lines.push(`${colors.bold}Type:${colors.reset} ${colorType(meta.type || meta.category || "Feature")}`);
618
+ lines.push(`${colors.bold}Status:${colors.reset} ${colorStatus(meta.status)}`);
619
+ lines.push(`${colors.bold}Complexity:${colors.reset} ${normalizeDashesAndMojibake(meta.complexity || "Medium")}`);
620
+ lines.push(`${colors.bold}Effort:${colors.reset} ${normalizeDashesAndMojibake(meta.estimatedEffort || "N/A")}`);
621
+ lines.push(`${colors.bold}Submitted By:${colors.reset} ${meta.submittedBy || "Unknown"}`);
622
+ if (meta.featureFlag) {
623
+ lines.push(`${colors.bold}Feature Flag:${colors.reset} ${colors.yellow}${meta.featureFlag}${colors.reset}`);
624
+ }
625
+ if (detail?.summary) {
626
+ lines.push("");
627
+ lines.push(`${colors.bold}Summary:${colors.reset}`);
628
+ lines.push(normalizeDashesAndMojibake(detail.summary));
629
+ }
630
+ if (detail?.breakdown && detail.breakdown.length > 0) {
631
+ lines.push("");
632
+ lines.push(`${colors.bold}Feature Breakdown:${colors.reset}`);
633
+ for (const b of detail.breakdown) {
634
+ lines.push(` \u2022 ${normalizeDashesAndMojibake(b)}`);
635
+ }
636
+ }
637
+ if (detail?.technicalDetails && detail.technicalDetails.length > 0) {
638
+ lines.push("");
639
+ lines.push(`${colors.bold}Technical Details:${colors.reset}`);
640
+ for (const t of detail.technicalDetails) {
641
+ lines.push(` \u2022 ${normalizeDashesAndMojibake(t)}`);
642
+ }
643
+ }
644
+ if (detail?.openQuestions && detail.openQuestions.length > 0) {
645
+ lines.push("");
646
+ lines.push(`${colors.bold}Open Decisions & Questions:${colors.reset}`);
647
+ for (const q of detail.openQuestions) {
648
+ lines.push(` \u2022 ${normalizeDashesAndMojibake(q)}`);
649
+ }
650
+ }
651
+ if (planMarkdown) {
652
+ lines.push("");
653
+ lines.push(`${colors.bold}Implementation Plan:${colors.reset}`);
654
+ lines.push(normalizeDashesAndMojibake(planMarkdown.trim()));
655
+ }
656
+ if (comments && comments.length > 0) {
657
+ lines.push("");
658
+ lines.push(`${colors.bold}Comments (${comments.length}):${colors.reset}`);
659
+ for (const c of comments) {
660
+ lines.push(` ${colors.dim}[${c.timestamp.slice(0, 10)}]${colors.reset} ${colors.bold}${normalizeDashesAndMojibake(c.author)}:${colors.reset} ${normalizeDashesAndMojibake(c.text)}`);
661
+ }
662
+ }
663
+ return lines.join("\n");
664
+ }
665
+ function pad(str, width) {
666
+ return str.padEnd(width, " ");
667
+ }
668
+ function colorStatus(status) {
669
+ switch (status) {
670
+ case "Completed":
671
+ return `${colors.green}${status}${colors.reset}`;
672
+ case "In Development":
673
+ return `${colors.green}${status}${colors.reset}`;
674
+ case "Rejected":
675
+ return `${colors.red}${status}${colors.reset}`;
676
+ default:
677
+ return `${colors.cyan}${status}${colors.reset}`;
678
+ }
679
+ }
680
+ function colorType(type) {
681
+ switch (type) {
682
+ case "Feature":
683
+ return `${colors.blue}${type}${colors.reset}`;
684
+ case "Platform":
685
+ return `${colors.magenta}${type}${colors.reset}`;
686
+ case "Tools":
687
+ return `${colors.yellow}${type}${colors.reset}`;
688
+ case "Bug":
689
+ return `${colors.red}${type}${colors.reset}`;
690
+ case "Idea":
691
+ return `${colors.green}${type}${colors.reset}`;
692
+ default:
693
+ return type;
694
+ }
695
+ }
696
+
541
697
  // src/storage/filesystem.ts
542
698
  function isProjectMatch(code, filter) {
543
699
  if (!code || !filter) return false;
@@ -1268,6 +1424,7 @@ ${formattedList}`
1268
1424
  comments,
1269
1425
  projectDescriptor: effectiveLoc.project
1270
1426
  };
1427
+ normalizeTicketFields(ticketObj);
1271
1428
  ticketObj.sha1 = computeTicketHash(ticketObj);
1272
1429
  ticketObj.lastHash = ticketObj.sha1;
1273
1430
  tickets.push(ticketObj);
@@ -1319,6 +1476,7 @@ ${formattedList}`
1319
1476
  comments,
1320
1477
  projectDescriptor: locInfo.loc.project
1321
1478
  };
1479
+ normalizeTicketFields(ticket);
1322
1480
  ticket.sha1 = computeTicketHash(ticket);
1323
1481
  ticket.lastHash = ticket.sha1;
1324
1482
  return ticket;
@@ -1411,13 +1569,18 @@ ${formattedList}`
1411
1569
  fs3.mkdirSync(ticketDir, { recursive: true });
1412
1570
  const now = (/* @__PURE__ */ new Date()).toISOString();
1413
1571
  const effectiveType = input.type || input.category || "Feature";
1572
+ const cleanTitle = normalizeDashesAndMojibake(input.title).slice(0, 48).trim();
1573
+ const cleanEffort = normalizeDashesAndMojibake(input.estimatedEffort || input.effort || "2.0 - 4.0 hours");
1574
+ const cleanComplexity = normalizeDashesAndMojibake(input.complexity || "Medium");
1575
+ const cleanSummary = input.summary ? normalizeDashesAndMojibake(input.summary) : void 0;
1576
+ const cleanDetailRaw = input.detailMarkdown || input.detail ? normalizeDashesAndMojibake(input.detailMarkdown || input.detail) : void 0;
1414
1577
  const meta = {
1415
1578
  id: nextId,
1416
- title: input.title.slice(0, 48).trim(),
1579
+ title: cleanTitle,
1417
1580
  type: effectiveType,
1418
1581
  category: effectiveType,
1419
- complexity: input.complexity || "Medium",
1420
- estimatedEffort: input.estimatedEffort || input.effort || "2.0 - 4.0 hours",
1582
+ complexity: cleanComplexity,
1583
+ estimatedEffort: cleanEffort,
1421
1584
  submittedBy: input.submittedBy || "Developer",
1422
1585
  timestamp: now,
1423
1586
  createdAt: now,
@@ -1428,7 +1591,7 @@ ${formattedList}`
1428
1591
  projectId: targetLoc.project.id,
1429
1592
  project: targetLoc.project.code
1430
1593
  };
1431
- const rawDetail = (input.detailMarkdown || input.detail)?.trim();
1594
+ const rawDetail = cleanDetailRaw?.trim();
1432
1595
  let detailMd;
1433
1596
  if (rawDetail) {
1434
1597
  if (/^#\s+[^\n]+/m.test(rawDetail)) {
@@ -1527,9 +1690,13 @@ ${input.summary || "Summary to be defined."}
1527
1690
  const isNowCompleted = updates.status === "Completed";
1528
1691
  const completedAt = isNowCompleted ? existing.meta.completedAt || now : updates.status && updates.status !== "Completed" ? void 0 : existing.meta.completedAt;
1529
1692
  const revision = (existing.meta.revision || 1) + 1;
1693
+ const sanitizedUpdates = { ...updates };
1694
+ if (sanitizedUpdates.title) sanitizedUpdates.title = normalizeDashesAndMojibake(sanitizedUpdates.title);
1695
+ if (sanitizedUpdates.estimatedEffort) sanitizedUpdates.estimatedEffort = normalizeDashesAndMojibake(sanitizedUpdates.estimatedEffort);
1696
+ if (sanitizedUpdates.complexity) sanitizedUpdates.complexity = normalizeDashesAndMojibake(sanitizedUpdates.complexity);
1530
1697
  const updatedMeta = {
1531
1698
  ...existing.meta,
1532
- ...updates,
1699
+ ...sanitizedUpdates,
1533
1700
  id: locInfo.id,
1534
1701
  project: locInfo.loc.project.code,
1535
1702
  updatedAt: now,
@@ -1565,7 +1732,7 @@ ${input.summary || "Summary to be defined."}
1565
1732
  if (!locInfo) return null;
1566
1733
  const planPath = path3.join(locInfo.ticketDir, "implementation_plan.md");
1567
1734
  if (!fs3.existsSync(planPath)) return null;
1568
- return fs3.readFileSync(planPath, "utf-8");
1735
+ return normalizeDashesAndMojibake(fs3.readFileSync(planPath, "utf-8"));
1569
1736
  }
1570
1737
  async savePlan(id, planMarkdown, lastHash) {
1571
1738
  const ticket = await this.getTicket(id);
@@ -1583,7 +1750,7 @@ ${input.summary || "Summary to be defined."}
1583
1750
  throw new Error(`Ticket #${id} could not be located on disk`);
1584
1751
  }
1585
1752
  const planPath = path3.join(locInfo.ticketDir, "implementation_plan.md");
1586
- writeSafeFile(planPath, planMarkdown);
1753
+ writeSafeFile(planPath, normalizeDashesAndMojibake(planMarkdown));
1587
1754
  const metaPath = path3.join(locInfo.ticketDir, "meta.json");
1588
1755
  if (fs3.existsSync(metaPath)) {
1589
1756
  try {
@@ -1615,8 +1782,8 @@ ${input.summary || "Summary to be defined."}
1615
1782
  const newComment = {
1616
1783
  id: `${locInfo.id}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
1617
1784
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1618
- author: comment.author || "User",
1619
- text: comment.text.trim()
1785
+ author: normalizeDashesAndMojibake(comment.author || "User"),
1786
+ text: normalizeDashesAndMojibake(comment.text.trim())
1620
1787
  };
1621
1788
  comments.push(newComment);
1622
1789
  writeSafeFile(commentsPath, JSON.stringify(comments, null, 2) + "\n");
@@ -1727,131 +1894,6 @@ var SecurityFilter = class {
1727
1894
  }
1728
1895
  };
1729
1896
 
1730
- // src/utils/formatter.ts
1731
- var isColorSupported = !process.env.NO_COLOR && (process.stdout.isTTY || process.env.FORCE_COLOR);
1732
- var colors = {
1733
- reset: isColorSupported ? "\x1B[0m" : "",
1734
- bold: isColorSupported ? "\x1B[1m" : "",
1735
- dim: isColorSupported ? "\x1B[2m" : "",
1736
- cyan: isColorSupported ? "\x1B[36m" : "",
1737
- green: isColorSupported ? "\x1B[32m" : "",
1738
- yellow: isColorSupported ? "\x1B[33m" : "",
1739
- magenta: isColorSupported ? "\x1B[35m" : "",
1740
- red: isColorSupported ? "\x1B[31m" : "",
1741
- blue: isColorSupported ? "\x1B[34m" : ""
1742
- };
1743
- function formatTicketListTable(tickets) {
1744
- if (tickets.length === 0) {
1745
- return `${colors.dim}No tickets found matching criteria.${colors.reset}`;
1746
- }
1747
- const rows = tickets.map((t) => {
1748
- const id = t.projectDescriptor?.code ? `${t.projectDescriptor.code}-${t.meta.id}` : `#${t.meta.id}`;
1749
- const project = t.projectDescriptor?.code || "CORE";
1750
- const type = t.meta.type || t.meta.category || "Feature";
1751
- const status = t.meta.status;
1752
- const title = t.meta.title;
1753
- return { id, project, type, status, title };
1754
- });
1755
- const idWidth = Math.max(4, ...rows.map((r) => r.id.length));
1756
- const projWidth = Math.max(7, ...rows.map((r) => r.project.length));
1757
- const typeWidth = Math.max(8, ...rows.map((r) => r.type.length));
1758
- const statusWidth = Math.max(14, ...rows.map((r) => r.status.length));
1759
- const header = `${colors.bold}${pad("ID", idWidth)} ${pad("Project", projWidth)} ${pad("Type", typeWidth)} ${pad("Status", statusWidth)} Title${colors.reset}`;
1760
- const divider = `${colors.dim}${"-".repeat(idWidth)} ${"-".repeat(projWidth)} ${"-".repeat(typeWidth)} ${"-".repeat(statusWidth)} ${"-".repeat(40)}${colors.reset}`;
1761
- const formattedRows = rows.map((r) => {
1762
- const statusColored = colorStatus(r.status);
1763
- const typeColored = colorType(r.type);
1764
- const projColored = `${colors.magenta}${r.project}${colors.reset}`;
1765
- return `${colors.bold}${pad(r.id, idWidth)}${colors.reset} ${pad(projColored, projWidth + (isColorSupported ? colors.magenta.length + colors.reset.length : 0))} ${pad(typeColored, typeWidth + (isColorSupported ? 9 : 0))} ${pad(statusColored, statusWidth + (isColorSupported ? 9 : 0))} ${r.title}`;
1766
- });
1767
- return [header, divider, ...formattedRows].join("\n");
1768
- }
1769
- function formatTicketDetail(ticket) {
1770
- const { meta, detail, comments, planMarkdown, projectDescriptor } = ticket;
1771
- const lines = [];
1772
- lines.push(`${colors.bold}${colors.cyan}Ticket #${meta.project ? `${meta.project}-${meta.id}` : meta.id}: ${meta.title}${colors.reset}`);
1773
- lines.push(`${colors.dim}${"=".repeat(60)}${colors.reset}`);
1774
- lines.push(`${colors.bold}Project:${colors.reset} ${projectDescriptor ? `${projectDescriptor.code} - ${projectDescriptor.name}` : "Default"}`);
1775
- lines.push(`${colors.bold}Type:${colors.reset} ${colorType(meta.type || meta.category || "Feature")}`);
1776
- lines.push(`${colors.bold}Status:${colors.reset} ${colorStatus(meta.status)}`);
1777
- lines.push(`${colors.bold}Complexity:${colors.reset} ${meta.complexity || "Medium"}`);
1778
- lines.push(`${colors.bold}Effort:${colors.reset} ${meta.estimatedEffort || "N/A"}`);
1779
- lines.push(`${colors.bold}Submitted By:${colors.reset} ${meta.submittedBy || "Unknown"}`);
1780
- if (meta.featureFlag) {
1781
- lines.push(`${colors.bold}Feature Flag:${colors.reset} ${colors.yellow}${meta.featureFlag}${colors.reset}`);
1782
- }
1783
- if (detail?.summary) {
1784
- lines.push("");
1785
- lines.push(`${colors.bold}Summary:${colors.reset}`);
1786
- lines.push(detail.summary);
1787
- }
1788
- if (detail?.breakdown && detail.breakdown.length > 0) {
1789
- lines.push("");
1790
- lines.push(`${colors.bold}Feature Breakdown:${colors.reset}`);
1791
- for (const b of detail.breakdown) {
1792
- lines.push(` \u2022 ${b}`);
1793
- }
1794
- }
1795
- if (detail?.technicalDetails && detail.technicalDetails.length > 0) {
1796
- lines.push("");
1797
- lines.push(`${colors.bold}Technical Details:${colors.reset}`);
1798
- for (const t of detail.technicalDetails) {
1799
- lines.push(` \u2022 ${t}`);
1800
- }
1801
- }
1802
- if (detail?.openQuestions && detail.openQuestions.length > 0) {
1803
- lines.push("");
1804
- lines.push(`${colors.bold}Open Decisions & Questions:${colors.reset}`);
1805
- for (const q of detail.openQuestions) {
1806
- lines.push(` \u2022 ${q}`);
1807
- }
1808
- }
1809
- if (planMarkdown) {
1810
- lines.push("");
1811
- lines.push(`${colors.bold}Implementation Plan:${colors.reset}`);
1812
- lines.push(planMarkdown.trim());
1813
- }
1814
- if (comments && comments.length > 0) {
1815
- lines.push("");
1816
- lines.push(`${colors.bold}Comments (${comments.length}):${colors.reset}`);
1817
- for (const c of comments) {
1818
- lines.push(` ${colors.dim}[${c.timestamp.slice(0, 10)}]${colors.reset} ${colors.bold}${c.author}:${colors.reset} ${c.text}`);
1819
- }
1820
- }
1821
- return lines.join("\n");
1822
- }
1823
- function pad(str, width) {
1824
- return str.padEnd(width, " ");
1825
- }
1826
- function colorStatus(status) {
1827
- switch (status) {
1828
- case "Completed":
1829
- return `${colors.green}${status}${colors.reset}`;
1830
- case "In Development":
1831
- return `${colors.green}${status}${colors.reset}`;
1832
- case "Rejected":
1833
- return `${colors.red}${status}${colors.reset}`;
1834
- default:
1835
- return `${colors.cyan}${status}${colors.reset}`;
1836
- }
1837
- }
1838
- function colorType(type) {
1839
- switch (type) {
1840
- case "Feature":
1841
- return `${colors.blue}${type}${colors.reset}`;
1842
- case "Platform":
1843
- return `${colors.magenta}${type}${colors.reset}`;
1844
- case "Tools":
1845
- return `${colors.yellow}${type}${colors.reset}`;
1846
- case "Bug":
1847
- return `${colors.red}${type}${colors.reset}`;
1848
- case "Idea":
1849
- return `${colors.green}${type}${colors.reset}`;
1850
- default:
1851
- return type;
1852
- }
1853
- }
1854
-
1855
1897
  // src/mcp/server.ts
1856
1898
  import readline from "node:readline";
1857
1899
  var EsedreMcpServer = class {
@@ -4129,6 +4171,18 @@ function printDuplicateProjectWarnings(storage, isJson) {
4129
4171
  }
4130
4172
  }
4131
4173
  async function main() {
4174
+ if (process.stdout && typeof process.stdout.setDefaultEncoding === "function") {
4175
+ try {
4176
+ process.stdout.setDefaultEncoding("utf-8");
4177
+ } catch {
4178
+ }
4179
+ }
4180
+ if (process.stderr && typeof process.stderr.setDefaultEncoding === "function") {
4181
+ try {
4182
+ process.stderr.setDefaultEncoding("utf-8");
4183
+ } catch {
4184
+ }
4185
+ }
4132
4186
  const rawArgs = process.argv.slice(2);
4133
4187
  const { command, positionals, flags } = parseArgs(rawArgs);
4134
4188
  try {
@@ -4704,12 +4758,12 @@ ${colors.yellow}\u26A0\uFE0F Warning: Daemon is running v${status.version}, but
4704
4758
  if (isJson) {
4705
4759
  console.log(JSON.stringify(tickets.map((t) => ({
4706
4760
  id: t.meta.id,
4707
- title: t.meta.title,
4761
+ title: normalizeDashesAndMojibake(t.meta.title),
4708
4762
  type: t.meta.type || t.meta.category,
4709
4763
  category: t.meta.type || t.meta.category,
4710
4764
  status: t.meta.status,
4711
- complexity: t.meta.complexity,
4712
- effort: t.meta.estimatedEffort,
4765
+ complexity: normalizeDashesAndMojibake(t.meta.complexity),
4766
+ effort: normalizeDashesAndMojibake(t.meta.estimatedEffort),
4713
4767
  project: t.projectDescriptor?.code || t.meta.project || "UNASSIGNED",
4714
4768
  sha1: t.sha1 || t.meta.sha1
4715
4769
  })), null, 2));
@@ -4806,11 +4860,12 @@ ${colors.dim}Total: ${tickets.length} tickets${colors.reset}`);
4806
4860
  return;
4807
4861
  }
4808
4862
  case "create": {
4809
- const title = flags["title"];
4810
- if (!title) {
4863
+ const rawTitle = flags["title"];
4864
+ if (!rawTitle) {
4811
4865
  console.error(`${colors.red}Error: --title is required${colors.reset}`);
4812
4866
  process.exit(1);
4813
4867
  }
4868
+ const title = normalizeDashesAndMojibake(rawTitle);
4814
4869
  const type = flags["type"] || flags["category"] || "Feature";
4815
4870
  const category = type;
4816
4871
  const projectCode = flags["project"] || discovered.config?.projectCode;
@@ -4819,9 +4874,9 @@ ${colors.dim}Total: ${tickets.length} tickets${colors.reset}`);
4819
4874
  console.error(`${colors.dim}Tip: Run 'ese init' to initialize this repository.${colors.reset}`);
4820
4875
  process.exit(1);
4821
4876
  }
4822
- const complexity = flags["complexity"] || "Medium";
4823
- const estimatedEffort = flags["effort"] || "2.0 - 4.0 hours";
4824
- const summary = flags["summary"] || void 0;
4877
+ const complexity = normalizeDashesAndMojibake(flags["complexity"] || "Medium");
4878
+ const estimatedEffort = normalizeDashesAndMojibake(flags["effort"] || "2.0 - 4.0 hours");
4879
+ const summary = flags["summary"] ? normalizeDashesAndMojibake(flags["summary"]) : void 0;
4825
4880
  const submittedBy = flags["author"] || "Developer";
4826
4881
  const val = validateProjectCode(projectCode);
4827
4882
  if (!val.valid) {
@@ -4871,9 +4926,9 @@ ${colors.dim}Total: ${tickets.length} tickets${colors.reset}`);
4871
4926
  const projectFlag = flags["project"];
4872
4927
  const lookupKey = projectFlag && /^\d+$/.test(idStr) ? `${projectFlag}-${idStr}` : idStr;
4873
4928
  const status = flags["status"];
4874
- const title = flags["title"];
4875
- const complexity = flags["complexity"];
4876
- const effort = flags["effort"];
4929
+ const title = flags["title"] ? normalizeDashesAndMojibake(flags["title"]) : void 0;
4930
+ const complexity = flags["complexity"] ? normalizeDashesAndMojibake(flags["complexity"]) : void 0;
4931
+ const effort = flags["effort"] ? normalizeDashesAndMojibake(flags["effort"]) : void 0;
4877
4932
  const inDev = flags["in-dev"] !== void 0 ? Boolean(flags["in-dev"]) : void 0;
4878
4933
  const flag = flags["flag"];
4879
4934
  const isForce = Boolean(flags["force"]);