@mindstudio-ai/remy 0.1.213 → 0.1.215
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/headless.js +174 -232
- package/dist/index.js +179 -236
- package/dist/prompt/static/authoring.md +1 -1
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -393,6 +393,9 @@ async function initOrgContext(config) {
|
|
|
393
393
|
log3.debug("org context init failed", { error: err.message });
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
|
+
function getOrgContext() {
|
|
397
|
+
return cached;
|
|
398
|
+
}
|
|
396
399
|
function renderOrgContextBlock() {
|
|
397
400
|
const ctx = cached;
|
|
398
401
|
const auth = ctx?.auth;
|
|
@@ -724,88 +727,23 @@ ${loadPlanStatus()}
|
|
|
724
727
|
import fs5 from "fs/promises";
|
|
725
728
|
|
|
726
729
|
// src/tools/spec/_helpers.ts
|
|
727
|
-
var HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
728
|
-
function parseHeadings(content) {
|
|
729
|
-
const lines = content.split("\n");
|
|
730
|
-
const headings = [];
|
|
731
|
-
for (let i = 0; i < lines.length; i++) {
|
|
732
|
-
const match = lines[i].match(HEADING_RE);
|
|
733
|
-
if (match) {
|
|
734
|
-
headings.push({
|
|
735
|
-
level: match[1].length,
|
|
736
|
-
text: match[2].trim(),
|
|
737
|
-
startLine: i,
|
|
738
|
-
contentStart: i + 1,
|
|
739
|
-
contentEnd: lines.length
|
|
740
|
-
// placeholder — resolved below
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
for (let i = 0; i < headings.length; i++) {
|
|
745
|
-
const current = headings[i];
|
|
746
|
-
let end = lines.length;
|
|
747
|
-
for (let j = i + 1; j < headings.length; j++) {
|
|
748
|
-
if (headings[j].level <= current.level) {
|
|
749
|
-
end = headings[j].startLine;
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
current.contentEnd = end;
|
|
754
|
-
}
|
|
755
|
-
return headings;
|
|
756
|
-
}
|
|
757
|
-
function resolveHeadingPath(content, headingPath) {
|
|
758
|
-
const lines = content.split("\n");
|
|
759
|
-
const headings = parseHeadings(content);
|
|
760
|
-
if (headingPath === "") {
|
|
761
|
-
const firstHeadingLine = headings.length > 0 ? headings[0].startLine : lines.length;
|
|
762
|
-
return {
|
|
763
|
-
startLine: 0,
|
|
764
|
-
contentStart: 0,
|
|
765
|
-
contentEnd: firstHeadingLine
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
const segments = headingPath.split(">").map((s) => s.trim());
|
|
769
|
-
let searchStart = 0;
|
|
770
|
-
let searchEnd = lines.length;
|
|
771
|
-
let resolved = null;
|
|
772
|
-
for (let si = 0; si < segments.length; si++) {
|
|
773
|
-
const segment = segments[si].toLowerCase();
|
|
774
|
-
const candidates = headings.filter(
|
|
775
|
-
(h) => h.startLine >= searchStart && h.startLine < searchEnd && h.text.toLowerCase() === segment
|
|
776
|
-
);
|
|
777
|
-
if (candidates.length === 0) {
|
|
778
|
-
const available = headings.filter((h) => h.startLine >= searchStart && h.startLine < searchEnd).map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n");
|
|
779
|
-
const searchedPath = segments.slice(0, si + 1).join(" > ");
|
|
780
|
-
throw new Error(
|
|
781
|
-
`Heading not found: "${searchedPath}"
|
|
782
|
-
|
|
783
|
-
Available headings:
|
|
784
|
-
${available || "(none)"}`
|
|
785
|
-
);
|
|
786
|
-
}
|
|
787
|
-
resolved = candidates[0];
|
|
788
|
-
searchStart = resolved.contentStart;
|
|
789
|
-
searchEnd = resolved.contentEnd;
|
|
790
|
-
}
|
|
791
|
-
return {
|
|
792
|
-
startLine: resolved.startLine,
|
|
793
|
-
contentStart: resolved.contentStart,
|
|
794
|
-
contentEnd: resolved.contentEnd
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
730
|
function validateSpecPath(filePath) {
|
|
798
731
|
if (!filePath.startsWith("src/")) {
|
|
799
732
|
throw new Error(`Spec tool paths must start with src/. Got: "${filePath}"`);
|
|
800
733
|
}
|
|
801
734
|
return filePath;
|
|
802
735
|
}
|
|
803
|
-
function
|
|
804
|
-
const
|
|
805
|
-
if (
|
|
806
|
-
return
|
|
736
|
+
function extractFrontmatter(content) {
|
|
737
|
+
const lines = content.split("\n");
|
|
738
|
+
if (lines.length === 0 || lines[0].trim() !== "---") {
|
|
739
|
+
return null;
|
|
807
740
|
}
|
|
808
|
-
|
|
741
|
+
for (let i = 1; i < lines.length; i++) {
|
|
742
|
+
if (lines[i].trim() === "---") {
|
|
743
|
+
return lines.slice(0, i + 1).join("\n");
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return null;
|
|
809
747
|
}
|
|
810
748
|
|
|
811
749
|
// src/tools/spec/readSpec.ts
|
|
@@ -981,11 +919,92 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
981
919
|
|
|
982
920
|
// src/tools/spec/editSpec.ts
|
|
983
921
|
import fs7 from "fs/promises";
|
|
922
|
+
|
|
923
|
+
// src/tools/code/editFile/_helpers.ts
|
|
924
|
+
function buildLineOffsets(content) {
|
|
925
|
+
const offsets = [0];
|
|
926
|
+
for (let i = 0; i < content.length; i++) {
|
|
927
|
+
if (content[i] === "\n") {
|
|
928
|
+
offsets.push(i + 1);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return offsets;
|
|
932
|
+
}
|
|
933
|
+
function lineAtOffset(offsets, charIndex) {
|
|
934
|
+
let lo = 0;
|
|
935
|
+
let hi = offsets.length - 1;
|
|
936
|
+
while (lo < hi) {
|
|
937
|
+
const mid = lo + hi + 1 >> 1;
|
|
938
|
+
if (offsets[mid] <= charIndex) {
|
|
939
|
+
lo = mid;
|
|
940
|
+
} else {
|
|
941
|
+
hi = mid - 1;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return lo + 1;
|
|
945
|
+
}
|
|
946
|
+
function findOccurrences(content, searchString) {
|
|
947
|
+
if (!searchString) {
|
|
948
|
+
return [];
|
|
949
|
+
}
|
|
950
|
+
const offsets = buildLineOffsets(content);
|
|
951
|
+
const results = [];
|
|
952
|
+
let pos = 0;
|
|
953
|
+
while (pos <= content.length - searchString.length) {
|
|
954
|
+
const idx = content.indexOf(searchString, pos);
|
|
955
|
+
if (idx === -1) {
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
results.push({ index: idx, line: lineAtOffset(offsets, idx) });
|
|
959
|
+
pos = idx + 1;
|
|
960
|
+
}
|
|
961
|
+
return results;
|
|
962
|
+
}
|
|
963
|
+
function flexibleMatch(content, searchString) {
|
|
964
|
+
const contentLines = content.split("\n");
|
|
965
|
+
const searchLines = searchString.split("\n").map((l) => l.trimStart());
|
|
966
|
+
if (searchLines.length === 0) {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
const matches = [];
|
|
970
|
+
for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
|
|
971
|
+
let allMatch = true;
|
|
972
|
+
for (let j = 0; j < searchLines.length; j++) {
|
|
973
|
+
if (contentLines[i + j].trimStart() !== searchLines[j]) {
|
|
974
|
+
allMatch = false;
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (allMatch) {
|
|
979
|
+
matches.push(i);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (matches.length !== 1) {
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
const startIdx = matches[0];
|
|
986
|
+
const matchedText = contentLines.slice(startIdx, startIdx + searchLines.length).join("\n");
|
|
987
|
+
const offsets = buildLineOffsets(content);
|
|
988
|
+
return {
|
|
989
|
+
matchedText,
|
|
990
|
+
index: offsets[startIdx],
|
|
991
|
+
line: startIdx + 1
|
|
992
|
+
// 1-based
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function replaceAt(content, index, oldLength, newString) {
|
|
996
|
+
return content.slice(0, index) + newString + content.slice(index + oldLength);
|
|
997
|
+
}
|
|
998
|
+
function formatOccurrenceError(count, lines, filePath) {
|
|
999
|
+
return `old_string found ${count} times in ${filePath} (at lines ${lines.join(", ")}) \u2014 must be unique. Include more surrounding context to disambiguate, or use replace_all to replace every occurrence.`;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/tools/spec/editSpec.ts
|
|
984
1003
|
var editSpecTool = {
|
|
985
1004
|
clearable: true,
|
|
986
1005
|
definition: {
|
|
987
1006
|
name: "editSpec",
|
|
988
|
-
description:
|
|
1007
|
+
description: "Make a targeted find/replace edit to a spec file (src/*.md). old_string must appear exactly once (minor indentation differences are handled automatically); set replace_all to true to replace every occurrence. Read the file with readSpec first so you match the exact text, and include the full enclosing structure rather than an inner fragment. The file's YAML frontmatter (the leading --- \u2026 --- block, which holds required fields like name) is protected \u2014 an edit that would remove or malform it is refused. For a full rewrite, use writeSpec.",
|
|
989
1008
|
inputSchema: {
|
|
990
1009
|
type: "object",
|
|
991
1010
|
properties: {
|
|
@@ -993,31 +1012,20 @@ var editSpecTool = {
|
|
|
993
1012
|
type: "string",
|
|
994
1013
|
description: "File path relative to project root, must start with src/ (e.g., src/app.md)."
|
|
995
1014
|
},
|
|
996
|
-
|
|
997
|
-
type: "
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
enum: ["replace", "insert_after", "insert_before", "delete"],
|
|
1008
|
-
description: "replace: swap content under this heading (keeps the heading line). insert_after: add content after this section. insert_before: add content before this heading. delete: remove this heading and all its content."
|
|
1009
|
-
},
|
|
1010
|
-
content: {
|
|
1011
|
-
type: "string",
|
|
1012
|
-
description: "MSFM markdown content for replace/insert operations. Not needed for delete."
|
|
1013
|
-
}
|
|
1014
|
-
},
|
|
1015
|
-
required: ["heading", "operation"]
|
|
1016
|
-
},
|
|
1017
|
-
description: "Array of edits to apply in order."
|
|
1015
|
+
old_string: {
|
|
1016
|
+
type: "string",
|
|
1017
|
+
description: "The exact string to find and replace. Must be unique in the file unless replace_all is true."
|
|
1018
|
+
},
|
|
1019
|
+
new_string: {
|
|
1020
|
+
type: "string",
|
|
1021
|
+
description: "The replacement string."
|
|
1022
|
+
},
|
|
1023
|
+
replace_all: {
|
|
1024
|
+
type: "boolean",
|
|
1025
|
+
description: "If true, replace every occurrence of old_string in the file. Defaults to false."
|
|
1018
1026
|
}
|
|
1019
1027
|
},
|
|
1020
|
-
required: ["path", "
|
|
1028
|
+
required: ["path", "old_string", "new_string"]
|
|
1021
1029
|
}
|
|
1022
1030
|
},
|
|
1023
1031
|
async execute(input) {
|
|
@@ -1028,69 +1036,66 @@ var editSpecTool = {
|
|
|
1028
1036
|
}
|
|
1029
1037
|
const release = await acquireFileLock(input.path);
|
|
1030
1038
|
try {
|
|
1031
|
-
let
|
|
1039
|
+
let content;
|
|
1032
1040
|
try {
|
|
1033
|
-
|
|
1041
|
+
content = await fs7.readFile(input.path, "utf-8");
|
|
1034
1042
|
} catch (err) {
|
|
1035
1043
|
return `Error reading file: ${err.message}`;
|
|
1036
1044
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
return `Error: ${err.message}
|
|
1045
|
-
|
|
1046
|
-
Document structure:
|
|
1047
|
-
${tree}`;
|
|
1045
|
+
const { old_string, new_string, replace_all } = input;
|
|
1046
|
+
const occurrences = findOccurrences(content, old_string);
|
|
1047
|
+
let updated;
|
|
1048
|
+
let notePrefix;
|
|
1049
|
+
if (replace_all) {
|
|
1050
|
+
if (occurrences.length === 0) {
|
|
1051
|
+
return `Error: old_string not found in ${input.path}.`;
|
|
1048
1052
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
lines.splice(range.startLine, 0, ...contentLines);
|
|
1077
|
-
break;
|
|
1078
|
-
}
|
|
1079
|
-
case "delete": {
|
|
1080
|
-
lines.splice(range.startLine, range.contentEnd - range.startLine);
|
|
1081
|
-
break;
|
|
1082
|
-
}
|
|
1083
|
-
default:
|
|
1084
|
-
return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
|
|
1053
|
+
updated = content;
|
|
1054
|
+
for (let i = occurrences.length - 1; i >= 0; i--) {
|
|
1055
|
+
updated = replaceAt(
|
|
1056
|
+
updated,
|
|
1057
|
+
occurrences[i].index,
|
|
1058
|
+
old_string.length,
|
|
1059
|
+
new_string
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
notePrefix = `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
|
|
1063
|
+
`;
|
|
1064
|
+
} else if (occurrences.length === 1) {
|
|
1065
|
+
updated = replaceAt(
|
|
1066
|
+
content,
|
|
1067
|
+
occurrences[0].index,
|
|
1068
|
+
old_string.length,
|
|
1069
|
+
new_string
|
|
1070
|
+
);
|
|
1071
|
+
notePrefix = `Updated ${input.path}
|
|
1072
|
+
`;
|
|
1073
|
+
} else if (occurrences.length > 1) {
|
|
1074
|
+
const lines = occurrences.map((o) => o.line);
|
|
1075
|
+
return `Error: ${formatOccurrenceError(occurrences.length, lines, input.path)}`;
|
|
1076
|
+
} else {
|
|
1077
|
+
const flex = flexibleMatch(content, old_string);
|
|
1078
|
+
if (!flex) {
|
|
1079
|
+
return `Error: old_string not found in ${input.path}. Make sure you've read the file first and copied the exact text.`;
|
|
1085
1080
|
}
|
|
1086
|
-
|
|
1081
|
+
updated = replaceAt(
|
|
1082
|
+
content,
|
|
1083
|
+
flex.index,
|
|
1084
|
+
flex.matchedText.length,
|
|
1085
|
+
new_string
|
|
1086
|
+
);
|
|
1087
|
+
notePrefix = `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
|
|
1088
|
+
`;
|
|
1089
|
+
}
|
|
1090
|
+
if (extractFrontmatter(content) !== null && extractFrontmatter(updated) === null) {
|
|
1091
|
+
return `Error: that edit would remove or malform the spec's YAML frontmatter (the leading \`--- \u2026 ---\` block, which holds required fields like \`name\`). Narrow old_string to the body content you meant to change and leave the frontmatter block intact.`;
|
|
1087
1092
|
}
|
|
1088
1093
|
try {
|
|
1089
|
-
await fs7.writeFile(input.path,
|
|
1094
|
+
await fs7.writeFile(input.path, updated, "utf-8");
|
|
1090
1095
|
} catch (err) {
|
|
1091
1096
|
return `Error writing file: ${err.message}`;
|
|
1092
1097
|
}
|
|
1093
|
-
return unifiedDiff(input.path,
|
|
1098
|
+
return `${notePrefix}${unifiedDiff(input.path, content, updated)}`;
|
|
1094
1099
|
} finally {
|
|
1095
1100
|
release();
|
|
1096
1101
|
}
|
|
@@ -1868,87 +1873,6 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
1868
1873
|
|
|
1869
1874
|
// src/tools/code/editFile/index.ts
|
|
1870
1875
|
import fs14 from "fs/promises";
|
|
1871
|
-
|
|
1872
|
-
// src/tools/code/editFile/_helpers.ts
|
|
1873
|
-
function buildLineOffsets(content) {
|
|
1874
|
-
const offsets = [0];
|
|
1875
|
-
for (let i = 0; i < content.length; i++) {
|
|
1876
|
-
if (content[i] === "\n") {
|
|
1877
|
-
offsets.push(i + 1);
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
return offsets;
|
|
1881
|
-
}
|
|
1882
|
-
function lineAtOffset(offsets, charIndex) {
|
|
1883
|
-
let lo = 0;
|
|
1884
|
-
let hi = offsets.length - 1;
|
|
1885
|
-
while (lo < hi) {
|
|
1886
|
-
const mid = lo + hi + 1 >> 1;
|
|
1887
|
-
if (offsets[mid] <= charIndex) {
|
|
1888
|
-
lo = mid;
|
|
1889
|
-
} else {
|
|
1890
|
-
hi = mid - 1;
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
return lo + 1;
|
|
1894
|
-
}
|
|
1895
|
-
function findOccurrences(content, searchString) {
|
|
1896
|
-
if (!searchString) {
|
|
1897
|
-
return [];
|
|
1898
|
-
}
|
|
1899
|
-
const offsets = buildLineOffsets(content);
|
|
1900
|
-
const results = [];
|
|
1901
|
-
let pos = 0;
|
|
1902
|
-
while (pos <= content.length - searchString.length) {
|
|
1903
|
-
const idx = content.indexOf(searchString, pos);
|
|
1904
|
-
if (idx === -1) {
|
|
1905
|
-
break;
|
|
1906
|
-
}
|
|
1907
|
-
results.push({ index: idx, line: lineAtOffset(offsets, idx) });
|
|
1908
|
-
pos = idx + 1;
|
|
1909
|
-
}
|
|
1910
|
-
return results;
|
|
1911
|
-
}
|
|
1912
|
-
function flexibleMatch(content, searchString) {
|
|
1913
|
-
const contentLines = content.split("\n");
|
|
1914
|
-
const searchLines = searchString.split("\n").map((l) => l.trimStart());
|
|
1915
|
-
if (searchLines.length === 0) {
|
|
1916
|
-
return null;
|
|
1917
|
-
}
|
|
1918
|
-
const matches = [];
|
|
1919
|
-
for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
|
|
1920
|
-
let allMatch = true;
|
|
1921
|
-
for (let j = 0; j < searchLines.length; j++) {
|
|
1922
|
-
if (contentLines[i + j].trimStart() !== searchLines[j]) {
|
|
1923
|
-
allMatch = false;
|
|
1924
|
-
break;
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
if (allMatch) {
|
|
1928
|
-
matches.push(i);
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
if (matches.length !== 1) {
|
|
1932
|
-
return null;
|
|
1933
|
-
}
|
|
1934
|
-
const startIdx = matches[0];
|
|
1935
|
-
const matchedText = contentLines.slice(startIdx, startIdx + searchLines.length).join("\n");
|
|
1936
|
-
const offsets = buildLineOffsets(content);
|
|
1937
|
-
return {
|
|
1938
|
-
matchedText,
|
|
1939
|
-
index: offsets[startIdx],
|
|
1940
|
-
line: startIdx + 1
|
|
1941
|
-
// 1-based
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
function replaceAt(content, index, oldLength, newString) {
|
|
1945
|
-
return content.slice(0, index) + newString + content.slice(index + oldLength);
|
|
1946
|
-
}
|
|
1947
|
-
function formatOccurrenceError(count, lines, filePath) {
|
|
1948
|
-
return `old_string found ${count} times in ${filePath} (at lines ${lines.join(", ")}) \u2014 must be unique. Include more surrounding context to disambiguate, or use replace_all to replace every occurrence.`;
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
// src/tools/code/editFile/index.ts
|
|
1952
1876
|
var editFileTool = {
|
|
1953
1877
|
clearable: true,
|
|
1954
1878
|
definition: {
|
|
@@ -5036,6 +4960,16 @@ var PROMPT_TEMPLATE = readAsset(SUBAGENT, "prompt.md").replace(/\{\{([^}]+)\}\}/
|
|
|
5036
4960
|
const k = key.trim();
|
|
5037
4961
|
return RUNTIME_PLACEHOLDERS.has(k) ? match : readAsset(SUBAGENT, k);
|
|
5038
4962
|
}).replace(/\n{3,}/g, "\n\n");
|
|
4963
|
+
function renderDesignSystemBlock(designSystem) {
|
|
4964
|
+
if (!designSystem) {
|
|
4965
|
+
return "";
|
|
4966
|
+
}
|
|
4967
|
+
return [
|
|
4968
|
+
"<design_system>",
|
|
4969
|
+
`The organization that owns this app publishes a design system at ${designSystem}. Treat it as the primary reference for this app's foundational visual language \u2014 palette, type, spacing, and the core component patterns that should feel consistent with the org's other products. Consult it for foundational decisions and fetch it with scrapeWebUrl when you need specifics (it's usually a hosted doc such as an llms.txt). It's a resource to orient toward, not a rulebook: you don't need to ground every detail in it, and app-specific or expressive choices are still yours to make.`,
|
|
4970
|
+
"</design_system>"
|
|
4971
|
+
].join("\n");
|
|
4972
|
+
}
|
|
5039
4973
|
function getDesignExpertPrompt(onboardingState) {
|
|
5040
4974
|
const specContext = loadSpecIndex();
|
|
5041
4975
|
const indices = getSampleIndices(
|
|
@@ -5065,6 +4999,14 @@ function getDesignExpertPrompt(onboardingState) {
|
|
|
5065
4999
|
prompt += `
|
|
5066
5000
|
|
|
5067
5001
|
${specContext}`;
|
|
5002
|
+
}
|
|
5003
|
+
const designSystemBlock = renderDesignSystemBlock(
|
|
5004
|
+
getOrgContext()?.designSystem
|
|
5005
|
+
);
|
|
5006
|
+
if (designSystemBlock) {
|
|
5007
|
+
prompt += `
|
|
5008
|
+
|
|
5009
|
+
${designSystemBlock}`;
|
|
5068
5010
|
}
|
|
5069
5011
|
const state = onboardingState ?? "onboardingFinished";
|
|
5070
5012
|
if (state !== "onboardingFinished") {
|
package/dist/index.js
CHANGED
|
@@ -369,93 +369,27 @@ var init_api = __esm({
|
|
|
369
369
|
});
|
|
370
370
|
|
|
371
371
|
// src/tools/spec/_helpers.ts
|
|
372
|
-
function parseHeadings(content) {
|
|
373
|
-
const lines = content.split("\n");
|
|
374
|
-
const headings = [];
|
|
375
|
-
for (let i = 0; i < lines.length; i++) {
|
|
376
|
-
const match = lines[i].match(HEADING_RE);
|
|
377
|
-
if (match) {
|
|
378
|
-
headings.push({
|
|
379
|
-
level: match[1].length,
|
|
380
|
-
text: match[2].trim(),
|
|
381
|
-
startLine: i,
|
|
382
|
-
contentStart: i + 1,
|
|
383
|
-
contentEnd: lines.length
|
|
384
|
-
// placeholder — resolved below
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
for (let i = 0; i < headings.length; i++) {
|
|
389
|
-
const current = headings[i];
|
|
390
|
-
let end = lines.length;
|
|
391
|
-
for (let j = i + 1; j < headings.length; j++) {
|
|
392
|
-
if (headings[j].level <= current.level) {
|
|
393
|
-
end = headings[j].startLine;
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
current.contentEnd = end;
|
|
398
|
-
}
|
|
399
|
-
return headings;
|
|
400
|
-
}
|
|
401
|
-
function resolveHeadingPath(content, headingPath) {
|
|
402
|
-
const lines = content.split("\n");
|
|
403
|
-
const headings = parseHeadings(content);
|
|
404
|
-
if (headingPath === "") {
|
|
405
|
-
const firstHeadingLine = headings.length > 0 ? headings[0].startLine : lines.length;
|
|
406
|
-
return {
|
|
407
|
-
startLine: 0,
|
|
408
|
-
contentStart: 0,
|
|
409
|
-
contentEnd: firstHeadingLine
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
const segments = headingPath.split(">").map((s) => s.trim());
|
|
413
|
-
let searchStart = 0;
|
|
414
|
-
let searchEnd = lines.length;
|
|
415
|
-
let resolved = null;
|
|
416
|
-
for (let si = 0; si < segments.length; si++) {
|
|
417
|
-
const segment = segments[si].toLowerCase();
|
|
418
|
-
const candidates = headings.filter(
|
|
419
|
-
(h) => h.startLine >= searchStart && h.startLine < searchEnd && h.text.toLowerCase() === segment
|
|
420
|
-
);
|
|
421
|
-
if (candidates.length === 0) {
|
|
422
|
-
const available = headings.filter((h) => h.startLine >= searchStart && h.startLine < searchEnd).map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n");
|
|
423
|
-
const searchedPath = segments.slice(0, si + 1).join(" > ");
|
|
424
|
-
throw new Error(
|
|
425
|
-
`Heading not found: "${searchedPath}"
|
|
426
|
-
|
|
427
|
-
Available headings:
|
|
428
|
-
${available || "(none)"}`
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
resolved = candidates[0];
|
|
432
|
-
searchStart = resolved.contentStart;
|
|
433
|
-
searchEnd = resolved.contentEnd;
|
|
434
|
-
}
|
|
435
|
-
return {
|
|
436
|
-
startLine: resolved.startLine,
|
|
437
|
-
contentStart: resolved.contentStart,
|
|
438
|
-
contentEnd: resolved.contentEnd
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
372
|
function validateSpecPath(filePath) {
|
|
442
373
|
if (!filePath.startsWith("src/")) {
|
|
443
374
|
throw new Error(`Spec tool paths must start with src/. Got: "${filePath}"`);
|
|
444
375
|
}
|
|
445
376
|
return filePath;
|
|
446
377
|
}
|
|
447
|
-
function
|
|
448
|
-
const
|
|
449
|
-
if (
|
|
450
|
-
return
|
|
378
|
+
function extractFrontmatter(content) {
|
|
379
|
+
const lines = content.split("\n");
|
|
380
|
+
if (lines.length === 0 || lines[0].trim() !== "---") {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
for (let i = 1; i < lines.length; i++) {
|
|
384
|
+
if (lines[i].trim() === "---") {
|
|
385
|
+
return lines.slice(0, i + 1).join("\n");
|
|
386
|
+
}
|
|
451
387
|
}
|
|
452
|
-
return
|
|
388
|
+
return null;
|
|
453
389
|
}
|
|
454
|
-
var HEADING_RE;
|
|
455
390
|
var init_helpers = __esm({
|
|
456
391
|
"src/tools/spec/_helpers.ts"() {
|
|
457
392
|
"use strict";
|
|
458
|
-
HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
459
393
|
}
|
|
460
394
|
});
|
|
461
395
|
|
|
@@ -657,6 +591,90 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
657
591
|
}
|
|
658
592
|
});
|
|
659
593
|
|
|
594
|
+
// src/tools/code/editFile/_helpers.ts
|
|
595
|
+
function buildLineOffsets(content) {
|
|
596
|
+
const offsets = [0];
|
|
597
|
+
for (let i = 0; i < content.length; i++) {
|
|
598
|
+
if (content[i] === "\n") {
|
|
599
|
+
offsets.push(i + 1);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return offsets;
|
|
603
|
+
}
|
|
604
|
+
function lineAtOffset(offsets, charIndex) {
|
|
605
|
+
let lo = 0;
|
|
606
|
+
let hi = offsets.length - 1;
|
|
607
|
+
while (lo < hi) {
|
|
608
|
+
const mid = lo + hi + 1 >> 1;
|
|
609
|
+
if (offsets[mid] <= charIndex) {
|
|
610
|
+
lo = mid;
|
|
611
|
+
} else {
|
|
612
|
+
hi = mid - 1;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return lo + 1;
|
|
616
|
+
}
|
|
617
|
+
function findOccurrences(content, searchString) {
|
|
618
|
+
if (!searchString) {
|
|
619
|
+
return [];
|
|
620
|
+
}
|
|
621
|
+
const offsets = buildLineOffsets(content);
|
|
622
|
+
const results = [];
|
|
623
|
+
let pos = 0;
|
|
624
|
+
while (pos <= content.length - searchString.length) {
|
|
625
|
+
const idx = content.indexOf(searchString, pos);
|
|
626
|
+
if (idx === -1) {
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
results.push({ index: idx, line: lineAtOffset(offsets, idx) });
|
|
630
|
+
pos = idx + 1;
|
|
631
|
+
}
|
|
632
|
+
return results;
|
|
633
|
+
}
|
|
634
|
+
function flexibleMatch(content, searchString) {
|
|
635
|
+
const contentLines = content.split("\n");
|
|
636
|
+
const searchLines = searchString.split("\n").map((l) => l.trimStart());
|
|
637
|
+
if (searchLines.length === 0) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const matches = [];
|
|
641
|
+
for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
|
|
642
|
+
let allMatch = true;
|
|
643
|
+
for (let j = 0; j < searchLines.length; j++) {
|
|
644
|
+
if (contentLines[i + j].trimStart() !== searchLines[j]) {
|
|
645
|
+
allMatch = false;
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (allMatch) {
|
|
650
|
+
matches.push(i);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (matches.length !== 1) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
const startIdx = matches[0];
|
|
657
|
+
const matchedText = contentLines.slice(startIdx, startIdx + searchLines.length).join("\n");
|
|
658
|
+
const offsets = buildLineOffsets(content);
|
|
659
|
+
return {
|
|
660
|
+
matchedText,
|
|
661
|
+
index: offsets[startIdx],
|
|
662
|
+
line: startIdx + 1
|
|
663
|
+
// 1-based
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function replaceAt(content, index, oldLength, newString) {
|
|
667
|
+
return content.slice(0, index) + newString + content.slice(index + oldLength);
|
|
668
|
+
}
|
|
669
|
+
function formatOccurrenceError(count, lines, filePath) {
|
|
670
|
+
return `old_string found ${count} times in ${filePath} (at lines ${lines.join(", ")}) \u2014 must be unique. Include more surrounding context to disambiguate, or use replace_all to replace every occurrence.`;
|
|
671
|
+
}
|
|
672
|
+
var init_helpers2 = __esm({
|
|
673
|
+
"src/tools/code/editFile/_helpers.ts"() {
|
|
674
|
+
"use strict";
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
|
|
660
678
|
// src/tools/spec/editSpec.ts
|
|
661
679
|
import fs4 from "fs/promises";
|
|
662
680
|
var editSpecTool;
|
|
@@ -664,13 +682,14 @@ var init_editSpec = __esm({
|
|
|
664
682
|
"src/tools/spec/editSpec.ts"() {
|
|
665
683
|
"use strict";
|
|
666
684
|
init_helpers();
|
|
685
|
+
init_helpers2();
|
|
667
686
|
init_diff();
|
|
668
687
|
init_fileLock();
|
|
669
688
|
editSpecTool = {
|
|
670
689
|
clearable: true,
|
|
671
690
|
definition: {
|
|
672
691
|
name: "editSpec",
|
|
673
|
-
description:
|
|
692
|
+
description: "Make a targeted find/replace edit to a spec file (src/*.md). old_string must appear exactly once (minor indentation differences are handled automatically); set replace_all to true to replace every occurrence. Read the file with readSpec first so you match the exact text, and include the full enclosing structure rather than an inner fragment. The file's YAML frontmatter (the leading --- \u2026 --- block, which holds required fields like name) is protected \u2014 an edit that would remove or malform it is refused. For a full rewrite, use writeSpec.",
|
|
674
693
|
inputSchema: {
|
|
675
694
|
type: "object",
|
|
676
695
|
properties: {
|
|
@@ -678,31 +697,20 @@ var init_editSpec = __esm({
|
|
|
678
697
|
type: "string",
|
|
679
698
|
description: "File path relative to project root, must start with src/ (e.g., src/app.md)."
|
|
680
699
|
},
|
|
681
|
-
|
|
682
|
-
type: "
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
enum: ["replace", "insert_after", "insert_before", "delete"],
|
|
693
|
-
description: "replace: swap content under this heading (keeps the heading line). insert_after: add content after this section. insert_before: add content before this heading. delete: remove this heading and all its content."
|
|
694
|
-
},
|
|
695
|
-
content: {
|
|
696
|
-
type: "string",
|
|
697
|
-
description: "MSFM markdown content for replace/insert operations. Not needed for delete."
|
|
698
|
-
}
|
|
699
|
-
},
|
|
700
|
-
required: ["heading", "operation"]
|
|
701
|
-
},
|
|
702
|
-
description: "Array of edits to apply in order."
|
|
700
|
+
old_string: {
|
|
701
|
+
type: "string",
|
|
702
|
+
description: "The exact string to find and replace. Must be unique in the file unless replace_all is true."
|
|
703
|
+
},
|
|
704
|
+
new_string: {
|
|
705
|
+
type: "string",
|
|
706
|
+
description: "The replacement string."
|
|
707
|
+
},
|
|
708
|
+
replace_all: {
|
|
709
|
+
type: "boolean",
|
|
710
|
+
description: "If true, replace every occurrence of old_string in the file. Defaults to false."
|
|
703
711
|
}
|
|
704
712
|
},
|
|
705
|
-
required: ["path", "
|
|
713
|
+
required: ["path", "old_string", "new_string"]
|
|
706
714
|
}
|
|
707
715
|
},
|
|
708
716
|
async execute(input) {
|
|
@@ -713,69 +721,66 @@ var init_editSpec = __esm({
|
|
|
713
721
|
}
|
|
714
722
|
const release = await acquireFileLock(input.path);
|
|
715
723
|
try {
|
|
716
|
-
let
|
|
724
|
+
let content;
|
|
717
725
|
try {
|
|
718
|
-
|
|
726
|
+
content = await fs4.readFile(input.path, "utf-8");
|
|
719
727
|
} catch (err) {
|
|
720
728
|
return `Error reading file: ${err.message}`;
|
|
721
729
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
return `Error: ${err.message}
|
|
730
|
-
|
|
731
|
-
Document structure:
|
|
732
|
-
${tree}`;
|
|
730
|
+
const { old_string, new_string, replace_all } = input;
|
|
731
|
+
const occurrences = findOccurrences(content, old_string);
|
|
732
|
+
let updated;
|
|
733
|
+
let notePrefix;
|
|
734
|
+
if (replace_all) {
|
|
735
|
+
if (occurrences.length === 0) {
|
|
736
|
+
return `Error: old_string not found in ${input.path}.`;
|
|
733
737
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
lines.splice(range.startLine, 0, ...contentLines);
|
|
762
|
-
break;
|
|
763
|
-
}
|
|
764
|
-
case "delete": {
|
|
765
|
-
lines.splice(range.startLine, range.contentEnd - range.startLine);
|
|
766
|
-
break;
|
|
767
|
-
}
|
|
768
|
-
default:
|
|
769
|
-
return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
|
|
738
|
+
updated = content;
|
|
739
|
+
for (let i = occurrences.length - 1; i >= 0; i--) {
|
|
740
|
+
updated = replaceAt(
|
|
741
|
+
updated,
|
|
742
|
+
occurrences[i].index,
|
|
743
|
+
old_string.length,
|
|
744
|
+
new_string
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
notePrefix = `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
|
|
748
|
+
`;
|
|
749
|
+
} else if (occurrences.length === 1) {
|
|
750
|
+
updated = replaceAt(
|
|
751
|
+
content,
|
|
752
|
+
occurrences[0].index,
|
|
753
|
+
old_string.length,
|
|
754
|
+
new_string
|
|
755
|
+
);
|
|
756
|
+
notePrefix = `Updated ${input.path}
|
|
757
|
+
`;
|
|
758
|
+
} else if (occurrences.length > 1) {
|
|
759
|
+
const lines = occurrences.map((o) => o.line);
|
|
760
|
+
return `Error: ${formatOccurrenceError(occurrences.length, lines, input.path)}`;
|
|
761
|
+
} else {
|
|
762
|
+
const flex = flexibleMatch(content, old_string);
|
|
763
|
+
if (!flex) {
|
|
764
|
+
return `Error: old_string not found in ${input.path}. Make sure you've read the file first and copied the exact text.`;
|
|
770
765
|
}
|
|
771
|
-
|
|
766
|
+
updated = replaceAt(
|
|
767
|
+
content,
|
|
768
|
+
flex.index,
|
|
769
|
+
flex.matchedText.length,
|
|
770
|
+
new_string
|
|
771
|
+
);
|
|
772
|
+
notePrefix = `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
|
|
773
|
+
`;
|
|
774
|
+
}
|
|
775
|
+
if (extractFrontmatter(content) !== null && extractFrontmatter(updated) === null) {
|
|
776
|
+
return `Error: that edit would remove or malform the spec's YAML frontmatter (the leading \`--- \u2026 ---\` block, which holds required fields like \`name\`). Narrow old_string to the body content you meant to change and leave the frontmatter block intact.`;
|
|
772
777
|
}
|
|
773
778
|
try {
|
|
774
|
-
await fs4.writeFile(input.path,
|
|
779
|
+
await fs4.writeFile(input.path, updated, "utf-8");
|
|
775
780
|
} catch (err) {
|
|
776
781
|
return `Error writing file: ${err.message}`;
|
|
777
782
|
}
|
|
778
|
-
return unifiedDiff(input.path,
|
|
783
|
+
return `${notePrefix}${unifiedDiff(input.path, content, updated)}`;
|
|
779
784
|
} finally {
|
|
780
785
|
release();
|
|
781
786
|
}
|
|
@@ -1951,6 +1956,9 @@ async function initOrgContext(config) {
|
|
|
1951
1956
|
log3.debug("org context init failed", { error: err.message });
|
|
1952
1957
|
}
|
|
1953
1958
|
}
|
|
1959
|
+
function getOrgContext() {
|
|
1960
|
+
return cached;
|
|
1961
|
+
}
|
|
1954
1962
|
function renderOrgContextBlock() {
|
|
1955
1963
|
const ctx = cached;
|
|
1956
1964
|
const auth = ctx?.auth;
|
|
@@ -2485,90 +2493,6 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
2485
2493
|
}
|
|
2486
2494
|
});
|
|
2487
2495
|
|
|
2488
|
-
// src/tools/code/editFile/_helpers.ts
|
|
2489
|
-
function buildLineOffsets(content) {
|
|
2490
|
-
const offsets = [0];
|
|
2491
|
-
for (let i = 0; i < content.length; i++) {
|
|
2492
|
-
if (content[i] === "\n") {
|
|
2493
|
-
offsets.push(i + 1);
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
return offsets;
|
|
2497
|
-
}
|
|
2498
|
-
function lineAtOffset(offsets, charIndex) {
|
|
2499
|
-
let lo = 0;
|
|
2500
|
-
let hi = offsets.length - 1;
|
|
2501
|
-
while (lo < hi) {
|
|
2502
|
-
const mid = lo + hi + 1 >> 1;
|
|
2503
|
-
if (offsets[mid] <= charIndex) {
|
|
2504
|
-
lo = mid;
|
|
2505
|
-
} else {
|
|
2506
|
-
hi = mid - 1;
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
return lo + 1;
|
|
2510
|
-
}
|
|
2511
|
-
function findOccurrences(content, searchString) {
|
|
2512
|
-
if (!searchString) {
|
|
2513
|
-
return [];
|
|
2514
|
-
}
|
|
2515
|
-
const offsets = buildLineOffsets(content);
|
|
2516
|
-
const results = [];
|
|
2517
|
-
let pos = 0;
|
|
2518
|
-
while (pos <= content.length - searchString.length) {
|
|
2519
|
-
const idx = content.indexOf(searchString, pos);
|
|
2520
|
-
if (idx === -1) {
|
|
2521
|
-
break;
|
|
2522
|
-
}
|
|
2523
|
-
results.push({ index: idx, line: lineAtOffset(offsets, idx) });
|
|
2524
|
-
pos = idx + 1;
|
|
2525
|
-
}
|
|
2526
|
-
return results;
|
|
2527
|
-
}
|
|
2528
|
-
function flexibleMatch(content, searchString) {
|
|
2529
|
-
const contentLines = content.split("\n");
|
|
2530
|
-
const searchLines = searchString.split("\n").map((l) => l.trimStart());
|
|
2531
|
-
if (searchLines.length === 0) {
|
|
2532
|
-
return null;
|
|
2533
|
-
}
|
|
2534
|
-
const matches = [];
|
|
2535
|
-
for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
|
|
2536
|
-
let allMatch = true;
|
|
2537
|
-
for (let j = 0; j < searchLines.length; j++) {
|
|
2538
|
-
if (contentLines[i + j].trimStart() !== searchLines[j]) {
|
|
2539
|
-
allMatch = false;
|
|
2540
|
-
break;
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
if (allMatch) {
|
|
2544
|
-
matches.push(i);
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2547
|
-
if (matches.length !== 1) {
|
|
2548
|
-
return null;
|
|
2549
|
-
}
|
|
2550
|
-
const startIdx = matches[0];
|
|
2551
|
-
const matchedText = contentLines.slice(startIdx, startIdx + searchLines.length).join("\n");
|
|
2552
|
-
const offsets = buildLineOffsets(content);
|
|
2553
|
-
return {
|
|
2554
|
-
matchedText,
|
|
2555
|
-
index: offsets[startIdx],
|
|
2556
|
-
line: startIdx + 1
|
|
2557
|
-
// 1-based
|
|
2558
|
-
};
|
|
2559
|
-
}
|
|
2560
|
-
function replaceAt(content, index, oldLength, newString) {
|
|
2561
|
-
return content.slice(0, index) + newString + content.slice(index + oldLength);
|
|
2562
|
-
}
|
|
2563
|
-
function formatOccurrenceError(count, lines, filePath) {
|
|
2564
|
-
return `old_string found ${count} times in ${filePath} (at lines ${lines.join(", ")}) \u2014 must be unique. Include more surrounding context to disambiguate, or use replace_all to replace every occurrence.`;
|
|
2565
|
-
}
|
|
2566
|
-
var init_helpers2 = __esm({
|
|
2567
|
-
"src/tools/code/editFile/_helpers.ts"() {
|
|
2568
|
-
"use strict";
|
|
2569
|
-
}
|
|
2570
|
-
});
|
|
2571
|
-
|
|
2572
2496
|
// src/tools/code/editFile/index.ts
|
|
2573
2497
|
import fs13 from "fs/promises";
|
|
2574
2498
|
var editFileTool;
|
|
@@ -5876,6 +5800,16 @@ var init_getUiInspirationSample = __esm({
|
|
|
5876
5800
|
});
|
|
5877
5801
|
|
|
5878
5802
|
// src/subagents/designExpert/prompt.ts
|
|
5803
|
+
function renderDesignSystemBlock(designSystem) {
|
|
5804
|
+
if (!designSystem) {
|
|
5805
|
+
return "";
|
|
5806
|
+
}
|
|
5807
|
+
return [
|
|
5808
|
+
"<design_system>",
|
|
5809
|
+
`The organization that owns this app publishes a design system at ${designSystem}. Treat it as the primary reference for this app's foundational visual language \u2014 palette, type, spacing, and the core component patterns that should feel consistent with the org's other products. Consult it for foundational decisions and fetch it with scrapeWebUrl when you need specifics (it's usually a hosted doc such as an llms.txt). It's a resource to orient toward, not a rulebook: you don't need to ground every detail in it, and app-specific or expressive choices are still yours to make.`,
|
|
5810
|
+
"</design_system>"
|
|
5811
|
+
].join("\n");
|
|
5812
|
+
}
|
|
5879
5813
|
function getDesignExpertPrompt(onboardingState) {
|
|
5880
5814
|
const specContext = loadSpecIndex();
|
|
5881
5815
|
const indices = getSampleIndices(
|
|
@@ -5905,6 +5839,14 @@ function getDesignExpertPrompt(onboardingState) {
|
|
|
5905
5839
|
prompt += `
|
|
5906
5840
|
|
|
5907
5841
|
${specContext}`;
|
|
5842
|
+
}
|
|
5843
|
+
const designSystemBlock = renderDesignSystemBlock(
|
|
5844
|
+
getOrgContext()?.designSystem
|
|
5845
|
+
);
|
|
5846
|
+
if (designSystemBlock) {
|
|
5847
|
+
prompt += `
|
|
5848
|
+
|
|
5849
|
+
${designSystemBlock}`;
|
|
5908
5850
|
}
|
|
5909
5851
|
const state = onboardingState ?? "onboardingFinished";
|
|
5910
5852
|
if (state !== "onboardingFinished") {
|
|
@@ -5922,6 +5864,7 @@ var init_prompt3 = __esm({
|
|
|
5922
5864
|
"use strict";
|
|
5923
5865
|
init_assets();
|
|
5924
5866
|
init_context();
|
|
5867
|
+
init_orgContext();
|
|
5925
5868
|
init_sampleCache();
|
|
5926
5869
|
init_getFontLibrarySample();
|
|
5927
5870
|
init_getDesignReferencesSample();
|
|
@@ -90,5 +90,5 @@ When generated code exists in `dist/`, you have both spec tools and code tools.
|
|
|
90
90
|
**Key principle: spec and code stay in sync.**
|
|
91
91
|
- When editing the spec, also update the affected code in the same turn.
|
|
92
92
|
- When the user asks for a code change that represents a behavioral change, also update the spec.
|
|
93
|
-
- Spec tools
|
|
93
|
+
- Spec tools work on `src/` files: `readSpec` to read (do this before editing), `editSpec` for targeted find/replace edits (same `old_string`/`new_string` model as `editFile`), `writeSpec` for a full-file write, `listSpecFiles` to see what exists.
|
|
94
94
|
- Code tools (`readFile`, `writeFile`, `editFile`, etc.) work on `dist/` and other project files.
|