draftify-cli 1.0.62 → 1.0.66

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/repl.js CHANGED
@@ -668,26 +668,135 @@ async function startRepl(initialUsername) {
668
668
  search = search.replace(/\r\n/g, '\n');
669
669
  replace = replace.replace(/\r\n/g, '\n');
670
670
  if (normalizedFile.includes(search)) {
671
+ // === STRATEGY 1: Exact match ===
671
672
  fileContent = normalizedFile.replace(search, () => replace);
672
673
  diffApplied = true;
673
674
  }
674
675
  else {
675
- // Fallback 1: try stripping leading/trailing empty lines/whitespace from the search block
676
+ // === STRATEGY 2: Trimmed match ===
676
677
  const trimmedSearch = search.trim();
677
678
  if (trimmedSearch && normalizedFile.includes(trimmedSearch)) {
678
679
  fileContent = normalizedFile.replace(trimmedSearch, () => replace.trim());
679
680
  diffApplied = true;
680
681
  }
681
682
  else {
682
- // Fallback 2: Regex fuzzy matching that ignores exact whitespace differences (e.g. indentation or newlines)
683
+ // === STRATEGY 3: Whitespace-flexible regex ===
683
684
  const escapedSearch = trimmedSearch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s+');
684
- const regex = new RegExp(escapedSearch);
685
- if (regex.test(normalizedFile)) {
686
- fileContent = normalizedFile.replace(regex, () => replace.trim());
685
+ const wsRegex = new RegExp(escapedSearch);
686
+ if (wsRegex.test(normalizedFile)) {
687
+ fileContent = normalizedFile.replace(wsRegex, () => replace.trim());
687
688
  diffApplied = true;
688
689
  }
689
690
  else {
690
- ui_1.ui.error(`Could not apply diff to ${filePath}: SEARCH block not exactly matched.`);
691
+ // === STRATEGY 4: Line-by-line fuzzy matching ===
692
+ // Find the region in the file that best matches the search block
693
+ const searchLines = trimmedSearch.split('\n').map(l => l.trim()).filter(l => l.length > 0);
694
+ const fileLines = normalizedFile.split('\n');
695
+ if (searchLines.length > 0) {
696
+ let bestStartIdx = -1;
697
+ let bestEndIdx = -1;
698
+ let bestScore = 0;
699
+ // Slide a window over the file lines to find the best matching region
700
+ for (let i = 0; i <= fileLines.length - searchLines.length; i++) {
701
+ let matchedCount = 0;
702
+ let lastMatchIdx = i - 1;
703
+ for (const searchLine of searchLines) {
704
+ // Look for this search line within a reasonable range
705
+ for (let j = lastMatchIdx + 1; j < Math.min(i + searchLines.length + 5, fileLines.length); j++) {
706
+ if (fileLines[j].trim() === searchLine) {
707
+ matchedCount++;
708
+ lastMatchIdx = j;
709
+ break;
710
+ }
711
+ }
712
+ }
713
+ const score = matchedCount / searchLines.length;
714
+ if (score > bestScore && score >= 0.6) {
715
+ bestScore = score;
716
+ bestStartIdx = i;
717
+ bestEndIdx = Math.min(lastMatchIdx, i + searchLines.length + 4);
718
+ }
719
+ }
720
+ if (bestStartIdx >= 0 && bestScore >= 0.6) {
721
+ const before = fileLines.slice(0, bestStartIdx).join('\n');
722
+ const after = fileLines.slice(bestEndIdx + 1).join('\n');
723
+ fileContent = before + (before ? '\n' : '') + replace.trim() + (after ? '\n' : '') + after;
724
+ diffApplied = true;
725
+ ui_1.ui.info(`Fuzzy matched diff for ${filePath} (${Math.round(bestScore * 100)}% confidence)`);
726
+ }
727
+ else {
728
+ // === STRATEGY 5: Anchor line matching (first + last non-empty line) ===
729
+ const firstSearchLine = searchLines[0];
730
+ const lastSearchLine = searchLines[searchLines.length - 1];
731
+ let anchorStart = -1;
732
+ let anchorEnd = -1;
733
+ for (let i = 0; i < fileLines.length; i++) {
734
+ if (fileLines[i].trim() === firstSearchLine) {
735
+ anchorStart = i;
736
+ break;
737
+ }
738
+ }
739
+ if (anchorStart >= 0) {
740
+ for (let i = anchorStart + 1; i < fileLines.length; i++) {
741
+ if (fileLines[i].trim() === lastSearchLine) {
742
+ anchorEnd = i;
743
+ break;
744
+ }
745
+ }
746
+ }
747
+ if (anchorStart >= 0 && anchorEnd >= 0 && (anchorEnd - anchorStart) <= searchLines.length * 2) {
748
+ const before = fileLines.slice(0, anchorStart).join('\n');
749
+ const after = fileLines.slice(anchorEnd + 1).join('\n');
750
+ fileContent = before + (before ? '\n' : '') + replace.trim() + (after ? '\n' : '') + after;
751
+ diffApplied = true;
752
+ ui_1.ui.info(`Anchor-matched diff for ${filePath} (first/last line anchors)`);
753
+ }
754
+ else {
755
+ // === STRATEGY 6: Partial content - find longest unique line sequence ===
756
+ // Use the 3 longest unique lines from the search as anchors
757
+ const uniqueSearchLines = searchLines
758
+ .filter(l => l.length > 10)
759
+ .sort((a, b) => b.length - a.length)
760
+ .slice(0, 3);
761
+ if (uniqueSearchLines.length >= 2) {
762
+ const firstUnique = uniqueSearchLines[0];
763
+ const secondUnique = uniqueSearchLines[1];
764
+ let uStart = -1, uEnd = -1;
765
+ for (let i = 0; i < fileLines.length; i++) {
766
+ if (fileLines[i].trim().includes(firstUnique) || firstUnique.includes(fileLines[i].trim())) {
767
+ if (uStart < 0)
768
+ uStart = i;
769
+ uEnd = i;
770
+ }
771
+ if (fileLines[i].trim().includes(secondUnique) || secondUnique.includes(fileLines[i].trim())) {
772
+ if (uStart < 0)
773
+ uStart = i;
774
+ uEnd = i;
775
+ }
776
+ }
777
+ if (uStart >= 0 && uEnd >= uStart) {
778
+ // Expand slightly to capture the full block
779
+ const expandedStart = Math.max(0, uStart - 1);
780
+ const expandedEnd = Math.min(fileLines.length - 1, uEnd + 1);
781
+ const before = fileLines.slice(0, expandedStart).join('\n');
782
+ const after = fileLines.slice(expandedEnd + 1).join('\n');
783
+ fileContent = before + (before ? '\n' : '') + replace.trim() + (after ? '\n' : '') + after;
784
+ diffApplied = true;
785
+ ui_1.ui.info(`Partial-matched diff for ${filePath} (unique line anchors)`);
786
+ }
787
+ else {
788
+ ui_1.ui.error(`Could not apply diff to ${filePath}: SEARCH block not matched (all 6 strategies failed).`);
789
+ }
790
+ }
791
+ else {
792
+ ui_1.ui.error(`Could not apply diff to ${filePath}: SEARCH block not matched (all strategies failed).`);
793
+ }
794
+ }
795
+ }
796
+ }
797
+ else {
798
+ ui_1.ui.error(`Could not apply diff to ${filePath}: SEARCH block is empty.`);
799
+ }
691
800
  }
692
801
  }
693
802
  }
package/dist/utils/api.js CHANGED
@@ -250,9 +250,12 @@ new lines
250
250
  >>>>>>>
251
251
  </FILE_MODIFY>
252
252
  CRITICAL RULES FOR <FILE_MODIFY>:
253
- - The SEARCH block MUST PERFECTLY MATCH the existing file content. Do not use ellipses (...) or comments like "// ... existing code ..." to skip lines.
253
+ - **MANDATORY: ALWAYS use <READ_FILE> to read the file FIRST before writing any <FILE_MODIFY> block.** Never rely on memory of what a file contains your memory of file content degrades over conversation turns. ALWAYS re-read.
254
+ - The SEARCH block MUST PERFECTLY MATCH the existing file content character-for-character. Do not use ellipses (...) or comments like "// ... existing code ..." to skip lines.
254
255
  - The indentation in the SEARCH block must exactly match the file.
255
- - Only include the minimal lines needed to uniquely identify the replacement location (usually 1-3 context lines).
256
+ - Keep SEARCH blocks SHORT: include only 1-5 lines — the absolute minimum needed to uniquely identify the replacement location. Shorter blocks are far less likely to mismatch.
257
+ - You can use multiple SEARCH/REPLACE blocks within a single <FILE_MODIFY> tag.
258
+ - **LARGE CHANGES RULE: If you need to change more than ~30% of a file, do NOT use <FILE_MODIFY> with many SEARCH blocks. Instead, use <FILE_CREATE> with the complete new file content to overwrite it entirely.** This is much more reliable than multiple diffs.
256
259
 
257
260
  3. To delete a file:
258
261
  <FILE_DELETE path="relative/path/to/file.ext" />
@@ -278,6 +281,7 @@ CRITICAL RULES FOR <FILE_MODIFY>:
278
281
 
279
282
  AUTONOMOUS WORKSPACE EXPLORATION:
280
283
  If the user asks you to modify the project (e.g. "Add a game over menu", "Fix the bug in the header") but you don't know the exact file structure or contents, DO NOT ask the user to show you the files. Instead, use <LIST_DIR path="." /> and <READ_FILE path="..." /> to autonomously explore the workspace. You can read multiple files or list directories in a single response. Wait for the CLI to return the results of your exploration before you generate the code modifications. You act like an autonomous agent.
284
+ IMPORTANT: You MUST ALWAYS <READ_FILE> any file before attempting <FILE_MODIFY> on it. This is not optional — it prevents diff mismatch failures.
281
285
 
282
286
  Always use these tags when you write code, explore files, or ask questions so the CLI can apply it automatically! Keep explanations short and outside the tags.
283
287
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "draftify-cli",
3
- "version": "1.0.62",
3
+ "version": "1.0.66",
4
4
  "description": "Draftify AI CLI tool",
5
5
  "main": "dist/index.js",
6
6
  "bin": {