feat: ekip davranis protokolunu guclendir

This commit is contained in:
2026-03-18 01:05:18 +03:00
parent 2d78b821d0
commit 4d6abff5c4
3 changed files with 142 additions and 51 deletions

View File

@@ -1,35 +1,26 @@
export function buildBootstrapPrompt(projectPath = null) { export function buildBootstrapPrompt(projectPath = null) {
const projectContext = projectPath const projectContext = projectPath
? `Aktif proje kok dizini: ${projectPath}. Bundan sonra tum analiz, yorum, gorev parcasi ve dosya referanslarini yalnizca bu proje uzerinden yap. Bu proje disina tasma.` ? `Aktif proje kok dizini: ${projectPath}. Tum analiz, yorum, gorev parcasi ve dosya referanslarini yalnizca bu proje uzerinden yapin. Bu proje disina tasmayin.`
: "Aktif proje henuz secilmedi. Kullanici proje secene kadar dosya baglaminda varsayim yapma."; : "Aktif proje henuz secilmedi. Kullanici proje secene kadar dosya baglaminda varsayim yapmayin.";
return [ return [
'Team agent modunu aktif et.', "Team agent modunu aktif et.",
'Team icerisindeki roller -> Team Lead: "Mazlum" (erkek), Frontend Developer: "Berkecan" (erkek), Backend Developer: "Simsar" (erkek), UI/UX Designer: "Aybuke" (disi), iOS Developer: "Ive" (erkek) ve takim uyelerine kahveleri getirmesi icin Trainee: "Irgatov" (erkek).', 'Takim: Team Lead: "Mazlum", Frontend Developer: "Berkecan", Backend Developer: "Simsar", UI/UX Designer: "Aybuke", iOS Developer: "Ive", Trainee: "Irgatov".',
"Bu takim yapisini aynen koru.",
projectContext, projectContext,
"Irgatov teknik ekip uyesi degildir; yalnizca kahve, icecek, ofis ici lojistik ve basit yardim isleriyle ilgilenir.", "Davranis protokolu:",
"Irgatov kod mimarisi, dosya yapisi, planlama, bug analizi, teknoloji secimi, UI/UX karari, backend karari veya iOS karari vermez.", "1. Her cevap mutlaka kisi adi etiketiyle baslar. Yalnizca su baslangiclar kullanilir: `Mazlum:`, `Berkecan:`, `Simsar:`, `Aybuke:`, `Ive:`, `Irgatov:`.",
"Teknik gorev dagitimi yaparken Irgatov'a teknik is yazma. Irgatov sadece kahve ve lojistik destek icin konussun.", "2. Kullanicinin hitabi daima `Patron`dur. Tum ekip uyeleri kullaniciya konusurken mutlaka `Patron` diye hitap eder ve saygili, olculu, profesyonel bir dil kullanir.",
"Takim ici tum mesajlarda konusan kisi zorunlu olarak ad etiketiyle baslasin.", "3. Ekip uyeleri kendi aralarindaki samimi dili kullaniciya karsi kullanmaz.",
"Her cevap yalnizca su formatla baslasin: `Mazlum:` veya `Berkecan:` veya `Simsar:` veya `Aybuke:` veya `Ive:` veya `Irgatov:`.", "4. Tum ekip uyeleri Team Lead icin `Mazlum Bey` hitabini kullanir.",
"Etiketsiz cevap verme. `Ben`, `Team Lead`, `Frontend Developer`, `UI/UX Designer`, `biz`, `takim olarak` gibi baslangiclar kullanma.", "5. Tum ekip uyeleri UI/UX Designer icin `UI Hanim` hitabini kullanir.",
"Gecerli ornek: `Mazlum: Buradayim.` Gecersiz ornek: `Buradayim.`", "6. Team Lead ve UI/UX Designer haric erkek ekip uyeleri kendi aralarinda gerektiginde `Frontend Kanka`, `Backend Kanka`, `iOS Kanka` gibi hitaplar kullanabilir. Bu hitaplar yalnizca ekip ici konusmalarda kullanilir.",
"Kullanici tek bir kisiye seslenirse sadece o kisi cevap versin ve cevabi kendi ad etiketiyle baslatsin.", "7. Ekip ici konusmalar hafif esprili ve ofis ortaminda yanina gidip konusuyormus gibi dogal olabilir. Ancak mizah kisa tutulur, teknik dogruluk her zaman once gelir, gereksiz roleplay yapilmaz.",
"Kullanici tum takima veya genel bir goreve seslenirse once `Mazlum:` cevap versin. Gerekirse digerleri ayri satirlarda kendi ad etiketiyle devam etsin.", "8. Ekip ici diyalog yalnizca gerektiginde kisa tutulur. Basit sorularda gereksiz cok kisili diyalog kurma.",
"Her ekip uyesi her mesajda kendi sabit adini kullanir, isim degistirmez.", "9. Kullanici tek bir kisiye seslenirse sadece o kisi cevap verir.",
"Ilk cevap olarak yalnizca takimin hazir oldugunu ve rollerin aktiflestigini bildir. Bu ilk cevap da `Mazlum:` ile baslasin." "10. Kullanici tum ekibe veya genel bir goreve seslenirse once Mazlum cevap verir. Gerekirse diger ekip uyeleri kisa katkilar yapar.",
].join(" "); "11. Proje tamamlandiginda, teslim ozeti veya briefing istendiginde son ozet yalnizca Mazlum tarafindan verilir.",
} "12. Irgatov teknik ekip uyesi degildir. Irgatov sadece kahve, icecek, servis, basit ofis lojistigi ve yardim isleriyle ilgilenir. Kod, mimari, dosya yapisi, planlama, bug analizi, teknoloji secimi, UI/UX, backend veya iOS konularinda teknik gorus bildirmez.",
"13. Karakter davranisi teknik dogrulugun onune gecmez. Gereksiz tekrar yapma, gereksiz uzun cevap verme, yanlis ama eglenceli cevap verme.",
export function buildProjectSelectionPrompt(projectPath) { "14. Ilk cevap yalnizca Mazlum tarafindan verilir. Takimin aktif oldugunu, rollerin hazir oldugunu ve aktif proje dizinini bildirir. Bu ilk cevap `Mazlum:` ile baslar."
return [
"Proje baglami guncellendi.",
`Yeni aktif proje kok dizini: ${projectPath}.`,
"Bu andan itibaren tum yorum, plan, gorev ve kod onerilerini yalnizca bu proje uzerinden yap.",
"Bu proje disinda dosya, klasor veya kod tabani varsayimi yapma.",
"Irgatov bu proje baglaminda da sadece kahve ve lojistik destek verir; teknik gorev almaz.",
"Kullanici yeni bir proje secene kadar bu proje varsayilan tek calisma alanidir.",
"Bu bildirimi tekrar etme; sadece yeni proje baglamina gore calismaya devam et."
].join(" "); ].join(" ");
} }

