fa
Feedback
ToCode

ToCode

رفتن به کانال در Telegram

טיפים קצרים למתכנתים מאת ינון פרק

نمایش بیشتر
1 419
مشترکین
اطلاعاتی وجود ندارد24 ساعت
+17 روز
-430 روز
آرشیو پست ها
ToCode
1 419
at TracingChannel.traceSync (node:diagnostics_channel:328:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:245:24)
    at Module.require (node:internal/modules/cjs/loader:1547:12)
    at require (node:internal/modules/helpers:152:16)
    at test2.js:5:17
    at embedderRunCjs (node:internal/main/embedding:89:10) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/Users/ynonp/tmp/blog/node-sea/sea' ]
}

Node.js v25.5.0
שילוב קבצים מוטמעים דרך נוספת לשלב קבצים חיצוניים היא באמצעות Assets. אסטים יוטמעו בתוך קובץ הבינארי ליד הסקריפט ויהיו זמינים לתוכנית שלנו בזמן ריצה. אפשר להטמיע בינארים אחרים כמו תמונות ואפשר גם להטמיע קבצי קוד. בואו ננסה את זה עם הקובץ utils.js מהדוגמה הקודמת. אני מעדכן את קובץ הקונפיגורציה כדי לכלול את הקובץ בתור קובץ מוטמע:
{
  "main": "test3.js",
  "executable": "/Users/ynonp/.nvm/versions/node/v25.5.0/bin/node",
  "disableExperimentalSEAWarning": true,
  "output": "sea",
  "assets": {
    "utils": "./utils.js"
  }
} 
אני ניגש לקבצים המוטמעים דרך פונקציה בשם sea.getAsset. הקוד הבא יודע לטעון מודול מתוך קובץ מוטמע באמצעות פונקציית requireFromString שמשתמשת ב API הלא מתועד module.prototype._compile:
const sea = require('node:sea');
const utilsSource = sea.getAsset('utils', 'utf8');

function requireFromString(src, filename) {
  var m = new module.constructor();
  m.paths = module.paths;
  m._compile(src, filename);
  return m.exports;
}

const {twice} = requireFromString(utilsSource, 'utils.js');

const user = process.env["USER"];
console.log(\Hello ${user}. 2 * 2 = ${twice(2)}\);
קימפול והפעלה של התוכנית האחרונה למרבה ההפתעה עובד ונותן לי להפעיל את הפונקציה twice גם אם אני מזיז את קובץ הבינארי שנוצר למקום אחר. סך הכל אפליקציות קובץ יחיד זה רעיון מדליק ויכול לחסוך התקנה של node או של גרסה רלוונטית של node על מכונה של לקוח. לדעתי כל עוד המימוש הוא הטמעה של סקריפט בתוך הבינארי של node כלומר שהגודל של הקובץ יוצא מעל 100 מגה, וכל עוד אין דרך קלה להשתמש במודולים צד-שלישי אנשים לא ימהרו להשתמש בזה.

ToCode
1 419
כזה ניסיתי: פיתוח אפליקציית קובץ בודד עם node 25 גרסה 25 של node.js הוסיפה פיצ'ר כיפי לאנשים שכותבים כלי שורת פקודה שנקרא Single Executable Application. בקצרה הוא מאפשר להטמיע תוכנית node בתוך קובץ ההפעלה של node.js עצמו ואז להפיץ את הקובץ הזה בתור אפליקציית קובץ יחיד, כלומר לקוח מקבל קובץ הפעלה אחד שאיך שמפעילים אותו מבצע את הקוד בסקריפט שהוטמע. בואו נראה איך זה עובד דרך 3 דוגמאות. אפליקציית שלום עולם אפליקציות קובץ יחיד נכתבות רק ב CommonJS (אני מקווה שלא שכחתם כבר שזה קיים. בכל מקרה זה הכתיב עם ה require האופייני ל node במקום כתיב ה import) ודורשות קובץ קונפיגורציה עם סיומת json שמכיל את הפרטים הבאים:
{
  "main": "/path/to/bundled/script.js",
  "executable": "/path/to/node/binary", // Optional, if not specified, uses the current Node.js binary
  "output": "/path/to/write/the/generated/executable",
  "disableExperimentalSEAWarning": true, // Default: false
  "useSnapshot": false,  // Default: false
  "useCodeCache": true, // Default: false
  "execArgv": ["--no-warnings", "--max-old-space-size=4096"], // Optional
  "execArgvExtension": "env", // Default: "env", options: "none", "env", "cli"
  "assets": {  // Optional
    "a.dat": "/path/to/a.dat",
    "b.txt": "/path/to/b.txt"
  }
} 
בשביל הניסוי אני יוצר קובץ קונפיגורציה בשם sea-config.json עם התוכן המינימלי הבא:
{
  "main": "test1.js",
  "executable": "/Users/ynonp/.nvm/versions/node/v25.5.0/bin/node",
  "disableExperimentalSEAWarning": true,
  "output": "sea",
} 
וקובץ test1.js עם התוכן הבא:
console.log('hello world');
מפעיל משורת הפקודה את הפקודות הבאות לפי ההוראות בתיעוד:
$ node --build-sea sea-config.json
$ codesign --sign - sea
ויש לי קובץ הפעלה בשם sea שמכיל את node.js ואת הסקריפט. אפשר להפעיל אותו ולראות את התוצאה:
$ ./sea
hello world
זאת כנראה תוכנית ה hello world הכבדה בעולם כי הקובץ שוקל 125 מגה, אבל זה ברור כי הוא מגיע עם כל הקוד של node.js עצמו. טעינת קובץ חיצוני בדוגמה השניה רציתי לראות איך לעבוד עם קובץ חיצוני. יצרתי קובץ בשם utils.js עם התוכן הבא:
function twice(x) {
  return x * 2;
}

