cyber-agentkit 1.1.0 → 1.2.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
@@ -587,6 +587,588 @@ function headlessCommand(harness, options) {
587
587
  };
588
588
  }
589
589
  //#endregion
590
+ //#region src/prose-update/split-sentences.ts
591
+ const ABBREVIATIONS = [
592
+ "e.g.",
593
+ "i.e.",
594
+ "etc.",
595
+ "vs."
596
+ ];
597
+ const FENCE$2 = /^\s*(`{3,}|~{3,})/;
598
+ const HEADING$2 = /^#{1,6}\s/;
599
+ const ITEM = /^\s*(?:[-*+]|\d+[.)])\s/;
600
+ const ROW = /^\s*\|/;
601
+ const QUOTE = /^\s*>/;
602
+ function endsAbbreviation(before) {
603
+ return ABBREVIATIONS.some((abbreviation) => {
604
+ if (!before.endsWith(abbreviation)) return false;
605
+ const start = before.length - abbreviation.length;
606
+ return start === 0 || /\s/.test(before[start - 1]);
607
+ });
608
+ }
609
+ function splitParagraph(paragraph) {
610
+ const parts = [];
611
+ let start = 0;
612
+ for (let i = 0; i < paragraph.length; i++) {
613
+ if (!".;?!".includes(paragraph[i])) continue;
614
+ const next = paragraph[i + 1];
615
+ if (next !== void 0 && !/\s/.test(next)) continue;
616
+ if (paragraph[i] === "." && endsAbbreviation(paragraph.slice(start, i + 1))) continue;
617
+ parts.push(paragraph.slice(start, i + 1));
618
+ start = i + 1;
619
+ }
620
+ parts.push(paragraph.slice(start));
621
+ return parts.map((part) => part.trim()).filter(Boolean);
622
+ }
623
+ /**
624
+ * Splits text into sentences (K4): at `.`, `;`, `?` or `!` before whitespace or the end, except after an
625
+ * abbreviation; at every blank line; a heading line, list item and table row are one sentence each; a fenced
626
+ * code block and a blockquote are each one sentence.
627
+ */
628
+ function splitSentences(input) {
629
+ const lines = input.split(/\r?\n/);
630
+ const out = [];
631
+ let paragraph = [];
632
+ let blank = false;
633
+ const gapBefore = () => out.length === 0 ? "" : blank ? "\n\n" : "\n";
634
+ const add = (text, kind) => {
635
+ out.push({
636
+ text,
637
+ kind,
638
+ gap: gapBefore()
639
+ });
640
+ blank = false;
641
+ };
642
+ const flush = () => {
643
+ if (!paragraph.length) return;
644
+ splitParagraph(paragraph.join(" ")).forEach((text, index) => {
645
+ if (index === 0) add(text, "text");
646
+ else out.push({
647
+ text,
648
+ kind: "text",
649
+ gap: " "
650
+ });
651
+ });
652
+ paragraph = [];
653
+ };
654
+ for (let i = 0; i < lines.length; i++) {
655
+ const line = lines[i];
656
+ const fence = FENCE$2.exec(line);
657
+ if (fence) {
658
+ flush();
659
+ const marker = fence[1][0];
660
+ const block = [line.trimEnd()];
661
+ while (++i < lines.length) {
662
+ block.push(lines[i].trimEnd());
663
+ const close = FENCE$2.exec(lines[i]);
664
+ if (close && close[1][0] === marker && lines[i].trim() === close[1]) break;
665
+ }
666
+ add(block.join("\n"), "fence");
667
+ } else if (!line.trim()) {
668
+ flush();
669
+ blank = true;
670
+ } else if (HEADING$2.test(line)) {
671
+ flush();
672
+ add(line.trim(), "heading");
673
+ } else if (QUOTE.test(line)) {
674
+ flush();
675
+ const block = [line.trim()];
676
+ while (i + 1 < lines.length && QUOTE.test(lines[i + 1])) block.push(lines[++i].trim());
677
+ add(block.join("\n"), "quote");
678
+ } else if (ROW.test(line)) {
679
+ flush();
680
+ add(line.trim(), "row");
681
+ } else if (ITEM.test(line)) {
682
+ flush();
683
+ add(line.trim(), "item");
684
+ } else paragraph.push(line.trim());
685
+ }
686
+ flush();
687
+ return out;
688
+ }
689
+ /** Two sentences are equal when they match after each run of whitespace becomes one space (K4). */
690
+ function sameSentence(a, b) {
691
+ return a.replace(/\s+/g, " ").trim() === b.replace(/\s+/g, " ").trim();
692
+ }
693
+ //#endregion
694
+ //#region src/prose-update/align.ts
695
+ /**
696
+ * Pairs of indexes `[a, b]` of the longest common subsequence of two sentence lists (K10). Equal sentences
697
+ * repeated in both lists are matched in order, earliest first.
698
+ */
699
+ function matchSentences(a, b) {
700
+ const table = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
701
+ const at = (i, j) => table[i][j];
702
+ for (let i = a.length - 1; i >= 0; i--) for (let j = b.length - 1; j >= 0; j--) table[i][j] = sameSentence(a[i].text, b[j].text) ? at(i + 1, j + 1) + 1 : Math.max(at(i + 1, j), at(i, j + 1));
703
+ const pairs = [];
704
+ let i = 0;
705
+ let j = 0;
706
+ while (i < a.length && j < b.length) if (sameSentence(a[i].text, b[j].text) && at(i, j) === at(i + 1, j + 1) + 1) pairs.push([i++, j++]);
707
+ else if (at(i + 1, j) >= at(i, j + 1)) i++;
708
+ else j++;
709
+ return pairs;
710
+ }
711
+ /** The number of sentences two lists share (K10). */
712
+ function shared(a, b) {
713
+ return matchSentences(a, b).length;
714
+ }
715
+ //#endregion
716
+ //#region src/prose-update/history.ts
717
+ const HEADING$1 = /^## Superseded(?:\s+(.*))?$/;
718
+ const FENCE$1 = /^(`{3,}|~{3,})(.*)$/;
719
+ const SEPARATOR = " — ";
720
+ function linesOf(text) {
721
+ const lines = [];
722
+ let start = 0;
723
+ while (start <= text.length) {
724
+ const newline = text.indexOf("\n", start);
725
+ const end = newline === -1 ? text.length : newline;
726
+ lines.push({
727
+ text: text.slice(start, end),
728
+ start,
729
+ end
730
+ });
731
+ if (newline === -1) break;
732
+ start = newline + 1;
733
+ }
734
+ return lines;
735
+ }
736
+ /** Each line index that opens a `## Superseded` entry, outside fences. */
737
+ function entryHeadings(lines) {
738
+ const found = [];
739
+ let fence;
740
+ lines.forEach((line, index) => {
741
+ const open = FENCE$1.exec(line.text);
742
+ if (fence) {
743
+ if (open && open[1] === fence && !open[2]?.trim()) fence = void 0;
744
+ } else if (open) fence = open[1];
745
+ else if (HEADING$1.test(line.text)) found.push(index);
746
+ });
747
+ return found;
748
+ }
749
+ /**
750
+ * Reads the retired wordings of a history text, newest first. Each entry is a `## Superseded <date> — <reason>`
751
+ * heading followed by the wording in a fence. An entry that does not parse is returned in `invalid`.
752
+ */
753
+ function parseHistory(text) {
754
+ const lines = linesOf(text);
755
+ const headings = entryHeadings(lines);
756
+ const entries = [];
757
+ const invalid = [];
758
+ headings.forEach((headingIndex, position) => {
759
+ const heading = lines[headingIndex];
760
+ const limit = headings[position + 1] ?? lines.length;
761
+ const bad = () => {
762
+ invalid.push({
763
+ position,
764
+ line: headingIndex + 1,
765
+ heading: heading.text
766
+ });
767
+ };
768
+ const openIndex = lines.findIndex((line, index) => index > headingIndex && index < limit && FENCE$1.test(line.text));
769
+ if (openIndex === -1) {
770
+ bad();
771
+ return;
772
+ }
773
+ const open = lines[openIndex];
774
+ const marker = FENCE$1.exec(open.text)[1];
775
+ const closeIndex = lines.findIndex((line, index) => index > openIndex && index < limit && line.text.trimEnd() === marker);
776
+ const close = lines[closeIndex];
777
+ if (!close) {
778
+ bad();
779
+ return;
780
+ }
781
+ const rest = HEADING$1.exec(heading.text)[1] ?? "";
782
+ const split = rest.indexOf(SEPARATOR);
783
+ const wording = text.slice(open.end + 1, Math.max(open.end + 1, close.start - 1));
784
+ entries.push({
785
+ wording,
786
+ date: split === -1 ? rest.trim() : rest.slice(0, split).trim(),
787
+ reason: split === -1 ? "" : rest.slice(split + 3).trim()
788
+ });
789
+ });
790
+ return {
791
+ entries,
792
+ invalid
793
+ };
794
+ }
795
+ function fenceFor(wording) {
796
+ const longest = Math.max(0, ...(wording.match(/^\s*`{3,}/gm) ?? []).map((run) => run.trim().length));
797
+ return "`".repeat(Math.max(3, longest + 1));
798
+ }
799
+ /**
800
+ * Returns the history text with the outgoing wording added as its newest entry and every other byte unchanged.
801
+ * The wording equal to the newest retired one changes nothing.
802
+ */
803
+ function retire(history, wording, date, reason) {
804
+ if (parseHistory(history).entries[0]?.wording === wording) return history;
805
+ const fence = fenceFor(wording);
806
+ const entry = `## Superseded ${date} ${SEPARATOR.trim()} ${reason}\n\n${fence}markdown\n${wording}\n${fence}\n`;
807
+ const lines = linesOf(history);
808
+ const first = entryHeadings(lines)[0];
809
+ if (first !== void 0) {
810
+ const at = lines[first].start;
811
+ return `${history.slice(0, at)}${entry}\n${history.slice(at)}`;
812
+ }
813
+ const body = history.endsWith("\n") || history === "" ? history : `${history}\n`;
814
+ return `${body}${body ? "\n" : ""}${entry}`;
815
+ }
816
+ //#endregion
817
+ //#region src/prose-update/merge.ts
818
+ function equal(a, b) {
819
+ return a.length === b.length && a.every((sentence, index) => sameSentence(sentence.text, b[index].text));
820
+ }
821
+ /** The revision kind of an offered part over its base part (K6). */
822
+ function revisionKind(base, offered) {
823
+ return offered.length >= base.length && equal(base, offered.slice(0, base.length)) ? "append" : "rewrite";
824
+ }
825
+ function appendTo(base, found, added) {
826
+ const last = matchSentences(base, found).find(([i]) => i === base.length - 1);
827
+ const out = [...found];
828
+ let at = last ? last[1] + 1 : out.length;
829
+ for (const sentence of added) {
830
+ const had = out.findIndex((x, index) => index >= at && sameSentence(x.text, sentence.text));
831
+ if (had === -1) out.splice(at++, 0, sentence);
832
+ else at = had + 1;
833
+ }
834
+ return out;
835
+ }
836
+ /**
837
+ * Three-way merge at sentence level of one part (R6 to R9). An append revision puts the new sentences after
838
+ * the base's last sentence as found (R8) and never collides. A rewrite is merged between the sentences both
839
+ * sides left alone; where both changed the same stretch differently the found sentences stay and the stretch
840
+ * is reported as a collision.
841
+ */
842
+ function mergeSentences(base, found, offered) {
843
+ const revision = revisionKind(base, offered);
844
+ if (revision === "append") return {
845
+ revision,
846
+ merged: appendTo(base, found, offered.slice(base.length)),
847
+ collisions: []
848
+ };
849
+ const inFound = new Map(matchSentences(base, found));
850
+ const inOffered = new Map(matchSentences(base, offered));
851
+ const anchors = [...inFound.keys()].filter((i) => inOffered.has(i)).sort((x, y) => x - y);
852
+ const merged = [];
853
+ const collisions = [];
854
+ let b = 0;
855
+ let f = 0;
856
+ let o = 0;
857
+ for (const anchor of [...anchors, base.length]) {
858
+ const toB = anchor;
859
+ const toF = anchor === base.length ? found.length : inFound.get(anchor);
860
+ const toO = anchor === base.length ? offered.length : inOffered.get(anchor);
861
+ const segment = {
862
+ base: base.slice(b, toB),
863
+ found: found.slice(f, toF),
864
+ offered: offered.slice(o, toO)
865
+ };
866
+ if (equal(segment.found, segment.base)) merged.push(...segment.offered);
867
+ else if (equal(segment.offered, segment.base) || equal(segment.found, segment.offered)) merged.push(...segment.found);
868
+ else {
869
+ merged.push(...segment.found);
870
+ collisions.push(segment);
871
+ }
872
+ if (anchor < base.length) merged.push(found[toF]);
873
+ b = toB + 1;
874
+ f = toF + 1;
875
+ o = toO + 1;
876
+ }
877
+ return {
878
+ revision,
879
+ merged,
880
+ collisions
881
+ };
882
+ }
883
+ //#endregion
884
+ //#region src/prose-update/parts.ts
885
+ const HEADING = /^(#{1,6})[ \t](.*)$/;
886
+ const FENCE = /^\s*(`{3,}|~{3,})/;
887
+ function titleOf(text) {
888
+ let end = text.trimEnd().length;
889
+ while (end > 0 && text[end - 1] === "#") end--;
890
+ return text.slice(0, end).trim();
891
+ }
892
+ /** Divides a document into parts by heading path. A heading inside a fence is not a heading. */
893
+ function parseParts(input) {
894
+ const lines = input.split(/\r?\n/);
895
+ const raw = [{
896
+ level: 0,
897
+ title: "",
898
+ body: []
899
+ }];
900
+ let fence;
901
+ for (const line of lines) {
902
+ const open = FENCE.exec(line);
903
+ if (fence) {
904
+ if (open && open[1][0] === fence[0] && line.trim() === open[1]) fence = void 0;
905
+ } else if (open) fence = open[1];
906
+ else {
907
+ const heading = HEADING.exec(line);
908
+ if (heading) {
909
+ raw.push({
910
+ heading: line.trim(),
911
+ level: heading[1].length,
912
+ title: titleOf(heading[2]),
913
+ body: []
914
+ });
915
+ continue;
916
+ }
917
+ }
918
+ raw[raw.length - 1]?.body.push(line);
919
+ }
920
+ const stack = [];
921
+ const seen = /* @__PURE__ */ new Map();
922
+ const parts = [];
923
+ for (const entry of raw) {
924
+ const sentences = splitSentences(entry.body.join("\n"));
925
+ const first = sentences[0];
926
+ if (entry.heading !== void 0 && first) first.gap = entry.body[0]?.trim() === "" ? "\n\n" : "\n";
927
+ if (entry.heading === void 0) {
928
+ if (!sentences.length) continue;
929
+ } else {
930
+ while (stack.length && stack[stack.length - 1].level >= entry.level) stack.pop();
931
+ stack.push({
932
+ level: entry.level,
933
+ title: entry.title
934
+ });
935
+ }
936
+ const path = stack.map((item) => item.title);
937
+ const base = path.join(" > ");
938
+ const count = (seen.get(base) ?? 0) + 1;
939
+ seen.set(base, count);
940
+ parts.push({
941
+ path,
942
+ key: count === 1 ? base : `${base} #${count}`,
943
+ heading: entry.heading,
944
+ sentences,
945
+ raw: [entry.heading, ...entry.body].filter((line) => line !== void 0).join("\n").trimEnd().replace(/^\n+/, "")
946
+ });
947
+ }
948
+ return parts;
949
+ }
950
+ /** Writes a part: its heading, then each sentence after the separator it came with. */
951
+ function renderPart(heading, sentences) {
952
+ let out = heading ?? "";
953
+ sentences.forEach((sentence, index) => {
954
+ if (index > 0) out += sentence.gap || " ";
955
+ else if (heading !== void 0) out += sentence.gap === "\n" ? "\n" : "\n\n";
956
+ out += sentence.text;
957
+ });
958
+ return out;
959
+ }
960
+ /** Lines the parts of several texts up by key. Keys keep the order of their first appearance. */
961
+ function alignParts(texts) {
962
+ const rows = /* @__PURE__ */ new Map();
963
+ texts.forEach((parts, column) => {
964
+ for (const part of parts) {
965
+ const row = rows.get(part.key) ?? {
966
+ key: part.key,
967
+ parts: texts.map(() => void 0)
968
+ };
969
+ row.parts[column] = part;
970
+ rows.set(part.key, row);
971
+ }
972
+ });
973
+ return [...rows.values()];
974
+ }
975
+ //#endregion
976
+ //#region src/prose-update/classify.ts
977
+ /** The offered wording is empty, so nothing can be classified (R33). */
978
+ var ProseUpdateError = class extends Error {
979
+ code;
980
+ constructor(code, message) {
981
+ super(message);
982
+ this.code = code;
983
+ this.name = "ProseUpdateError";
984
+ }
985
+ };
986
+ const ORDER = [
987
+ "conflict",
988
+ "undetermined",
989
+ "update",
990
+ "owners",
991
+ "up-to-date"
992
+ ];
993
+ const norm = (text) => text.replace(/\s+/g, " ").trim();
994
+ const sameList = (a, b) => a.length === b.length && a.every((sentence, index) => sameSentence(sentence.text, b[index].text));
995
+ function retiredOf(history) {
996
+ const list = typeof history === "string" ? parseHistory(history).entries.map(({ wording, date }) => ({
997
+ wording,
998
+ date
999
+ })) : history.map((wording) => ({
1000
+ wording,
1001
+ date: void 0
1002
+ }));
1003
+ const seen = /* @__PURE__ */ new Set();
1004
+ return list.map(({ wording, date }, index) => ({
1005
+ text: wording,
1006
+ date,
1007
+ sentences: splitSentences(wording),
1008
+ index
1009
+ })).filter((retired) => {
1010
+ const key = retired.sentences.map((sentence) => norm(sentence.text)).join("\0");
1011
+ if (seen.has(key)) return false;
1012
+ seen.add(key);
1013
+ return true;
1014
+ });
1015
+ }
1016
+ function chooseBase(found, retired) {
1017
+ const counts = retired.map((r) => ({
1018
+ r,
1019
+ count: shared(found, r.sentences)
1020
+ }));
1021
+ const most = Math.max(0, ...counts.map((c) => c.count));
1022
+ if (most < 1) return {};
1023
+ const top = counts.filter((c) => c.count === most).map((c) => c.r);
1024
+ return top.length > 1 ? { tied: top } : { base: top[0] };
1025
+ }
1026
+ function render(part, heading, sentences) {
1027
+ return renderPart(heading ?? part?.heading, sentences);
1028
+ }
1029
+ /** Classifies each part of the found text against the offered wording and the history, and merges where it can. */
1030
+ function classify(input) {
1031
+ if (!splitSentences(input.offered).length) throw new ProseUpdateError("empty-offered", "The offered wording is empty.");
1032
+ const foundSentences = splitSentences(input.found);
1033
+ const foundParts = parseParts(input.found);
1034
+ const offeredParts = parseParts(input.offered);
1035
+ const retired = retiredOf(input.history);
1036
+ const whole = (reason, extra = {}) => {
1037
+ const keys = alignParts([foundParts, offeredParts]);
1038
+ return {
1039
+ outcome: "undetermined",
1040
+ reason,
1041
+ parts: (keys.length ? keys : [{
1042
+ key: "",
1043
+ parts: []
1044
+ }]).map((row) => ({
1045
+ path: (row.parts[0] ?? row.parts[1])?.path ?? [],
1046
+ key: row.key,
1047
+ outcome: "undetermined",
1048
+ reason,
1049
+ ...extra
1050
+ })),
1051
+ text: input.found
1052
+ };
1053
+ };
1054
+ if (!foundSentences.length) return whole("empty-section");
1055
+ if (!retired.length) return whole("no-history");
1056
+ const { base, tied } = chooseBase(foundSentences, retired);
1057
+ if (tied) return whole("tied-base", { tied: tied.map((r) => r.index) });
1058
+ const baseParts = base ? parseParts(base.text) : [];
1059
+ const historySet = new Set(retired.flatMap((r) => r.sentences.map((sentence) => norm(sentence.text))));
1060
+ const baseRef = base && {
1061
+ index: base.index,
1062
+ date: base.date
1063
+ };
1064
+ const rows = alignParts([
1065
+ baseParts,
1066
+ foundParts,
1067
+ offeredParts
1068
+ ]);
1069
+ const results = /* @__PURE__ */ new Map();
1070
+ const merged = /* @__PURE__ */ new Map();
1071
+ for (const row of rows) {
1072
+ const [b, f, o] = row.parts;
1073
+ const result = {
1074
+ path: (f ?? o ?? b)?.path ?? [],
1075
+ key: row.key,
1076
+ outcome: "owners"
1077
+ };
1078
+ results.set(row.key, result);
1079
+ const withBase = (outcome, baseSentences, foundSentencesOfPart, offeredSentencesOfPart) => {
1080
+ result.outcome = outcome;
1081
+ if (baseRef) result.base = baseRef;
1082
+ result.shared = shared(foundSentencesOfPart, baseSentences);
1083
+ result.baseSentences = baseSentences.length;
1084
+ result.revision = revisionKind(baseSentences, offeredSentencesOfPart);
1085
+ };
1086
+ if (!f && o) {
1087
+ if (!b) {
1088
+ result.outcome = "update";
1089
+ result.text = o.raw;
1090
+ merged.set(row.key, o.raw);
1091
+ } else if (sameList(b.sentences, o.sentences)) {
1092
+ result.outcome = "up-to-date";
1093
+ result.text = "";
1094
+ } else {
1095
+ withBase("conflict", b.sentences, [], o.sentences);
1096
+ result.collisions = [{
1097
+ base: b.sentences,
1098
+ found: [],
1099
+ offered: o.sentences
1100
+ }];
1101
+ }
1102
+ } else if (f && !o) {
1103
+ if (!b) result.text = f.raw;
1104
+ else if (sameList(b.sentences, f.sentences)) {
1105
+ result.outcome = "update";
1106
+ result.text = "";
1107
+ merged.set(row.key, "");
1108
+ } else {
1109
+ withBase("conflict", b.sentences, f.sentences, []);
1110
+ result.collisions = [{
1111
+ base: b.sentences,
1112
+ found: f.sentences,
1113
+ offered: []
1114
+ }];
1115
+ }
1116
+ } else if (f && o) {
1117
+ const stale = f.sentences.some((sentence) => historySet.has(norm(sentence.text)) && !o.sentences.some((x) => sameSentence(x.text, sentence.text)));
1118
+ const common = shared(f.sentences, o.sentences);
1119
+ if (!f.sentences.length) {
1120
+ result.outcome = "undetermined";
1121
+ result.reason = "empty-section";
1122
+ } else if (common === o.sentences.length && !stale) {
1123
+ result.outcome = "up-to-date";
1124
+ result.text = f.raw;
1125
+ } else if (!base || !b?.sentences.length) {
1126
+ if (common > 0) {
1127
+ result.outcome = "undetermined";
1128
+ result.reason = "no-base";
1129
+ } else result.text = f.raw;
1130
+ } else if (!shared(f.sentences, b.sentences) && common === 0) result.text = f.raw;
1131
+ else {
1132
+ const merge = mergeSentences(b.sentences, f.sentences, o.sentences);
1133
+ withBase(merge.collisions.length ? "conflict" : "update", b.sentences, f.sentences, o.sentences);
1134
+ result.text = render(f, f.heading ?? o.heading, merge.merged);
1135
+ merged.set(row.key, result.text);
1136
+ if (merge.collisions.length) result.collisions = merge.collisions;
1137
+ }
1138
+ }
1139
+ }
1140
+ const outcome = ORDER.find((o) => [...results.values()].some((r) => r.outcome === o));
1141
+ const changed = [...results.values()].some((r) => r.outcome === "update" || r.outcome === "conflict");
1142
+ const parts = documentOrder(foundParts, offeredParts, results).map((key) => results.get(key));
1143
+ const text = changed ? assemble(input.found, foundParts, offeredParts, merged, results) : input.found;
1144
+ const reason = outcome === "undetermined" ? parts.find((part) => part.outcome === "undetermined")?.reason : void 0;
1145
+ return {
1146
+ outcome,
1147
+ ...reason ? { reason } : {},
1148
+ ...baseRef ? { base: baseRef } : {},
1149
+ parts,
1150
+ text
1151
+ };
1152
+ }
1153
+ /** Found parts in order, each added part after the nearest preceding offered part the found text holds (R22). */
1154
+ function documentOrder(found, offered, results) {
1155
+ const inFound = new Set(found.map((part) => part.key));
1156
+ const after = /* @__PURE__ */ new Map();
1157
+ offered.forEach((part, index) => {
1158
+ if (inFound.has(part.key)) return;
1159
+ const anchor = offered.slice(0, index).reverse().find((earlier) => inFound.has(earlier.key))?.key;
1160
+ after.set(anchor, [...after.get(anchor) ?? [], part.key]);
1161
+ });
1162
+ const keys = [...after.get(void 0) ?? []];
1163
+ for (const part of found) keys.push(part.key, ...after.get(part.key) ?? []);
1164
+ const rest = [...results.keys()].filter((key) => !keys.includes(key));
1165
+ return [...keys, ...rest];
1166
+ }
1167
+ function assemble(found, foundParts, offeredParts, merged, results) {
1168
+ const raw = new Map([...offeredParts, ...foundParts].map((part) => [part.key, part.raw]));
1169
+ return documentOrder(foundParts, offeredParts, results).filter((key) => results.get(key).outcome !== "up-to-date" || foundParts.some((p) => p.key === key)).map((key) => merged.get(key) ?? (foundParts.some((p) => p.key === key) ? raw.get(key) : void 0)).filter((piece) => piece !== void 0 && piece !== "").join("\n\n") + (found.endsWith("\n") ? "\n" : "");
1170
+ }
1171
+ //#endregion
590
1172
  //#region src/skills/skill-invocation.ts
591
1173
  /**
592
1174
  * How a user invokes a plugin's skill in a harness.
@@ -662,4 +1244,4 @@ function skillInvocation(harness, { plugin, skill }) {
662
1244
  }
663
1245
  }
664
1246
  //#endregion
665
- export { adminSkillFolders, checkPluginDependencies, checkReferences, declaredDependencies, declaredPluginDependencies, defaultLayer, detectHarness, enabledPlugins, harnessIds, harnessSkillFolders, headlessCommand, headlessInvocation, installedPlugins, listReferences, loadAsset, loadReference, managedPolicyLocations, packageDir, parseReferenceName, pluginManifestName, pluginManifestPaths, pluginSkillFolders, pluginStorage, projectReferencesDir, readPluginDependencies, referenceImplementations, referenceLayers, referenceNames, resolveReference, searchReferences, skillFoldersVerified, skillInvocation, skillName, skillsDirectories, skillsIn, supportsPluginDependencies, userReferencesDir, whereReference };
1247
+ export { ProseUpdateError, adminSkillFolders, checkPluginDependencies, checkReferences, classify, declaredDependencies, declaredPluginDependencies, defaultLayer, detectHarness, enabledPlugins, harnessIds, harnessSkillFolders, headlessCommand, headlessInvocation, installedPlugins, listReferences, loadAsset, loadReference, managedPolicyLocations, packageDir, parseHistory, parseReferenceName, pluginManifestName, pluginManifestPaths, pluginSkillFolders, pluginStorage, projectReferencesDir, readPluginDependencies, referenceImplementations, referenceLayers, referenceNames, resolveReference, retire, searchReferences, skillFoldersVerified, skillInvocation, skillName, skillsDirectories, skillsIn, supportsPluginDependencies, userReferencesDir, whereReference };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyber-agentkit",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "An agent-harness-agnostic toolkit: the fundamental features agent tools need, working the same on every harness. Detect the running harness, check plugin dependencies, resolve layered reference documents, and lease git worktrees.",
5
5
  "keywords": [
6
6
  "agent-harness",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "cyber-agentkit",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "description": "An agent-harness-agnostic toolkit: the fundamental features agent tools need, working the same on every harness. Detect the running harness, check plugin dependencies, resolve layered reference documents, and lease git worktrees.",
6
6
  "author": {
7
7
  "name": "unional"
@@ -21,7 +21,8 @@
21
21
  },
22
22
  "scripts": {
23
23
  "skills/reference/scripts/reference.mjs": "src/references/reference.skill-script.ts",
24
- "skills/asset/scripts/asset.mjs": "src/assets/asset.skill-script.ts"
24
+ "skills/asset/scripts/asset.mjs": "src/assets/asset.skill-script.ts",
25
+ "skills/prose-update/scripts/prose-update.mjs": "src/prose-update/prose-update.skill-script.ts"
25
26
  }
26
27
  }
27
28
  }