View File

@@ -5,46 +5,143 @@ import { buildBootstrapPrompt } from "./bootstrapPrompt.js";
import { LogService } from "./logService.js"; import { LogService } from "./logService.js";
import { PtyService } from "./ptyService.js"; import { PtyService } from "./ptyService.js";
import { getClaudeEnv, getPublicRuntimeConfig } from "./config.js"; import { getClaudeEnv, getPublicRuntimeConfig } from "./config.js";
import { findMentionedMember } from "./teamConfig.js"; import { findMentionedMember, findMentionedMembers } from "./teamConfig.js";
function cleanChunk(value) { function cleanChunk(value) {
return stripAnsi(value).replace(/\r/g, ""); return stripAnsi(value).replace(/\r/g, "");
} }
function normalizeText(value) {
return String(value ?? "")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.trim();
}
function isLikelyFollowUp(prompt) { function isLikelyFollowUp(prompt) {
const normalized = String(prompt ?? "").trim(); const normalized = normalizeText(prompt);
const wordCount = normalized.split(/\s+/).filter(Boolean).length; const wordCount = normalized.split(/\s+/).filter(Boolean).length;
return [ return [
wordCount <= 8, wordCount <= 8,
/^(evet|hayir|tamam|olur|olsun|sade|sekersiz|şekersiz|detaylandir|detaylandır|devam|peki|neden|nasil|nasıl)\b/i.test(normalized) /^(evet|hayir|tamam|olur|olsun|sade|sekersiz|detaylandir|detaylandir|devam|peki|neden|nasil|biraz ac|kisalt|ornek ver)\b/i.test(normalized)
].some(Boolean); ].some(Boolean);
} }
function buildRoutedPrompt(prompt, lastDirectedMember = null) { function isBriefingRequest(prompt) {
const explicitTarget = findMentionedMember(prompt); const normalized = normalizeText(prompt);
const targetMember = explicitTarget ?? (lastDirectedMember && isLikelyFollowUp(prompt) ? lastDirectedMember : null); return [
"brief",
"briefing",
"ozet",
"durum raporu",
"tamamlandi mi",
"teslim durumu",
"son durum",
"rapor ver",
"breef"
].some((token) => normalized.includes(token));
}
if (!targetMember) { function isCoordinationRequest(prompt) {
const normalized = normalizeText(prompt);
return [
"kendi aranizda konusun",
"aranizda konusun",
"toplanin",
"koordine olun",
"birbirinizle konusun",
"tartisin",
"degerlendirin",
"ekipce karar verin",
"kendi aralarinizda",
"kendi aranizda"
].some((token) => normalized.includes(token));
}
function buildGeneralPrompt(prompt) {
return { return {
mode: "general",
targetMember: null, targetMember: null,
routedPrompt: `Not: Bu genel mesajdir. Once Mazlum cevap versin ve konusan herkes ad etiketi kullansin. Irgatov teknik gorev almaz; sadece kahve ve lojistik destek verir. Kullanici mesaji: ${prompt}` routedPrompt: `Yonlendirme notu: Bu mesaj tum ekibe yoneliktir. Once Mazlum cevap versin. Gerekirse diger ekip uyeleri kendi ad etiketiyle kisa katkilar yapsin. Kullaniciya konusurken herkes Patron diye hitap etsin. Ekip ici diyalog sadece gerekiyorsa kisa olsun. Gereksiz roleplay yapma. Sonuc net ve uygulanabilir olsun. Kullanici mesaji: ${prompt}`
}; };
} }
function buildBriefingPrompt(prompt) {
return {
mode: "briefing",
targetMember: null,
routedPrompt: `Yonlendirme notu: Bu mesaj proje ozeti veya teslim briefigi gerektiriyor. Son ozet yalnizca Mazlum tarafindan verilsin. Cevap Mazlum: ile baslasin. Kullaniciya mutlaka Patron diye hitap et. Ozet duzenli, yonetsel ve net olsun. Gerekirse yapilanlar, kalan riskler ve sonraki adimlar kisaca belirtilsin. Diger ekip uyeleri yalnizca zorunluysa kisa katkida bulunsun. Kullanici mesaji: ${prompt}`
};
}
function buildCoordinationPrompt(prompt) {
return {
mode: "coordination",
targetMember: null,
routedPrompt: `Yonlendirme notu: Bu mesaj kisa ekip ici koordinasyon gerektiriyor. Once Mazlum durumu acsin. Gerekirse ilgili ekip uyeleri kendi ad etiketiyle kisa konussun. Ekip ici hitap kurallarini uygula: Mazlum Bey, UI Hanim, erkek ekip uyeleri arasinda gerektiginde Frontend Kanka, Backend Kanka, iOS Kanka. Diyalog kisa olsun. Ardindan net sonuc veya karar acikca verilsin. Kullaniciya donecek cerceve saygili olsun ve Patron hitabi korunsun. Kullanici mesaji: ${prompt}`
};
}
function buildDirectPrompt(prompt, targetMember) {
if (targetMember.name === "Irgatov") { if (targetMember.name === "Irgatov") {
return { return {
mode: "irgatov_direct",
targetMember, targetMember,
routedPrompt: `Not: Bu mesaj Irgatov icindir. Irgatov sadece kahve, icecek, servis ve basit lojistik destek konularinda cevap versin. Teknik plan, kod, mimari veya dosya yapisi onermesin. Cevap \`Irgatov:\` ile baslasin. Kullanici mesaji: ${prompt}` routedPrompt: `Yonlendirme notu: Bu mesaj Irgatov icindir. Yalnizca Irgatov cevap versin. Cevap Irgatov: ile baslasin. Kullaniciya Patron diye hitap et. Irgatov sadece kahve, icecek, servis ve basit ofis lojistigi konularinda cevap verir. Teknik plan, kod, mimari, dosya yapisi veya teknoloji secimi hakkinda gorus bildirmez. Kullanici mesaji: ${prompt}`
}; };
} }
return { return {
mode: "direct",
targetMember, targetMember,
routedPrompt: `Not: Bu mesaj ${targetMember.name} icindir. Yalnizca ${targetMember.name} cevap versin ve cevap \`${targetMember.name}:\` ile baslasin. Kullanici mesaji: ${prompt}` routedPrompt: `Yonlendirme notu: Bu mesaj dogrudan ${targetMember.name} icindir. Yalnizca ${targetMember.name} cevap versin. Cevap mutlaka ${targetMember.name}: ile baslasin. Kullaniciya mutlaka Patron diye hitap et. Kullaniciya karsi saygili, net ve profesyonel dil kullan. Gereksiz ekip ici diyalog kurma. Gerekirse cok kisa ofis tonu kullanabilirsin ama teknik icerigi golgeleme. Kullanici mesaji: ${prompt}`
}; };
} }
function buildFollowUpPrompt(prompt, lastDirectedMember = null) {
if (!lastDirectedMember) {
return buildGeneralPrompt(prompt);
}
if (lastDirectedMember.name === "Irgatov") {
return buildDirectPrompt(prompt, lastDirectedMember);
}
return {
mode: "follow_up",
targetMember: lastDirectedMember,
routedPrompt: `Yonlendirme notu: Bu mesaj onceki konusmanin devamidir. Mumkunse onceki hedef kisi cevap versin. Cevap mevcut baglami korusun. Kullaniciya mutlaka Patron diye hitap et. Yalnizca gerekliyse kisa cevap ver. Gereksiz yeni ekip diyalogu baslatma. Cevap ${lastDirectedMember.name}: ile baslasin. Kullanici mesaji: ${prompt}`
};
}
function buildRoutedPrompt(prompt, lastDirectedMember = null) {
const mentionedMembers = findMentionedMembers(prompt);
if (isBriefingRequest(prompt)) {
return buildBriefingPrompt(prompt);
}
if (isCoordinationRequest(prompt)) {
return buildCoordinationPrompt(prompt);
}
if (mentionedMembers.length === 1) {
return buildDirectPrompt(prompt, mentionedMembers[0]);
}
if (mentionedMembers.length > 1) {
return buildGeneralPrompt(prompt);
}
if (lastDirectedMember && isLikelyFollowUp(prompt)) {
return buildFollowUpPrompt(prompt, lastDirectedMember);
}
return buildGeneralPrompt(prompt);
}
export class SessionManager { export class SessionManager {
constructor({ io, config }) { constructor({ io, config }) {
this.io = io; this.io = io;

View File

@@ -2,9 +2,9 @@ const TEAM_MEMBERS = [
{ id: "mazlum", name: "Mazlum", aliases: ["mazlum", "team lead", "lead"] }, { id: "mazlum", name: "Mazlum", aliases: ["mazlum", "team lead", "lead"] },
{ id: "berkecan", name: "Berkecan", aliases: ["berkecan", "frontend developer", "frontend"] }, { id: "berkecan", name: "Berkecan", aliases: ["berkecan", "frontend developer", "frontend"] },
{ id: "simsar", name: "Simsar", aliases: ["simsar", "backend developer", "backend"] }, { id: "simsar", name: "Simsar", aliases: ["simsar", "backend developer", "backend"] },
{ id: "aybuke", name: "Aybuke", aliases: ["aybuke", "aybüke", "ui/ux designer", "designer"] }, { id: "aybuke", name: "Aybuke", aliases: ["aybuke", "aybüke", "ui/ux designer", "designer", "ui"] },
{ id: "ive", name: "Ive", aliases: ["ive", "ios developer", "ios"] }, { id: "ive", name: "Ive", aliases: ["ive", "ios developer", "ios", "ioscu", "ios developer"] },
{ id: "irgatov", name: "Irgatov", aliases: ["irgatov", "trainee", "intern"] } { id: "irgatov", name: "Irgatov", aliases: ["irgatov", "trainee", "intern", "stajyer"] }
]; ];
function normalizeText(value) { function normalizeText(value) {
@@ -14,18 +14,21 @@ function normalizeText(value) {
.toLowerCase(); .toLowerCase();
} }
export function findMentionedMember(prompt) { export function findMentionedMembers(prompt) {
const normalizedPrompt = normalizeText(prompt); const normalizedPrompt = normalizeText(prompt);
const matches = [];
for (const member of TEAM_MEMBERS) { for (const member of TEAM_MEMBERS) {
for (const alias of member.aliases) { if (member.aliases.some((alias) => normalizedPrompt.includes(normalizeText(alias)))) {
if (normalizedPrompt.includes(normalizeText(alias))) { matches.push(member);
return member;
}
} }
} }
return null; return matches;
}
export function findMentionedMember(prompt) {
return findMentionedMembers(prompt)[0] ?? null;
} }
export { TEAM_MEMBERS }; export { TEAM_MEMBERS };