module.exports = {
  twice
}
וקובץ בשם test2.js עם התוכן הבא:
const process = require('node:process');
const {twice} = require('./utils.js');

const user = process.env["USER"];
console.log(\Hello ${user}. 2 * 2 = ${twice(2)}\);
עדכנתי את שם קובץ המקור בקונפיגורציה ל test2.js ובניתי לפי ההוראות. התוצאה הפעם פחות מוצלחת:
$ ./sea
node:internal/main/embedding:113
    throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
    ^

Error [ERR_UNKNOWN_BUILTIN_MODULE]: No such built-in module: ./utils.js
    at embedderRequire (node:internal/main/embedding:113:11)
    at test2.js:2:15
    at embedderRunCjs (node:internal/main/embedding:89:10) {
  code: 'ERR_UNKNOWN_BUILTIN_MODULE'
}

Node.js v25.5.0
פקודות require בתוך single executable application יודעות לעבוד רק עם מודולים מובנים ב node ולא עם קבצים חיצוניים. בשביל לטעון קובץ חיצוני אנחנו צריכים לשים אותו על המכונה בה נפעיל את האפליקציה (כלומר זו כבר תהיה אפליקציית שני קבצים) ולעדכן את הקוד של test2.js:
const process = require('node:process');
const { createRequire } = require('node:module');
require = createRequire(__filename);

const {twice} = require('./utils.js');

const user = process.env["USER"];
console.log(\Hello ${user}. 2 * 2 = ${twice(2)}\);
וזה עובד כל עוד הקובץ utils.js נמצא באותה תיקיה ממנה אני מפעיל את הקוד. אם אני מוחק את המודול או מזיז את sea לתיקיה אחרת אני מקבל את השגיאה:
$ ./sea
node:internal/modules/cjs/loader:1450
  throw err;
  ^

Error: Cannot find module './utils.js'
Require stack:
- /Users/ynonp/tmp/blog/node-sea/sea
    at Module._resolveFilename (node:internal/modules/cjs/loader:1447:15)
    at defaultResolveImpl (node:internal/modules/cjs/loader:1058:19)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1063:22)
    at Module._load (node:internal/modules/cjs/loader:1233:25)

ToCode
1 419
הטריק עם הערכות זמנים כשיש יותר מדי זמן אנחנו מבזבזים אותו. מפתח שיכול לבנות פיצ'ר בשבוע ומקבל חודשיים כדי לפתח את זה יעבוד יותר לאט (גם אם לא במודע) ויעשה עבודה מיותרת. מרצה שתכנן ללמד חומר של שעה ב-8 שעות ימצא את עצמו חוזר על אותן דוגמאות ובסוף משחרר את התלמידים שעתיים לפני הזמן. כשיש פחות מדי זמן אנחנו מעגלים פינות כדי להספיק ולהגיע ליעד. מתכנת שיכול לבנות פיצ'ר בשבוע אבל מקבל יומיים בשבילו אולי יוותר על הבדיקות, אולי על העיצוב, אולי על אבטחת מידע ואולי בכלל לא יסיים בזמן. מרצה שתכנן ללמד חומר של שעתיים בשעה ידבר מהר מדי ולא יעביר את המסר. ברור שכדאי להתאים את לוחות הזמנים למשימה. ההחלטה האסטרטגית היא לאיזה צד עדיף לטעות. כשחושבים על מיקסום הערך של העבודה שלנו ברור למה כמעט תמיד עדיף לטעות בהערכת יתר מאשר בהערכת חסר.

