@omerrgocmen/crewctl 1.0.1 → 1.0.3

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.
Files changed (96) hide show
  1. package/README.md +2 -0
  2. package/orchestrator/README.md +429 -427
  3. package/orchestrator/config.default.json +71 -65
  4. package/orchestrator/roles/executor.md +26 -18
  5. package/orchestrator/roles/operator.md +77 -75
  6. package/orchestrator/roles/planner.md +5 -0
  7. package/orchestrator/roles/reviewer.md +63 -54
  8. package/orchestrator/skills/acceptance-criteria.md +18 -18
  9. package/orchestrator/skills/accessibility-audit.md +16 -16
  10. package/orchestrator/skills/accessible-forms.md +16 -16
  11. package/orchestrator/skills/api-design.md +27 -27
  12. package/orchestrator/skills/api-documentation.md +16 -16
  13. package/orchestrator/skills/architecture-decision-record.md +18 -18
  14. package/orchestrator/skills/authentication-design.md +16 -16
  15. package/orchestrator/skills/authorization-review.md +16 -16
  16. package/orchestrator/skills/backward-compatibility.md +17 -17
  17. package/orchestrator/skills/changelog-writing.md +16 -16
  18. package/orchestrator/skills/ci-pipeline-design.md +16 -16
  19. package/orchestrator/skills/cli-design.md +16 -16
  20. package/orchestrator/skills/code-review.md +27 -27
  21. package/orchestrator/skills/configuration-management.md +16 -16
  22. package/orchestrator/skills/container-review.md +16 -16
  23. package/orchestrator/skills/contract-testing.md +16 -16
  24. package/orchestrator/skills/dashboard-design.md +16 -16
  25. package/orchestrator/skills/database-migration.md +16 -16
  26. package/orchestrator/skills/database-schema-design.md +16 -16
  27. package/orchestrator/skills/debugging.md +26 -26
  28. package/orchestrator/skills/dependency-review.md +16 -16
  29. package/orchestrator/skills/design-review.md +29 -29
  30. package/orchestrator/skills/design-system.md +16 -16
  31. package/orchestrator/skills/docs-api-reference.md +16 -16
  32. package/orchestrator/skills/docs-troubleshooting.md +16 -16
  33. package/orchestrator/skills/docs-tutorial.md +16 -16
  34. package/orchestrator/skills/docs-writing.md +25 -25
  35. package/orchestrator/skills/empty-error-loading-states.md +16 -16
  36. package/orchestrator/skills/end-to-end-testing.md +16 -16
  37. package/orchestrator/skills/error-handling.md +16 -16
  38. package/orchestrator/skills/frontend-design.md +28 -28
  39. package/orchestrator/skills/git-commit-writing.md +16 -16
  40. package/orchestrator/skills/graphql-design.md +16 -16
  41. package/orchestrator/skills/incident-runbook.md +18 -18
  42. package/orchestrator/skills/input-validation.md +16 -16
  43. package/orchestrator/skills/integration-testing.md +16 -16
  44. package/orchestrator/skills/interaction-design.md +16 -16
  45. package/orchestrator/skills/landing-page-design.md +16 -16
  46. package/orchestrator/skills/observability-design.md +16 -16
  47. package/orchestrator/skills/openapi-contract.md +16 -16
  48. package/orchestrator/skills/performance-profiling.md +16 -16
  49. package/orchestrator/skills/privacy-review.md +16 -16
  50. package/orchestrator/skills/property-based-testing.md +16 -16
  51. package/orchestrator/skills/pull-request-writing.md +16 -16
  52. package/orchestrator/skills/refactoring.md +16 -16
  53. package/orchestrator/skills/release-readiness.md +16 -16
  54. package/orchestrator/skills/responsive-design.md +16 -16
  55. package/orchestrator/skills/secrets-management.md +16 -16
  56. package/orchestrator/skills/secure-file-upload.md +16 -16
  57. package/orchestrator/skills/security-review.md +26 -26
  58. package/orchestrator/skills/semantic-versioning.md +17 -17
  59. package/orchestrator/skills/seo-on-page.md +16 -16
  60. package/orchestrator/skills/seo-structured-data.md +16 -16
  61. package/orchestrator/skills/seo-technical-audit.md +16 -16
  62. package/orchestrator/skills/sql-query-review.md +16 -16
  63. package/orchestrator/skills/supply-chain-security.md +16 -16
  64. package/orchestrator/skills/test-strategy.md +16 -16
  65. package/orchestrator/skills/threat-modeling.md +16 -16
  66. package/orchestrator/skills/unit-testing.md +16 -16
  67. package/orchestrator/skills/write-tests.md +28 -28
  68. package/orchestrator/src/cli-registry.js +616 -613
  69. package/orchestrator/src/cli.js +131 -131
  70. package/orchestrator/src/doctor.js +64 -64
  71. package/orchestrator/src/engine.js +216 -32
  72. package/orchestrator/src/server.js +766 -733
  73. package/orchestrator/src/skill-registry.js +272 -272
  74. package/orchestrator/src/store.js +399 -379
  75. package/orchestrator/web/OrbitControls.js +1417 -1417
  76. package/orchestrator/web/android-chrome-192x192.png +0 -0
  77. package/orchestrator/web/android-chrome-512x512.png +0 -0
  78. package/orchestrator/web/apple-touch-icon.png +0 -0
  79. package/orchestrator/web/board.html +5 -0
  80. package/orchestrator/web/code.html +5 -0
  81. package/orchestrator/web/favicon-16x16.png +0 -0
  82. package/orchestrator/web/favicon-32x32.png +0 -0
  83. package/orchestrator/web/favicon.ico +0 -0
  84. package/orchestrator/web/flow.html +741 -736
  85. package/orchestrator/web/index.html +588 -539
  86. package/orchestrator/web/jsm/postprocessing/EffectComposer.js +231 -231
  87. package/orchestrator/web/jsm/postprocessing/MaskPass.js +104 -104
  88. package/orchestrator/web/jsm/postprocessing/Pass.js +95 -95
  89. package/orchestrator/web/jsm/postprocessing/RenderPass.js +99 -99
  90. package/orchestrator/web/jsm/postprocessing/ShaderPass.js +77 -77
  91. package/orchestrator/web/jsm/postprocessing/UnrealBloomPass.js +415 -415
  92. package/orchestrator/web/jsm/shaders/CopyShader.js +45 -45
  93. package/orchestrator/web/jsm/shaders/LuminosityHighPassShader.js +66 -66
  94. package/orchestrator/web/site.webmanifest +12 -0
  95. package/orchestrator/web/three.module.min.js +6 -6
  96. package/package.json +1 -1
