markform 0.1.14 → 0.1.16

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.
Files changed (49) hide show
  1. package/README.md +48 -39
  2. package/dist/ai-sdk.d.mts +3 -2
  3. package/dist/ai-sdk.mjs +3 -2
  4. package/dist/ai-sdk.mjs.map +1 -0
  5. package/dist/{apply-CUH_16RD.mjs → apply-CXsI5N9x.mjs} +639 -9
  6. package/dist/apply-CXsI5N9x.mjs.map +1 -0
  7. package/dist/bin.mjs +3 -2
  8. package/dist/bin.mjs.map +1 -0
  9. package/dist/{cli-ixzNVirs.mjs → cli-BsFessUW.mjs} +713 -120
  10. package/dist/cli-BsFessUW.mjs.map +1 -0
  11. package/dist/cli.d.mts +2 -1
  12. package/dist/cli.mjs +1 -1
  13. package/dist/{coreTypes-CnKsB1H3.d.mts → coreTypes-DE6Giau5.d.mts} +10 -1
  14. package/dist/coreTypes-DiCddBKu.mjs +2 -1
  15. package/dist/coreTypes-DiCddBKu.mjs.map +1 -0
  16. package/dist/index.d.mts +15 -2
  17. package/dist/index.mjs +2 -2
  18. package/dist/session-XDrocA3j.mjs +2 -1
  19. package/dist/session-XDrocA3j.mjs.map +1 -0
  20. package/dist/{shared-D3dNi-Gn.mjs → shared-CCq4haEV.mjs} +9 -1
  21. package/dist/shared-CCq4haEV.mjs.map +1 -0
  22. package/dist/shared-fUKfJ1UA.mjs +4 -0
  23. package/dist/{src-BKRKMdgR.mjs → src-Dv3IZSQU.mjs} +15 -6
  24. package/dist/src-Dv3IZSQU.mjs.map +1 -0
  25. package/docs/markform-reference.md +58 -7
  26. package/docs/markform-spec.md +142 -0
  27. package/examples/movie-research/movie-deep-research-mock-filled.form.md +64 -40
  28. package/examples/movie-research/movie-deep-research.form.md +58 -37
  29. package/examples/movie-research/movie-research-demo.form.md +9 -6
  30. package/examples/rejection-test/rejection-test-mock-filled.form.md +5 -7
  31. package/examples/rejection-test/rejection-test-mock-filled.report.md +4 -2
  32. package/examples/rejection-test/rejection-test-mock-filled.schema.json +1 -1
  33. package/examples/rejection-test/rejection-test.form.md +5 -7
  34. package/examples/rejection-test/rejection-test.session.yaml +10 -10
  35. package/examples/simple/simple-comment-syntax.form.md +79 -0
  36. package/examples/simple/simple-mock-filled.form.md +20 -9
  37. package/examples/simple/simple-mock-filled.report.md +30 -7
  38. package/examples/simple/simple-skipped-filled.form.md +22 -10
  39. package/examples/simple/simple-skipped-filled.report.md +27 -4
  40. package/examples/simple/simple-skipped-filled.yml +1 -1
  41. package/examples/simple/simple-with-skips.session.yaml +35 -19
  42. package/examples/simple/simple.form.md +20 -9
  43. package/examples/simple/simple.raw.md +18 -25
  44. package/examples/simple/simple.session.yaml +35 -19
  45. package/examples/startup-deep-research/startup-deep-research.form.md +126 -103
  46. package/examples/startup-research/startup-research-mock-filled.form.md +19 -10
  47. package/examples/startup-research/startup-research.form.md +36 -29
  48. package/package.json +9 -3
  49. package/dist/shared-CNqwaxUt.mjs +0 -4
@@ -2,7 +2,7 @@
2
2
  import YAML from "yaml";
3
3
 
4
4
  //#region src/errors.ts