ToCode
1 419
גם קוד עושה סטוריטלינג יום אחד ימציאו מנועי AI שיודעים לפתח תוכנה ולא רק לייצר שורות קוד. עד שזה יקרה הנה כמה סיפורים מהשבוע האחרון שאני מקווה שישכנעו גם אתכם לקרוא את הקוד שיוצא ממנועי השפה הגדולים שלכם: 1. סוכן קידוד כותב קוד פונקציית צד שרת שמקבלת פרמטר שמורכב משתי מילים user_name. הוא ממשיך לכתוב את קוד צד הלקוח ובטעות שולח את הערך userName. הקוד לא עובד ואני מבקש מסוכן הקידוד לתקן (הכל באותה שיחה). "אני רואה שיש בעיה בשם המשתנה וקוד צד הלקוח שולח אותו בשם אחר ממה שהשרת מצפה לקבל. זה בטח כתוב שם מסיבה טובה אז אני אשלח את הערך גם עם השם הנכון וגם עם השם השגוי". התוצאה - קוד עובד ששולח מידע כפול לשרת. 2. מבקש מסוכן קידוד לתקן בדיקה שלא עברה. הסוכן מזהה שהבדיקה פונה לשרת, מצפה לקבל מערך ואז בודקת דברים לגבי הפריטים בתוך המערך. השרת מחזיר ערך מסוג אחר ולכן הבדיקה נכשלת. סוכן הקידוד שרוצה לתקן את הבדיקה מזהה את הבעיה ומוסיף תנאי - אם השרת מחזיר משהו שאינו מערך תסמן את הבדיקה כהצלחה. 3. סוכן קידוד מתבקש לממש פונקציה במהלכה מתקבל מילון ויש לקחת ערך מתוכו. הקוד הוא ברובי והסוכן כותב:
name = payload[:name] || payload['name']
בתיאוריה קוד "חזק" שתמיד יעבוד לא משנה איך יגיעו אליו הערכים. בפרקטיקה קוד מבלבל כי הפונקציה מופעלת על אוביקט ספציפי ובתנאים מסוימים. בנקודה הזאת בקוד אנחנו יודעים בדיוק מה טיפוסי המפתחות ב payload. קוד שמטפל במקרים לא קיימים לא עוזר להבנה או ליציבות של המערכת, הוא רק מאפשר למקרים לא הגיוניים להתרסק במקום אחר. הבעיה היא שכל 3 הדוגמאות עשויות להיראות נכונות למי שלא מכיר את הטכנולוגיה, Best Practices או שפת התכנות הרלוונטית. קוד מספר סיפור וככל שהסיפור בנוי טוב יותר כך קל לנו יותר להרחיב ולתחזק את המערכת.