@@ -240,37 +240,71 @@ function extractVerdict(text) {
240
240
  return /PASS/i.test(matches[matches.length - 1]) ? "PASS" : "FAIL";
241
241
  }
242
242
 
243
+ // Siniflandirilamayan hatada "gemini cikis kodu 1." gibi bilgisiz bir ilk satir yerine metnin
244
+ // icindeki GERCEK hata cumlesini bulur; kullaniciya gosterilen tek satir buysa anlamli olmali.
245
+ function meaningfulErrorLine(raw) {
246
+ const all = String(raw).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
247
+ // Yigin izleri ve motorun kendi sardigi "<cli> cikis kodu N." satiri teshis tasimaz;
248
+ // gercek neden hemen arkalarindaki satirdadir.
249
+ const useful = all.filter((line) => !/^at\s|^node:internal|^[{}[\]]+$/.test(line) && !/cikis kodu/i.test(line));
250
+ const signal = useful.find((line) => /error|hata|failed|failure|denied|invalid|exceeded|limit|quota|unable|cannot|must/i.test(line) && line.length > 20);
251
+ return signal || useful[0] || all[0] || String(raw).trim();
252
+ }
253
+
254
+ // Ham CLI ciktisini kullaniciya "ne oldu + ne yapmali" seklinde anlamli bir teshise cevirir.
255
+ // SIRA ONEMLIDIR: spesifik desenler once gelmeli. Ornegin Gemini'nin "GEMINI_API_KEY ...
256
+ // not found" mesaji bir PATH/kurulum sorunu DEGILDIR; genis /not found/ deseni bu yuzden
257
+ // daraltildi ve kimlik kontrollerinin arkasina alindi.
243
258
  function classifyCliError(error) {
244
259
  const raw = String(error?.message || error || "Bilinmeyen CLI hatasi");
260
+ const of = (code, summary, action) => ({ code, summary, action, raw: clip(raw, 5000) });
245
261
  if (/requires a newer version|upgrade to the latest|model metadata.*not found|unsupported.*model|model.*unsupported/i.test(raw)) {
246
- return { code: "VERSION_INCOMPATIBLE", summary: "CLI sürümü seçilen modeli desteklemiyor.", action: "CLI aracını güncelleyin veya desteklenen bir model seçin.", raw: clip(raw, 5000) };
262
+ return of("VERSION_INCOMPATIBLE", "CLI sürümü seçilen modeli desteklemiyor.", "CLI aracını güncelleyin veya desteklenen bir model seçin.");
247
263
  }
248
264
  if (/sessiz kaldi|cikti uretmedi|CLI_STALLED/i.test(raw)) {
249
- return { code: "CLI_STALLED", summary: "CLI uzun süre yeni çıktı üretmeyince otomatik durduruldu.", action: "Önceki ilerleme kayıtları korundu; operatör bu oturumda farklı bir agent kullanacak.", raw: clip(raw, 5000) };
265
+ return of("CLI_STALLED", "CLI uzun süre yeni çıktı üretmeyince otomatik durduruldu.", "Önceki ilerleme kayıtları korundu; operatör bu oturumda farklı bir agent kullanacak.");
266
+ }
267
+ if (/API key not valid|API_KEY_INVALID|invalid[_ ]api[_ ]key|incorrect api key/i.test(raw)) {
268
+ return of("AUTH_INVALID", "API anahtarı geçersiz veya reddedildi.", "Agent ayarlarındaki API anahtarını yenileyin (Gemini için GEMINI_API_KEY / GOOGLE_API_KEY).");
269
+ }
270
+ // Gemini CLI oturum acilmadiginda "Please set an Auth method" / "GEMINI_API_KEY environment
271
+ // variable not found" yazar; her ikisi de kurulum degil OTURUM sorunudur.
272
+ if (/set an auth method|GEMINI_API_KEY|GOOGLE_API_KEY|unauthorized|unauthenticated|authentication|login required|not logged in|please (run )?login|credentials? (not found|missing|expired)|PERMISSION_DENIED|\b40[13]\b/i.test(raw)) {
273
+ return of("AUTH_REQUIRED", "CLI oturum açmamış; kimlik doğrulaması gerekiyor.", "Terminalde ilgili CLI ile oturum açın (Gemini: `gemini` çalıştırıp 'Login with Google' veya GEMINI_API_KEY tanımlayın), sonra görevi tekrar deneyin.");
274
+ }
275
+ // Kota (gunluk/aylik hak bitti) ile hiz siniri (kisa sureli) AYRI teshislerdir: ilki
276
+ // beklemekle gecmez, ikincisi gecer. Kullanicinin sordugu "limitim mi bitti" ayrimi budur.
277
+ if (/RESOURCE_EXHAUSTED|quota exceeded|exceeded your current quota|daily limit|günlük limit|gunluk limit|out of credits|insufficient (credit|balance|quota)|billing/i.test(raw)) {
278
+ return of("QUOTA_EXCEEDED", "Sağlayıcı kotanız doldu (günlük/aylık hak veya bakiye bitti).", "Kota yenilenene kadar bu agent kullanılamaz; başka bir agent seçin, faturalandırmayı yükseltin veya API anahtarını kotası olan bir hesapla değiştirin.");
250
279
  }
251
- if (/API key not valid|API_KEY_INVALID/i.test(raw)) {
252
- return { code: "AUTH_INVALID", summary: "API anahtari gecersiz veya kullanilamiyor.", action: "Bu agentin kimlik bilgilerini duzeltin ya da baska bir agent kullanin.", raw: clip(raw, 5000) };
280
+ if (/rate.?limit|too many requests|\b429\b/i.test(raw)) {
281
+ return of("RATE_LIMIT", "İstek hızı sınırına takıldı (kota bitmedi, çok sık istek gönderildi).", "Kısa bir süre bekleyip tekrar deneyin; operatör bu turda alternatif bir agent kullanabilir.");
253
282
  }
254
- if (/unauthorized|unauthenticated|authentication|login required|not logged in/i.test(raw)) {
255
- return { code: "AUTH_REQUIRED", summary: "CLI oturumu veya kimlik dogrulamasi gerekli.", action: "CLI'yi terminalde oturum acarak dogrulayin.", raw: clip(raw, 5000) };
283
+ if (/overloaded|\bUNAVAILABLE\b|\b(503|500)\b|internal error|try again later/i.test(raw)) {
284
+ return of("MODEL_OVERLOADED", "Model sağlayıcısı geçici olarak aşırı yüklü ya da hata döndürdü.", "Sağlayıcı kaynaklı geçici bir sorun; birkaç dakika sonra tekrar deneyin.");
256
285
  }
257
- if (/rate.?limit|too many requests|quota/i.test(raw)) {
258
- return { code: "RATE_LIMIT", summary: "Saglayici kota veya hiz sinirina takildi.", action: "Daha sonra deneyin veya alternatif agent kullanin.", raw: clip(raw, 5000) };
286
+ if (/location is not supported|not available in your country|region.*not supported/i.test(raw)) {
287
+ return of("REGION_BLOCKED", "Model bulunduğunuz bölgede kullanıma kapalı.", "Desteklenen bir bölge/hesap kullanın veya başka bir sağlayıcının agent'ını seçin.");
259
288
  }
260
289
  if (/ConnectionRefused|Unable to connect|provider hatasi|provider error|ECONNREFUSED/i.test(raw)) {
261
- return { code: "PROVIDER_UNAVAILABLE", summary: "Secilen model saglayicisina baglanilamadi.", action: "OpenCode icin baska bir model secin veya saglayici baglantisini duzeltin.", raw: clip(raw, 5000) };
290
+ return of("PROVIDER_UNAVAILABLE", "Seçilen model sağlayıcısına bağlanılamadı.", "Sağlayıcı bağlantısını kontrol edin veya başka bir model seçin.");
262
291
  }
263
- if (/not recognized as an internal|ENOENT|not found/i.test(raw)) {
264
- return { code: "CLI_NOT_FOUND", summary: "CLI komutu bulunamadi veya baslatilamadi.", action: "Agent komutunu ve PATH ayarini kontrol edin.", raw: clip(raw, 5000) };
292
+ if (/ENOTFOUND|EAI_AGAIN|ECONNRESET|ETIMEDOUT|getaddrinfo|fetch failed|socket hang up|network (error|unreachable)|proxy/i.test(raw)) {
293
+ return of("NETWORK_ERROR", " bağlantısı kurulamadı (DNS/proxy/internet).", "İnternet veya kurumsal proxy ayarlarınızı kontrol edip görevi tekrar çalıştırın.");
294
+ }
295
+ if (/not recognized as an internal|is not recognized|command not found|spawn .*ENOENT|\bENOENT\b|no such file or directory/i.test(raw)) {
296
+ return of("CLI_NOT_FOUND", "CLI komutu bulunamadı; kurulu değil veya PATH üzerinde görünmüyor.", "CLI'yi kurun (Gemini: `npm install -g @google/gemini-cli`) ve Ayarlar → Agent'lar bölümünde komut adını doğrulayın.");
265
297
  }
266
298
  if (/timeout|timed out|zaman asim|zaman aşım/i.test(raw)) {
267
- return { code: "TIMEOUT", summary: "CLI zaman asimina ugradi.", action: "Timeout degerini artirin veya gorevi daha kucuk parcalara bolun.", raw: clip(raw, 5000) };
299
+ return of("TIMEOUT", "CLI ayrılan süre içinde tamamlanamadı.", "Agent ayarlarından timeout değerini artırın veya görevi daha küçük parçalara bölün.");
268
300
  }
269
- return { code: "CLI_FAILED", summary: clip(raw.split(/\r?\n/)[0], 500), action: "Teknik ayrintiyi inceleyin veya alternatif agent kullanin.", raw: clip(raw, 5000) };
301
+ return of("CLI_FAILED", clip(meaningfulErrorLine(raw), 500), "Ham CLI çıktısı teknik ayrıntılarda saklandı; sorun sürerse alternatif bir agent deneyin.");
270
302
  }
271
303
 
272
- const RECOVERABLE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "RATE_LIMIT", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "TIMEOUT", "CLI_STALLED", "CLI_FAILED", "VERSION_INCOMPATIBLE"]);
273
- const QUARANTINE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "CLI_STALLED", "VERSION_INCOMPATIBLE"]);
304
+ const RECOVERABLE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "RATE_LIMIT", "QUOTA_EXCEEDED", "MODEL_OVERLOADED", "NETWORK_ERROR", "REGION_BLOCKED", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "TIMEOUT", "CLI_STALLED", "CLI_FAILED", "VERSION_INCOMPATIBLE"]);
305
+ // Karantina = bu oturumda tekrar denemek anlamsiz. Kota/bolge kisiti beklemekle gecmez;
306
+ // hiz siniri (RATE_LIMIT) ve gecici saglayici hatasi (MODEL_OVERLOADED) gecer, karantinaya alinmaz.
307
+ const QUARANTINE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "QUOTA_EXCEEDED", "REGION_BLOCKED", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "CLI_STALLED", "VERSION_INCOMPATIBLE"]);
274
308
 
