devdaily-ai 0.6.0 → 0.6.1

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,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.1] - 2026-02-15
9
+
10
+ ### Fixed
11
+
12
+ - **Slack webhook formatting** — Standup messages sent to Slack via `--send` / `--slack` now correctly render bold, italic, and other formatting. Previously, raw Markdown syntax (e.g. `**bold**`) was sent directly into Slack's `mrkdwn` blocks, which don't support double-asterisk bold. The notification pipeline now converts Markdown → Slack mrkdwn format using the existing `formatOutput()` formatter before sending, so `**text**` becomes `*text*`, `*italic*` becomes `_italic_`, headers become bold lines, and links use Slack's `<url|text>` syntax.
13
+
8
14
  ## [0.5.0] - 2025-07-13
9
15
 
10
16
  ### Added
package/dist/index.js CHANGED
@@ -3386,6 +3386,147 @@ Requirements:
3386
3386
  }
3387
3387
  };
3388
3388
 
3389
+ // src/utils/formatter.ts
3390
+ function formatOutput(markdown, format = "markdown", meta) {
3391
+ switch (format) {
3392
+ case "plain":
3393
+ return { text: toPlain(markdown), raw: markdown, format };
3394
+ case "slack":
3395
+ return { text: toSlack(markdown), raw: markdown, format };
3396
+ case "json":
3397
+ return { text: toJSON(markdown, meta), raw: markdown, format };
3398
+ case "markdown":
3399
+ default:
3400
+ return { text: markdown, raw: markdown, format };
3401
+ }
3402
+ }
3403
+ function validateFormat(value, fallback = "markdown") {
3404
+ const valid = ["markdown", "slack", "plain", "json"];
3405
+ if (valid.includes(value)) {
3406
+ return value;
3407
+ }
3408
+ return fallback;
3409
+ }
3410
+ function toPlain(md) {
3411
+ let text = md;
3412
+ text = text.replace(/^[ \t]*([-*_]){3,}[ \t]*$/gm, "");
3413
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, (_match, heading) => {
3414
+ const cleaned = heading.trim();
3415
+ if (/[.:!?]$/.test(cleaned)) {
3416
+ return cleaned;
3417
+ }
3418
+ return `${cleaned}:`;
3419
+ });
3420
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
3421
+ text = text.replace(/__(.+?)__/g, "$1");
3422
+ text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "$1");
3423
+ text = text.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, "$1");
3424
+ text = text.replace(/~~(.+?)~~/g, "$1");
3425
+ text = text.replace(/`([^`]+)`/g, "$1");
3426
+ text = text.replace(/```[\s\S]*?\n([\s\S]*?)```/g, "$1");
3427
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
3428
+ text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "[Image: $1]");
3429
+ text = text.replace(/^[ \t]*[-*]\s+/gm, "\u2022 ");
3430
+ text = text.replace(/^>+\s?/gm, "");
3431
+ text = text.replace(/\n{3,}/g, "\n\n");
3432
+ return text.trim();
3433
+ }
3434
+ function toSlack(md) {
3435
+ let text = md;
3436
+ const PH_START = "\uE000";
3437
+ const PH_END = "\uE001";
3438
+ const codeBlocks = [];
3439
+ text = text.replace(/```\w*\n([\s\S]*?)```/g, (_match, code) => {
3440
+ const placeholder = `${PH_START}CB${codeBlocks.length}${PH_END}`;
3441
+ codeBlocks.push("```\n" + code + "```");
3442
+ return placeholder;
3443
+ });
3444
+ const inlineCodes = [];
3445
+ text = text.replace(/`([^`]+)`/g, (_match, code) => {
3446
+ const placeholder = `${PH_START}IC${inlineCodes.length}${PH_END}`;
3447
+ inlineCodes.push("`" + code + "`");
3448
+ return placeholder;
3449
+ });
3450
+ text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "<$2|$1>");
3451
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
3452
+ text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "_$1_");
3453
+ text = text.replace(/\*\*(.+?)\*\*/g, "*$1*");
3454
+ text = text.replace(/(?<!\uE000)__(.+?)__(?!\uE001)/g, "*$1*");
3455
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
3456
+ text = text.replace(/~~(.+?)~~/g, "~$1~");
3457
+ text = text.replace(/^[ \t]*[-*]\s+/gm, "\u2022 ");
3458
+ text = text.replace(/^[ \t]*([-*_]){3,}[ \t]*$/gm, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
3459
+ for (let i = 0; i < codeBlocks.length; i++) {
3460
+ text = text.replace(`${PH_START}CB${i}${PH_END}`, codeBlocks[i]);
3461
+ }
3462
+ for (let i = 0; i < inlineCodes.length; i++) {
3463
+ text = text.replace(`${PH_START}IC${i}${PH_END}`, inlineCodes[i]);
3464
+ }
3465
+ text = text.replace(/\n{3,}/g, "\n\n");
3466
+ return text.trim();
3467
+ }
3468
+ function toJSON(md, meta) {
3469
+ const sections = parseSections(md);
3470
+ const output = {
3471
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
3472
+ };
3473
+ if (meta) {
3474
+ output.meta = { ...meta };
3475
+ }
3476
+ output.sections = sections;
3477
+ output.raw = md;
3478
+ return JSON.stringify(output, null, 2);
3479
+ }
3480
+ function parseSections(md) {
3481
+ const lines = md.split("\n");
3482
+ const sections = [];
3483
+ let current = null;
3484
+ let bodyLines = [];
3485
+ const flushCurrent = () => {
3486
+ if (current) {
3487
+ current.body = bodyLines.join("\n").trim();
3488
+ current.items = extractListItems(current.body);
3489
+ sections.push(current);
3490
+ }
3491
+ };
3492
+ for (const line of lines) {
3493
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
3494
+ if (headingMatch) {
3495
+ flushCurrent();
3496
+ current = {
3497
+ heading: headingMatch[2].trim(),
3498
+ level: headingMatch[1].length,
3499
+ items: [],
3500
+ body: ""
3501
+ };
3502
+ bodyLines = [];
3503
+ } else {
3504
+ bodyLines.push(line);
3505
+ }
3506
+ }
3507
+ flushCurrent();
3508
+ if (sections.length === 0 && md.trim().length > 0) {
3509
+ const body = md.trim();
3510
+ sections.push({
3511
+ heading: "Content",
3512
+ level: 0,
3513
+ items: extractListItems(body),
3514
+ body
3515
+ });
3516
+ }
3517
+ return sections;
3518
+ }
3519
+ function extractListItems(body) {
3520
+ const items = [];
3521
+ for (const line of body.split("\n")) {
3522
+ const match = line.match(/^[ \t]*(?:[-*]|\d+[.)]) \s*(.+)$/);
3523
+ if (match) {
3524
+ items.push(match[1].trim());
3525
+ }
3526
+ }
3527
+ return items;
3528
+ }
3529
+
3389
3530
  // src/core/notifications.ts