ToCode
1 419
מה צריך בשביל להבין אלכס קונדוב כותב: > Writing code by hand gave me the pace of thought I needed to understand a problem deeply. After a couple of years at a company, I knew the codebase by heart, every corner of it, every service, and every script. > I find it difficult to say the same thing now. ומביא אותנו לשאלה מהכותרת: "מה צריך בשביל להבין"? יש אנשים שצריכים לכתוב. כן יש כח בכתיבה עצמה ושמעתי המון אנשים ממליצים שבשביל לזכור מילים בשפה חדשה למשל כדאי לכתוב אותן ממש על כרטיסיות. יש אנשים שמבינים טוב יותר דרך תרשימים. הם רוצים לראות את התמונה הגדולה, מה קשור למה, תרשימי UML, תרשימי ארכיטקטורה, ריבועים על לוח. רגע, לאן הריבוע הזה מתחבר, ומה קורה כשהריבוע הזה מפסיק להגיב. יש אנשים שצריכים לראות משהו כמה פעמים כדי להבין אותו. החזרתיות עושה עבורם את הקסם ועדיף כמה פעמים בצורות שונות, למשל קודם לראות דוגמת קוד, אחרי זה לכתוב אחת, אחרי זה לשמוע פודקסט שמסביר על הקוד הזה ואז וידאו ואז לקרוא כמה בלוגים ואז לכתוב דוגמה אחרת עם אותן פונקציות. יש את אלה שפשוט צריכים זמן. הם רואים משהו ואז ממשיכים הלאה וכמה שעות או ימים אחר כך פתאום האסימון נופל ואז הם יכולים לחזור ולהסביר לך את הדבר הזה או לחזור אליו כדי ללמוד עוד קצת. צ'אטג'יפיטי הוא רעיון מדהים לאנשים האלה שמתנתקים ואז פתאום אחרי כמה שעות שואלים את ה AI שאלה ואז שוב מתנתקים עד השאלה הבאה. המוח רץ גם כשנראה שהם עושים דברים אחרים. יש את מי שלומדים כשהם מסבירים לאחרים. קל למצוא ברשת המלצות ללכת להרצות בכנס או לפתוח בלוג מקצועי כדי להשתפר כי להסביר לאחרים מכריח אותך להבין טוב יותר את החומר. יש אנשים שלומדים דרך עשייה. הם צריכים לדבר שפה כדי לזכור אותה, הם צריכים לדבג קוד ולשנות אותו. עבורם בשביל להכיר מערכת הכי חשוב לכתוב לה בדיקות, גם אם תמחוק את הבדיקות בסוף זה לא משנה. אני חושב שאנשים אלה המציאו את ה TDD. ורוב האנשים כנראה צריכים קצת מכל דבר. שילוב של השיטות. כניסת ה AI לעולם הפיתוח משנה את האופן בו אנו עובדים ביום יום. אנשים שהיו רגילים להבין דרך הכתיבה מגלים שמה שבעבר קרה מעצמו עכשיו דורש השקעה. אחרים שממילא היו צריכים לפעול אחרת כדי להבין את הקוד לא רואים או מרגישים את המצוקה. "אני לעולם לא אהיה מתכנת Python טוב כי התחלתי ללמוד פייתון אחרי שהיה AI" זה פחד אמיתי של אנשים שרגילים ללמוד ולהבין דברים בצורה מסוימת והצורה הזאת כבר לא קורית מעצמה. הבנה היא לא בונוס או אופציה. עלינו למצוא את הדרכים שלנו להצליח ולהשתפר, במיוחד בעולם שבו הדברים האלה לא יקרו מעצמם.