275
309
  function resolveExecutionMode(task) {
276
310
  if (["fast", "balanced", "deep"].includes(task.executionMode)) return task.executionMode;
@@ -316,18 +350,81 @@ function normalizeCliOutput(agent, stdout) {
316
350
  return { text: texts.join("\n").trim(), error: errors.join("\n").trim() };
317
351
  }
318
352
 
353
+ // Metindeki DENGELI suslu parantezli en dis JSON nesnesi adaylarini cikarir. String icindeki
354
+ // parantez/kacis karakterleri sayilmaz; boylece "final" metninde gecen bir { veya } eski
355
+ // "ilk-{ .. son-}" kestirmesini bozmaz. Ayrica model aciklama + nesne + ek aciklama yazdiginda
356
+ // (ya da birden fazla nesne dondurdugunde) her nesne ayri aday olur.
357
+ function jsonObjectCandidates(text) {
358
+ const src = String(text);
359
+ const found = [];
360
+ const stack = [];
361
+ let inString = false, escaped = false;
362
+ for (let i = 0; i < src.length; i++) {
363
+ const ch = src[i];
364
+ if (inString) {
365
+ if (escaped) escaped = false;
366
+ else if (ch === "\\") escaped = true;
367
+ else if (ch === '"') inString = false;
368
+ continue;
369
+ }
370
+ if (ch === '"') inString = true;
371
+ else if (ch === "{") stack.push(i);
372
+ else if (ch === "}" && stack.length) {
373
+ const start = stack.pop();
374
+ if (!stack.length) found.push(src.slice(start, i + 1));
375
+ }
376
+ }
377
+ return found;
378
+ }
379
+
380
+ // Model bazen JSON'u tek tirnak, sondaki virgul veya akilli tirnakla yazar. Kati parse
381
+ // basarisiz oldugunda son care olarak bu yaygin sapmalari onarip bir kez daha dener.
382
+ function repairJsonText(candidate) {
383
+ return String(candidate)
384
+ .replace(/[“”]/g, '"')
385
+ .replace(/[‘’]/g, "'")
386
+ .replace(/,\s*([}\]])/g, "$1");
387
+ }
388
+
389
+ const PROTOCOL_KEYS = /"(status|assignments|summary|completionCriteria|final)"\s*:/;
390
+
319
391
  function parseJson(text, label) {
392
+ const raw = String(text);
320
393
  const candidates = [];
321
- const fenced = String(text).match(/```(?:json)?\s*([\s\S]*?)```/i);
322
- if (fenced) candidates.push(fenced[1]);
323
- candidates.push(String(text).trim());
324
- const first = String(text).indexOf("{");
325
- const last = String(text).lastIndexOf("}");
326
- if (first >= 0 && last > first) candidates.push(String(text).slice(first, last + 1));
394
+ for (const fence of raw.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)) candidates.push(fence[1]);
395
+ candidates.push(raw.trim());
396
+ // Protokol anahtari iceren nesneler once denenir; ayni onem sirasinda SONDAKI nesne kazanir
397
+ // (modeller once dusunce/aciklama, en sona nihai protokol nesnesini yazar).
398
+ const objects = jsonObjectCandidates(raw).reverse();
399
+ candidates.push(...objects.filter((o) => PROTOCOL_KEYS.test(o)), ...objects.filter((o) => !PROTOCOL_KEYS.test(o)));
327
400
  for (const candidate of candidates) {
328
- try { return JSON.parse(candidate); } catch {}
401
+ for (const attempt of [candidate, repairJsonText(candidate)]) {
402
+ try {
403
+ const parsed = JSON.parse(attempt);
404
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
405
+ } catch {}
406
+ }
329
407
  }