3390
3531
  function getTypeEmoji(type) {
3391
3532
  switch (type) {
@@ -3427,11 +3568,12 @@ function formatSlackMessage(message) {
3427
3568
  }
3428
3569
  });
3429
3570
  }
3571
+ const slackText = formatOutput(message.text, "slack").text;
3430
3572
  blocks.push({
3431
3573
  type: "section",
3432
3574
  text: {
3433
3575
  type: "mrkdwn",
3434
- text: message.text
3576
+ text: slackText
3435
3577
  }
3436
3578
  });
3437
3579
  if (message.ticketLinks && message.ticketLinks.length > 0) {
@@ -3988,147 +4130,6 @@ function getWeekEnd(weeksAgo = 0) {
3988
4130
  return end;
3989
4131
  }
3990
4132
 
3991
- // src/utils/formatter.ts
3992
- function formatOutput(markdown, format = "markdown", meta) {
3993
- switch (format) {
3994
- case "plain":
3995
- return { text: toPlain(markdown), raw: markdown, format };
3996
- case "slack":
3997
- return { text: toSlack(markdown), raw: markdown, format };
3998
- case "json":
3999
- return { text: toJSON(markdown, meta), raw: markdown, format };
4000
- case "markdown":
4001
- default:
4002
- return { text: markdown, raw: markdown, format };
4003
- }
4004
- }
4005
- function validateFormat(value, fallback = "markdown") {
4006
- const valid = ["markdown", "slack", "plain", "json"];
4007
- if (valid.includes(value)) {
4008
- return value;
4009
- }
4010
- return fallback;
4011
- }
4012
- function toPlain(md) {
4013
- let text = md;
4014
- text = text.replace(/^[ \t]*([-*_]){3,}[ \t]*$/gm, "");
4015
- text = text.replace(/^#{1,6}\s+(.+)$/gm, (_match, heading) => {
4016
- const cleaned = heading.trim();
4017
- if (/[.:!?]$/.test(cleaned)) {
4018
- return cleaned;
4019
- }
4020
- return `${cleaned}:`;
4021
- });
4022
- text = text.replace(/\*\*(.+?)\*\*/g, "$1");
4023
- text = text.replace(/__(.+?)__/g, "$1");
4024
- text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "$1");
4025
- text = text.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, "$1");
4026
- text = text.replace(/~~(.+?)~~/g, "$1");
4027
- text = text.replace(/`([^`]+)`/g, "$1");
4028
- text = text.replace(/```[\s\S]*?\n([\s\S]*?)```/g, "$1");
4029
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
4030
- text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "[Image: $1]");
4031
- text = text.replace(/^[ \t]*[-*]\s+/gm, "\u2022 ");
4032
- text = text.replace(/^>+\s?/gm, "");
4033
- text = text.replace(/\n{3,}/g, "\n\n");
4034
- return text.trim();
4035
- }
4036
- function toSlack(md) {
4037
- let text = md;
4038
- const PH_START = "\uE000";
4039
- const PH_END = "\uE001";
4040
- const codeBlocks = [];
4041
- text = text.replace(/```\w*\n([\s\S]*?)```/g, (_match, code) => {
4042
- const placeholder = `${PH_START}CB${codeBlocks.length}${PH_END}`;
4043
- codeBlocks.push("```\n" + code + "```");
4044
- return placeholder;
4045
- });
4046
- const inlineCodes = [];
4047
- text = text.replace(/`([^`]+)`/g, (_match, code) => {
4048
- const placeholder = `${PH_START}IC${inlineCodes.length}${PH_END}`;
4049
- inlineCodes.push("`" + code + "`");
4050
- return placeholder;
4051
- });
4052
- text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "<$2|$1>");
4053
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
4054
- text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "_$1_");
4055
- text = text.replace(/\*\*(.+?)\*\*/g, "*$1*");
4056
- text = text.replace(/(?<!\uE000)__(.+?)__(?!\uE001)/g, "*$1*");
4057
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
4058
- text = text.replace(/~~(.+?)~~/g, "~$1~");
4059
- text = text.replace(/^[ \t]*[-*]\s+/gm, "\u2022 ");
4060
- text = text.replace(/^[ \t]*([-*_]){3,}[ \t]*$/gm, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
4061
- for (let i = 0; i < codeBlocks.length; i++) {
4062
- text = text.replace(`${PH_START}CB${i}${PH_END}`, codeBlocks[i]);
4063
- }
4064
- for (let i = 0; i < inlineCodes.length; i++) {
4065
- text = text.replace(`${PH_START}IC${i}${PH_END}`, inlineCodes[i]);
4066
- }
4067
- text = text.replace(/\n{3,}/g, "\n\n");
4068
- return text.trim();
4069
- }
4070
- function toJSON(md, meta) {
4071
- const sections = parseSections(md);
4072
- const output = {
4073
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4074
- };
4075
- if (meta) {
4076
- output.meta = { ...meta };
4077
- }
4078
- output.sections = sections;
4079
- output.raw = md;
4080
- return JSON.stringify(output, null, 2);
4081
- }
4082
- function parseSections(md) {
4083
- const lines = md.split("\n");
4084
- const sections = [];
4085
- let current = null;
4086
- let bodyLines = [];
4087
- const flushCurrent = () => {
4088
- if (current) {
4089
- current.body = bodyLines.join("\n").trim();
4090
- current.items = extractListItems(current.body);
4091
- sections.push(current);
4092
- }
4093
- };
4094
- for (const line of lines) {
4095
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
4096
- if (headingMatch) {
4097
- flushCurrent();
4098
- current = {
4099
- heading: headingMatch[2].trim(),
4100
- level: headingMatch[1].length,
4101
- items: [],
4102
- body: ""
4103
- };
4104
- bodyLines = [];
4105
- } else {
4106
- bodyLines.push(line);
4107
- }
4108
- }
4109
- flushCurrent();
4110
- if (sections.length === 0 && md.trim().length > 0) {
4111
- const body = md.trim();
4112
- sections.push({
4113
- heading: "Content",
4114
- level: 0,
4115
- items: extractListItems(body),
4116
- body
4117
- });
4118
- }
4119
- return sections;
4120
- }
4121
- function extractListItems(body) {
4122
- const items = [];
4123
- for (const line of body.split("\n")) {
4124
- const match = line.match(/^[ \t]*(?:[-*]|\d+[.)]) \s*(.+)$/);
4125
- if (match) {
4126
- items.push(match[1].trim());
4127
- }
4128
- }
4129
- return items;
4130
- }
4131
-
4132
4133
  // src/core/work-journal.ts
4133
4134
  import {
4134
4135
  existsSync as existsSync2,