ToCode
1 419
איך אני עובד עם git גיט הוא כלי ניהול הגרסאות המרכזי בתעשייה והוא סופר גמיש. בגלל הגמישות אפשר להתאים אותו לכל ארגון ולכל שיטת עבודה שזה דבר טוב, אבל תוצאת לוואי של יכולת זו היא שקל מאוד להתבלבל ולבחור שיטת עבודה שלא מתאימה לכם. בפוסט היום אשתף שני עקרונות מנחים שעוזרים לי להחליט איך לעבוד עם גיט בפרויקט ואראה איך ליישם אותם במספר סיטואציות. עקרונות מנחים שני העקרונות הבאים משותפים לכל פרויקט קוד שעובד עם גיט: 1. מרגע ששלחתי קומיטים למאגר מרוחק קשה להוציא אותם 2. גיט הוא מאגר מידע קומיט בגיט הוא אבן ואבן שנזרקת לתוך הבאר קשה מאוד להוציא. המשחק הראשון שלנו הוא לזרוק כמה שפחות אבנים ובמיוחד להיזהר מלזרוק את האבנים הלא נכונות. קומיט מכיל את מצב הפרויקט ברגע נתון. מרגע שדוחפים קומיט עם מפתח גישה לאחד הקבצים המפתח הזה יישאר בריפו לעד, גם אם בקומיט עתידי נמחק את הקובץ. מרגע שמישהו לקח קומיט שלי וממשיך לבנות עליו קוד יהיה קשה מאוד לבטל את הקומיט כי אותו קוד שפותח על בסיסו יהפוך ללא רלוונטי. העקרון השני אומר שגיט הוא מאגר מידע שאמור לענות לי על שאלות לגבי הקוד. כשאני רואה חלק בקוד שאני לא מבין או לא מבין את ההקשר או מאיפה הוא הגיע אני אוכל להכנס לגיט וללמוד משם את כל סיפור הרקע. כשמאגר המידע בנוי טוב אנשים משתמשים בו, לומדים על הקוד ויכולים לקבל החלטות טובות יותר. מאגר מידע שבנוי רע הוא חסר ערך, יוצר בלבול ומכריח אותנו לתקשר באמצעים אחרים פחות יעילים. מבנה קומיט קומיט בגיט הוא עותק מלא של הפרויקט בנקודה מסוימת בזמן. בהסתכלות על גיט כמאגר מידע קומיט מייצג פעולה או שינוי מצב של הפרויקט והודעת הקומיט צריכה להסביר את השינוי הזה. גיט כמאגר מידע צריך להציג לי היסטוריה של השינויים בפרויקט ולספר סיפור - מה קרה, מי עשה, למה עשו, במה לא טיפלו, במה טיפלו אחר כך, מה חשבו שיקרה, מה באמת קרה. ניקח לדוגמה את הריפו של opencode ונראה כמה קומיטים אחרונים משם: https://github.com/anomalyco/opencode/commits/dev/ קומיט 0cc206a:
update: border radius on popover card
שינוי הקוד משנה את ערך border-radius בקובץ שנקרא session-review.css. מההודעה אני לומד שהערך הזה משמש להצגת כרטיס popover שזה טוב. ההודעה לא מסבירה למה החליטו לשנות את הערך, האם זה חלק משינוי עיצוב כללי יותר או משהו נקודתי שעלה מהערה של משתמש או ממקום אחר. הקומיט מספר לי מה עשו אבל לא למה עשו. קומיט c9215e8:
fix(ui): style review tab comment button to match file tab - blue background, white comment icon
הפעם ההודעה מספרת סיפור יותר מלא. יש פה תיקון של ממשק לא עקבי, כפתור comment בטאב ה review נראה קודם שונה מכפתור commment בטאב ה file. גם בלי להכיר את הפרויקט ואת הטאבים השונים אני מבין מאיפה זה בא ולמה החליטו לבצע את השינוי. גם השינויים עצמם ב CSS ובקובץ ה tsx תואמים את ההערה. בשני הקומיטים השינויים עצמם בקוד מאוד מדויקים, מדובר על 2-3 שורות שהשתנו. לעומתם קומיט c4d223e קצת יותר מבלבל. הודעת הקומיט היא:
perf(app): faster workspace creation
ותוכן הקומיט כולל 329 שורות שנוספו ו 44 שורות שנמחקו. כן אני יכול לבקש מ AI שיתמצת לי את השינוי לפי השינויים בקוד אבל אני לא יכול להיות בטוח שאקבל תשובה מדויקת. הייתי שמח לקבל הודעה ארוכה יותר שמסבירה מה בדיוק נעשה כדי לשפר את מהירות יצירת ה workspace. מה היתה הבעיה הקודמת ביצירת ה workspace, איך פתרתם אותה והאם יש מצבים בהם לא טיפלתם או משהו שהשארתם לעתיד. דבר נוסף שאני לומד מקריאת מסך רשימת הקומיטים של opencode הוא שפשוט יש שם המון קומיטים. באותו רצף עבודה ספרתי כמעט 30 קומיטים, כולל תיקוני UI שקשורים לאותו נושא ומפוזרים על פני 6-7 קומיטים שונים ולפעמים דורסים אחד את השני. הבעיה בריבוי קומיטים מסוג זה היא שגם ההודעות מפוצלות בין המון קומיטים וקשה להבין איזה הודעה שייכת לאיזה שינוי קוד. נשווה את זה לעץ הקומיטים של קודקס שהוא כבר הרבה יותר מסודר: https://github.com/openai/codex/commits/main/ קומיט 713ae22 מגיע עם ההודעה הבאה:
Another round of improvements for config error messages (#9746)
In a [recent PR](#9182), I made some
improvements to config error messages so errors didn't leave app server
clients in a dead state. This is a follow-on PR to make these error

ToCode
1 419
דוגמת קוד RAG לקראת הוובינר בשבוע הבא ביום חמישי האחרון דיברנו בוובינר השבועי על Embedding. ראינו איך לקחת פוסט מהבלוג ולהפוך אותו לוקטור, איך לחשב מרחקים בין הוקטורים שמצאנו וראינו שכשאנחנו מייצרים וקטורים משני פוסטים שמדברים על נושא דומה המרחק ביניהם יהיה קצר. אחד השימושים למנגנון ה Embedding נקרא RAG או Retrieval-augmented generation. הרעיון הוא שאחרי שיצרנו וקטורי Embedding מכל הפוסטים נוכל ליצור וקטור כזה גם משאלה של משתמש ואז נוסיף את הפוסטים הרלוונטים לפרומפט בצורה אוטומטית. בואו נראה איך זה עובד בעזרת פרויקט לדוגמה. התקנה והפעלה בקישור: https://github.com/ynonp/embedding-demo נמצא הקוד של סוכן צ'אט חכם משולב RAG. בשביל להריץ את הפרויקט תצטרכו להרים פוסטגרס עם pgvector רצוי בדוקר עם הפקודה הבאה:
$ docker run --rm -p 5454:5432 -e POSTGRES_PASSWORD=password pgvector/pgvector:pg18-trixie
תצטרכו גם את ollama מותקן על המחשב עם מודל האמבדינג nomic-embed-text-v2-moe:latest. לאחר מכן אפשר ליצור את הנתונים בבסיס הנתונים לדוגמה עם הפקודות:
$ ./bin/rails db:migrate
$ ./bin/rails r script/scrape_data.rb
$ ./bin/rails r script/index_posts.rb
אחרי הפעלת שלושת הפקודות תקבלו בבסיס הנתונים טבלת פוסטים עם 200 פוסטים אחרונים מהבלוג פה, ולכל פוסט יהיה וקטור Embedding שמתאים לו שחושב באמצעות המודל nomic-embed-text-v2-moe:latest. חישוב מרחק מפרומפט נפתח את הקובץ app/models/post.rb ושם נמצא את הפונקציה cosine_distance_from_prompt:
  def cosine_distance_from_prompt(prompt, model: EMBED_MODEL)
    return nil unless embedding.present?

    # Calculate embedding for the prompt
    embed = RubyLLM.embed(prompt, provider: :ollama, model:, dimensions: 768)
    prompt_embedding = embed.vectors

    # Calculate cosine distance using PostgreSQL's <=> operator
    conn = self.class.connection.raw_connection
    result = conn.exec_params(
      "SELECT embedding <=> $1::vector AS distance FROM posts WHERE id = $2",
      [prompt_embedding, id]
    )

    result.first['distance'].to_f
  end
המתודה מקבלת פרומפט, מחשבת וקטור אמבדינג ממנו ומחזירה את המרחק מהפרומפט לאותו פוסט. הנה כמה דוגמאות הפעלה עם פוסטים נבחרים והפרומפט "סכנות אבטחה ב vs code":
3.3.5 :004 > Post.find_by(slug: '2026-01-vscode-tasks').cosine_distance_from_prompt("סכנות אבטחה ב vs code")
 => 0.6270134320979381

3.3.5 :005 > Post.find_by(slug: '2026-01-basic-knowledge').cosine_distance_from_prompt("סכנות אבטחה ב vs code")
 => 0.7205912733782678

3.3.5 :006 > Post.find_by(slug: '2026-01-learn-worktree-with-ai').cosine_distance_from_prompt("סכנות אבטחה ב vs code")
 => 0.658548735705814

3.3.5 :008 > Post.find_by(slug: '2026-01-aoc2025day8').cosine_distance_from_prompt("סכנות אבטחה ב vs code")
 => 0.6936170916427378
ובאמת הפוסט על tasks.json היה הכי קרוב לפרומפט שזה הגיוני אבל ההבדלים עדיין לא מאוד משמעותיים. יש פה רמז שאולי מודל האמבדינג שבחרתי לא מספיק מדויק לתוכן של הבלוג. עוד נחזור לזה וננסה את הדוגמה עם מודל אחר. בינתיים בואו נראה מה זה אומר לשלב פוסטים בסוכן. העברת מידע נוסף לסוכן הסוכן כתוב ב Ruby ומשתמש בספריית ruby_llm. בשביל לשלוח שאלה ל AI בספריה זו כל מה שצריך זו קריאת פונקציה אחת:
chat = Chat.create(model: "gpt-5-mini")
chat.ask "מהן סכנות האבטחה ב VS Code"
מפעילים את הפרויקט עם ./bin/dev, נכנסים ל localhost:3000/chats, לוחצים על הכפתור לפתיחת שיחה חדשה ומקלידים את הפרומפט ונוכל לראות את התשובה הבסיסית של הסוכן. הפונקציה הבאה ברשימה עדיין במודל post אחראית על חיפוש פוסטים שרלוונטים לפרומפט מסוים:
  def self.find_relevant_for_prompt(user_prompt, limit: 5)
    # Calculate embedding for the prompt
    embed = RubyLLM.embed(user_prompt, provider: :ollama, model: EMBED_MODEL)
    prompt_embedding = embed.vectors

    # Search database directly using vector search
    conn = connection.raw_connection
    conn.exec_params(
      "SELECT *, embedding <=> $1::vector AS distance FROM posts ORDER BY embedding <=> $1::vector LIMIT $2",
      [prompt_embedding, limit]
    )
  end

ToCode
1 419
היום למדתי: למה VS Code מתעקש לשאול כל פעם אם אני סומך על הפרויקט הזה? אין דיאלוג שיותר מעייף אותי מ"האם אתה סומך על הפרויקט הזה" שמופיע כל פעם לפני שאני פותח פרויקט ב VS Code. מצד אחד ברור שאסור לפתוח לזרים אבל מה בכלל צריך לבדוק בפרויקט לפני שפותחים אותו ב VS Code, ומה זה אומר שאני סומך על הפרויקט? אז אני עדיין לא יודע לענות על כל השאלות אבל בפוסט היום אני רוצה להראות טכניקה אחת של פרויקטים זדוניים להשתלט לנו על המחשבים ואפילו לבנות פרויקט זדוני לדוגמה איתה, והטכניקה שאני מדבר עליה היא הקובץ tasks.json. יצירת פרויקט זדוני הקובץ tasks.json נועד כדי לאפשר למפתחים להריץ דברים בצורה קלה מתוך העורך, למשל להריץ קומפילציה, משיכת קוד משרת מרוחק או בדיקה אוטומטית. בואו ננסה את זה, אני יוצר תיקייה חדשה ופותח אותה ב VS Code ואז יוצר תיקיה חדשה בשם .vscode ובתוכה קובץ חדש בשם tasks.json. בתוך הקובץ אני כותב את הקוד:
{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Ynon Run Tests",
      "type": "shell",
      "command": "echo Run Tests"
    }
  ]
}
שומר ולוחץ Ctrl+Shift+P בשביל להכנס לתפריט ואז בוחר Tasks: Run Task. יופיע לי דיאלוג לבחירת משימה מהירה ואני יכול לבחור את המשימה שלי משם ולראות את הסקריפט שלי רץ. עד פה הכל קל. מה שהופך את הפרויקט לזדוני הוא מפתח runOptions שם ניתן להגדיר מתי המשימה תרוץ אוטומטית. בואו ננסה את זה עם עוד כמה תוספות ונכתוב בקובץ את התוכן הבא:
{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Ynon Run Tests",
      "type": "shell",
      "command": "date >> tests.txt",
      "problemMatcher": [],
      "isBackground": true,
      "presentation": {
        "reveal": "never",
      },
      "runOptions": {
        "runOn": "folderOpen"
      }
    }
  ]
}
הגדרת problemMatcher ריק מבטלת את הדיאלוג ששאל איך להציג שגיאות, הגדרת isBackground מאפשרת למשימה לרוץ ברקע, הגדרת reveal: never מונעת את הצגת המסוף עם פלט המשימה ו runOn: folderOpen אומרת שהמשימה תרוץ כל פעם שפותחים קובץ מתוך התיקייה. עכשיו אפשר לצאת מ VS Code ולהכנס מחדש ולראות שהפקודה התבצעה, נוצר קובץ חדש בשם tests.txt ובו התאריך וזמן ההרצה. הפקודה תמשיך לרוץ שוב ושוב ברקע כשנפתח עוד קבצים בפרויקט. מה עושים סירוב לתת אמון בפרויקטים יכניס אתכם למצב מוגבל בעבודה. במצב מוגבל לא ניתן להריץ משימות מתוך tasks, לא ניתן להכנס למצב Debugging ויש יכולות ותוספים של VS Code שיפעלו בצורה מוגבלת. כמה דוגמאות לדברים שלא עובדים במצב המוגבל: 1. הצגת תמונות במסוף 2. הצגת היסטוריה במסוף 3. תיקון מהיר לפקודות מסוף 4. שימוש ב Skills בעבודה עם AI 5. שימוש בקבצי Agents.md בעבודה עם AI סך הכל אפשר בהחלט לערוך קבצים במצב מוגבל וגם לעבוד על פרויקט אבל כשרוצים לעבוד בצורה רצינית ובמיוחד לשלב עבודה עם סוכני קידוד או אינטגרציה עם המסוף דברים מתחילים להסתבך. אפשר לראות בהגדרות את הרשימה המלאה. מצד שני עבודה במצב "אמון גבוה" פותחת דלת לקוד זדוני לרוץ ללא השגחה על המכונה. איך אתם רגילים לעבוד? אפשר להגיב במייל או בטלגרם.