330
- throw new Error(`${label} gecerli JSON dondurmedi.`);
408
+ const error = new Error(`${label} gecerli JSON dondurmedi.`);
409
+ error.rawText = raw;
410
+ throw error;
411
+ }
412
+
413
+ // Protokol hatasinda operatorun ham metnini kullaniciya gosterilebilir bir cevaba cevirir.
414
+ // Yarim kalmis/kirik JSON parcalarini ve kod bloklarini atar; geriye anlamli duz metin
415
+ // kalmiyorsa null doner (o zaman normal hata akisi calisir).
416
+ function conversationalAnswer(error) {
417
+ if (!/gecerli JSON dondurmedi/i.test(String(error?.message || ""))) return null;
418
+ const raw = String(error.rawText || "");
419
+ if (!raw.trim()) return null;
420
+ const stripped = raw
421
+ .replace(/```[\s\S]*?```/g, " ")
422
+ .replace(/\{[\s\S]*\}/g, " ")
423
+ .replace(/[\s\S]*?^\s*\{[\s\S]*$/m, (match) => match.split("{")[0])
424
+ .replace(/\n{3,}/g, "\n\n")
425
+ .trim();
426
+ // Cok kisa artiklar (or. tek noktalama) cevap sayilmaz.
427
+ return stripped.length >= 8 ? stripped : null;
331
428
  }
332
429
 
333
430
  function capabilitiesFor(agent) {
@@ -415,12 +512,22 @@ function normalizeAssignments(value, cfg, operatorName, usedIds, taskText = "")
415
512
  // Beceriler kullanici-kapilidir: operator yalnizca cfg.skills.enabled icindekileri iliştirebilir.
416
513
  // Tanimsiz/etkin olmayan beceri sessizce dusurulur; asla gorevi olduren bir hata degildir.
417
514
  const hasExplicitSkills = Object.prototype.hasOwnProperty.call(raw, "skills");
515
+ const autoMatchOn = cfg.skills?.autoMatch !== false;
418
516
  const requestedSkills = Array.isArray(raw.skills)
419
517
  ? raw.skills
420
- : (!hasExplicitSkills && cfg.skills?.autoMatch !== false
518
+ : (!hasExplicitSkills && autoMatchOn
421
519
  ? skillRegistry.suggest(cfg, `${taskText}\n${instruction}`, kind)
422
520
  : []);
423
- const skills = skillRegistry.resolveForAssignment(requestedSkills, cfg).map((skill) => skill.name);
521
+ let skills = skillRegistry.resolveForAssignment(requestedSkills, cfg).map((skill) => skill.name);
522
+ // Operator kodlama/planlama/review isinde beceri iliştirmeyi atlarsa (or. skills:[] dönerse),
523
+ // kullanicinin etkin becerilerinden goreve GERCEKTEN uyanlari otomatik ekle. suggest() yalnizca
524
+ // lexical eslesme oldugunda skill döner; alakasiz beceri asla iliştirilmez. Boylece "operator
525
+ // becerileri kullanmiyor" durumu ortadan kalkar.
526
+ if (!skills.length && autoMatchOn && ["implement", "plan", "review"].includes(kind)) {
527
+ skills = skillRegistry.resolveForAssignment(
528
+ skillRegistry.suggest(cfg, `${taskText}\n${instruction}`, kind), cfg
529
+ ).map((skill) => skill.name);
530
+ }
424
531
  let agent = requestedAgent;
425
532
  const unavailable = new Set(cfg.runtimeUnavailableAgents || []);
426
533
  if (unavailable.has(agent) || !supportsKind(cfg.agents[agent], kind)) {
@@ -679,15 +786,20 @@ class Engine extends EventEmitter {
679
786
  } catch (error) {
680
787
  if (!this.salvage(task, error)) {
681
788
  const found = store.findTask(task.id);
789
+ const failure = classifyCliError(error);
682
790
  task.status = "failed";
683
791
  task.error = error.message;
792
+ // Siniflandirilmis teshis gorevle birlikte saklanir; arayuz "ne oldu / ne yapmali"
793
+ // ayrimini ham hata metnini kirparak tahmin etmek yerine buradan okur.
794
+ task.failure = failure;
684
795
  task.finishedAt = new Date().toISOString();
685
796
  if (found) store.moveTask(found.state, "failed", task);
686
- this.publish("log", { level: "error", msg: `HATA: ${error.message}` }, task.id);
797
+ this.publish("log", { level: "error", msg: `HATA (${failure.code}): ${failure.summary}` }, task.id);
798
+ this.publish("log", { level: "warn", msg: `Ne yapmali: ${failure.action}` }, task.id);
687
799
  if (task.kind === "operator-chat") {
688
- const failure = classifyCliError(error);
689
- this.publish("result", { id: task.id, kind: "operator-chat", parentTaskId: task.parentTaskId, status: "failed", error: failure.summary }, task.id);
800
+ this.publish("result", { id: task.id, kind: "operator-chat", parentTaskId: task.parentTaskId, status: "failed", error: failure.summary, failure }, task.id);
690
801
  }
802
+ this.notifyOutcome(task, "failed", { error: failure.summary, failureCode: failure.code, action: failure.action });
691
803
  this.emit("queue");
692
804
  }
693
805
  } finally {
@@ -836,7 +948,12 @@ class Engine extends EventEmitter {
836
948
  this.publish("activity", { ...base, kind: "process.finished", exitCode: code, signal, durationMs, reason: silenceTimedOut ? "silence-timeout" : (timedOut ? "timeout" : null) });
837
949
  if (silenceTimedOut) return reject(new Error(`CLI_STALLED: ${agent.cmd} ${Math.round(silenceTimeoutMs / 1000)} saniye boyunca cikti uretmedi ve otomatik durduruldu.`));
838
950
  if (timedOut) return reject(new Error(`${agent.cmd} ${Math.round(timeoutMs / 1000)} saniyede zaman asimina ugradi${signal ? ` (signal=${signal})` : ""}. ${clip(stdout || stderr, 500)}`));
839
- if (code !== 0) return reject(new Error(`${agent.cmd} cikis kodu ${code ?? "yok"}${signal ? ` (signal=${signal})` : ""}. ${clip(stderr || stdout, 500)}`));
951
+ // Teshis icin HER IKI akis da gerekir: bazi CLI'lar (Gemini dahil) kota/oturum hatasini
952
+ // stdout'a, yigin izini stderr'e yazar. Yalnizca birini almak siniflandiriciyi kor birakiyordu.
953
+ if (code !== 0) {
954
+ const detail = [stderr, stdout].map((stream) => String(stream || "").trim()).filter(Boolean).join("\n");
955
+ return reject(new Error(`${agent.cmd} cikis kodu ${code ?? "yok"}${signal ? ` (signal=${signal})` : ""}.\n${clip(detail, 2000)}`));
956
+ }
840
957
  const normalized = normalizeCliOutput(agent, stdout);
841
958
  if (normalized.error) return reject(new Error(`OpenCode model/provider hatasi: ${clip(normalized.error, 500)}`));
842
959
  if (!normalized.text) return reject(new Error(`${agent.cmd} kullanilabilir cikti dondurmedi.${stderr ? ` ${clip(stderr, 300)}` : ""}`));
@@ -850,6 +967,15 @@ class Engine extends EventEmitter {
850
967
  });
851
968
  }
852
969
 
970
+ // Katalogda dosya yazabilen (implement) kullanilabilir bir uzman var mi? Operatorun kendisi ve
971
+ // bu oturumda karantinaya alinan agentler haric. Tek executor (or. opencode) sessizlik nedeniyle
972
+ // dustugunde takim fiilen dosya uretemez hale gelir; bu kontrol o durumu erken yakalar.
973
+ hasUsableImplementAgent(cfg, operatorName) {
974
+ return Object.entries(cfg.agents || {}).some(([name, agent]) =>
975
+ name !== operatorName && agent.enabled !== false && agentUsable(agent)
976
+ && !this.unhealthyAgents.has(name) && supportsKind(agent, "implement"));
977
+ }
978
+
853
979
  agentCatalog(cfg, operatorName) {
854
980
  return Object.entries(cfg.agents)
855
981
  .filter(([name, agent]) => name !== operatorName && agent.enabled !== false && agentUsable(agent) && !this.unhealthyAgents.has(name))
@@ -877,7 +1003,13 @@ class Engine extends EventEmitter {
877
1003
  // auth/timeout veya proses hatasi ayni operatoru tekrar calistirmamalidir.
878
1004
  if (!/gecerli JSON dondurmedi/i.test(String(error?.message || ""))) throw error;
879
1005
  if (attempt >= retries) break;
880
- correction = `\n\nONCEKI CEVAP PROTOKOLE UYMADI: ${error.message}\nAciklama veya Markdown eklemeden yalnizca istenen JSON nesnesini yeniden dondur.`;
1006
+ // Modelin ne dondurdugunu duzeltme istegine geri veriyoruz: "yalnizca JSON dondur"
1007
+ // talimatini tekrarlamak, hatayi goremeyen model icin cogu zaman ayni cevabi uretiyordu.
1008
+ correction = `\n\nONCEKI CEVAP PROTOKOLE UYMADI: ${error.message}\n` +
1009
+ `Senin onceki cevabin (kirpilmis):\n"""\n${clip(String(error.rawText || ""), 1200)}\n"""\n` +
1010
+ `Bu cevap gecerli bir JSON nesnesi degildi. Simdi SADECE tek bir JSON nesnesi dondur: ` +
1011
+ `ilk karakter { ve son karakter } olsun; oncesinde/sonrasinda selamlama, aciklama, dusunce, ` +
1012
+ `Markdown veya \`\`\` kod bloğu OLMASIN. Metin alanlarindaki cift tirnaklari \\" ile kacir.`;
881
1013
  this.publish("log", { level: "warn", msg: `${label} protokol hatasi; operator yeniden deneniyor (${attempt + 2}/${retries + 1}).` });
882
1014
  }
883
1015
  }
@@ -939,7 +1071,8 @@ class Engine extends EventEmitter {
939
1071
  : "";
940
1072
  const protocol = phase === "plan"
941
1073
  ? `Yalnizca gecerli JSON dondur. Gorev bir is/degisiklik/arastirma gerektiriyorsa delege et: {"summary":"yaklasim", "completionCriteria":["..."], "assignments":[{"id":"benzersiz-id", "agent":"catalog-name", "kind":"implement|review|research|plan", "instruction":"net gorev ve teslimat", "dependsOn":[]${skillField}}]} (en az bir assignment). ` +
942
- `Gorev yalnizca bir bilgi/soru ise ve yaniti sana verilen baglamdan (or. Beceriler bolumu, agent katalogu, proje hafizasi) dogrudan biliyorsan, HICBIR delegasyon acmadan: {"status":"complete", "final":"kisa ve net cevap", "verification":"cevabin dayandigi kaynak"}. Gercek is gerektiren gorevi bu kestirmeyle atlatma.${skillProtocol}`
1074
+ `Gorev yalnizca bir bilgi/soru VEYA bir selamlasma/sohbet mesaji ise (or. "merhaba", "nasilsin", "kac beceri var") ve yaniti dogrudan verebiliyorsan, HICBIR delegasyon acmadan: {"status":"complete", "final":"kullaniciya gosterilecek CEVABIN TAM METNI", "verification":"cevabin dayandigi kaynak"}. ` +
1075
+ `Selamlasmalarda bile cevabi JSON DISINA yazma: kullaniciya gosterilen tek alan "final"dir, nesnenin disindaki her metin atilir. Gercek is gerektiren gorevi bu kestirmeyle atlatma.${skillProtocol}`
943
1076
  : `Yalnizca gecerli JSON dondur. Is tamamlanmadiysa {"status":"continue", "reason":"...", "assignments":[{"id":"...", "agent":"...", "kind":"implement|review|research|plan", "instruction":"...", "dependsOn":[]${skillField}}]}; tum kabul kriterleri karsilandiysa {"status":"complete", "final":"en fazla 5 kisa maddeyle ne yapildi", "verification":"tek satir dogrulama"}. Dosya listesini motor ekleyecek; final icinde uzun log veya ham agent cevabi tekrarlama. Continue icin en az bir yeni assignment zorunlu.${skillProtocol}\n` +
944
1077
  `INCELEME DISIPLINI: Bagimsiz denetim VERDICT: PASS verdiyse ayni teslimat icin YENI review delegasyonu acma; complete dondur ve dusuk/orta onemdeki kalan notlari final metninde kalan risk olarak belirt. Ekipte bulunmayan dogrulama yetenegini (or. canli tarayici) tamamlanma sarti yapma; NOT RUN kalan dusuk riskli kontroller teslimati engellemez.`;
945
1078
  const strategy = task.executionMode === "fast"
@@ -948,6 +1081,11 @@ class Engine extends EventEmitter {
948
1081
  ? "DERIN MOD: Isi uygun uzmanliklar arasinda dagit — planlama, uygulama, test ve bagimsiz inceleme icin AYRI ve dogru uzmanlari kullan."
949
1082
  : "DENGELI MOD: Katalogda planner, executor ve reviewer varsa ucunu da ILK planda kullan. PLANLAMA -> UYGULAMA -> BAGIMSIZ INCELEME delegasyonlarini dependsOn ile ayni turda zincirle ve isi tek turda bitirmeyi hedefle. Ayni rolde birden fazla esdeger agent varsa yalnizca en uygun olani sec.";
950
1083
  return `${role}\n\n---\n## Operator protokolu\n${protocol}\n` +
1084
+ // OpenCode gibi sohbet odakli CLI'lar varsayilan olarak once bir cumle yazip JSON'u
1085
+ // arkasina ekliyor; motor bunu ayiklayabilse de belirsizlik protokol hatasi uretiyordu.
1086
+ `## Cikti bicimi (KATI)\nCevabin TAMAMI tek bir JSON nesnesi olmali: ilk karakter { , son karakter } . ` +
1087
+ `Oncesinde/sonrasinda selamlama, aciklama, dusunce notu, Markdown basligi veya \`\`\` kod bloğu YAZMA. ` +
1088
+ `Kullaniciya iletmek istedigin her seyi ilgili JSON alanina ("final" veya "summary") koy; nesnenin disindaki metin kullaniciya ULASMAZ.\n` +
951
1089
  `Yalnizca katalogdaki agent adlarini kullan. Her assignment kind degeri secilen agentin allowedKinds listesinde olmali. Kendine gorev atama. Ayni delegasyon id'sini tekrar kullanma.\n` +
952
1090
  `Bir agent basarisiz veya kullanilamaz raporlandiysa ayni isi ona tekrar verme; katalogdaki alternatif bir uzmani sec.\n` +
953
1091
  `AJAN SECIMI: Her alt gorevi, katalogdaki YETENEKLERE ve role gore o ise EN UYGUN uzmana ata; tum isi tek bir CLI'ye yigma. Isin ihtiyac duydugu her uzmanlik icin dogru uzmani sec (plan/tasarim -> planlama yetenegi; uygulama -> implementation; inceleme/dogrulama -> review/test; arastirma -> research/web). Ayni KIND icin birden fazla eşdeğer agent varsa YALNIZCA birini (en uygun ve en dusuk maliyetli) kullan; ayni isi iki eşdeğer agente verme. Farkli roller (or. bir uygulayici + bir inceleyici) FARKLI islerdir, tekrar sayilmaz.\n` +
@@ -1108,7 +1246,19 @@ class Engine extends EventEmitter {
1108
1246
  const shortcutRetries = requiresWork ? Math.max(1, cfg.operator?.protocolRetries ?? 1) : 0;
1109
1247
  let plan, shortcutAttempt = 0, planCorrection = "";
1110
1248
  while (true) {
1111
- plan = await this.invokeOperatorJson(operatorCli, this.operatorPrompt(task, cfg, memory, state, "plan") + planCorrection, cfg, "operator-plan", "Operator plani");
1249
+ try {
1250
+ plan = await this.invokeOperatorJson(operatorCli, this.operatorPrompt(task, cfg, memory, state, "plan") + planCorrection, cfg, "operator-plan", "Operator plani");
1251
+ } catch (error) {
1252
+ // Sohbet kurtarma: gorev delegasyon gerektirmiyorsa (selamlasma, kisa soru) ve operator
1253
+ // protokol nesnesi yerine duz metinle cevap verdiyse, kullaniciyi "gecerli JSON
1254
+ // dondurmedi" hatasiyla karsilamak yanlistir — istenen sey zaten o metindi. Yalnizca
1255
+ // is gerektirmeyen gorevlerde ve elde gercek bir metin varken devreye girer.
1256
+ const prose = conversationalAnswer(error);
1257
+ if (requiresWork || !prose) throw error;
1258
+ this.publish("log", { level: "warn", msg: "Operator protokol nesnesi dondurmedi; gorev is gerektirmedigi icin duz metin cevabi teslimat sayildi." }, task.id);
1259
+ task.teamState = state;
1260
+ return this.complete(task, state, "done", prose, { verification: "Operatorun dogrudan yaniti (protokol nesnesi yerine duz metin)." });
1261
+ }
1112
1262
  const isShortcut = plan && String(plan.status).toLowerCase() === "complete" && !Array.isArray(plan.assignments);
1113
1263
  if (!isShortcut) break;
1114
1264
  if (!requiresWork) {
@@ -1160,6 +1310,22 @@ class Engine extends EventEmitter {
1160
1310
  recoveryRounds++;
1161
1311
  this.publish("log", { level: "warn", msg: `CLI altyapi hatasi tur butcesinden dusulmedi; ${maxRecoveryRounds - recoveryRounds} ek kurtarma turu kaldi.` }, task.id);
1162
1312
  }
1313
+ // Uygulama agenti kalmadi guvenligi: Gorev acikca dosya olusturma/degistirme gerektiriyor
1314
+ // fakat katalogda 'implement' yapabilen kullanilabilir hicbir agent kalmadiysa (or. tek
1315
+ // executor opencode sessizlik nedeniyle karantinaya alindi) ve simdiye kadar tamamlanmis bir
1316
+ // uygulama isi da yoksa, HICBIR ek tur dosya uretemez. Operatoru planlayici/gap-analizi
1317
+ // dongusunde bosuna dondurup tur ve cagri butcesini yakmak yerine net, eyleme donuk bir
1318
+ // hatayla dur (kullanicinin gordugu "circle seklinde uzayip gitme" sorununu bu keser).
1319
+ if (taskRequiresDelegation(task.prompt)
1320
+ && !this.hasUsableImplementAgent(cfg, operatorCli)
1321
+ && !Object.values(state.results).some((result) => result.kind === "implement" && result.status === "completed")) {
1322
+ const dead = [...this.unhealthyAgents.keys()];
1323
+ throw new Error(
1324
+ "Bu gorev dosya olusturma/degistirme gerektiriyor fakat kullanilabilir bir uygulama (implement) agenti yok" +
1325
+ (dead.length ? ` (${dead.join(", ")} bu oturumda kullanim disi kaldi)` : "") +
1326
+ ". Ayarlar > Agent'lar'dan 'implementation' yetenekli bir agent (Codex/Claude/Gemini/OpenCode) ekleyip etkinlestirin veya opencode'un oturum/model durumunu kontrol edin, sonra gorevi tekrar calistirin."
1327
+ );
1328
+ }
1163
1329
  if (task.executionMode === "fast" && assignments.every((assignment) => state.results[assignment.id]?.status === "completed")) {
1164
1330
  const reports = assignments.map((assignment) => state.results[assignment.id].result).join("\n\n");
1165
1331
  this.publish("log", { level: "info", msg: "FAST mod: uzman teslimati basarili; ikinci operator degerlendirme cagrisi atlandi." }, task.id);
@@ -1355,9 +1521,26 @@ class Engine extends EventEmitter {
1355
1521
  store.appendMemory(`${task.id} [${status}] ${task.prompt}`, `${task.summary}\nDosyalar: ${[...changes.created, ...changes.modified, ...changes.deleted].join(", ")}`);
1356
1522
  this.publish("result", { id: task.id, prompt: task.prompt, status, dir: this._cwd, changes, summary: task.summary, delivery: task.delivery, operator: task.operatorCli, rounds: state.round }, task.id);
1357
1523
  this.publish("log", { level: "ok", msg: `GOREV tamamlandi: ${task.id}` }, task.id);
1524
+ this.notifyOutcome(task, status, { summary: task.summary, rounds: state.round, fileCount: files.length, verification: task.delivery.verification });
1358
1525
  this.emit("queue");
1359
1526
  this._textBefore = null;
1360
1527
  }
1528
+
1529
+ // Dis bildirim icin tek cikis noktasi: server.js bu event'i dinleyip (varsa) webhook'a POST atar.
1530
+ // Motoru HTTP'den bagimsiz tutar; bildirim basarisiz olsa bile gorev akisini etkilemez.
1531
+ notifyOutcome(task, status, extra = {}) {
1532
+ try {
1533
+ this.emit("notify", {
1534
+ status,
1535
+ id: task.id,
1536
+ prompt: task.prompt,
1537
+ operator: task.operatorCli || null,
1538
+ dir: this._cwd || task.targetDir || null,
1539
+ at: new Date().toISOString(),
1540
+ ...extra,
1541
+ });
1542
+ } catch {}
1543
+ }
1361
1544
  }
1362
1545
 
1363
1546
  module.exports = new Engine();
@@ -1365,4 +1548,5 @@ module.exports = new Engine();
1365
1548
  module.exports._internals = {
1366
1549
  normalizeAssignments, ensureBalancedRoleChain, extractVerdict, clipMiddle, snapshotDir, diffSnapshots,
1367
1550
  captureTextSnapshot, buildLineDiff, describeFileDiff, isSensitiveDiffPath, taskRequiresDelegation,
1551
+ parseJson, conversationalAnswer, classifyCliError, RECOVERABLE_CLI_ERRORS, QUARANTINE_CLI_ERRORS,
1368
1552
  };