taskin 4.0.0 → 4.1.0

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/index.js CHANGED
@@ -15,12 +15,12 @@ var __export = (target, all) => {
15
15
  __defProp(target, name, { get: all[name], enumerable: true });
16
16
  };
17
17
 
18
- // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_supports-color@7.2.0_tsx@4.21.0_typescript@6.0.3_yaml@2.9.0/node_modules/tsup/assets/esm_shims.js
18
+ // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.26_supports-color@7.2.0_tsx@4.23.13_typescript@6.0.3_yaml@2.9.0/node_modules/tsup/assets/esm_shims.js
19
19
  import path from "path";
20
20
  import { fileURLToPath } from "url";
21
21
  var getFilename, getDirname, __dirname;
22
22
  var init_esm_shims = __esm({
23
- "../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_supports-color@7.2.0_tsx@4.21.0_typescript@6.0.3_yaml@2.9.0/node_modules/tsup/assets/esm_shims.js"() {
23
+ "../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.26_supports-color@7.2.0_tsx@4.23.13_typescript@6.0.3_yaml@2.9.0/node_modules/tsup/assets/esm_shims.js"() {
24
24
  "use strict";
25
25
  getFilename = () => fileURLToPath(import.meta.url);
26
26
  getDirname = () => path.dirname(getFilename());
@@ -449,6 +449,230 @@ var init_inline_metadata = __esm({
449
449
  }
450
450
  });
451
451
 
452
+ // ../file-system-task-provider/src/metadata-style/metadata-block.ts
453
+ function headerEndIndex(lines) {
454
+ const index = lines.findIndex((line) => SECTION_HEADING.test(line));
455
+ return index === -1 ? lines.length : index;
456
+ }
457
+ function readMetadataBlock(content) {
458
+ const lines = splitLines(content);
459
+ const headerEnd = headerEndIndex(lines);
460
+ let start = -1;
461
+ for (let index = 0; index < headerEnd; index++) {
462
+ const line = lines[index] ?? "";
463
+ if (isHeading(line) || line.trim() === "") continue;
464
+ if (METADATA_LINE.test(line)) {
465
+ start = index;
466
+ }
467
+ break;
468
+ }
469
+ if (start === -1) return void 0;
470
+ let end = start;
471
+ while (end < headerEnd) {
472
+ const line = lines[end] ?? "";
473
+ if (isHeading(line) || !METADATA_LINE.test(line)) break;
474
+ end++;
475
+ }
476
+ const blockLines = lines.slice(start, end);
477
+ const fields = [];
478
+ for (const line of blockLines) {
479
+ const match = line.match(METADATA_LINE);
480
+ if (!match?.[1]) continue;
481
+ fields.push({ label: match[1], value: match[2] ?? "" });
482
+ }
483
+ return { fields, lines: blockLines, start, end };
484
+ }
485
+ function replaceMetadataBlock(content, newLines) {
486
+ const lines = splitLines(content);
487
+ const block = readMetadataBlock(content);
488
+ if (block) {
489
+ lines.splice(block.start, block.end - block.start, ...newLines);
490
+ return lines.join("\n");
491
+ }
492
+ if (newLines.length === 0) return content;
493
+ const titleIndex = lines.findIndex((line) => line.startsWith("# "));
494
+ if (titleIndex === -1) {
495
+ return [...newLines, "", ...lines].join("\n");
496
+ }
497
+ lines.splice(titleIndex + 1, 0, "", ...newLines);
498
+ return lines.join("\n");
499
+ }
500
+ function removeMetadataBlock(content) {
501
+ const block = readMetadataBlock(content);
502
+ if (!block) return content;
503
+ const lines = splitLines(content);
504
+ lines.splice(block.start, block.end - block.start);
505
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n");
506
+ }
507
+ var METADATA_LINE, SECTION_HEADING, isHeading, splitLines, sameLabel;
508
+ var init_metadata_block = __esm({
509
+ "../file-system-task-provider/src/metadata-style/metadata-block.ts"() {
510
+ "use strict";
511
+ init_esm_shims();
512
+ METADATA_LINE = /^(?:-[ \t]+)?([^:\n]{1,40}?)[ \t]*:[ \t]*(.*?)[ \t]*\\?[ \t]*$/;
513
+ SECTION_HEADING = /^#{2,}\s/;
514
+ isHeading = (line) => line.startsWith("#");
515
+ splitLines = (content) => content.split(/\r?\n/);
516
+ sameLabel = (a, b) => a.toLowerCase() === b.toLowerCase();
517
+ }
518
+ });
519
+
520
+ // ../file-system-task-provider/src/metadata-style/metadata-style.base.ts
521
+ function defineMetadataStyle(definition) {
522
+ const format = (fields) => definition.formatLines(fields).join("\n");
523
+ return {
524
+ id: definition.id,
525
+ matches: definition.matches,
526
+ format,
527
+ read(content, label) {
528
+ const block = readMetadataBlock(content);
529
+ return block?.fields.find((field) => sameLabel(field.label, label))?.value;
530
+ },
531
+ write(content, label, value) {
532
+ const block = readMetadataBlock(content);
533
+ const fields = [...block?.fields ?? []];
534
+ const index = fields.findIndex((field) => sameLabel(field.label, label));
535
+ if (value === void 0) {
536
+ if (index === -1) return content;
537
+ fields.splice(index, 1);
538
+ return fields.length === 0 ? removeMetadataBlock(content) : replaceMetadataBlock(content, format(fields).split("\n"));
539
+ }
540
+ if (index === -1) {
541
+ fields.push({ label, value });
542
+ } else {
543
+ fields[index] = { label: fields[index]?.label ?? label, value };
544
+ }
545
+ return replaceMetadataBlock(content, format(fields).split("\n"));
546
+ }
547
+ };
548
+ }
549
+ var init_metadata_style_base = __esm({
550
+ "../file-system-task-provider/src/metadata-style/metadata-style.base.ts"() {
551
+ "use strict";
552
+ init_esm_shims();
553
+ init_metadata_block();
554
+ }
555
+ });
556
+
557
+ // ../file-system-task-provider/src/metadata-style/metadata-style.hard-break.ts
558
+ var hardBreakMetadataStyle;
559
+ var init_metadata_style_hard_break = __esm({
560
+ "../file-system-task-provider/src/metadata-style/metadata-style.hard-break.ts"() {
561
+ "use strict";
562
+ init_esm_shims();
563
+ init_inline_metadata();
564
+ init_metadata_style_base();
565
+ hardBreakMetadataStyle = defineMetadataStyle({
566
+ id: "hard-break",
567
+ // Uma linha com a barra basta: nenhum dos outros dois estilos a produz.
568
+ matches: (blockLines) => blockLines.some((line) => line.trimEnd().endsWith(HARD_BREAK)),
569
+ formatLines: (fields) => fields.map((field, index) => {
570
+ const isLast = index === fields.length - 1;
571
+ return `${field.label}: ${field.value}${isLast ? "" : HARD_BREAK}`;
572
+ })
573
+ });
574
+ }
575
+ });
576
+
577
+ // ../file-system-task-provider/src/metadata-style/metadata-style.list.ts
578
+ var LIST_MARKER, listMetadataStyle;
579
+ var init_metadata_style_list = __esm({
580
+ "../file-system-task-provider/src/metadata-style/metadata-style.list.ts"() {
581
+ "use strict";
582
+ init_esm_shims();
583
+ init_metadata_style_base();
584
+ LIST_MARKER = "- ";
585
+ listMetadataStyle = defineMetadataStyle({
586
+ id: "list",
587
+ // Um bloco so esta neste estilo se **todas** as linhas forem itens: uma so
588
+ // com `- ` no meio de linhas soltas e um arquivo misto, nao um bloco `list`.
589
+ matches: (blockLines) => blockLines.length > 0 && blockLines.every((line) => line.trimStart().startsWith(LIST_MARKER)),
590
+ formatLines: (fields) => fields.map((field) => `${LIST_MARKER}${field.label}: ${field.value}`)
591
+ });
592
+ }
593
+ });
594
+
595
+ // ../file-system-task-provider/src/metadata-style/metadata-style.plain.ts
596
+ var plainMetadataStyle;
597
+ var init_metadata_style_plain = __esm({
598
+ "../file-system-task-provider/src/metadata-style/metadata-style.plain.ts"() {
599
+ "use strict";
600
+ init_esm_shims();
601
+ init_metadata_style_base();
602
+ plainMetadataStyle = defineMetadataStyle({
603
+ id: "plain",
604
+ matches: () => true,
605
+ formatLines: (fields) => fields.map((field) => `${field.label}: ${field.value}`)
606
+ });
607
+ }
608
+ });
609
+
610
+ // ../file-system-task-provider/src/metadata-style/metadata-style.ts
611
+ function isMetadataStyleId(value) {
612
+ return typeof value === "string" && METADATA_STYLE_IDS.includes(value);
613
+ }
614
+ function getMetadataStyle(id) {
615
+ return METADATA_STYLES[id];
616
+ }
617
+ function detectMetadataStyle(content) {
618
+ const block = readMetadataBlock(content);
619
+ if (!block || block.lines.length === 0) return void 0;
620
+ return DETECTION_ORDER.find((style) => style.matches(block.lines));
621
+ }
622
+ function resolveMetadataStyle(content, fallback = DEFAULT_METADATA_STYLE_ID) {
623
+ return detectMetadataStyle(content) ?? getMetadataStyle(fallback);
624
+ }
625
+ function readMetadataField(content, ...labels) {
626
+ const block = readMetadataBlock(content);
627
+ if (!block) return void 0;
628
+ for (const label of labels) {
629
+ const field = block.fields.find((candidate) => sameLabel(candidate.label, label));
630
+ if (field) return field.value;
631
+ }
632
+ return void 0;
633
+ }
634
+ function writeMetadataField(content, label, value, fallback = DEFAULT_METADATA_STYLE_ID) {
635
+ return resolveMetadataStyle(content, fallback).write(content, label, value);
636
+ }
637
+ function convertMetadataStyle(content, target) {
638
+ const block = readMetadataBlock(content);
639
+ if (!block || block.fields.length === 0) return content;
640
+ const lines = getMetadataStyle(target).format(block.fields).split("\n");
641
+ if (lines.join("\n") === block.lines.join("\n")) return content;
642
+ return replaceMetadataBlock(content, lines);
643
+ }
644
+ var DEFAULT_METADATA_STYLE_ID, DETECTION_ORDER, METADATA_STYLES, METADATA_STYLE_IDS;
645
+ var init_metadata_style = __esm({
646
+ "../file-system-task-provider/src/metadata-style/metadata-style.ts"() {
647
+ "use strict";
648
+ init_esm_shims();
649
+ init_metadata_block();
650
+ init_metadata_style_hard_break();
651
+ init_metadata_style_list();
652
+ init_metadata_style_plain();
653
+ DEFAULT_METADATA_STYLE_ID = "list";
654
+ DETECTION_ORDER = [hardBreakMetadataStyle, listMetadataStyle, plainMetadataStyle];
655
+ METADATA_STYLES = {
656
+ "hard-break": hardBreakMetadataStyle,
657
+ list: listMetadataStyle,
658
+ plain: plainMetadataStyle
659
+ };
660
+ METADATA_STYLE_IDS = ["list", "hard-break", "plain"];
661
+ }
662
+ });
663
+
664
+ // ../file-system-task-provider/src/metadata-style/index.ts
665
+ var init_metadata_style2 = __esm({
666
+ "../file-system-task-provider/src/metadata-style/index.ts"() {
667
+ "use strict";
668
+ init_esm_shims();
669
+ init_metadata_style_hard_break();
670
+ init_metadata_style();
671
+ init_metadata_style_list();
672
+ init_metadata_style_plain();
673
+ }
674
+ });
675
+
452
676
  // ../file-system-task-provider/src/file-system-metrics-adapter.ts
453
677
  import {
454
678
  TASK_STATUSES,
@@ -641,7 +865,7 @@ var init_file_system_metrics_adapter = __esm({
641
865
  "use strict";
642
866
  init_esm_shims();
643
867
  init_assignee_identity();
644
- init_inline_metadata();
868
+ init_metadata_style2();
645
869
  MILLISECONDS_PER_SECOND = 1e3;
646
870
  SECONDS_PER_MINUTE = 60;
647
871
  MINUTES_PER_HOUR = 60;
@@ -692,11 +916,7 @@ var init_file_system_metrics_adapter = __esm({
692
916
  const titleMatch = content.match(TASK_TITLE_PATTERNS.withDash) ?? content.match(TASK_TITLE_PATTERNS.withNumber);
693
917
  const title = titleMatch?.[1] ?? file.replace(/\.md$/, "");
694
918
  const contentWithoutCodeBlocks = removeCodeBlocks(content);
695
- const extract = (name) => {
696
- const rx = new RegExp(`^${name}:\\s*(.+)$`, "im");
697
- const captured = contentWithoutCodeBlocks.match(rx)?.[1];
698
- return captured === void 0 ? void 0 : stripHardBreak(captured);
699
- };
919
+ const extract = (name) => readMetadataField(contentWithoutCodeBlocks, name);
700
920
  const statusValue = extract("Status");
701
921
  const assigneeValue = extract("Assignee");
702
922
  const typeValue = extract("Type");
@@ -955,7 +1175,7 @@ __export(task_validator_exports, {
955
1175
  });
956
1176
  import { readFile, writeFile } from "fs/promises";
957
1177
  import { TASK_STATUSES as TASK_STATUSES2 } from "@opentask/taskin-types";
958
- async function fixTaskFile(filePath) {
1178
+ async function fixTaskFile(filePath, options = {}) {
959
1179
  try {
960
1180
  const content = await readFile(filePath, "utf-8");
961
1181
  const locale = detectLocale(content);
@@ -963,66 +1183,32 @@ async function fixTaskFile(filePath) {
963
1183
  const statusPattern = new RegExp(`##\\s*(?:Status|${i18n.status})\\s*\\n\\s*([^\\n\\r]+)`, "i");
964
1184
  const typePattern = new RegExp(`##\\s*(?:Type|${i18n.type})\\s*\\n\\s*([^\\n\\r]+)`, "i");
965
1185
  const assigneePattern = new RegExp(`##\\s*(?:Assignee|${i18n.assignee})\\s*\\n\\s*([^\\n\\r]+)`, "i");
966
- const hasSectionStatus = statusPattern.test(content);
967
- const hasSectionType = typePattern.test(content);
968
- const hasSectionAssignee = assigneePattern.test(content);
969
- const inlineStatusPattern = /^(Status|Tipo):\s*/i;
970
- const inlineTypePattern = /^(Type|Tipo):\s*/i;
971
- const inlineAssigneePattern = /^(Assignee|Responsável):\s*/i;
972
- const lines = content.split(/\r?\n/);
973
- const inlineStatusLine = lines.find((l) => inlineStatusPattern.test(l));
974
- const inlineTypeLine = lines.find((l) => inlineTypePattern.test(l));
975
- const inlineAssigneeLine = lines.find((l) => inlineAssigneePattern.test(l));
976
- const hasInlineStatus = !!inlineStatusLine;
977
- const hasInlineType = !!inlineTypeLine;
978
- const hasInlineAssignee = !!inlineAssigneeLine;
979
- const endsWithHardBreak = (line) => !!line && line.endsWith(HARD_BREAK);
980
- const needsSpaceFix = hasInlineStatus && !endsWithHardBreak(inlineStatusLine) || hasInlineType && !endsWithHardBreak(inlineTypeLine) || hasInlineAssignee && !endsWithHardBreak(inlineAssigneeLine);
981
- if (!hasSectionStatus && !hasSectionType && !hasSectionAssignee && !needsSpaceFix) {
982
- return false;
983
- }
1186
+ const statusMatch = content.match(statusPattern);
1187
+ const typeMatch = content.match(typePattern);
1188
+ const assigneeMatch = content.match(assigneePattern);
1189
+ const hasSectionMetadata = !!(statusMatch || typeMatch || assigneeMatch);
984
1190
  let newContent = content;
985
- if (hasSectionStatus || hasSectionType || hasSectionAssignee) {
986
- const statusMatch = content.match(statusPattern);
987
- const typeMatch = content.match(typePattern);
988
- const assigneeMatch = content.match(assigneePattern);
989
- if (statusMatch) {
990
- newContent = newContent.replace(statusPattern, "");
991
- }
992
- if (typeMatch) {
993
- newContent = newContent.replace(typePattern, "");
994
- }
995
- if (assigneeMatch) {
996
- newContent = newContent.replace(assigneePattern, "");
1191
+ if (hasSectionMetadata) {
1192
+ for (const pattern of [statusPattern, typePattern, assigneePattern]) {
1193
+ newContent = newContent.replace(pattern, "");
997
1194
  }
998
1195
  newContent = newContent.replace(/\n{3,}/g, "\n\n");
999
- const titleLineIdx = newContent.split("\n").findIndex((line) => line.trim().startsWith("# "));
1000
- if (titleLineIdx === -1) {
1196
+ if (!newContent.split("\n").some((line) => line.trim().startsWith("# "))) {
1001
1197
  return false;
1002
1198
  }
1003
- const contentLines = newContent.split("\n");
1004
- const beforeTitle = contentLines.slice(0, titleLineIdx + 1);
1005
- const afterTitle = contentLines.slice(titleLineIdx + 1);
1006
- const inlineMetadata = [];
1007
- if (statusMatch?.[1]) {
1008
- inlineMetadata.push(`Status: ${statusMatch[1].trim()}${HARD_BREAK}`);
1009
- }
1010
- if (typeMatch?.[1]) {
1011
- inlineMetadata.push(`Type: ${typeMatch[1].trim()}${HARD_BREAK}`);
1012
- }
1013
- if (assigneeMatch?.[1]) {
1014
- inlineMetadata.push(`Assignee: ${assigneeMatch[1].trim()}${HARD_BREAK}`);
1015
- }
1016
- newContent = [...beforeTitle, "", ...inlineMetadata, "", ...afterTitle].join("\n");
1017
1199
  }
1018
- if (needsSpaceFix) {
1019
- for (const key of ["Status|Tipo", "Type|Tipo", "Assignee|Respons\xE1vel"]) {
1020
- newContent = newContent.replace(
1021
- new RegExp(`^(${key}):[ \\t]*(.+?)(?:\\\\)?[ \\t]*$`, "im"),
1022
- (_match, label, value) => `${label}: ${value.trim()}${HARD_BREAK}`
1023
- );
1200
+ const style = options.convertTo ? getMetadataStyle(options.convertTo) : resolveMetadataStyle(newContent, options.metadataStyle ?? DEFAULT_METADATA_STYLE_ID);
1201
+ if (hasSectionMetadata) {
1202
+ const migrated = [
1203
+ ["Status", statusMatch?.[1]?.trim()],
1204
+ ["Type", typeMatch?.[1]?.trim()],
1205
+ ["Assignee", assigneeMatch?.[1]?.trim()]
1206
+ ];
1207
+ for (const [label, value] of migrated) {
1208
+ if (value) newContent = style.write(newContent, label, value);
1024
1209
  }
1025
1210
  }
1211
+ newContent = convertMetadataStyle(newContent, style.id);
1026
1212
  const finalContentRaw = `${newContent.replace(/\n{3,}/g, "\n\n").trim()}
1027
1213
  `;
1028
1214
  const originalContentRaw = `${content.replace(/\n{3,}/g, "\n\n").trim()}
@@ -1049,9 +1235,10 @@ async function validateTaskFile(filePath) {
1049
1235
  const locale = detectLocale(content);
1050
1236
  const i18n = getI18n(locale);
1051
1237
  const hasTitleSection = lines.some((line) => line.trim().startsWith("# "));
1052
- const inlineStatusPattern = new RegExp(`^(?:Status|${i18n.status}):\\s*.+$`, "im");
1053
1238
  const sectionStatusPattern = new RegExp(`##\\s*(?:Status|${i18n.status})`, "i");
1054
- const hasInlineStatus = inlineStatusPattern.test(content);
1239
+ const inlineStatus = readMetadataField(content, i18n.status, "Status");
1240
+ const isMetadataLine = (line) => new RegExp(`^(?:-\\s+)?(?:Status|${i18n.status})\\s*:`, "i").test(line.trim());
1241
+ const hasInlineStatus = inlineStatus !== void 0;
1055
1242
  const hasSectionStatus = sectionStatusPattern.test(content);
1056
1243
  const hasDescriptionSection = content.includes("## Description") || content.includes("## Descri\xE7\xE3o");
1057
1244
  if (!hasTitleSection) {
@@ -1083,10 +1270,9 @@ ${i18n.status}: <todo|in-progress|done>`
1083
1270
  ${i18n.status}: <todo|in-progress|done>`
1084
1271
  });
1085
1272
  } else {
1086
- const statusMatch = content.match(inlineStatusPattern);
1087
- const statusValue = statusMatch ? stripHardBreak(statusMatch[0].split(":")[1] ?? "").toLowerCase() : "";
1273
+ const statusValue = (inlineStatus ?? "").toLowerCase();
1088
1274
  if (!ACCEPTED_STATUSES.includes(statusValue)) {
1089
- const statusLineIdx = lines.findIndex((line) => inlineStatusPattern.test(line.trim()));
1275
+ const statusLineIdx = lines.findIndex(isMetadataLine);
1090
1276
  issues.push({
1091
1277
  file: filePath,
1092
1278
  line: statusLineIdx >= 0 ? statusLineIdx + 1 : void 0,
@@ -1150,7 +1336,7 @@ var init_task_validator = __esm({
1150
1336
  "use strict";
1151
1337
  init_esm_shims();
1152
1338
  init_i18n();
1153
- init_inline_metadata();
1339
+ init_metadata_style2();
1154
1340
  ACCEPTED_STATUSES = [...TASK_STATUSES2, "todo"];
1155
1341
  ACCEPTED_STATUSES_LABEL = ACCEPTED_STATUSES.join(", ");
1156
1342
  }
@@ -1442,16 +1628,8 @@ function parsePrioritizationFields(priorityMatch, groupMatch, groupNameMatch, di
1442
1628
  ...difficulty !== void 0 && !Number.isNaN(difficulty) && { difficulty }
1443
1629
  };
1444
1630
  }
1445
- function setInlineField(content, fieldName, value) {
1446
- const linePattern = new RegExp(`^${fieldName}:\\s*.+$\\n?`, "im");
1447
- if (value === void 0) {
1448
- return linePattern.test(content) ? content.replace(linePattern, "") : content;
1449
- }
1450
- if (new RegExp(`^${fieldName}:\\s*.+$`, "im").test(content)) {
1451
- return content.replace(new RegExp(`^${fieldName}:\\s*.+$`, "im"), `${fieldName}: ${value}${HARD_BREAK}`);
1452
- }
1453
- return content.replace(/(^#.*\n)/, `$1${fieldName}: ${value}${HARD_BREAK}
1454
- `);
1631
+ function setInlineField(content, fieldName, value, fallbackStyle) {
1632
+ return resolveMetadataStyle(content, fallbackStyle).write(content, fieldName, value);
1455
1633
  }
1456
1634
  var TITLE_PATTERN, FileSystemTaskProvider;
1457
1635
  var init_file_system_task_provider = __esm({
@@ -1461,20 +1639,52 @@ var init_file_system_task_provider = __esm({
1461
1639
  init_src();
1462
1640
  init_assignee_identity();
1463
1641
  init_i18n();
1464
- init_inline_metadata();
1642
+ init_metadata_style2();
1465
1643
  init_task_validator();
1466
1644
  init_user_registry();
1467
1645
  init_users_file_location();
1468
1646
  TITLE_PATTERN = /^#\s+(?:🧩\s+)?Task\s+\d+\s*[—-]\s*(.+)$/im;
1469
1647
  FileSystemTaskProvider = class {
1470
- constructor(tasksDirectory, userRegistry, locale = "en-US", logger) {
1648
+ constructor(tasksDirectory, userRegistry, locale = "en-US", logger, options = {}) {
1471
1649
  this.tasksDirectory = tasksDirectory;
1472
1650
  this.userRegistry = userRegistry;
1473
1651
  this.locale = locale;
1474
1652
  this.logger = logger ?? NullLogger;
1653
+ this.metadataStyle = options.metadataStyle ?? DEFAULT_METADATA_STYLE_ID;
1654
+ this.convertMetadataStyleTo = options.convertMetadataStyleTo;
1475
1655
  }
1476
1656
  locale;
1477
1657
  logger;
1658
+ metadataStyle;
1659
+ convertMetadataStyleTo;
1660
+ /**
1661
+ * Reads the metadata a task file carries, whatever style it is written in
1662
+ * and whichever of the two locales named the fields.
1663
+ */
1664
+ readInlineMetadata(content) {
1665
+ const i18n = getI18n(detectLocale(content));
1666
+ const read = (english, localized) => readMetadataField(content, localized, english);
1667
+ return {
1668
+ status: read("Status", i18n.status),
1669
+ type: read("Type", i18n.type),
1670
+ assignee: read("Assignee", i18n.assignee),
1671
+ priority: read("Priority", i18n.priority),
1672
+ group: read("Group", i18n.group),
1673
+ groupName: read("GroupName", i18n.groupName),
1674
+ difficulty: read("Difficulty", i18n.difficulty)
1675
+ };
1676
+ }
1677
+ /**
1678
+ * The label to write a field under: the one the file already uses, or the
1679
+ * English name.
1680
+ *
1681
+ * Without this, writing `Prioridade` into a file that already says
1682
+ * `Priority` appends a second line instead of updating the first.
1683
+ */
1684
+ labelFor(content, english, localized) {
1685
+ if (readMetadataField(content, localized) !== void 0) return localized;
1686
+ return english;
1687
+ }
1478
1688
  /**
1479
1689
  * A raiz do projeto: o diretório que contém `TASKS/` e `.taskin/`.
1480
1690
  *
@@ -1519,8 +1729,7 @@ var init_file_system_task_provider = __esm({
1519
1729
  */
1520
1730
  readAssigneeLine(content) {
1521
1731
  const i18n = getI18n(detectLocale(content));
1522
- const match = content.match(/^Assignee:\s*(.*)$/im) ?? content.match(new RegExp(`^${i18n.assignee}:\\s*(.*)$`, "im"));
1523
- return match?.[1] === void 0 ? void 0 : stripHardBreak(match[1]);
1732
+ return readMetadataField(content, "Assignee", i18n.assignee);
1524
1733
  }
1525
1734
  async pathExists(target) {
1526
1735
  try {
@@ -1539,25 +1748,15 @@ var init_file_system_task_provider = __esm({
1539
1748
  const filePath = path6.join(this.tasksDirectory, taskFile);
1540
1749
  const content = await fs4.readFile(filePath, "utf-8");
1541
1750
  const title = content.match(TITLE_PATTERN)?.[1]?.trim() ?? "Untitled";
1542
- const contentLocale = detectLocale(content);
1543
- const i18n = getI18n(contentLocale);
1544
- const extractInline = (name, localizedName) => {
1545
- const names = localizedName && localizedName !== name ? [localizedName, name] : [name];
1546
- for (const n of names) {
1547
- const escapedName = n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1548
- const rx = new RegExp(`^${escapedName}:\\s*(.+)$`, "im");
1549
- const captured = content.match(rx)?.[1];
1550
- if (captured !== void 0) return stripHardBreak(captured);
1551
- }
1552
- return null;
1553
- };
1554
- const statusMatch = extractInline("Status", i18n.status);
1555
- const typeMatch = extractInline("Type", i18n.type);
1556
- const assigneeMatch = extractInline("Assignee", i18n.assignee);
1557
- const priorityMatch = extractInline("Priority", i18n.priority);
1558
- const groupMatch = extractInline("Group", i18n.group);
1559
- const groupNameMatch = extractInline("GroupName", i18n.groupName);
1560
- const difficultyMatch = extractInline("Difficulty", i18n.difficulty);
1751
+ const {
1752
+ status: statusMatch,
1753
+ type: typeMatch,
1754
+ assignee: assigneeMatch,
1755
+ priority: priorityMatch,
1756
+ group: groupMatch,
1757
+ groupName: groupNameMatch,
1758
+ difficulty: difficultyMatch
1759
+ } = this.readInlineMetadata(content);
1561
1760
  let assignee;
1562
1761
  if (assigneeMatch) {
1563
1762
  const assigneeValue = assigneeMatch.trim();
@@ -1588,27 +1787,35 @@ var init_file_system_task_provider = __esm({
1588
1787
  const hasSectionMetadata = /##\s*(Status|Type|Assignee)/i.test(currentContent);
1589
1788
  if (hasSectionMetadata) {
1590
1789
  const { fixTaskFile: fixTaskFile2 } = await Promise.resolve().then(() => (init_task_validator(), task_validator_exports));
1591
- await fixTaskFile2(task.filePath);
1790
+ await fixTaskFile2(task.filePath, { metadataStyle: this.metadataStyle });
1592
1791
  }
1593
1792
  const content = await fs4.readFile(task.filePath, "utf-8");
1594
- let updatedContent;
1595
- if (/^Status:\s*.+$/im.test(content)) {
1596
- updatedContent = content.replace(/^Status:\s*.+$/im, `Status: ${task.status}${HARD_BREAK}`);
1597
- } else {
1598
- updatedContent = content.replace(/(^#.*\n)/, `$1Status: ${task.status}${HARD_BREAK}
1599
- `);
1600
- }
1793
+ const i18n = getI18n(detectLocale(content));
1794
+ const style = resolveMetadataStyle(content, this.metadataStyle);
1795
+ let updatedContent = style.write(content, this.labelFor(content, "Status", i18n.status), task.status);
1796
+ updatedContent = setInlineField(
1797
+ updatedContent,
1798
+ this.labelFor(updatedContent, "Priority", i18n.priority),
1799
+ task.order !== void 0 ? String(task.order) : void 0,
1800
+ this.metadataStyle
1801
+ );
1802
+ updatedContent = setInlineField(
1803
+ updatedContent,
1804
+ this.labelFor(updatedContent, "Group", i18n.group),
1805
+ task.groupId || void 0,
1806
+ this.metadataStyle
1807
+ );
1601
1808
  updatedContent = setInlineField(
1602
1809
  updatedContent,
1603
- "Priority",
1604
- task.order !== void 0 ? String(task.order) : void 0
1810
+ this.labelFor(updatedContent, "GroupName", i18n.groupName),
1811
+ task.groupName || void 0,
1812
+ this.metadataStyle
1605
1813
  );
1606
- updatedContent = setInlineField(updatedContent, "Group", task.groupId || void 0);
1607
- updatedContent = setInlineField(updatedContent, "GroupName", task.groupName || void 0);
1608
1814
  updatedContent = setInlineField(
1609
1815
  updatedContent,
1610
- "Difficulty",
1611
- task.difficulty !== void 0 ? String(task.difficulty) : void 0
1816
+ this.labelFor(updatedContent, "Difficulty", i18n.difficulty),
1817
+ task.difficulty !== void 0 ? String(task.difficulty) : void 0,
1818
+ this.metadataStyle
1612
1819
  );
1613
1820
  await fs4.writeFile(task.filePath, updatedContent, "utf-8");
1614
1821
  }
@@ -1622,25 +1829,15 @@ var init_file_system_task_provider = __esm({
1622
1829
  const filePath = path6.join(this.tasksDirectory, file);
1623
1830
  const content = await fs4.readFile(filePath, "utf-8");
1624
1831
  const title = content.match(TITLE_PATTERN)?.[1]?.trim() ?? "Untitled";
1625
- const contentLocale = detectLocale(content);
1626
- const i18n = getI18n(contentLocale);
1627
- const extractInline = (name, localizedName) => {
1628
- const names = localizedName && localizedName !== name ? [localizedName, name] : [name];
1629
- for (const n of names) {
1630
- const escapedName = n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1631
- const rx = new RegExp(`^${escapedName}:\\s*(.+)$`, "im");
1632
- const captured = content.match(rx)?.[1];
1633
- if (captured !== void 0) return stripHardBreak(captured);
1634
- }
1635
- return null;
1636
- };
1637
- const statusMatch = extractInline("Status", i18n.status);
1638
- const typeMatch = extractInline("Type", i18n.type);
1639
- const assigneeMatch = extractInline("Assignee", i18n.assignee);
1640
- const priorityMatch = extractInline("Priority", i18n.priority);
1641
- const groupMatch = extractInline("Group", i18n.group);
1642
- const groupNameMatch = extractInline("GroupName", i18n.groupName);
1643
- const difficultyMatch = extractInline("Difficulty", i18n.difficulty);
1832
+ const {
1833
+ status: statusMatch,
1834
+ type: typeMatch,
1835
+ assignee: assigneeMatch,
1836
+ priority: priorityMatch,
1837
+ group: groupMatch,
1838
+ groupName: groupNameMatch,
1839
+ difficulty: difficultyMatch
1840
+ } = this.readInlineMetadata(content);
1644
1841
  let assignee;
1645
1842
  if (assigneeMatch) {
1646
1843
  const assigneeValue = assigneeMatch.trim();
@@ -1714,11 +1911,14 @@ var init_file_system_task_provider = __esm({
1714
1911
  }
1715
1912
  generateTaskMarkdown(data) {
1716
1913
  const { id, title, type, description, assignee, i18n } = data;
1914
+ const metadata = getMetadataStyle(this.metadataStyle).format([
1915
+ { label: i18n.status, value: "pending" },
1916
+ { label: i18n.type, value: type },
1917
+ { label: i18n.assignee, value: assignee }
1918
+ ]);
1717
1919
  return `# \u{1F9E9} Task ${id} \u2014 ${title}
1718
1920
 
1719
- ${i18n.status}: pending${HARD_BREAK}
1720
- ${i18n.type}: ${type}${HARD_BREAK}
1721
- ${i18n.assignee}: ${assignee}${HARD_BREAK}
1921
+ ${metadata}
1722
1922
 
1723
1923
  ## ${i18n.description}
1724
1924
  ${description || i18n.descriptionPlaceholder}
@@ -1762,7 +1962,10 @@ ${i18n.notesPlaceholder}
1762
1962
  }
1763
1963
  let fixedCount = 0;
1764
1964
  for (const filePath of taskFiles) {
1765
- const wasFixed = await fixTaskFile(filePath);
1965
+ const wasFixed = await fixTaskFile(filePath, {
1966
+ metadataStyle: this.metadataStyle,
1967
+ ...this.convertMetadataStyleTo !== void 0 && { convertTo: this.convertMetadataStyleTo }
1968
+ });
1766
1969
  if (wasFixed) {
1767
1970
  fixedCount++;
1768
1971
  }
@@ -1802,32 +2005,45 @@ var init_task_file_types = __esm({
1802
2005
  // ../file-system-task-provider/src/index.ts
1803
2006
  var src_exports = {};
1804
2007
  __export(src_exports, {
2008
+ DEFAULT_METADATA_STYLE_ID: () => DEFAULT_METADATA_STYLE_ID,
1805
2009
  FileSystemMetricsAdapter: () => FileSystemMetricsAdapter,
1806
2010
  FileSystemTaskProvider: () => FileSystemTaskProvider,
1807
2011
  HARD_BREAK: () => HARD_BREAK,
2012
+ METADATA_STYLES: () => METADATA_STYLES,
2013
+ METADATA_STYLE_IDS: () => METADATA_STYLE_IDS,
1808
2014
  NullLogger: () => NullLogger,
1809
2015
  PARKED_USERS_FILE_NAME: () => PARKED_USERS_FILE_NAME,
1810
2016
  TASKIN_DIR_NAME: () => TASKIN_DIR_NAME,
1811
2017
  USERS_FILE_NAME: () => USERS_FILE_NAME,
1812
2018
  UserRegistry: () => UserRegistry,
1813
2019
  classifyAssignee: () => classifyAssignee,
2020
+ convertMetadataStyle: () => convertMetadataStyle,
1814
2021
  createTaskWithSync: () => createTaskWithSync,
1815
2022
  detectLocale: () => detectLocale,
2023
+ detectMetadataStyle: () => detectMetadataStyle,
1816
2024
  fixAssignees: () => fixAssignees,
1817
2025
  fixUsersFileLocation: () => fixUsersFileLocation,
1818
2026
  foldAssignee: () => foldAssignee,
1819
2027
  getI18n: () => getI18n,
2028
+ getMetadataStyle: () => getMetadataStyle,
1820
2029
  getNextTaskNumberAfterSync: () => getNextTaskNumberAfterSync,
2030
+ hardBreakMetadataStyle: () => hardBreakMetadataStyle,
1821
2031
  i18nConfig: () => i18nConfig,
1822
2032
  inspectUsersFileLocation: () => inspectUsersFileLocation,
2033
+ isMetadataStyleId: () => isMetadataStyleId,
2034
+ listMetadataStyle: () => listMetadataStyle,
2035
+ plainMetadataStyle: () => plainMetadataStyle,
1823
2036
  pushAfterCreate: () => pushAfterCreate,
2037
+ readMetadataField: () => readMetadataField,
2038
+ resolveMetadataStyle: () => resolveMetadataStyle,
1824
2039
  resolveUsersFilePaths: () => resolveUsersFilePaths,
1825
2040
  squashTaskFileOnDone: () => squashTaskFileOnDone,
1826
2041
  stripHardBreak: () => stripHardBreak,
1827
2042
  syncBeforeCreate: () => syncBeforeCreate,
1828
2043
  validateAssignees: () => validateAssignees,
1829
2044
  validateSeededUsers: () => validateSeededUsers,
1830
- validateUsersFileLocation: () => validateUsersFileLocation
2045
+ validateUsersFileLocation: () => validateUsersFileLocation,
2046
+ writeMetadataField: () => writeMetadataField
1831
2047
  });
1832
2048
  var init_src2 = __esm({
1833
2049
  "../file-system-task-provider/src/index.ts"() {
@@ -1839,6 +2055,7 @@ var init_src2 = __esm({
1839
2055
  init_file_system_task_provider();
1840
2056
  init_i18n();
1841
2057
  init_inline_metadata();
2058
+ init_metadata_style2();
1842
2059
  init_task_file_types();
1843
2060
  init_user_registry();
1844
2061
  init_users_file_location();
@@ -3029,6 +3246,10 @@ var AVAILABLE_PROVIDERS = [
3029
3246
  tasksDir: {
3030
3247
  type: "string",
3031
3248
  description: "Directory to store task files"
3249
+ },
3250
+ metadataStyle: {
3251
+ type: "string",
3252
+ description: "Marking of the metadata block for new files (list | hard-break | plain)"
3032
3253
  }
3033
3254
  }
3034
3255
  },
@@ -3133,11 +3354,16 @@ function expandProviderConfig(config) {
3133
3354
  return expanded;
3134
3355
  }
3135
3356
  var buildFileSystemProvider = async ({ projectRoot, providerConfig, tasksDirOverride }) => {
3136
- const { FileSystemTaskProvider: FileSystemTaskProvider2, UserRegistry: UserRegistry2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
3357
+ const { FileSystemTaskProvider: FileSystemTaskProvider2, isMetadataStyleId: isMetadataStyleId2, UserRegistry: UserRegistry2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
3137
3358
  const configuredTasksDir = typeof providerConfig.tasksDir === "string" ? providerConfig.tasksDir : "TASKS";
3138
3359
  const tasksDir = path7.resolve(projectRoot, tasksDirOverride ?? configuredTasksDir);
3139
3360
  const userRegistry = new UserRegistry2({ taskinDir: path7.join(projectRoot, ".taskin") });
3140
- const provider = new FileSystemTaskProvider2(tasksDir, userRegistry);
3361
+ const metadataStyle = isMetadataStyleId2(providerConfig.metadataStyle) ? providerConfig.metadataStyle : void 0;
3362
+ const convertMetadataStyleTo = isMetadataStyleId2(providerConfig.convertMetadataStyleTo) ? providerConfig.convertMetadataStyleTo : void 0;
3363
+ const provider = new FileSystemTaskProvider2(tasksDir, userRegistry, void 0, void 0, {
3364
+ ...metadataStyle !== void 0 && { metadataStyle },
3365
+ ...convertMetadataStyleTo !== void 0 && { convertMetadataStyleTo }
3366
+ });
3141
3367
  return { provider, userRegistry };
3142
3368
  };
3143
3369
  var PROVIDER_BUILDERS = {
@@ -3169,7 +3395,7 @@ async function resolveTaskProvider(options = {}, builders = PROVIDER_BUILDERS) {
3169
3395
  }
3170
3396
  const context = {
3171
3397
  projectRoot,
3172
- providerConfig: expandProviderConfig(config.provider.config),
3398
+ providerConfig: { ...expandProviderConfig(config.provider.config), ...options.configOverrides },
3173
3399
  ...options.tasksDir !== void 0 && { tasksDirOverride: options.tasksDir }
3174
3400
  };
3175
3401
  const { provider, userRegistry } = await build(context);
@@ -4825,7 +5051,7 @@ async function setupProviderConfig(provider, cwd) {
4825
5051
  }
4826
5052
  async function setupFileSystemProvider(cwd) {
4827
5053
  const tasksDir = join3(cwd, "TASKS");
4828
- const { FileSystemTaskProvider: FileSystemTaskProvider2, UserRegistry: UserRegistry2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
5054
+ const { DEFAULT_METADATA_STYLE_ID: DEFAULT_METADATA_STYLE_ID2, FileSystemTaskProvider: FileSystemTaskProvider2, getMetadataStyle: getMetadataStyle2, UserRegistry: UserRegistry2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
4829
5055
  const userRegistry = new UserRegistry2({ taskinDir: join3(cwd, ".taskin") });
4830
5056
  const fileSystemProvider = new FileSystemTaskProvider2(tasksDir, userRegistry);
4831
5057
  await fileSystemProvider.initialize();
@@ -4838,11 +5064,14 @@ async function setupFileSystemProvider(cwd) {
4838
5064
  } else {
4839
5065
  const sampleTaskFile = join3(tasksDir, "task-001-setup-project.md");
4840
5066
  info("Creating sample task...");
5067
+ const sampleMetadata = getMetadataStyle2(DEFAULT_METADATA_STYLE_ID2).format([
5068
+ { label: "Status", value: "pending" },
5069
+ { label: "Type", value: "chore" },
5070
+ { label: "Assignee", value: "developer" }
5071
+ ]);
4841
5072
  const sampleTask = `# Task 001 \u2014 Setup Project
4842
5073
 
4843
- Status: pending
4844
- Type: chore
4845
- Assignee: developer
5074
+ ${sampleMetadata}
4846
5075
 
4847
5076
  ## Description
4848
5077
 
@@ -4862,7 +5091,8 @@ You can edit or delete this file. Use \`taskin list\` to see all tasks.
4862
5091
  success(`\u2713 Created sample task ${colors.highlight("task-001-setup-project.md")}`);
4863
5092
  }
4864
5093
  return {
4865
- tasksDir: "TASKS"
5094
+ tasksDir: "TASKS",
5095
+ metadataStyle: DEFAULT_METADATA_STYLE_ID2
4866
5096
  };
4867
5097
  }
4868
5098
  async function promptCreateFirstUser(cwd) {
@@ -4909,6 +5139,7 @@ async function promptCreateFirstUser(cwd) {
4909
5139
  // src/commands/lint.ts
4910
5140
  init_esm_shims();
4911
5141
  import chalk5 from "chalk";
5142
+ var METADATA_STYLES2 = ["list", "hard-break", "plain"];
4912
5143
  var lintCommand = defineCommand({
4913
5144
  name: "lint",
4914
5145
  description: "\u{1F50D} Validate task markdown files",
@@ -4921,6 +5152,10 @@ var lintCommand = defineCommand({
4921
5152
  {
4922
5153
  flags: "-f, --fix",
4923
5154
  description: "Automatically fix task file format issues"
5155
+ },
5156
+ {
5157
+ flags: "-m, --metadata-style <style>",
5158
+ description: `Rewrite the metadata block in this style with --fix (${METADATA_STYLES2.join(" | ")})`
4924
5159
  }
4925
5160
  ],
4926
5161
  handler: async (options) => {
@@ -4928,7 +5163,19 @@ var lintCommand = defineCommand({
4928
5163
  }
4929
5164
  });
4930
5165
  async function executeLint(options) {
4931
- const { provider, providerType } = await resolveTaskProvider(options.path ? { tasksDir: options.path } : {});
5166
+ const style = options.metadataStyle;
5167
+ if (style !== void 0 && !METADATA_STYLES2.includes(style)) {
5168
+ console.error(chalk5.red(`Unknown metadata style "${style}". Use one of: ${METADATA_STYLES2.join(", ")}.`));
5169
+ process.exit(1);
5170
+ }
5171
+ if (style !== void 0 && !options.fix) {
5172
+ console.error(chalk5.red("--metadata-style rewrites files, so it requires --fix."));
5173
+ process.exit(1);
5174
+ }
5175
+ const { provider, providerType } = await resolveTaskProvider({
5176
+ ...options.path ? { tasksDir: options.path } : {},
5177
+ ...style !== void 0 && { configOverrides: { metadataStyle: style, convertMetadataStyleTo: style } }
5178
+ });
4932
5179
  if (options.fix) {
4933
5180
  console.log(`\u{1F527} Fixing tasks (provider: ${providerType})
4934
5181
  `);
@@ -6723,14 +6970,10 @@ var FileSystemTaskLinter = class {
6723
6970
  return null;
6724
6971
  }
6725
6972
  extractMetadata(content) {
6726
- const [headerSection = ""] = content.split(/^##/m);
6727
- const statusMatch = headerSection.match(/^Status:\s*(.+)$/im);
6728
- const typeMatch = headerSection.match(/^Type:\s*(.+)$/im);
6729
- const assigneeMatch = headerSection.match(/^Assignee:\s*(.+)$/im);
6730
6973
  return {
6731
- status: statusMatch?.[1] === void 0 ? void 0 : stripHardBreak(statusMatch[1]).toLowerCase(),
6732
- type: typeMatch?.[1] === void 0 ? void 0 : stripHardBreak(typeMatch[1]).toLowerCase(),
6733
- assignee: assigneeMatch?.[1] === void 0 ? void 0 : stripHardBreak(assigneeMatch[1])
6974
+ status: readMetadataField(content, "Status")?.toLowerCase(),
6975
+ type: readMetadataField(content, "Type")?.toLowerCase(),
6976
+ assignee: readMetadataField(content, "Assignee")
6734
6977
  };
6735
6978
  }
6736
6979
  /**