ToCode
1 419
מי שלא בא עם ידע בסיסי בעבודה עם כלי AI יכול להיות מאוד רלוונטי את המשפט הזה שמעתי בהרצאה על AI: "בגיוס ג'וניורים, מי שלא בא עם ידע בסיסי בעבודה עם כלי AI לא רלוונטי". אם גם אתם שמעתם דברים דומים בבקשה קחו אוויר. בעבודה עם ג'וניורים הבעיה שלנו היא עודף שימוש בכלי AI: 1. ג'וניורים נותנים ל AI לכתוב קוד ושולחים PR בלי להבין מה בוצע. 2. ג'וניורים חושבים שהם מבינים אבל בעצם קראו רק את מה שה AI כתב ולא אימתו את זה מול התיעוד הרשמי. 3. ג'וניורים לא עצרו לחפש פתרונות יעילים יותר ולא השוו בין מספר אופציות. 4. ג'וניורים לא ראו את בעיות האבטחה שהם הכניסו למערכת. גם היום, ובמיוחד ככל ש AI ייכנס יותר לעומק בחברות, אנחנו צריכים אנשים שמבינים את היסודות. אנשים עם חשיבה ביקורתית, אנשים שיודעים לקחת אחריות על קוד וארכיטקטורה. אנשים שיודעים לחשוב על מערכת תוכנה ולחבר את החלקים בצורה אמינה. דוגמה קטנה מעולם שיפור ביצועי מערכת Full Stack - הרבה יותר חשוב להבין לעומק מה גורם לאיטיות ומה ה Trade Offs הרלוונטים מאשר להבין איך לחבר את לייטהאוס ב MCP כדי שקלוד ישפר ביצועים באוטומט.

