@omerrgocmen/crewctl 1.3.1 → 1.3.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.
- package/README.md +193 -193
- package/orchestrator/README.md +473 -473
- package/orchestrator/config.default.json +96 -90
- package/orchestrator/roles/executor.md +57 -57
- package/orchestrator/roles/operator.md +77 -77
- package/orchestrator/roles/planner.md +58 -58
- package/orchestrator/roles/reviewer.md +63 -63
- package/orchestrator/skills/acceptance-criteria.md +18 -18
- package/orchestrator/skills/accessibility-audit.md +16 -16
- package/orchestrator/skills/accessible-forms.md +16 -16
- package/orchestrator/skills/api-design.md +27 -27
- package/orchestrator/skills/api-documentation.md +16 -16
- package/orchestrator/skills/architecture-decision-record.md +18 -18
- package/orchestrator/skills/authentication-design.md +16 -16
- package/orchestrator/skills/authorization-review.md +16 -16
- package/orchestrator/skills/backward-compatibility.md +17 -17
- package/orchestrator/skills/changelog-writing.md +16 -16
- package/orchestrator/skills/ci-pipeline-design.md +16 -16
- package/orchestrator/skills/cli-design.md +16 -16
- package/orchestrator/skills/code-review.md +27 -27
- package/orchestrator/skills/configuration-management.md +16 -16
- package/orchestrator/skills/container-review.md +16 -16
- package/orchestrator/skills/contract-testing.md +16 -16
- package/orchestrator/skills/dashboard-design.md +16 -16
- package/orchestrator/skills/database-migration.md +16 -16
- package/orchestrator/skills/database-schema-design.md +16 -16
- package/orchestrator/skills/debugging.md +26 -26
- package/orchestrator/skills/dependency-review.md +16 -16
- package/orchestrator/skills/design-review.md +29 -29
- package/orchestrator/skills/design-system.md +16 -16
- package/orchestrator/skills/docs-api-reference.md +16 -16
- package/orchestrator/skills/docs-troubleshooting.md +16 -16
- package/orchestrator/skills/docs-tutorial.md +16 -16
- package/orchestrator/skills/docs-writing.md +25 -25
- package/orchestrator/skills/empty-error-loading-states.md +16 -16
- package/orchestrator/skills/end-to-end-testing.md +16 -16
- package/orchestrator/skills/error-handling.md +16 -16
- package/orchestrator/skills/frontend-design.md +28 -28
- package/orchestrator/skills/git-commit-writing.md +16 -16
- package/orchestrator/skills/graphql-design.md +16 -16
- package/orchestrator/skills/incident-runbook.md +18 -18
- package/orchestrator/skills/input-validation.md +16 -16
- package/orchestrator/skills/integration-testing.md +16 -16
- package/orchestrator/skills/interaction-design.md +16 -16
- package/orchestrator/skills/landing-page-design.md +16 -16
- package/orchestrator/skills/observability-design.md +16 -16
- package/orchestrator/skills/openapi-contract.md +16 -16
- package/orchestrator/skills/performance-profiling.md +16 -16
- package/orchestrator/skills/privacy-review.md +16 -16
- package/orchestrator/skills/property-based-testing.md +16 -16
- package/orchestrator/skills/pull-request-writing.md +16 -16
- package/orchestrator/skills/refactoring.md +16 -16
- package/orchestrator/skills/release-readiness.md +16 -16
- package/orchestrator/skills/responsive-design.md +16 -16
- package/orchestrator/skills/secrets-management.md +16 -16
- package/orchestrator/skills/secure-file-upload.md +16 -16
- package/orchestrator/skills/security-review.md +26 -26
- package/orchestrator/skills/semantic-versioning.md +17 -17
- package/orchestrator/skills/seo-on-page.md +16 -16
- package/orchestrator/skills/seo-structured-data.md +16 -16
- package/orchestrator/skills/seo-technical-audit.md +16 -16
- package/orchestrator/skills/sql-query-review.md +16 -16
- package/orchestrator/skills/supply-chain-security.md +16 -16
- package/orchestrator/skills/test-strategy.md +16 -16
- package/orchestrator/skills/threat-modeling.md +16 -16
- package/orchestrator/skills/unit-testing.md +16 -16
- package/orchestrator/skills/write-tests.md +28 -28
- package/orchestrator/src/checkpoints.js +183 -183
- package/orchestrator/src/cli-registry.js +125 -3
- package/orchestrator/src/cli.js +140 -140
- package/orchestrator/src/doctor.js +64 -64
- package/orchestrator/src/engine.js +2112 -1914
- package/orchestrator/src/schedule.js +124 -124
- package/orchestrator/src/server.js +942 -817
- package/orchestrator/src/skill-registry.js +272 -272
- package/orchestrator/src/store.js +583 -478
- package/orchestrator/web/OrbitControls.js +1417 -1417
- package/orchestrator/web/board.html +146 -146
- package/orchestrator/web/code.html +213 -213
- package/orchestrator/web/flow.html +741 -741
- package/orchestrator/web/index.html +784 -622
- package/orchestrator/web/jsm/postprocessing/EffectComposer.js +231 -231
- package/orchestrator/web/jsm/postprocessing/MaskPass.js +104 -104
- package/orchestrator/web/jsm/postprocessing/Pass.js +95 -95
- package/orchestrator/web/jsm/postprocessing/RenderPass.js +99 -99
- package/orchestrator/web/jsm/postprocessing/ShaderPass.js +77 -77
- package/orchestrator/web/jsm/postprocessing/UnrealBloomPass.js +415 -415
- package/orchestrator/web/jsm/shaders/CopyShader.js +45 -45
- package/orchestrator/web/jsm/shaders/LuminosityHighPassShader.js +66 -66
- package/orchestrator/web/site.webmanifest +12 -12
- package/orchestrator/web/three.module.min.js +6 -6
- package/package.json +51 -51
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
// checkpoints.js — gorev-basi otomatik surumleme (sifir bagimlilik).
|
|
2
|
-
// Her gorev CALISMADAN ONCE calisma klasorunun bir surumu (checkpoint) alinir; ajan kodu
|
|
3
|
-
// bozarsa tek tikla o surume donulur. Depolama daima birebir dosya KOPYASIDIR (deterministik,
|
|
4
|
-
// git durumundan bagimsiz, geri-alinabilir). "git-farkinda": klasor bir git deposuysa hangi
|
|
5
|
-
// dosyalarin yedeklenecegi `git ls-files` ile belirlenir (boylece .gitignore'a uyulur ve
|
|
6
|
-
// build ciktilari/gecici dosyalar surume girmez); depoysa degilse guvenli bir walk kullanilir.
|
|
7
|
-
// NOT: fs.cpSync Windows'ta non-ASCII yollarda (or. C:\Users\Ömer) cokebiliyor; tum kopyalar
|
|
8
|
-
// Unicode-guvenli fs.copyFileSync ile yapilir.
|
|
9
|
-
const { spawnSync } = require("child_process");
|
|
10
|
-
const fs = require("fs");
|
|
11
|
-
const path = require("path");
|
|
12
|
-
const store = require("./store");
|
|
13
|
-
|
|
14
|
-
const isWin = process.platform === "win32";
|
|
15
|
-
const CP_ROOT = path.join(store.ROOT, "state", "checkpoints");
|
|
16
|
-
const MAX_FILES = 5000; // Bundan buyuk klasorlerde surum alma (guvenlik/performans).
|
|
17
|
-
const MAX_BYTES = 200 * 1024 * 1024;
|
|
18
|
-
const DEFAULT_RETENTION = 20;
|
|
19
|
-
|
|
20
|
-
// snapshotDir (engine) ile AYNI ignore mantigi: node_modules/.git ve orkestratorun kendi
|
|
21
|
-
// runtime durumu (queue/state/memory) surume girmemeli — aksi halde checkpoint kendi icine
|
|
22
|
-
// kopyalanip sisebilir.
|
|
23
|
-
function ignored(rel) {
|
|
24
|
-
const r = String(rel).replace(/\\/g, "/");
|
|
25
|
-
return r.includes("node_modules") || r.includes(".git/") || r === ".git" || r.startsWith(".git/") ||
|
|
26
|
-
r.startsWith("orchestrator/queue") || r.startsWith("orchestrator/state") ||
|
|
27
|
-
r.startsWith("orchestrator/memory") || r.startsWith("orchestrator/node_modules");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function isGitRepo(cwd) {
|
|
31
|
-
try {
|
|
32
|
-
const r = spawnSync("git", ["-C", cwd, "rev-parse", "--is-inside-work-tree"], {
|
|
33
|
-
encoding: "utf8", timeout: 8000, windowsHide: true, shell: isWin,
|
|
34
|
-
});
|
|
35
|
-
return r.status === 0 && /true/i.test(String(r.stdout || ""));
|
|
36
|
-
} catch { return false; }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Tracked + izlenmeyen ama .gitignore'da olmayan dosyalar; NUL ayrac unicode/bosluk-guvenli.
|
|
40
|
-
function gitFileList(cwd) {
|
|
41
|
-
try {
|
|
42
|
-
const r = spawnSync("git", ["-C", cwd, "ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
|
|
43
|
-
encoding: "utf8", timeout: 20000, maxBuffer: 64 * 1024 * 1024, windowsHide: true, shell: isWin,
|
|
44
|
-
});
|
|
45
|
-
if (r.status !== 0) return null;
|
|
46
|
-
return String(r.stdout || "").split("\0").filter(Boolean);
|
|
47
|
-
} catch { return null; }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function walkFileList(cwd) {
|
|
51
|
-
const out = [];
|
|
52
|
-
let count = 0;
|
|
53
|
-
const walk = (abs, rel) => {
|
|
54
|
-
if (count > MAX_FILES + 1) return;
|
|
55
|
-
let entries;
|
|
56
|
-
try { entries = fs.readdirSync(abs, { withFileTypes: true }); } catch { return; }
|
|
57
|
-
for (const entry of entries) {
|
|
58
|
-
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
59
|
-
if (ignored(childRel)) continue;
|
|
60
|
-
if (entry.isSymbolicLink()) continue; // Sembolik baglantilari izleme (klasor disina cikmasin).
|
|
61
|
-
const childAbs = path.join(abs, entry.name);
|
|
62
|
-
if (entry.isDirectory()) walk(childAbs, childRel);
|
|
63
|
-
else { out.push(childRel); count++; }
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
walk(cwd, "");
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function fileList(cwd) {
|
|
71
|
-
if (isGitRepo(cwd)) {
|
|
72
|
-
const git = gitFileList(cwd);
|
|
73
|
-
// Bos liste (or. calisma klasoru gitignore'da) walk'a duser; aksi halde surum bos kalir
|
|
74
|
-
// ve restore hicbir sey yapamaz. Dolu liste .gitignore'a uyan git-farkinda yoldur.
|
|
75
|
-
if (git && git.length) return { files: git.filter((rel) => !ignored(rel)), backend: "git" };
|
|
76
|
-
}
|
|
77
|
-
return { files: walkFileList(cwd), backend: "copy" };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Yol guvenligi: hedef daima cwd icinde kalmali (path traversal / sembolik kacis engeli).
|
|
81
|
-
function within(root, rel) {
|
|
82
|
-
const base = path.resolve(root);
|
|
83
|
-
const target = path.resolve(base, rel);
|
|
84
|
-
return target === base || target.startsWith(base + path.sep) ? target : null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function ensureRoot() { fs.mkdirSync(CP_ROOT, { recursive: true }); }
|
|
88
|
-
|
|
89
|
-
function readManifest(id) {
|
|
90
|
-
try { return JSON.parse(fs.readFileSync(path.join(CP_ROOT, path.basename(String(id)), "manifest.json"), "utf8")); }
|
|
91
|
-
catch { return null; }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function enforceRetention(cwd, retention) {
|
|
95
|
-
const keep = Math.max(1, Number(retention) || DEFAULT_RETENTION);
|
|
96
|
-
try {
|
|
97
|
-
const mine = fs.readdirSync(CP_ROOT)
|
|
98
|
-
.map(readManifest).filter((m) => m && path.resolve(m.cwd) === path.resolve(cwd))
|
|
99
|
-
.sort((a, b) => (a.at < b.at ? 1 : -1));
|
|
100
|
-
for (const m of mine.slice(keep)) {
|
|
101
|
-
try { fs.rmSync(path.join(CP_ROOT, m.id), { recursive: true, force: true }); } catch {}
|
|
102
|
-
}
|
|
103
|
-
} catch {}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Calisma klasorunun mevcut halinden bir surum (checkpoint) olusturur. Basarisiz/atlanmis
|
|
107
|
-
// durumda gorevi ASLA oldurmez; {ok:false,...} doner ve cagiran log'lar.
|
|
108
|
-
function createCheckpoint(cwd, meta = {}) {
|
|
109
|
-
try {
|
|
110
|
-
ensureRoot();
|
|
111
|
-
const resolvedCwd = path.resolve(cwd);
|
|
112
|
-
if (!fs.existsSync(resolvedCwd)) return { ok: false, skipped: true, reason: "Calisma klasoru bulunamadi." };
|
|
113
|
-
const { files, backend } = fileList(resolvedCwd);
|
|
114
|
-
if (files.length > MAX_FILES) return { ok: false, skipped: true, reason: `Klasor cok buyuk (${files.length} dosya > ${MAX_FILES}); surum alinmadi.` };
|
|
115
|
-
const id = `cp-${new Date().toISOString().replace(/[-:T.]/g, "").slice(0, 17)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
116
|
-
const dir = path.join(CP_ROOT, id);
|
|
117
|
-
const filesDir = path.join(dir, "files");
|
|
118
|
-
fs.mkdirSync(filesDir, { recursive: true });
|
|
119
|
-
const stored = [];
|
|
120
|
-
let bytes = 0;
|
|
121
|
-
for (const rel of files) {
|
|
122
|
-
const src = within(resolvedCwd, rel);
|
|
123
|
-
if (!src) continue;
|
|
124
|
-
let st;
|
|
125
|
-
try { st = fs.lstatSync(src); } catch { continue; }
|
|
126
|
-
if (!st.isFile()) continue; // Sembolik baglanti / klasor / silinmis izlenen dosyayi atla.
|
|
127
|
-
bytes += st.size;
|
|
128
|
-
if (bytes > MAX_BYTES) { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} return { ok: false, skipped: true, reason: `Klasor cok buyuk (>${Math.round(MAX_BYTES / 1048576)}MB); surum alinmadi.` }; }
|
|
129
|
-
const dest = path.join(filesDir, rel);
|
|
130
|
-
try { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.copyFileSync(src, dest); stored.push(rel); } catch {}
|
|
131
|
-
}
|
|
132
|
-
const manifest = {
|
|
133
|
-
id, at: new Date().toISOString(), cwd: resolvedCwd, backend,
|
|
134
|
-
fileCount: stored.length, bytes, files: stored,
|
|
135
|
-
taskId: meta.taskId || null, label: String(meta.label || "").slice(0, 300), kind: meta.kind || "pre-task",
|
|
136
|
-
};
|
|
137
|
-
fs.writeFileSync(path.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
138
|
-
enforceRetention(resolvedCwd, meta.retention);
|
|
139
|
-
return { ok: true, ...manifest };
|
|
140
|
-
} catch (error) { return { ok: false, error: error.message }; }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Bir surumu geri yukler. Once MEVCUT hali de bir surum yapar (redo guvenligi), sonra:
|
|
144
|
-
// (1) surumden SONRA olusan dosyalari siler, (2) surumdeki dosyalari geri yazar.
|
|
145
|
-
function restoreCheckpoint(id, opts = {}) {
|
|
146
|
-
const manifest = readManifest(id);
|
|
147
|
-
if (!manifest) return { ok: false, error: "Surum bulunamadi." };
|
|
148
|
-
const cwd = path.resolve(manifest.cwd);
|
|
149
|
-
if (!fs.existsSync(cwd)) return { ok: false, error: "Calisma klasoru bulunamadi." };
|
|
150
|
-
let redoId = null;
|
|
151
|
-
if (opts.createRedo !== false) {
|
|
152
|
-
const redo = createCheckpoint(cwd, { taskId: manifest.taskId, kind: "pre-restore", label: `Geri alma oncesi (${manifest.id})`, retention: opts.retention });
|
|
153
|
-
if (redo.ok) redoId = redo.id;
|
|
154
|
-
}
|
|
155
|
-
const filesDir = path.join(CP_ROOT, path.basename(String(id)), "files");
|
|
156
|
-
const keep = new Set(manifest.files);
|
|
157
|
-
let deleted = 0;
|
|
158
|
-
for (const rel of fileList(cwd).files) {
|
|
159
|
-
if (keep.has(rel)) continue;
|
|
160
|
-
const target = within(cwd, rel);
|
|
161
|
-
if (!target) continue;
|
|
162
|
-
try { if (fs.existsSync(target)) { fs.rmSync(target, { force: true }); deleted++; } } catch {}
|
|
163
|
-
}
|
|
164
|
-
let restored = 0;
|
|
165
|
-
for (const rel of manifest.files) {
|
|
166
|
-
const src = path.join(filesDir, rel);
|
|
167
|
-
const dest = within(cwd, rel);
|
|
168
|
-
if (!dest) continue;
|
|
169
|
-
try { if (!fs.existsSync(src)) continue; fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.copyFileSync(src, dest); restored++; } catch {}
|
|
170
|
-
}
|
|
171
|
-
return { ok: true, id: manifest.id, cwd, restored, deleted, redoId };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function listCheckpoints(cwd) {
|
|
175
|
-
try {
|
|
176
|
-
ensureRoot();
|
|
177
|
-
const all = fs.readdirSync(CP_ROOT).map(readManifest).filter(Boolean);
|
|
178
|
-
const filtered = cwd ? all.filter((m) => path.resolve(m.cwd) === path.resolve(cwd)) : all;
|
|
179
|
-
return filtered.sort((a, b) => (a.at < b.at ? 1 : -1)).map(({ files, ...rest }) => rest);
|
|
180
|
-
} catch { return []; }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
module.exports = { CP_ROOT, createCheckpoint, restoreCheckpoint, listCheckpoints, readManifest, fileList, isGitRepo, _internals: { ignored, within, walkFileList } };
|
|
1
|
+
// checkpoints.js — gorev-basi otomatik surumleme (sifir bagimlilik).
|
|
2
|
+
// Her gorev CALISMADAN ONCE calisma klasorunun bir surumu (checkpoint) alinir; ajan kodu
|
|
3
|
+
// bozarsa tek tikla o surume donulur. Depolama daima birebir dosya KOPYASIDIR (deterministik,
|
|
4
|
+
// git durumundan bagimsiz, geri-alinabilir). "git-farkinda": klasor bir git deposuysa hangi
|
|
5
|
+
// dosyalarin yedeklenecegi `git ls-files` ile belirlenir (boylece .gitignore'a uyulur ve
|
|
6
|
+
// build ciktilari/gecici dosyalar surume girmez); depoysa degilse guvenli bir walk kullanilir.
|
|
7
|
+
// NOT: fs.cpSync Windows'ta non-ASCII yollarda (or. C:\Users\Ömer) cokebiliyor; tum kopyalar
|
|
8
|
+
// Unicode-guvenli fs.copyFileSync ile yapilir.
|
|
9
|
+
const { spawnSync } = require("child_process");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const store = require("./store");
|
|
13
|
+
|
|
14
|
+
const isWin = process.platform === "win32";
|
|
15
|
+
const CP_ROOT = path.join(store.ROOT, "state", "checkpoints");
|
|
16
|
+
const MAX_FILES = 5000; // Bundan buyuk klasorlerde surum alma (guvenlik/performans).
|
|
17
|
+
const MAX_BYTES = 200 * 1024 * 1024;
|
|
18
|
+
const DEFAULT_RETENTION = 20;
|
|
19
|
+
|
|
20
|
+
// snapshotDir (engine) ile AYNI ignore mantigi: node_modules/.git ve orkestratorun kendi
|
|
21
|
+
// runtime durumu (queue/state/memory) surume girmemeli — aksi halde checkpoint kendi icine
|
|
22
|
+
// kopyalanip sisebilir.
|
|
23
|
+
function ignored(rel) {
|
|
24
|
+
const r = String(rel).replace(/\\/g, "/");
|
|
25
|
+
return r.includes("node_modules") || r.includes(".git/") || r === ".git" || r.startsWith(".git/") ||
|
|
26
|
+
r.startsWith("orchestrator/queue") || r.startsWith("orchestrator/state") ||
|
|
27
|
+
r.startsWith("orchestrator/memory") || r.startsWith("orchestrator/node_modules");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isGitRepo(cwd) {
|
|
31
|
+
try {
|
|
32
|
+
const r = spawnSync("git", ["-C", cwd, "rev-parse", "--is-inside-work-tree"], {
|
|
33
|
+
encoding: "utf8", timeout: 8000, windowsHide: true, shell: isWin,
|
|
34
|
+
});
|
|
35
|
+
return r.status === 0 && /true/i.test(String(r.stdout || ""));
|
|
36
|
+
} catch { return false; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Tracked + izlenmeyen ama .gitignore'da olmayan dosyalar; NUL ayrac unicode/bosluk-guvenli.
|
|
40
|
+
function gitFileList(cwd) {
|
|
41
|
+
try {
|
|
42
|
+
const r = spawnSync("git", ["-C", cwd, "ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
|
|
43
|
+
encoding: "utf8", timeout: 20000, maxBuffer: 64 * 1024 * 1024, windowsHide: true, shell: isWin,
|
|
44
|
+
});
|
|
45
|
+
if (r.status !== 0) return null;
|
|
46
|
+
return String(r.stdout || "").split("\0").filter(Boolean);
|
|
47
|
+
} catch { return null; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function walkFileList(cwd) {
|
|
51
|
+
const out = [];
|
|
52
|
+
let count = 0;
|
|
53
|
+
const walk = (abs, rel) => {
|
|
54
|
+
if (count > MAX_FILES + 1) return;
|
|
55
|
+
let entries;
|
|
56
|
+
try { entries = fs.readdirSync(abs, { withFileTypes: true }); } catch { return; }
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
59
|
+
if (ignored(childRel)) continue;
|
|
60
|
+
if (entry.isSymbolicLink()) continue; // Sembolik baglantilari izleme (klasor disina cikmasin).
|
|
61
|
+
const childAbs = path.join(abs, entry.name);
|
|
62
|
+
if (entry.isDirectory()) walk(childAbs, childRel);
|
|
63
|
+
else { out.push(childRel); count++; }
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
walk(cwd, "");
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fileList(cwd) {
|
|
71
|
+
if (isGitRepo(cwd)) {
|
|
72
|
+
const git = gitFileList(cwd);
|
|
73
|
+
// Bos liste (or. calisma klasoru gitignore'da) walk'a duser; aksi halde surum bos kalir
|
|
74
|
+
// ve restore hicbir sey yapamaz. Dolu liste .gitignore'a uyan git-farkinda yoldur.
|
|
75
|
+
if (git && git.length) return { files: git.filter((rel) => !ignored(rel)), backend: "git" };
|
|
76
|
+
}
|
|
77
|
+
return { files: walkFileList(cwd), backend: "copy" };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Yol guvenligi: hedef daima cwd icinde kalmali (path traversal / sembolik kacis engeli).
|
|
81
|
+
function within(root, rel) {
|
|
82
|
+
const base = path.resolve(root);
|
|
83
|
+
const target = path.resolve(base, rel);
|
|
84
|
+
return target === base || target.startsWith(base + path.sep) ? target : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function ensureRoot() { fs.mkdirSync(CP_ROOT, { recursive: true }); }
|
|
88
|
+
|
|
89
|
+
function readManifest(id) {
|
|
90
|
+
try { return JSON.parse(fs.readFileSync(path.join(CP_ROOT, path.basename(String(id)), "manifest.json"), "utf8")); }
|
|
91
|
+
catch { return null; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function enforceRetention(cwd, retention) {
|
|
95
|
+
const keep = Math.max(1, Number(retention) || DEFAULT_RETENTION);
|
|
96
|
+
try {
|
|
97
|
+
const mine = fs.readdirSync(CP_ROOT)
|
|
98
|
+
.map(readManifest).filter((m) => m && path.resolve(m.cwd) === path.resolve(cwd))
|
|
99
|
+
.sort((a, b) => (a.at < b.at ? 1 : -1));
|
|
100
|
+
for (const m of mine.slice(keep)) {
|
|
101
|
+
try { fs.rmSync(path.join(CP_ROOT, m.id), { recursive: true, force: true }); } catch {}
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Calisma klasorunun mevcut halinden bir surum (checkpoint) olusturur. Basarisiz/atlanmis
|
|
107
|
+
// durumda gorevi ASLA oldurmez; {ok:false,...} doner ve cagiran log'lar.
|
|
108
|
+
function createCheckpoint(cwd, meta = {}) {
|
|
109
|
+
try {
|
|
110
|
+
ensureRoot();
|
|
111
|
+
const resolvedCwd = path.resolve(cwd);
|
|
112
|
+
if (!fs.existsSync(resolvedCwd)) return { ok: false, skipped: true, reason: "Calisma klasoru bulunamadi." };
|
|
113
|
+
const { files, backend } = fileList(resolvedCwd);
|
|
114
|
+
if (files.length > MAX_FILES) return { ok: false, skipped: true, reason: `Klasor cok buyuk (${files.length} dosya > ${MAX_FILES}); surum alinmadi.` };
|
|
115
|
+
const id = `cp-${new Date().toISOString().replace(/[-:T.]/g, "").slice(0, 17)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
116
|
+
const dir = path.join(CP_ROOT, id);
|
|
117
|
+
const filesDir = path.join(dir, "files");
|
|
118
|
+
fs.mkdirSync(filesDir, { recursive: true });
|
|
119
|
+
const stored = [];
|
|
120
|
+
let bytes = 0;
|
|
121
|
+
for (const rel of files) {
|
|
122
|
+
const src = within(resolvedCwd, rel);
|
|
123
|
+
if (!src) continue;
|
|
124
|
+
let st;
|
|
125
|
+
try { st = fs.lstatSync(src); } catch { continue; }
|
|
126
|
+
if (!st.isFile()) continue; // Sembolik baglanti / klasor / silinmis izlenen dosyayi atla.
|
|
127
|
+
bytes += st.size;
|
|
128
|
+
if (bytes > MAX_BYTES) { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} return { ok: false, skipped: true, reason: `Klasor cok buyuk (>${Math.round(MAX_BYTES / 1048576)}MB); surum alinmadi.` }; }
|
|
129
|
+
const dest = path.join(filesDir, rel);
|
|
130
|
+
try { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.copyFileSync(src, dest); stored.push(rel); } catch {}
|
|
131
|
+
}
|
|
132
|
+
const manifest = {
|
|
133
|
+
id, at: new Date().toISOString(), cwd: resolvedCwd, backend,
|
|
134
|
+
fileCount: stored.length, bytes, files: stored,
|
|
135
|
+
taskId: meta.taskId || null, label: String(meta.label || "").slice(0, 300), kind: meta.kind || "pre-task",
|
|
136
|
+
};
|
|
137
|
+
fs.writeFileSync(path.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
138
|
+
enforceRetention(resolvedCwd, meta.retention);
|
|
139
|
+
return { ok: true, ...manifest };
|
|
140
|
+
} catch (error) { return { ok: false, error: error.message }; }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Bir surumu geri yukler. Once MEVCUT hali de bir surum yapar (redo guvenligi), sonra:
|
|
144
|
+
// (1) surumden SONRA olusan dosyalari siler, (2) surumdeki dosyalari geri yazar.
|
|
145
|
+
function restoreCheckpoint(id, opts = {}) {
|
|
146
|
+
const manifest = readManifest(id);
|
|
147
|
+
if (!manifest) return { ok: false, error: "Surum bulunamadi." };
|
|
148
|
+
const cwd = path.resolve(manifest.cwd);
|
|
149
|
+
if (!fs.existsSync(cwd)) return { ok: false, error: "Calisma klasoru bulunamadi." };
|
|
150
|
+
let redoId = null;
|
|
151
|
+
if (opts.createRedo !== false) {
|
|
152
|
+
const redo = createCheckpoint(cwd, { taskId: manifest.taskId, kind: "pre-restore", label: `Geri alma oncesi (${manifest.id})`, retention: opts.retention });
|
|
153
|
+
if (redo.ok) redoId = redo.id;
|
|
154
|
+
}
|
|
155
|
+
const filesDir = path.join(CP_ROOT, path.basename(String(id)), "files");
|
|
156
|
+
const keep = new Set(manifest.files);
|
|
157
|
+
let deleted = 0;
|
|
158
|
+
for (const rel of fileList(cwd).files) {
|
|
159
|
+
if (keep.has(rel)) continue;
|
|
160
|
+
const target = within(cwd, rel);
|
|
161
|
+
if (!target) continue;
|
|
162
|
+
try { if (fs.existsSync(target)) { fs.rmSync(target, { force: true }); deleted++; } } catch {}
|
|
163
|
+
}
|
|
164
|
+
let restored = 0;
|
|
165
|
+
for (const rel of manifest.files) {
|
|
166
|
+
const src = path.join(filesDir, rel);
|
|
167
|
+
const dest = within(cwd, rel);
|
|
168
|
+
if (!dest) continue;
|
|
169
|
+
try { if (!fs.existsSync(src)) continue; fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.copyFileSync(src, dest); restored++; } catch {}
|
|
170
|
+
}
|
|
171
|
+
return { ok: true, id: manifest.id, cwd, restored, deleted, redoId };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function listCheckpoints(cwd) {
|
|
175
|
+
try {
|
|
176
|
+
ensureRoot();
|
|
177
|
+
const all = fs.readdirSync(CP_ROOT).map(readManifest).filter(Boolean);
|
|
178
|
+
const filtered = cwd ? all.filter((m) => path.resolve(m.cwd) === path.resolve(cwd)) : all;
|
|
179
|
+
return filtered.sort((a, b) => (a.at < b.at ? 1 : -1)).map(({ files, ...rest }) => rest);
|
|
180
|
+
} catch { return []; }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = { CP_ROOT, createCheckpoint, restoreCheckpoint, listCheckpoints, readManifest, fileList, isGitRepo, _internals: { ignored, within, walkFileList } };
|
|
@@ -19,7 +19,13 @@ const DEFINITIONS = {
|
|
|
19
19
|
claude: {
|
|
20
20
|
command: "claude",
|
|
21
21
|
versionArgs: ["--version"],
|
|
22
|
-
|
|
22
|
+
// stream-json + --verbose: claude 'text'/'json' modunda uzun isi TOPLU (tek seferde) doker;
|
|
23
|
+
// ara adimlarda stdout sessiz kalir ve motorun sessizlik sayaci calisan kosmayi yanlislikla
|
|
24
|
+
// oldururdu. stream-json her adimda (assistant mesaji, tool cagrisi/sonucu) olay akitir; boylece
|
|
25
|
+
// sessizlik sifirlanir. Nihai metin + kullanim son "result" olayindan ayiklanir.
|
|
26
|
+
defaultArgs: ["-p", "--permission-mode", "acceptEdits", "--output-format", "stream-json", "--verbose"],
|
|
27
|
+
// Claude artik akitiyor; kisa sessizlik esigi guvenli. (sizeFactor buyuk gorevde yine acar.)
|
|
28
|
+
silenceTimeoutSeconds: 240,
|
|
23
29
|
description: "Claude Code uygulama, analiz ve test agenti",
|
|
24
30
|
capabilities: ["implementation", "debugging", "testing", "review", "web"],
|
|
25
31
|
roleFile: "roles/executor.md",
|
|
@@ -310,7 +316,17 @@ function effectiveAgent(agent, cfg) {
|
|
|
310
316
|
}
|
|
311
317
|
if (adapter === "claude") {
|
|
312
318
|
if (!copy.args.includes("-p") && !copy.args.includes("--print")) copy.args.unshift("-p");
|
|
313
|
-
|
|
319
|
+
// Cikti formatini DAIMA stream-json'a normalize et (eski config.json'lar 'text'/'json' tutuyor
|
|
320
|
+
// olabilir). Boylece claude akitir ve sessizlik-timeout yanlis-pozitifi ortadan kalkar.
|
|
321
|
+
const ofIdx = copy.args.indexOf("--output-format");
|
|
322
|
+
if (ofIdx >= 0) copy.args[ofIdx + 1] = "stream-json";
|
|
323
|
+
else copy.args.push("--output-format", "stream-json");
|
|
324
|
+
// stream-json, -p (print) modunda --verbose ZORUNLU kilar; yoksa claude hata verir.
|
|
325
|
+
if (!copy.args.includes("--verbose")) copy.args.push("--verbose");
|
|
326
|
+
// Claude Code model secimini oturum baslangicinda --model ile kabul eder. Alias kullanmak
|
|
327
|
+
// (sonnet/opus/haiku vb.) sağlayiciya gore guncel, erisilebilir surume cozumlenmesini saglar.
|
|
328
|
+
const model = String(copy.model || settings.model || "").trim();
|
|
329
|
+
if (model && !copy.args.includes("--model") && !copy.args.includes("-m")) copy.args.push("--model", model);
|
|
314
330
|
}
|
|
315
331
|
if (adapter === "gemini") {
|
|
316
332
|
// Otonom, non-interaktif calisma icin onay modu sart; yoksa izin bekleyip takilir.
|
|
@@ -733,6 +749,112 @@ function listCodexModels(options = {}) {
|
|
|
733
749
|
});
|
|
734
750
|
}
|
|
735
751
|
|
|
752
|
+
// Claude Code'un Agent SDK kontrol protokolu, oturum acilisinda o kullanicinin
|
|
753
|
+
// hesabina/saglayicisina/kurum politikasina gore gercekten secilebilir modelleri dondurur.
|
|
754
|
+
// Statik model kimlikleri tutmak hem hizla eskir hem de baska bir kullanicida gorunmeyen
|
|
755
|
+
// bir modeli secilebilir gosterir. Bu normalizer yalnizca UI'nin ihtiyaci olan alanlari
|
|
756
|
+
// alir; initialize yanitindaki hesap bilgilerini disari sizdirmaz.
|
|
757
|
+
function normalizeClaudeModels(models) {
|
|
758
|
+
const seen = new Set();
|
|
759
|
+
const normalized = [];
|
|
760
|
+
for (const model of Array.isArray(models) ? models : []) {
|
|
761
|
+
const id = String(model?.value || "").trim();
|
|
762
|
+
if (!id || seen.has(id)) continue;
|
|
763
|
+
seen.add(id);
|
|
764
|
+
normalized.push({
|
|
765
|
+
id,
|
|
766
|
+
resolvedModel: String(model.resolvedModel || "").trim(),
|
|
767
|
+
displayName: String(model.displayName || id).trim(),
|
|
768
|
+
description: String(model.description || "").trim(),
|
|
769
|
+
supportsEffort: model.supportsEffort === true,
|
|
770
|
+
supportedEffortLevels: Array.isArray(model.supportedEffortLevels)
|
|
771
|
+
? model.supportedEffortLevels.map(String).filter(Boolean)
|
|
772
|
+
: [],
|
|
773
|
+
supportsAdaptiveThinking: model.supportsAdaptiveThinking === true,
|
|
774
|
+
supportsFastMode: model.supportsFastMode === true,
|
|
775
|
+
supportsAutoMode: model.supportsAutoMode === true,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
return normalized;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function listClaudeModels(options = {}) {
|
|
782
|
+
const args = ["--output-format", "stream-json", "--verbose", "--input-format", "stream-json", "--no-session-persistence"];
|
|
783
|
+
const command = buildCommand(options.command || RESOLVED.get("claude") || DEFINITIONS.claude.command, args);
|
|
784
|
+
const timeoutMs = Math.max(5000, Number(options.timeoutMs || 20000));
|
|
785
|
+
const requestId = "crewctl-claude-models";
|
|
786
|
+
return new Promise((resolve, reject) => {
|
|
787
|
+
let buffer = "", stderr = "", settled = false;
|
|
788
|
+
let child, unregister = () => {};
|
|
789
|
+
const finish = (error, value) => {
|
|
790
|
+
if (settled) return;
|
|
791
|
+
settled = true;
|
|
792
|
+
clearTimeout(timer);
|
|
793
|
+
unregister();
|
|
794
|
+
terminateProbeTree(child);
|
|
795
|
+
if (error) reject(error); else resolve(value);
|
|
796
|
+
};
|
|
797
|
+
const timer = setTimeout(
|
|
798
|
+
() => finish(new Error(`Claude Code model listesi ${Math.round(timeoutMs / 1000)} saniyede alinamadi.`)),
|
|
799
|
+
timeoutMs,
|
|
800
|
+
);
|
|
801
|
+
try {
|
|
802
|
+
child = spawn(command.file, command.args, {
|
|
803
|
+
shell: command.shell,
|
|
804
|
+
windowsHide: true,
|
|
805
|
+
windowsVerbatimArguments: !!command.verbatim,
|
|
806
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
807
|
+
env: {
|
|
808
|
+
...process.env,
|
|
809
|
+
CLAUDE_CODE_ENTRYPOINT: "sdk-ts",
|
|
810
|
+
CLAUDE_AGENT_SDK_CLIENT_APP: "crewctl",
|
|
811
|
+
},
|
|
812
|
+
});
|
|
813
|
+
} catch (error) {
|
|
814
|
+
finish(error);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
unregister = registerProbe(child);
|
|
818
|
+
child.stdout?.setEncoding("utf8");
|
|
819
|
+
child.stderr?.setEncoding("utf8");
|
|
820
|
+
child.stdout?.on("data", (chunk) => {
|
|
821
|
+
buffer += String(chunk);
|
|
822
|
+
const lines = buffer.split(/\r?\n/);
|
|
823
|
+
buffer = lines.pop() || "";
|
|
824
|
+
for (const line of lines) {
|
|
825
|
+
let message;
|
|
826
|
+
try { message = JSON.parse(line); } catch { continue; }
|
|
827
|
+
if (message.type !== "control_response" || message.response?.request_id !== requestId) continue;
|
|
828
|
+
if (message.response?.subtype !== "success") {
|
|
829
|
+
finish(new Error(message.response?.error || "Claude Code model listesi alinamadi."));
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const models = message.response?.response?.models;
|
|
833
|
+
if (!Array.isArray(models)) {
|
|
834
|
+
finish(new Error("Bu Claude Code surumu dinamik model katalogunu desteklemiyor."));
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
finish(null, normalizeClaudeModels(models));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
child.stderr?.on("data", (chunk) => {
|
|
842
|
+
stderr = `${stderr}${String(chunk)}`.slice(-2048);
|
|
843
|
+
});
|
|
844
|
+
child.on("error", (error) => finish(error));
|
|
845
|
+
child.on("close", (code) => {
|
|
846
|
+
if (settled) return;
|
|
847
|
+
finish(new Error(stderr.trim() || `Claude Code model katalogu okunamadan cikti (kod ${code}).`));
|
|
848
|
+
});
|
|
849
|
+
child.stdin?.on("error", () => {});
|
|
850
|
+
child.stdin?.write(`${JSON.stringify({
|
|
851
|
+
type: "control_request",
|
|
852
|
+
request_id: requestId,
|
|
853
|
+
request: { subtype: "initialize" },
|
|
854
|
+
})}\n`);
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
736
858
|
function addMissingAgents(cfg, discovered) {
|
|
737
859
|
cfg.agents ||= {};
|
|
738
860
|
const ignored = new Set(Array.isArray(cfg.discoveryIgnoredAdapters) ? cfg.discoveryIgnoredAdapters.map(String) : []);
|
|
@@ -845,4 +967,4 @@ function ensureValidOperator(cfg, discovered) {
|
|
|
845
967
|
return changed;
|
|
846
968
|
}
|
|
847
969
|
|
|
848
|
-
module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, authHint, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, discoveryCacheFrom, applyOpenCodeFallback, healthCheckAll, listCodexModels, abortActiveProbes, currentProbeGeneration, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
|
|
970
|
+
module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, authHint, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, discoveryCacheFrom, applyOpenCodeFallback, healthCheckAll, listCodexModels, listClaudeModels, normalizeClaudeModels, abortActiveProbes, currentProbeGeneration, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
|