ralph-mem 0.1.0

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.
@@ -0,0 +1,404 @@
1
+ import {
2
+ DEFAULT_CONFIG,
3
+ dump,
4
+ getProjectConfigPath,
5
+ loadConfig,
6
+ loadYamlConfig
7
+ } from "../chunk-c3a91ngd.js";
8
+ import"../chunk-w40c0y00.js";
9
+ import"../chunk-ns0dgdnb.js";
10
+
11
+ // src/skills/ralph-config.ts
12
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
+ var VALID_CONFIG_KEYS = new Set([
15
+ "ralph.max_iterations",
16
+ "ralph.max_duration_ms",
17
+ "ralph.no_progress_threshold",
18
+ "ralph.context_budget",
19
+ "ralph.cooldown_ms",
20
+ "memory.auto_inject",
21
+ "memory.max_inject_tokens",
22
+ "memory.retention_days",
23
+ "search.fts_first",
24
+ "search.embedding_fallback",
25
+ "search.default_limit",
26
+ "privacy.exclude_patterns",
27
+ "logging.level",
28
+ "logging.file"
29
+ ]);
30
+ var CONFIG_KEY_TYPES = {
31
+ "ralph.max_iterations": "number",
32
+ "ralph.max_duration_ms": "number",
33
+ "ralph.no_progress_threshold": "number",
34
+ "ralph.context_budget": "number",
35
+ "ralph.cooldown_ms": "number",
36
+ "memory.auto_inject": "boolean",
37
+ "memory.max_inject_tokens": "number",
38
+ "memory.retention_days": "number",
39
+ "search.fts_first": "boolean",
40
+ "search.embedding_fallback": "boolean",
41
+ "search.default_limit": "number",
42
+ "privacy.exclude_patterns": "array",
43
+ "logging.level": "string",
44
+ "logging.file": "boolean"
45
+ };
46
+ function parseConfigArgs(argsString) {
47
+ const args = {};
48
+ const trimmed = argsString.trim();
49
+ if (!trimmed) {
50
+ return args;
51
+ }
52
+ const tokens = [];
53
+ let current = "";
54
+ let inQuotes = false;
55
+ let quoteChar = "";
56
+ for (const char of trimmed) {
57
+ if ((char === '"' || char === "'") && !inQuotes) {
58
+ inQuotes = true;
59
+ quoteChar = char;
60
+ } else if (char === quoteChar && inQuotes) {
61
+ inQuotes = false;
62
+ quoteChar = "";
63
+ } else if (char === " " && !inQuotes) {
64
+ if (current) {
65
+ tokens.push(current);
66
+ current = "";
67
+ }
68
+ } else {
69
+ current += char;
70
+ }
71
+ }
72
+ if (current) {
73
+ tokens.push(current);
74
+ }
75
+ if (tokens[0] === "set" && tokens.length >= 3) {
76
+ args.subcommand = "set";
77
+ args.key = tokens[1];
78
+ args.value = tokens.slice(2).join(" ");
79
+ } else if (tokens[0] === "init") {
80
+ args.subcommand = "init";
81
+ } else if (tokens[0] === "get" && tokens.length >= 2) {
82
+ args.subcommand = "get";
83
+ args.key = tokens[1];
84
+ } else if (tokens[0] && !tokens[0].startsWith("-")) {
85
+ args.subcommand = "get";
86
+ args.key = tokens[0];
87
+ }
88
+ return args;
89
+ }
90
+ function isValidConfigKey(key) {
91
+ return VALID_CONFIG_KEYS.has(key);
92
+ }
93
+ function getConfigValue(config, key) {
94
+ const [section, prop] = key.split(".");
95
+ if (!section || !prop)
96
+ return;
97
+ const sectionObj = config[section];
98
+ if (!sectionObj || typeof sectionObj !== "object")
99
+ return;
100
+ return sectionObj[prop];
101
+ }
102
+ function setConfigValue(config, key, value) {
103
+ const [section, prop] = key.split(".");
104
+ if (!section || !prop)
105
+ return;
106
+ if (!config[section]) {
107
+ config[section] = {};
108
+ }
109
+ config[section][prop] = value;
110
+ }
111
+ function parseConfigValue(key, valueStr) {
112
+ const expectedType = CONFIG_KEY_TYPES[key];
113
+ switch (expectedType) {
114
+ case "number": {
115
+ const num = Number(valueStr);
116
+ if (Number.isNaN(num)) {
117
+ throw new Error(`값이 숫자여야 합니다: ${key}`);
118
+ }
119
+ return num;
120
+ }
121
+ case "boolean": {
122
+ const lower = valueStr.toLowerCase();
123
+ if (lower === "true" || lower === "1" || lower === "yes") {
124
+ return true;
125
+ }
126
+ if (lower === "false" || lower === "0" || lower === "no") {
127
+ return false;
128
+ }
129
+ throw new Error(`값이 boolean이어야 합니다 (true/false): ${key}`);
130
+ }
131
+ case "array": {
132
+ try {
133
+ const parsed = JSON.parse(valueStr);
134
+ if (Array.isArray(parsed)) {
135
+ return parsed;
136
+ }
137
+ } catch {}
138
+ return valueStr.split(",").map((s) => s.trim());
139
+ }
140
+ default:
141
+ return valueStr;
142
+ }
143
+ }
144
+ function validateConfigValue(key, value) {
145
+ const expectedType = CONFIG_KEY_TYPES[key];
146
+ switch (expectedType) {
147
+ case "number":
148
+ return typeof value === "number" && !Number.isNaN(value);
149
+ case "boolean":
150
+ return typeof value === "boolean";
151
+ case "array":
152
+ return Array.isArray(value);
153
+ case "string":
154
+ return typeof value === "string";
155
+ default:
156
+ return true;
157
+ }
158
+ }
159
+ function formatConfig(config, configPath) {
160
+ const lines = [];
161
+ lines.push(`⚙️ Ralph 설정
162
+ `);
163
+ lines.push("ralph:");
164
+ lines.push(` max_iterations: ${config.ralph.max_iterations}`);
165
+ lines.push(` max_duration_ms: ${config.ralph.max_duration_ms}`);
166
+ lines.push(` no_progress_threshold: ${config.ralph.no_progress_threshold}`);
167
+ lines.push(` context_budget: ${config.ralph.context_budget}`);
168
+ lines.push(` cooldown_ms: ${config.ralph.cooldown_ms}`);
169
+ if (config.ralph.success_criteria.length > 0) {
170
+ lines.push(" success_criteria:");
171
+ for (const c of config.ralph.success_criteria) {
172
+ lines.push(` - type: ${c.type}`);
173
+ if (c.command)
174
+ lines.push(` command: ${c.command}`);
175
+ }
176
+ }
177
+ lines.push("");
178
+ lines.push("memory:");
179
+ lines.push(` auto_inject: ${config.memory.auto_inject}`);
180
+ lines.push(` max_inject_tokens: ${config.memory.max_inject_tokens}`);
181
+ lines.push(` retention_days: ${config.memory.retention_days}`);
182
+ lines.push("");
183
+ lines.push("search:");
184
+ lines.push(` fts_first: ${config.search.fts_first}`);
185
+ lines.push(` embedding_fallback: ${config.search.embedding_fallback}`);
186
+ lines.push(` default_limit: ${config.search.default_limit}`);
187
+ lines.push("");
188
+ lines.push("privacy:");
189
+ lines.push(` exclude_patterns: [${config.privacy.exclude_patterns.join(", ")}]`);
190
+ lines.push("");
191
+ lines.push("logging:");
192
+ lines.push(` level: ${config.logging.level}`);
193
+ lines.push(` file: ${config.logging.file}`);
194
+ lines.push("");
195
+ if (configPath && existsSync(configPath)) {
196
+ lines.push(`설정 파일: ${configPath}`);
197
+ } else {
198
+ lines.push("설정 파일: (기본값 사용)");
199
+ }
200
+ return lines.join(`
201
+ `);
202
+ }
203
+ function formatConfigValue(key, value) {
204
+ if (Array.isArray(value)) {
205
+ return `${key}: [${value.join(", ")}]`;
206
+ }
207
+ return `${key}: ${value}`;
208
+ }
209
+ function saveConfig(projectPath, config) {
210
+ const configPath = getProjectConfigPath(projectPath);
211
+ const configDir = dirname(configPath);
212
+ if (!existsSync(configDir)) {
213
+ mkdirSync(configDir, { recursive: true });
214
+ }
215
+ const yaml = dump(config, { indent: 2 });
216
+ writeFileSync(configPath, yaml, "utf-8");
217
+ }
218
+ function createInitialConfig(options) {
219
+ const ralph = { ...DEFAULT_CONFIG.ralph };
220
+ if (options.testCommand) {
221
+ ralph.success_criteria = [
222
+ {
223
+ type: "test_pass",
224
+ command: options.testCommand
225
+ }
226
+ ];
227
+ }
228
+ return { ralph };
229
+ }
230
+ function detectProjectType(projectPath) {
231
+ if (existsSync(join(projectPath, "package.json"))) {
232
+ return "node";
233
+ }
234
+ if (existsSync(join(projectPath, "pyproject.toml")) || existsSync(join(projectPath, "setup.py")) || existsSync(join(projectPath, "requirements.txt"))) {
235
+ return "python";
236
+ }
237
+ if (existsSync(join(projectPath, "go.mod"))) {
238
+ return "go";
239
+ }
240
+ if (existsSync(join(projectPath, "Cargo.toml"))) {
241
+ return "rust";
242
+ }
243
+ return "other";
244
+ }
245
+ function getSuggestedCommands(projectType) {
246
+ switch (projectType) {
247
+ case "node":
248
+ return { test: "npm test", build: "npm run build" };
249
+ case "python":
250
+ return { test: "pytest", build: "pip install -e ." };
251
+ case "go":
252
+ return { test: "go test ./...", build: "go build" };
253
+ case "rust":
254
+ return { test: "cargo test", build: "cargo build" };
255
+ default:
256
+ return { test: "make test", build: "make build" };
257
+ }
258
+ }
259
+ function createRalphConfigSkill(context) {
260
+ const { projectPath } = context;
261
+ return {
262
+ name: "/ralph config",
263
+ show() {
264
+ const config = loadConfig(projectPath);
265
+ const configPath = getProjectConfigPath(projectPath);
266
+ const message = formatConfig(config, configPath);
267
+ return {
268
+ success: true,
269
+ message,
270
+ config
271
+ };
272
+ },
273
+ get(key) {
274
+ if (!isValidConfigKey(key)) {
275
+ return {
276
+ success: false,
277
+ message: "",
278
+ error: `잘못된 설정 키: ${key}`
279
+ };
280
+ }
281
+ const config = loadConfig(projectPath);
282
+ const value = getConfigValue(config, key);
283
+ return {
284
+ success: true,
285
+ message: formatConfigValue(key, value)
286
+ };
287
+ },
288
+ set(key, valueStr) {
289
+ if (!isValidConfigKey(key)) {
290
+ return {
291
+ success: false,
292
+ message: "",
293
+ error: `잘못된 설정 키: ${key}`
294
+ };
295
+ }
296
+ let value;
297
+ try {
298
+ value = parseConfigValue(key, valueStr);
299
+ } catch (error) {
300
+ return {
301
+ success: false,
302
+ message: "",
303
+ error: error instanceof Error ? error.message : String(error)
304
+ };
305
+ }
306
+ if (!validateConfigValue(key, value)) {
307
+ return {
308
+ success: false,
309
+ message: "",
310
+ error: `타입이 맞지 않습니다: ${key}는 ${CONFIG_KEY_TYPES[key]}이어야 합니다`
311
+ };
312
+ }
313
+ const configPath = getProjectConfigPath(projectPath);
314
+ const projectConfig = loadYamlConfig(configPath);
315
+ setConfigValue(projectConfig, key, value);
316
+ saveConfig(projectPath, projectConfig);
317
+ return {
318
+ success: true,
319
+ message: `✅ 설정 저장됨: ${formatConfigValue(key, value)}`
320
+ };
321
+ },
322
+ init() {
323
+ const configPath = getProjectConfigPath(projectPath);
324
+ if (existsSync(configPath)) {
325
+ return {
326
+ success: false,
327
+ message: "",
328
+ error: `설정 파일이 이미 존재합니다: ${configPath}`
329
+ };
330
+ }
331
+ const projectType = detectProjectType(projectPath);
332
+ const commands = getSuggestedCommands(projectType);
333
+ const config = createInitialConfig({
334
+ projectType,
335
+ testCommand: commands.test
336
+ });
337
+ saveConfig(projectPath, config);
338
+ return {
339
+ success: true,
340
+ message: `✅ 설정 파일 생성됨: ${configPath}
341
+
342
+ 프로젝트 유형: ${projectType}
343
+ 테스트 명령: ${commands.test}
344
+ 빌드 명령: ${commands.build}
345
+
346
+ 설정 수정: /ralph config set <key> <value>
347
+ 설정 조회: /ralph config`
348
+ };
349
+ },
350
+ execute(args) {
351
+ if (!args.subcommand) {
352
+ return this.show();
353
+ }
354
+ switch (args.subcommand) {
355
+ case "get":
356
+ if (!args.key) {
357
+ return this.show();
358
+ }
359
+ return this.get(args.key);
360
+ case "set":
361
+ if (!args.key || args.value === undefined) {
362
+ return {
363
+ success: false,
364
+ message: "",
365
+ error: "사용법: /ralph config set <key> <value>"
366
+ };
367
+ }
368
+ return this.set(args.key, args.value);
369
+ case "init":
370
+ return this.init();
371
+ default:
372
+ return this.show();
373
+ }
374
+ },
375
+ parseArgs(argsString) {
376
+ return parseConfigArgs(argsString);
377
+ }
378
+ };
379
+ }
380
+ async function executeRalphConfig(argsString, context) {
381
+ const skill = createRalphConfigSkill(context);
382
+ const args = skill.parseArgs(argsString);
383
+ const result = skill.execute(args);
384
+ if (result.error) {
385
+ return `❌ ${result.error}`;
386
+ }
387
+ return result.message;
388
+ }
389
+ export {
390
+ validateConfigValue,
391
+ setConfigValue,
392
+ saveConfig,
393
+ parseConfigValue,
394
+ parseConfigArgs,
395
+ isValidConfigKey,
396
+ getSuggestedCommands,
397
+ getConfigValue,
398
+ formatConfigValue,
399
+ formatConfig,
400
+ executeRalphConfig,
401
+ detectProjectType,
402
+ createRalphConfigSkill,
403
+ createInitialConfig
404
+ };