ToCode
1 419
טיפ גיט: שינוי מקומי בלי קומיט על קבצים במעקב אתם כבר מכירים את gitignore ואת .git/info/exclude וזוכרים את הבעיה המעצבנת בשניהם: מנגנון ההתעלמות מאפשר להתעלם מקבצים קיימים על הדיסק שאינם בריפו. אבל מרגע שקובץ נמצא בריפו לא משנה כמה פעמים תכתבו אותו ב gitignore השינויים בו תמיד יופיעו ב status. קובץ שנמצא במעקב עוקף את ה gitignore. אז מה עושים עם שינויים מקומיים בקבצים במעקב שלא צריכים להיכנס לריפו? לדוגמה שינוי בקובץ docker-compose שמשנה את מיפויי הפורטים ספציפית על המחשב שלכם. התשובה נקראת skip worktree bit ואפשר לקרוא עליה בדף התיעוד man git-update-index. בקצרה זה מתג שנשמר מקומית אצלכם ואומר לגיט לשמור על השינויים המקומיים שלכם כל עוד אפשר. אנחנו מדליקים אותו לקובץ מסוים עם:
git update-index --skip-worktree docker-compose.yml
ומבטלים עם:
git update-index --no-skip-worktree docker-compose.yml
דוגמה? בטח. ניצור ריפו עם שני קבצים:
$ git init .
$ date > a.txt
$ date > b.txt
$ git add .
$ git commit -m 'initial commit'
עכשיו נשנה את b.txt ונראה בסטטוס את השינוי:
$ date > b.txt
ynonp@ynons-MacBook-Air ~/tmp/blog/skipworktree (main*) $ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   b.txt

