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.
- package/README.md +48 -39
- package/dist/ai-sdk.d.mts +3 -2
- package/dist/ai-sdk.mjs +3 -2
- package/dist/ai-sdk.mjs.map +1 -0
- package/dist/{apply-CUH_16RD.mjs → apply-CXsI5N9x.mjs} +639 -9
- package/dist/apply-CXsI5N9x.mjs.map +1 -0
- package/dist/bin.mjs +3 -2
- package/dist/bin.mjs.map +1 -0
- package/dist/{cli-ixzNVirs.mjs → cli-BsFessUW.mjs} +713 -120
- package/dist/cli-BsFessUW.mjs.map +1 -0
- package/dist/cli.d.mts +2 -1
- package/dist/cli.mjs +1 -1
- package/dist/{coreTypes-CnKsB1H3.d.mts → coreTypes-DE6Giau5.d.mts} +10 -1
- package/dist/coreTypes-DiCddBKu.mjs +2 -1
- package/dist/coreTypes-DiCddBKu.mjs.map +1 -0
- package/dist/index.d.mts +15 -2
- package/dist/index.mjs +2 -2
- package/dist/session-XDrocA3j.mjs +2 -1
- package/dist/session-XDrocA3j.mjs.map +1 -0
- package/dist/{shared-D3dNi-Gn.mjs → shared-CCq4haEV.mjs} +9 -1
- package/dist/shared-CCq4haEV.mjs.map +1 -0
- package/dist/shared-fUKfJ1UA.mjs +4 -0
- package/dist/{src-BKRKMdgR.mjs → src-Dv3IZSQU.mjs} +15 -6
- package/dist/src-Dv3IZSQU.mjs.map +1 -0
- package/docs/markform-reference.md +58 -7
- package/docs/markform-spec.md +142 -0
- package/examples/movie-research/movie-deep-research-mock-filled.form.md +64 -40
- package/examples/movie-research/movie-deep-research.form.md +58 -37
- package/examples/movie-research/movie-research-demo.form.md +9 -6
- package/examples/rejection-test/rejection-test-mock-filled.form.md +5 -7
- package/examples/rejection-test/rejection-test-mock-filled.report.md +4 -2
- package/examples/rejection-test/rejection-test-mock-filled.schema.json +1 -1
- package/examples/rejection-test/rejection-test.form.md +5 -7
- package/examples/rejection-test/rejection-test.session.yaml +10 -10
- package/examples/simple/simple-comment-syntax.form.md +79 -0
- package/examples/simple/simple-mock-filled.form.md +20 -9
- package/examples/simple/simple-mock-filled.report.md +30 -7
- package/examples/simple/simple-skipped-filled.form.md +22 -10
- package/examples/simple/simple-skipped-filled.report.md +27 -4
- package/examples/simple/simple-skipped-filled.yml +1 -1
- package/examples/simple/simple-with-skips.session.yaml +35 -19
- package/examples/simple/simple.form.md +20 -9
- package/examples/simple/simple.raw.md +18 -25
- package/examples/simple/simple.session.yaml +35 -19
- package/examples/startup-deep-research/startup-deep-research.form.md +126 -103
- package/examples/startup-research/startup-research-mock-filled.form.md +19 -10
- package/examples/startup-research/startup-research.form.md +36 -29
- package/package.json +9 -3
- 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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
1183
|
-
|
|
1184
|
-
|
|
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 {
|
|
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
|