feat: proje secimi ve otomatik ekip akisini ekle

This commit is contained in:
2026-03-17 00:40:50 +03:00
parent 3641190a77
commit c312b83604
6 changed files with 130 additions and 5 deletions

View File

@@ -1,8 +1,16 @@
export function buildBootstrapPrompt() {
export function buildBootstrapPrompt(projectPath = null) {
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 henuz secilmedi. Kullanici proje secene kadar dosya baglaminda varsayim yapma.";
return [
'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).',
"Bu takim yapisini aynen koru.",
projectContext,
"Irgatov teknik ekip uyesi degildir; yalnizca kahve, icecek, ofis ici lojistik ve basit yardim isleriyle ilgilenir.",
"Irgatov kod mimarisi, dosya yapisi, planlama, bug analizi, teknoloji secimi, UI/UX karari, backend karari veya iOS karari vermez.",
"Teknik gorev dagitimi yaparken Irgatov'a teknik is yazma. Irgatov sadece kahve ve lojistik destek icin konussun.",
"Takim ici tum mesajlarda konusan kisi zorunlu olarak ad etiketiyle baslasin.",
"Her cevap yalnizca su formatla baslasin: `Mazlum:` veya `Berkecan:` veya `Simsar:` veya `Aybuke:` veya `Ive:` veya `Irgatov:`.",
"Etiketsiz cevap verme. `Ben`, `Team Lead`, `Frontend Developer`, `UI/UX Designer`, `biz`, `takim olarak` gibi baslangiclar kullanma.",
@@ -13,3 +21,15 @@ export function buildBootstrapPrompt() {
"Ilk cevap olarak yalnizca takimin hazir oldugunu ve rollerin aktiflestigini bildir. Bu ilk cevap da `Mazlum:` ile baslasin."
].join(" ");
}
export function buildProjectSelectionPrompt(projectPath) {
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(" ");
}

View File

@@ -4,6 +4,7 @@ import path from "path";
import { fileURLToPath } from "url";
import { Server } from "socket.io";
import { getPublicRuntimeConfig, getRuntimeConfig } from "./config.js";
import { selectProjectFolder } from "./projectPicker.js";
import { SessionManager } from "./sessionManager.js";
import { registerSocketHandlers } from "./socketHandlers.js";
@@ -21,6 +22,8 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const webDistPath = path.resolve(__dirname, "../web/dist");
app.use(express.json());
app.get("/health", (req, res) => {
res.json({
ok: true,
@@ -37,6 +40,22 @@ app.get("/api/session/state", (req, res) => {
});
});
app.post("/api/project/select", async (req, res) => {
try {
const selectedPath = req.body?.projectPath ? String(req.body.projectPath) : await selectProjectFolder();
await sessionManager.setProjectPath(selectedPath);
res.json({
ok: true,
projectPath: sessionManager.getState().currentProjectPath
});
} catch (error) {
res.status(500).json({
ok: false,
error: error.message
});
}
});
if (config.nodeEnv === "production") {
app.use(express.static(webDistPath));
app.get("*", (req, res) => {

10
server/projectPicker.js Normal file
View File

@@ -0,0 +1,10 @@
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
export async function selectProjectFolder() {
const script = 'POSIX path of (choose folder with prompt "Select project folder")';
const { stdout } = await execFileAsync("/usr/bin/osascript", ["-e", script]);
return String(stdout ?? "").trim();
}

View File

@@ -1,5 +1,8 @@
import fs from "fs";
import path from "path";
import stripAnsi from "strip-ansi";
import { buildBootstrapPrompt } from "./bootstrapPrompt.js";
import { buildProjectSelectionPrompt } from "./bootstrapPrompt.js";
import { LogService } from "./logService.js";
import { PtyService } from "./ptyService.js";
import { getClaudeEnv, getPublicRuntimeConfig } from "./config.js";
@@ -26,7 +29,14 @@ function buildRoutedPrompt(prompt, lastDirectedMember = null) {
if (!targetMember) {
return {
targetMember: null,
routedPrompt: `Not: Bu genel mesajdir. Once Mazlum cevap versin ve konusan herkes ad etiketi kullansin. Kullanici mesaji: ${prompt}`
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}`
};
}
if (targetMember.name === "Irgatov") {
return {
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}`
};
}
@@ -44,11 +54,13 @@ export class SessionManager {
this.ptyService = null;
this.chatOutput = "";
this.lastDirectedMember = null;
this.currentProjectPath = null;
this.state = {
status: "idle",
startedAt: null,
teamActivated: false,
lastError: null,
currentProjectPath: null,
runtime: getPublicRuntimeConfig(config)
};
}
@@ -56,6 +68,7 @@ export class SessionManager {
getState() {
return {
...this.state,
currentProjectPath: this.currentProjectPath,
runtime: getPublicRuntimeConfig(this.config)
};
}
@@ -94,6 +107,39 @@ export class SessionManager {
this.emitState();
}
getActiveWorkspaceDir() {
return this.currentProjectPath ?? this.config.workspaceDir;
}
async setProjectPath(projectPath) {
const resolved = projectPath ? path.resolve(projectPath) : null;
const wasRunning = this.ptyService?.isRunning() ?? false;
if (resolved && (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory())) {
throw new Error(`Selected project path is invalid: ${resolved}`);
}
if (wasRunning) {
await this.stop();
}
this.currentProjectPath = resolved;
this.lastDirectedMember = null;
this.setState({ currentProjectPath: resolved });
this.addLog("system", `Current project set to ${resolved ?? "None"}`);
if (wasRunning) {
await this.start();
if (resolved) {
await this.activateTeam();
} else {
const prompt = buildProjectSelectionPrompt(this.getActiveWorkspaceDir());
await this.sendRawPrompt(prompt, { label: `[project] Switched active project to ${this.getActiveWorkspaceDir()}` });
}
}
}
async start() {
if (this.ptyService?.isRunning()) {
throw new Error("Session is already running");
@@ -105,7 +151,7 @@ export class SessionManager {
this.io.emit("chat:reset");
this.ptyService = new PtyService({
cwd: this.config.workspaceDir,
cwd: this.getActiveWorkspaceDir(),
env: getClaudeEnv(this.config)
});
@@ -116,7 +162,7 @@ export class SessionManager {
lastError: null
});
this.addLog("lifecycle", `Starting Claude session in ${this.config.workspaceDir}`);
this.addLog("lifecycle", `Starting Claude session in ${this.getActiveWorkspaceDir()}`);
try {
await this.ptyService.start({
@@ -174,7 +220,7 @@ export class SessionManager {
}
async activateTeam() {
const prompt = buildBootstrapPrompt();
const prompt = buildBootstrapPrompt(this.currentProjectPath);
this.lastDirectedMember = null;
await this.sendRawPrompt(prompt, { label: "[bootstrap] Team activation prompt sent" });
this.setState({ teamActivated: true });

View File

@@ -52,5 +52,15 @@ export function registerSocketHandlers(io, sessionManager) {
sessionManager.clearLogs();
callback?.({ ok: true });
});
socket.on("project:select", async ({ projectPath }, callback) => {
try {
await sessionManager.setProjectPath(projectPath);
callback?.({ ok: true, projectPath: sessionManager.getState().currentProjectPath });
} catch (error) {
socket.emit("session:error", { message: error.message });
callback?.({ ok: false, error: error.message });
}
});
});
}