5
- const VERSION = "0.1.14";
5
+ const VERSION = "0.1.16";
6
6
  /**
7
7
  * Base error class for all markform errors.
8
8
  * Consumers can catch this to handle any markform error.
@@ -467,6 +467,457 @@ function deriveSchemaPath(basePath) {
467
467
  return base + SCHEMA_EXTENSION;
468
468
  }
469
469
 
470
+ //#endregion
471
+ //#region src/engine/preprocess.ts
472
+ const MARKFORM_TAGS = new Set([
473
+ "form",
474
+ "field",
475
+ "group",
476
+ "note",
477
+ "instructions",
478
+ "description"
479
+ ]);
480
+ /**
481
+ * Check if a string starts with a known Markform tag name.
482
+ * Returns the tag name if found, null otherwise.
483
+ */
484
+ function startsWithMarkformTag(content) {
485
+ const trimmed = content.trim();
486
+ for (const tag of MARKFORM_TAGS) if (trimmed === tag || trimmed.startsWith(tag + " ") || trimmed.startsWith(tag + "/")) return tag;
487
+ return null;
488
+ }
489
+ /**
490
+ * Check if content represents a valid form tag with id= attribute.
491
+ * A valid form tag must:
492
+ * - Start with "form " (the tag name followed by space for attributes)
493
+ * - Include an `id=` attribute in any valid Markdoc syntax:
494
+ * - Double quotes: id="value"
495
+ * - Single quotes: id='value'
496
+ * - Unquoted: id=value
497
+ * - Expression: id={variable}
498
+ * - With spaces: id = "value"
499
+ */
500
+ function isValidFormTag(content) {
501
+ const trimmed = content.trim();
502
+ if (!trimmed.startsWith("form ")) return false;
503
+ return trimmed.includes("=") && /\bid\s*=/.test(trimmed);
504
+ }
505
+ /** Parser state for tracking code blocks */
506
+ var State = /* @__PURE__ */ function(State$1) {
507
+ State$1[State$1["NORMAL"] = 0] = "NORMAL";
508
+ State$1[State$1["FENCED_CODE"] = 1] = "FENCED_CODE";
509
+ return State$1;
510
+ }(State || {});
511
+ /**
512
+ * Check if position is at the start of a line (or at position 0).
513
+ */
514
+ function isAtLineStart(input, pos) {
515
+ return pos === 0 || input[pos - 1] === "\n";
516
+ }
517
+ /**
518
+ * Check if position is in leading whitespace of a line.
519
+ * Returns true if all characters between the last newline (or start) and pos are spaces.
520
+ */
521
+ function isInLeadingWhitespace(input, pos) {
522
+ let j = pos - 1;
523
+ while (j >= 0 && input[j] !== "\n") {
524
+ if (input[j] !== " " && input[j] !== " ") return false;
525
+ j--;
526
+ }
527
+ return true;
528
+ }
529
+ /**
530
+ * Match a fenced code block opening at the given position.
531
+ * Returns fence info if found, null otherwise.
532
+ * Handles 0-3 leading spaces per CommonMark spec.
533
+ */
534
+ function matchFenceOpening(input, pos) {
535
+ let indent = 0;
536
+ let i = pos;
537
+ while (i < input.length && input[i] === " ") {
538
+ indent++;
539
+ i++;
540
+ }
541
+ if (indent >= 4) return null;
542
+ const fenceChar = input[i];
543
+ if (fenceChar !== "`" && fenceChar !== "~") return null;
544
+ let fenceLength = 0;
545
+ while (i + fenceLength < input.length && input[i + fenceLength] === fenceChar) fenceLength++;
546
+ if (fenceLength < 3) return null;
547
+ let endOfLine = i + fenceLength;
548
+ while (endOfLine < input.length && input[endOfLine] !== "\n") endOfLine++;
549
+ if (endOfLine < input.length) endOfLine++;
550
+ return {
551
+ char: fenceChar,
552
+ length: fenceLength,
553
+ fullMatch: input.slice(pos, endOfLine)
554
+ };
555
+ }
556
+ /**
557
+ * Check if position matches a closing fence for the given opening fence.
558
+ */
559
+ function matchFenceClosing(input, pos, fenceChar, fenceLength) {
560
+ let indent = 0;
561
+ let i = pos;
562
+ while (indent < 4 && i < input.length && input[i] === " ") {
563
+ indent++;
564
+ i++;
565
+ }
566
+ if (input[i] !== fenceChar) return false;
567
+ let closingLength = 0;
568
+ while (i + closingLength < input.length && input[i + closingLength] === fenceChar) closingLength++;
569
+ if (closingLength < fenceLength) return false;
570
+ let afterFence = i + closingLength;
571
+ while (afterFence < input.length && input[afterFence] !== "\n") {
572
+ if (input[afterFence] !== " " && input[afterFence] !== " ") return false;
573
+ afterFence++;
574
+ }
575
+ return true;
576
+ }
577
+ /**
578
+ * Find the end of an inline code span starting at the given position.
579
+ * Returns the position after the closing backticks, or -1 if not a valid span.
580
+ */
581
+ function findInlineCodeEnd(input, pos) {
582
+ let openCount = 0;
583
+ let i = pos;
584
+ while (i < input.length && input[i] === "`") {
585
+ openCount++;
586
+ i++;
587
+ }
588
+ if (openCount === 0) return -1;
589
+ while (i < input.length) if (input[i] === "`") {
590
+ let closeCount = 0;
591
+ while (i < input.length && input[i] === "`") {
592
+ closeCount++;
593
+ i++;
594
+ }
595
+ if (closeCount === openCount) return i;
596
+ } else if (input[i] === "\n") i++;
597
+ else i++;
598
+ return -1;
599
+ }
600
+ /**
601
+ * Transform HTML comment syntax to Markdoc syntax.
602
+ *
603
+ * Transformation rules:
604
+ * - The form tag with `id=` is always transformed (establishes Markform document)
605
+ * - Other tags are ONLY transformed when inside the form tag boundaries
606
+ * - Comments outside the form pass through unchanged (prevents collisions)
607
+ *
608
+ * Patterns transformed (when inside form):
609
+ * - `<!-- tagname ... -->` → `{% tagname ... %}`
610
+ * - `<!-- /tagname -->` → `{% /tagname %}`
611
+ * - `<!-- tagname ... /-->` → `{% tagname ... /%}`
612
+ * - `<!-- #id -->` → `{% #id %}`
613
+ * - `<!-- .class -->` → `{% .class %}`
614
+ *
615
+ * Code blocks (fenced and inline) are preserved unchanged.
616
+ *
617
+ * @param input - The markdown content
618
+ * @returns The preprocessed content with Markdoc syntax
619
+ */
620
+ function preprocessCommentSyntax(input) {
621
+ let output = "";
622
+ let state = State.NORMAL;
623
+ let fenceChar = "";
624
+ let fenceLength = 0;
625
+ let i = 0;
626
+ let insideForm = false;
627
+ while (i < input.length) switch (state) {
628
+ case State.NORMAL:
629
+ if (isAtLineStart(input, i)) {
630
+ const fence = matchFenceOpening(input, i);
631
+ if (fence) {
632
+ state = State.FENCED_CODE;
633
+ fenceChar = fence.char;
634
+ fenceLength = fence.length;
635
+ output += fence.fullMatch;
636
+ i += fence.fullMatch.length;
637
+ continue;
638
+ }
639
+ }
640
+ if (input[i] === "`") {
641
+ let backtickCount = 0;
642
+ let j = i;
643
+ while (j < input.length && input[j] === "`") {
644
+ backtickCount++;
645
+ j++;
646
+ }
647
+ if (!(backtickCount >= 3 && isInLeadingWhitespace(input, i))) {
648
+ const end = findInlineCodeEnd(input, i);
649
+ if (end !== -1) {
650
+ output += input.slice(i, end);
651
+ i = end;
652
+ continue;
653
+ }
654
+ }
655
+ }
656
+ if (input.slice(i, i + 4) === "<!--") {
657
+ const endComment = input.indexOf("-->", i + 4);
658
+ if (endComment !== -1) {
659
+ const interior = input.slice(i + 4, endComment).trim();
660
+ if (isValidFormTag(interior)) {
661
+ if (interior.endsWith("/")) output += "{% " + interior.slice(0, -1).trim() + " /%}";
662
+ else {
663
+ output += "{% " + interior + " %}";
664
+ insideForm = true;
665
+ }
666
+ i = endComment + 3;
667
+ continue;
668
+ }
669
+ if (interior === "/form" || interior.startsWith("/form ")) {
670
+ output += "{% " + interior + " %}";
671
+ insideForm = false;
672
+ i = endComment + 3;
673
+ continue;
674
+ }
675
+ if (insideForm) {
676
+ const tagName = startsWithMarkformTag(interior);
677
+ if (tagName && tagName !== "form") {
678
+ if (interior.endsWith("/")) output += "{% " + interior.slice(0, -1).trim() + " /%}";
679
+ else output += "{% " + interior + " %}";
680
+ i = endComment + 3;
681
+ continue;
682
+ }
683
+ if (interior.startsWith("/")) {
684
+ if (startsWithMarkformTag(interior.slice(1))) {
685
+ output += "{% " + interior + " %}";
686
+ i = endComment + 3;
687
+ continue;
688
+ }
689
+ }
690
+ if (interior.startsWith("#") || interior.startsWith(".")) {
691
+ output += "{% " + interior + " %}";
692
+ i = endComment + 3;
693
+ continue;
694
+ }
695
+ }
696
+ }
697
+ }
698
+ output += input[i];
699
+ i++;
700
+ break;
701
+ case State.FENCED_CODE:
702
+ if (isAtLineStart(input, i) && matchFenceClosing(input, i, fenceChar, fenceLength)) {
703
+ let endLine = i;
704
+ while (endLine < input.length && input[endLine] !== "\n") endLine++;
705
+ if (endLine < input.length) endLine++;
706
+ output += input.slice(i, endLine);
707
+ i = endLine;
708
+ state = State.NORMAL;
709
+ fenceChar = "";
710
+ fenceLength = 0;
711
+ continue;
712
+ }
713
+ output += input[i];
714
+ i++;
715
+ break;
716
+ }
717
+ return output;
718
+ }
719
+ /**
720
+ * Detect which syntax style is used in a document.
721
+ *
722
+ * Detection is based on the FORM TAG only:
723
+ * - `<!-- form ... id="..." -->` → 'comments' style
724
+ * - `{% form ... id="..." %}` → 'tags' style
725
+ *
726
+ * The form tag must have `id=` attribute to be recognized as a valid Markform document.
727
+ * Other patterns (field, group, #id, .class) do not trigger detection by themselves.
728
+ *
729
+ * Code blocks (fenced and inline) are skipped to avoid false positives from examples.
730
+ *
731
+ * @param input - The markdown content
732
+ * @returns The detected syntax style, defaults to 'tags' if no valid form tag found
733
+ */
734
+ function detectSyntaxStyle(input) {
735
+ let state = State.NORMAL;
736
+ let fenceChar = "";
737
+ let fenceLength = 0;
738
+ let i = 0;
739
+ while (i < input.length) switch (state) {
740
+ case State.NORMAL:
741
+ if (isAtLineStart(input, i)) {
742
+ const fence = matchFenceOpening(input, i);
743
+ if (fence) {
744
+ state = State.FENCED_CODE;
745
+ fenceChar = fence.char;
746
+ fenceLength = fence.length;
747
+ i += fence.fullMatch.length;
748
+ continue;
749
+ }
750
+ }
751
+ if (input[i] === "`") {
752
+ let backtickCount = 0;
753
+ let j = i;
754
+ while (j < input.length && input[j] === "`") {
755
+ backtickCount++;
756
+ j++;
757
+ }
758
+ if (!(backtickCount >= 3 && isInLeadingWhitespace(input, i))) {
759
+ const end = findInlineCodeEnd(input, i);
760
+ if (end !== -1) {
761
+ i = end;
762
+ continue;
763
+ }
764
+ }
765
+ }
766
+ if (input.slice(i, i + 4) === "<!--") {
767
+ const endComment = input.indexOf("-->", i + 4);
768
+ if (endComment !== -1) {
769
+ if (isValidFormTag(input.slice(i + 4, endComment).trim())) return "comments";
770
+ }
771
+ }
772
+ if (input.slice(i, i + 2) === "{%") {
773
+ const endTag = input.indexOf("%}", i + 2);
774
+ if (endTag !== -1) {
775
+ if (isValidFormTag(input.slice(i + 2, endTag).trim())) return "tags";
776
+ }
777
+ }
778
+ i++;
779
+ break;
780
+ case State.FENCED_CODE:
781
+ if (isAtLineStart(input, i) && matchFenceClosing(input, i, fenceChar, fenceLength)) {
782
+ let endLine = i;
783
+ while (endLine < input.length && input[endLine] !== "\n") endLine++;
784
+ if (endLine < input.length) endLine++;
785
+ i = endLine;
786
+ state = State.NORMAL;
787
+ fenceChar = "";
788
+ fenceLength = 0;
789
+ continue;
790
+ }
791
+ i++;
792
+ break;
793
+ }
794
+ return "tags";
795
+ }
796
+ /**
797
+ * Validate that a document uses only the specified syntax style.
798
+ *
799
+ * Scans the document for patterns of the "wrong" syntax and returns violations.
800
+ * Only checks within form tag boundaries (comments outside form are ignored).
801
+ * Code blocks (fenced and inline) are skipped.
802
+ *
803
+ * @param input - The markdown content
804
+ * @param expectedSyntax - The syntax style that should be used
805
+ * @returns Array of violations (empty if document is consistent)
806
+ */
807
+ function validateSyntaxConsistency(input, expectedSyntax) {
808
+ const violations = [];
809
+ let state = State.NORMAL;
810
+ let fenceChar = "";
811
+ let fenceLength = 0;
812
+ let i = 0;
813
+ let lineNumber = 1;
814
+ let insideForm = false;
815
+ while (i < input.length) {
816
+ if (input[i] === "\n") lineNumber++;
817
+ switch (state) {
818
+ case State.NORMAL:
819
+ if (isAtLineStart(input, i)) {
820
+ const fence = matchFenceOpening(input, i);
821
+ if (fence) {
822
+ state = State.FENCED_CODE;
823
+ fenceChar = fence.char;
824
+ fenceLength = fence.length;
825
+ for (const ch of fence.fullMatch) if (ch === "\n") lineNumber++;
826
+ i += fence.fullMatch.length;
827
+ continue;
828
+ }
829
+ }
830
+ if (input[i] === "`") {
831
+ let backtickCount = 0;
832
+ let k = i;
833
+ while (k < input.length && input[k] === "`") {
834
+ backtickCount++;
835
+ k++;
836
+ }
837
+ if (!(backtickCount >= 3 && isInLeadingWhitespace(input, i))) {
838
+ const end = findInlineCodeEnd(input, i);
839
+ if (end !== -1) {
840
+ for (let m = i; m < end; m++) if (input[m] === "\n") lineNumber++;
841
+ i = end;
842
+ continue;
843
+ }
844
+ }
845
+ }
846
+ if (input.slice(i, i + 4) === "<!--") {
847
+ const endComment = input.indexOf("-->", i + 4);
848
+ if (endComment !== -1) {
849
+ const interior = input.slice(i + 4, endComment).trim();
850
+ if (isValidFormTag(interior)) insideForm = true;
851
+ else if (interior === "/form" || interior.startsWith("/form ")) insideForm = false;
852
+ }
853
+ }
854
+ if (input.slice(i, i + 2) === "{%") {
855
+ const endTag = input.indexOf("%}", i + 2);
856
+ if (endTag !== -1) {
857
+ const interior = input.slice(i + 2, endTag).trim();
858
+ if (isValidFormTag(interior)) insideForm = true;
859
+ else if (interior === "/form" || interior.startsWith("/form ")) insideForm = false;
860
+ }
861
+ }
862
+ if (insideForm) {
863
+ if (expectedSyntax === "comments") {
864
+ if (input.slice(i, i + 2) === "{%") {
865
+ const endTag = input.indexOf("%}", i + 2);
866
+ if (endTag !== -1) {
867
+ const pattern = input.slice(i, endTag + 2);
868
+ violations.push({
869
+ line: lineNumber,
870
+ pattern,
871
+ foundSyntax: "tags"
872
+ });
873
+ }
874
+ }
875
+ } else if (input.slice(i, i + 4) === "<!--") {
876
+ const endComment = input.indexOf("-->", i + 4);
877
+ if (endComment !== -1) {
878
+ const interior = input.slice(i + 4, endComment).trim();
879
+ const pattern = input.slice(i, endComment + 3);
880
+ if (startsWithMarkformTag(interior) || isValidFormTag(interior)) violations.push({
881
+ line: lineNumber,
882
+ pattern,
883
+ foundSyntax: "comments"
884
+ });
885
+ else if (interior.startsWith("/") && startsWithMarkformTag(interior.slice(1))) violations.push({
886
+ line: lineNumber,
887
+ pattern,
888
+ foundSyntax: "comments"
889
+ });
890
+ else if (/^[#.][a-zA-Z_-]/.test(interior)) violations.push({
891
+ line: lineNumber,
892
+ pattern,
893
+ foundSyntax: "comments"
894
+ });
895
+ }
896
+ }
897
+ }
898
+ i++;
899
+ break;
900
+ case State.FENCED_CODE:
901
+ if (isAtLineStart(input, i) && matchFenceClosing(input, i, fenceChar, fenceLength)) {
902
+ let endLine = i;
903
+ while (endLine < input.length && input[endLine] !== "\n") endLine++;
904
+ if (endLine < input.length) {
905
+ endLine++;
906
+ lineNumber++;
907
+ }
908
+ i = endLine;
909
+ state = State.NORMAL;
910
+ fenceChar = "";
911
+ fenceLength = 0;
912
+ continue;
913
+ }
914
+ i++;
915
+ break;
916
+ }
917
+ }
918
+ return violations;
919
+ }
920
+
470
921
  //#endregion