no changes added to commit (use "git add" and/or "git commit -a")
נדליק את skip worktree bit:
$ git update-index --skip-worktree b.txt
ynonp@ynons-MacBook-Air ~/tmp/blog/skipworktree (main) $ git status
On branch main
nothing to commit, working tree clean
ועכשיו השינוי המקומי שלי לא מפריע לעבודה השוטפת. נסיון למחוק את השינוי המקומי שלי עם restore לא עובד וגיט אומר שהוא לא מכיר את הקובץ:
$ git restore b.txt
error: pathspec 'b.txt' did not match any file(s) known to git
ב ls-files לעומת זאת הקובץ כן מופיע:
$ git ls-files
a.txt
b.txt
ואם נוסיף -v נוכל לראות שהוא במצב S כלומר skip-worktree:
$ git ls-files -v
H a.txt
S b.txt
כשאני יוצר ענף חדש הקובץ עדיין שומר על השינוי המקומי שלי ואם אני מנסה לשנות אותו ולהכניס לגיט אני אקבל הודעת שגיאה ב add. לסיכום הקובץ למעשה קיבל "הגנת גיט" - גיט יעשה הכל כדי לא לדרוס את השינוי המקומי שלי ולא לאפשר לי להכניס את השינוי המקומי שלי לריפו. פיצ'ר מאוד שימושי כשרוצים להגן על קבצים עם שינויים מקומיים, כל עוד אתם זוכרים שהדלקתם את הביט ולא מתבלבלים כשגיט מדלג על השינויים.