471
922
  //#region src/utils/keySort.ts
472
923
  /**
@@ -496,6 +947,47 @@ function priorityKeyComparator(priorityKeys) {
496
947
  });
497
948
  }
498
949
 
950
+ //#endregion
951
+ //#region src/utils/urlFormat.ts
952
+ /**
953
+ * Create a friendly abbreviated display name for a URL.
954
+ * - Drops "www." prefix from domain
955
+ * - Adds first portion of path (up to maxPathChars) if present
956
+ * - Adds ellipsis (…) if path is truncated
957
+ *
958
+ * @param url - The URL to abbreviate
959
+ * @param maxPathChars - Maximum characters to include from the path (default: 12)
960
+ * @returns Friendly abbreviated URL (e.g., "example.com/docs/api…")
961
+ */
962
+ function friendlyUrlAbbrev(url, maxPathChars = 12) {
963
+ try {
964
+ const parsed = new URL(url);
965
+ let hostname = parsed.hostname;
966
+ if (hostname.startsWith("www.")) hostname = hostname.slice(4);
967
+ const path = parsed.pathname.slice(1);
968
+ if (!path) return hostname;
969
+ if (path.length <= maxPathChars) return `${hostname}/${path}`;
970
+ return `${hostname}/${path.slice(0, maxPathChars)}…`;
971
+ } catch {
972
+ let result = url;
973
+ result = result.replace(/^https?:\/\//, "");
974
+ result = result.replace(/^www\./, "");
975
+ const maxLen = 30;
976
+ if (result.length > maxLen) return result.slice(0, maxLen) + "…";
977
+ return result;
978
+ }
979
+ }
980
+ /**
981
+ * Format a URL as a markdown link with a friendly abbreviated display text.
982
+ * The full URL is preserved as the link target.
983
+ *
984
+ * @param url - The URL to format
985
+ * @returns Markdown link in format [friendly-abbrev](url)
986
+ */
987
+ function formatUrlAsMarkdownLink(url) {
988
+ return `[${friendlyUrlAbbrev(url)}](${url})`;
989
+ }
990
+
499
991
  //#endregion
500
992
  //#region src/engine/serialize.ts
501
993
  /**
@@ -545,6 +1037,137 @@ function pickFence(value) {
545
1037
  };
546
1038
  }
547
1039
  /**
1040
+ * Transform Markdoc syntax to HTML comment syntax.
1041
+ *
1042
+ * Patterns transformed:
1043
+ * - `{% tagname ... %}` → `<!-- tagname ... -->`
1044
+ * - `{% /tagname %}` → `<!-- /tagname -->`
1045
+ * - `{% tagname ... /%}` → `<!-- tagname ... /-->`
1046
+ * - `{% #id %}` → `<!-- #id -->`
1047
+ * - `{% .class %}` → `<!-- .class -->`
1048
+ *
1049
+ * Code blocks (fenced) are preserved unchanged.
1050
+ *
1051
+ * @param input - The Markdoc content
1052
+ * @returns The content with HTML comment syntax
1053
+ */
1054
+ function postprocessToCommentSyntax(input) {
1055
+ let output = "";
1056
+ let inFencedCode = false;
1057
+ let fenceChar = "";
1058
+ let fenceLength = 0;
1059
+ let i = 0;
1060
+ while (i < input.length) {
1061
+ if (!inFencedCode && (i === 0 || input[i - 1] === "\n")) {
1062
+ let indent = 0;
1063
+ let j = i;
1064
+ while (j < input.length && input[j] === " " && indent < 4) {
1065
+ indent++;
1066
+ j++;
1067
+ }
1068
+ if (indent < 4 && (input[j] === "`" || input[j] === "~")) {
1069
+ const fc = input[j];
1070
+ let len = 0;
1071
+ while (j + len < input.length && input[j + len] === fc) len++;
1072
+ if (len >= 3) {
1073
+ inFencedCode = true;
1074
+ fenceChar = fc;
1075
+ fenceLength = len;
1076
+ let endLine = j + len;
1077
+ while (endLine < input.length && input[endLine] !== "\n") endLine++;
1078
+ if (endLine < input.length) endLine++;
1079
+ output += input.slice(i, endLine);
1080
+ i = endLine;
1081
+ continue;
1082
+ }
1083
+ }
1084
+ }
1085
+ if (inFencedCode && (i === 0 || input[i - 1] === "\n")) {
1086
+ let indent = 0;
1087
+ let j = i;
1088
+ while (j < input.length && input[j] === " " && indent < 4) {
1089
+ indent++;
1090
+ j++;
1091
+ }
1092
+ if (input[j] === fenceChar) {
1093
+ let len = 0;
1094
+ while (j + len < input.length && input[j + len] === fenceChar) len++;
1095
+ if (len >= fenceLength) {
1096
+ let afterFence = j + len;
1097
+ let isClosing = true;
1098
+ while (afterFence < input.length && input[afterFence] !== "\n") {
1099
+ if (input[afterFence] !== " " && input[afterFence] !== " ") {
1100
+ isClosing = false;
1101
+ break;
1102
+ }
1103
+ afterFence++;
1104
+ }
1105
+ if (isClosing) {
1106
+ if (afterFence < input.length) afterFence++;
1107
+ output += input.slice(i, afterFence);
1108
+ i = afterFence;
1109
+ inFencedCode = false;
1110
+ fenceChar = "";
1111
+ fenceLength = 0;
1112
+ continue;
1113
+ }
1114
+ }
1115
+ }
1116
+ }
1117
+ if (inFencedCode) {
1118
+ output += input[i];
1119
+ i++;
1120
+ continue;
1121
+ }
1122
+ if (input[i] === "`") {
1123
+ let backtickCount = 0;
1124
+ let j = i;
1125
+ while (j < input.length && input[j] === "`") {
1126
+ backtickCount++;
1127
+ j++;
1128
+ }
1129
+ if (!(backtickCount >= 3 && isInLeadingWhitespace(input, i))) {
1130
+ const end = findInlineCodeEnd(input, i);
1131
+ if (end !== -1) {
1132
+ output += input.slice(i, end);
1133
+ i = end;
1134
+ continue;
1135
+ }
1136
+ }
1137
+ }
1138
+ if (input.slice(i, i + 2) === "{%") {
1139
+ const endTag = input.indexOf("%}", i + 2);
1140
+ if (endTag !== -1) {
1141
+ const interior = input.slice(i + 2, endTag).trim();
1142
+ if (interior.endsWith("/")) {
1143
+ const tagContent = interior.slice(0, -1).trim();
1144
+ if (tagContent.startsWith("#") || tagContent.startsWith(".")) output += "<!-- " + tagContent + " /-->";
1145
+ else output += "<!-- " + tagContent + " /-->";
1146
+ i = endTag + 2;
1147
+ continue;
1148
+ }
1149
+ if (interior.startsWith("/")) {
1150
+ const tagName = interior.slice(1).trim();
1151
+ output += "<!-- /" + tagName + " -->";
1152
+ i = endTag + 2;
1153
+ continue;
1154
+ }
1155
+ if (interior.startsWith("#") || interior.startsWith(".")) {
1156
+ output += "<!-- " + interior + " -->";
1157
+ i = endTag + 2;
1158
+ continue;
1159
+ }
1160
+ output += "<!-- " + interior + " -->";
1161
+ i = endTag + 2;
1162
+ continue;
1163
+ }
1164
+ }
1165
+ output += input[i];
1166
+ i++;
1167
+ }
1168
+ return output;
1169
+ }
1170
+ /**
548
1171
  * Format a value fence block with the given content.
549
1172
  * Uses smart fence selection to avoid collision with code blocks in content.
550
1173
  */
@@ -899,12 +1522,14 @@ function serializeYearField(field, response) {
899
1522
  }
900
1523
  /**
901
1524
  * Serialize a cell value for table output.
1525
+ * URL-typed columns are formatted as markdown links with domain as display text.
902
1526
  */
903
- function serializeCellValue(cell, _columnType) {
1527
+ function serializeCellValue(cell, columnType) {
904
1528
  if (cell.state === "skipped") return cell.reason ? `%SKIP:${cell.reason}%` : "%SKIP%";
905
1529
  if (cell.state === "aborted") return cell.reason ? `%ABORT:${cell.reason}%` : "%ABORT%";
906
1530
  if (cell.value === void 0 || cell.value === null) return "";
907
1531
  if (typeof cell.value === "number") return String(cell.value);
1532
+ if (columnType === "url") return formatUrlAsMarkdownLink(cell.value);
908
1533
  return cell.value;
909
1534
  }
910
1535
  /**
@@ -1129,7 +1754,9 @@ function buildFrontmatter(metadata, specVersion) {
1129
1754
  */
1130
1755
  function serializeForm(form, opts) {
1131
1756
  const specVersion = opts?.specVersion ?? MF_SPEC_VERSION;
1132
- return `${buildFrontmatter(form.metadata, specVersion)}\n\n${serializeFormSchema(form.schema, form.responsesByFieldId, form.docs, form.notes)}\n`;
1757
+ let result = `${buildFrontmatter(form.metadata, specVersion)}\n\n${serializeFormSchema(form.schema, form.responsesByFieldId, form.docs, form.notes)}\n`;
1758
+ if ((opts?.syntaxStyle ?? form.syntaxStyle ?? "tags") === "comments") result = postprocessToCommentSyntax(result);
1759
+ return result;
1133
1760
  }
1134
1761
  /** Map checkbox state to GFM marker for raw markdown output */
1135
1762
  const STATE_TO_GFM_MARKER = {
@@ -1149,6 +1776,7 @@ function serializeFieldRaw(field, responses) {
1149
1776
  const response = responses[field.id];
1150
1777
  const lines = [];
1151
1778
  lines.push(`**${field.label}:**`);
1779
+ lines.push("");
1152
1780
  const value = response?.state === "answered" ? response.value : void 0;
1153
1781
  switch (field.kind) {
1154
1782
  case "string": {
@@ -1179,9 +1807,10 @@ function serializeFieldRaw(field, responses) {
1179
1807
  case "multi_select": {
1180
1808
  const multiValue = value;
1181
1809
  const selectedSet = new Set(multiValue?.selected ?? []);
1182
- const selectedOpts = field.options.filter((opt) => selectedSet.has(opt.id));
1183
- if (selectedOpts.length > 0) for (const opt of selectedOpts) lines.push(`- ${opt.label}`);
1184
- else lines.push("_(none selected)_");
1810
+ for (const opt of field.options) {
1811
+ const marker = selectedSet.has(opt.id) ? "x" : " ";
1812
+ lines.push(`- [${marker}] ${opt.label}`);
1813
+ }
1185
1814
  break;
1186
1815
  }
1187
1816
  case "checkboxes": {
@@ -1194,13 +1823,13 @@ function serializeFieldRaw(field, responses) {
1194
1823
  }
1195
1824
  case "url": {
1196
1825
  const urlValue = value;
1197
- if (urlValue?.value) lines.push(urlValue.value);
1826
+ if (urlValue?.value) lines.push(formatUrlAsMarkdownLink(urlValue.value));
1198
1827
  else lines.push("_(empty)_");
1199
1828
  break;
1200
1829
  }
1201
1830
  case "url_list": {
1202
1831
  const urlListValue = value;
1203
- if (urlListValue?.items && urlListValue.items.length > 0) for (const item of urlListValue.items) lines.push(`- ${item}`);
1832
+ if (urlListValue?.items && urlListValue.items.length > 0) for (const item of urlListValue.items) lines.push(`- ${formatUrlAsMarkdownLink(item)}`);
1204
1833
  else lines.push("_(empty)_");
1205
1834
  break;
1206
1835
  }
@@ -3078,4 +3707,5 @@ function applyPatches(form, patches) {
3078
3707
  }
3079
3708
 
3080
3709
  //#endregion
3081
- export { isParseError as $, deriveExportPath as A, parseModelIdForDisplay as B, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as C, MAX_FORMS_IN_MENU as D, DEFAULT_ROLE_INSTRUCTIONS as E, SUGGESTED_LLMS as F, MarkformParseError as G, MarkformConfigError as H, WEB_SEARCH_CONFIG as I, ParseError as J, MarkformPatchError as K, formatSuggestedLlms as L, deriveSchemaPath as M, detectFileType as N, REPORT_EXTENSION as O, parseRolesFlag as P, isMarkformError as Q, getWebSearchConfig as R, DEFAULT_PRIORITY as S, DEFAULT_ROLES as T, MarkformError as U, MarkformAbortError as V, MarkformLlmError as W, isConfigError as X, isAbortError as Y, isLlmError as Z, DEFAULT_MAX_ISSUES_PER_TURN as _, validate as a, DEFAULT_MAX_TURNS as b, computeProgressSummary as c, serializeForm as d, isPatchError as et, serializeRawMarkdown as f, DEFAULT_FORMS_DIR as g, ALL_EXTENSIONS as h, inspect as i, deriveReportPath as j, USER_ROLE as k, computeStructureSummary as l, AGENT_ROLE as m, getAllFields as n, isValidationError as nt, computeAllSummaries as o, serializeReport as p, MarkformValidationError as q, getFieldsForRoles as r, computeFormState as s, applyPatches as t, isRetryableError as tt, isFormComplete as u, DEFAULT_MAX_PATCHES_PER_TURN as v, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as w, DEFAULT_PORT as x, DEFAULT_MAX_STEPS_PER_TURN as y, hasWebSearchSupport as z };
3710
+ export { isConfigError as $, MAX_FORMS_IN_MENU as A, formatSuggestedLlms as B, DEFAULT_MAX_TURNS as C, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as D, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as E, deriveSchemaPath as F, MarkformConfigError as G, hasWebSearchSupport as H, detectFileType as I, MarkformParseError as J, MarkformError as K, parseRolesFlag as L, USER_ROLE as M, deriveExportPath as N, DEFAULT_ROLES as O, deriveReportPath as P, isAbortError as Q, SUGGESTED_LLMS as R, DEFAULT_MAX_STEPS_PER_TURN as S, DEFAULT_PRIORITY as T, parseModelIdForDisplay as U, getWebSearchConfig as V, MarkformAbortError as W, MarkformValidationError as X, MarkformPatchError as Y, ParseError as Z, validateSyntaxConsistency as _, validate as a, isValidationError as at, DEFAULT_MAX_ISSUES_PER_TURN as b, computeProgressSummary as c, serializeForm as d, isLlmError as et, serializeRawMarkdown as f, preprocessCommentSyntax as g, detectSyntaxStyle as h, inspect as i, isRetryableError as it, REPORT_EXTENSION as j, DEFAULT_ROLE_INSTRUCTIONS as k, computeStructureSummary as l, friendlyUrlAbbrev as m, getAllFields as n, isParseError as nt, computeAllSummaries as o, serializeReport as p, MarkformLlmError as q, getFieldsForRoles as r, isPatchError as rt, computeFormState as s, applyPatches as t, isMarkformError as tt, isFormComplete as u, AGENT_ROLE as v, DEFAULT_PORT as w, DEFAULT_MAX_PATCHES_PER_TURN as x, DEFAULT_FORMS_DIR as y, WEB_SEARCH_CONFIG as z };
3711
+ //# sourceMappingURL=apply-CXsI5N9x.mjs.map