ralph-cli-sandboxed 0.4.1 → 0.4.2
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 +30 -0
- package/dist/commands/action.js +9 -9
- package/dist/commands/chat.js +13 -12
- package/dist/commands/config.js +2 -1
- package/dist/commands/daemon.js +4 -3
- package/dist/commands/docker.js +102 -66
- package/dist/commands/fix-config.js +2 -1
- package/dist/commands/fix-prd.js +2 -2
- package/dist/commands/init.js +78 -17
- package/dist/commands/listen.js +3 -1
- package/dist/commands/notify.js +1 -1
- package/dist/commands/once.js +17 -9
- package/dist/commands/prd.js +4 -1
- package/dist/commands/run.js +40 -25
- package/dist/commands/slack.js +2 -2
- package/dist/config/responder-presets.json +69 -0
- package/dist/index.js +1 -1
- package/dist/providers/discord.d.ts +28 -0
- package/dist/providers/discord.js +227 -14
- package/dist/providers/slack.d.ts +41 -1
- package/dist/providers/slack.js +389 -8
- package/dist/providers/telegram.d.ts +30 -0
- package/dist/providers/telegram.js +185 -5
- package/dist/responders/claude-code-responder.d.ts +48 -0
- package/dist/responders/claude-code-responder.js +203 -0
- package/dist/responders/cli-responder.d.ts +62 -0
- package/dist/responders/cli-responder.js +298 -0
- package/dist/responders/llm-responder.d.ts +135 -0
- package/dist/responders/llm-responder.js +582 -0
- package/dist/templates/macos-scripts.js +2 -4
- package/dist/templates/prompts.js +4 -2
- package/dist/tui/ConfigEditor.js +19 -5
- package/dist/tui/components/ArrayEditor.js +1 -1
- package/dist/tui/components/EditorPanel.js +10 -6
- package/dist/tui/components/HelpPanel.d.ts +1 -1
- package/dist/tui/components/HelpPanel.js +1 -1
- package/dist/tui/components/JsonSnippetEditor.js +8 -5
- package/dist/tui/components/KeyValueEditor.js +54 -9
- package/dist/tui/components/LLMProvidersEditor.d.ts +22 -0
- package/dist/tui/components/LLMProvidersEditor.js +357 -0
- package/dist/tui/components/ObjectEditor.js +1 -1
- package/dist/tui/components/Preview.js +1 -1
- package/dist/tui/components/RespondersEditor.d.ts +22 -0
- package/dist/tui/components/RespondersEditor.js +437 -0
- package/dist/tui/components/SectionNav.js +27 -3
- package/dist/utils/chat-client.d.ts +4 -0
- package/dist/utils/chat-client.js +12 -5
- package/dist/utils/config.d.ts +84 -0
- package/dist/utils/config.js +78 -1
- package/dist/utils/daemon-client.d.ts +21 -0
- package/dist/utils/daemon-client.js +28 -1
- package/dist/utils/llm-client.d.ts +82 -0
- package/dist/utils/llm-client.js +185 -0
- package/dist/utils/message-queue.js +6 -6
- package/dist/utils/notification.d.ts +6 -1
- package/dist/utils/notification.js +103 -2
- package/dist/utils/prd-validator.js +60 -19
- package/dist/utils/prompt.js +22 -12
- package/dist/utils/responder-logger.d.ts +47 -0
- package/dist/utils/responder-logger.js +129 -0
- package/dist/utils/responder-presets.d.ts +92 -0
- package/dist/utils/responder-presets.js +156 -0
- package/dist/utils/responder.d.ts +88 -0
- package/dist/utils/responder.js +207 -0
- package/dist/utils/stream-json.js +6 -6
- package/docs/CHAT-RESPONDERS.md +785 -0
- package/docs/DEVELOPMENT.md +25 -0
- package/docs/chat-architecture.md +251 -0
- package/package.json +11 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, writeFileSync, mkdirSync, copyFileSync, chmodSync } from "fs";
|
|
2
2
|
import { join, basename, dirname } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { getLanguages, generatePromptTemplate, DEFAULT_PRD, DEFAULT_PROGRESS, getCliProviders, getSkillsForLanguage } from "../templates/prompts.js";
|
|
5
|
-
import { generateGenXcodeScript, hasSwiftUI, hasFastlane, generateFastfile, generateAppfile, generateFastlaneReadmeSection } from "../templates/macos-scripts.js";
|
|
6
|
-
import { promptSelectWithArrows, promptConfirm, promptInput, promptMultiSelectWithArrows } from "../utils/prompt.js";
|
|
4
|
+
import { getLanguages, generatePromptTemplate, DEFAULT_PRD, DEFAULT_PROGRESS, getCliProviders, getSkillsForLanguage, } from "../templates/prompts.js";
|
|
5
|
+
import { generateGenXcodeScript, hasSwiftUI, hasFastlane, generateFastfile, generateAppfile, generateFastlaneReadmeSection, } from "../templates/macos-scripts.js";
|
|
6
|
+
import { promptSelectWithArrows, promptConfirm, promptInput, promptMultiSelectWithArrows, } from "../utils/prompt.js";
|
|
7
7
|
import { dockerInit } from "./docker.js";
|
|
8
|
+
import { getBundleDisplayOptions, displayOptionToBundleId, bundleToRespondersConfig, getPresetDisplayOptions, displayOptionToPresetId, presetsToRespondersConfig, } from "../utils/responder-presets.js";
|
|
8
9
|
// Get package root directory (works for both dev and installed package)
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = dirname(__filename);
|
|
@@ -41,6 +42,7 @@ export async function init(args) {
|
|
|
41
42
|
let selectedKey;
|
|
42
43
|
let selectedTechnologies = [];
|
|
43
44
|
let selectedSkills = [];
|
|
45
|
+
let selectedResponders = {};
|
|
44
46
|
let checkCommand;
|
|
45
47
|
let testCommand;
|
|
46
48
|
if (useDefaults) {
|
|
@@ -62,7 +64,7 @@ export async function init(args) {
|
|
|
62
64
|
else {
|
|
63
65
|
// Step 1: Select CLI provider (first)
|
|
64
66
|
const providerKeys = Object.keys(CLI_PROVIDERS);
|
|
65
|
-
const providerNames = providerKeys.map(k => `${CLI_PROVIDERS[k].name} - ${CLI_PROVIDERS[k].description}`);
|
|
67
|
+
const providerNames = providerKeys.map((k) => `${CLI_PROVIDERS[k].name} - ${CLI_PROVIDERS[k].description}`);
|
|
66
68
|
const selectedProviderName = await promptSelectWithArrows("Select your AI CLI provider:", providerNames);
|
|
67
69
|
const selectedProviderIndex = providerNames.indexOf(selectedProviderName);
|
|
68
70
|
selectedCliProviderKey = providerKeys[selectedProviderIndex];
|
|
@@ -73,9 +75,13 @@ export async function init(args) {
|
|
|
73
75
|
const customArgsInput = await promptInput("Enter default arguments (space-separated): ");
|
|
74
76
|
const customArgs = customArgsInput.trim() ? customArgsInput.trim().split(/\s+/) : [];
|
|
75
77
|
const customYoloArgsInput = await promptInput("Enter yolo/auto-approve arguments (space-separated): ");
|
|
76
|
-
const customYoloArgs = customYoloArgsInput.trim()
|
|
78
|
+
const customYoloArgs = customYoloArgsInput.trim()
|
|
79
|
+
? customYoloArgsInput.trim().split(/\s+/)
|
|
80
|
+
: [];
|
|
77
81
|
const customPromptArgsInput = await promptInput("Enter prompt arguments (e.g., -p for flag-based, leave empty for positional): ");
|
|
78
|
-
const customPromptArgs = customPromptArgsInput.trim()
|
|
82
|
+
const customPromptArgs = customPromptArgsInput.trim()
|
|
83
|
+
? customPromptArgsInput.trim().split(/\s+/)
|
|
84
|
+
: [];
|
|
79
85
|
cliConfig = {
|
|
80
86
|
command: customCommand || "claude",
|
|
81
87
|
args: customArgs,
|
|
@@ -94,7 +100,7 @@ export async function init(args) {
|
|
|
94
100
|
console.log(`\nSelected CLI provider: ${CLI_PROVIDERS[selectedCliProviderKey].name}`);
|
|
95
101
|
// Step 2: Select language (second)
|
|
96
102
|
const languageKeys = Object.keys(LANGUAGES);
|
|
97
|
-
const languageNames = languageKeys.map(k => `${LANGUAGES[k].name} - ${LANGUAGES[k].description}`);
|
|
103
|
+
const languageNames = languageKeys.map((k) => `${LANGUAGES[k].name} - ${LANGUAGES[k].description}`);
|
|
98
104
|
const selectedName = await promptSelectWithArrows("Select your project language/runtime:", languageNames);
|
|
99
105
|
const selectedIndex = languageNames.indexOf(selectedName);
|
|
100
106
|
selectedKey = languageKeys[selectedIndex];
|
|
@@ -102,11 +108,11 @@ export async function init(args) {
|
|
|
102
108
|
console.log(`\nSelected language: ${config.name}`);
|
|
103
109
|
// Step 3: Select technology stack if available (third)
|
|
104
110
|
if (config.technologies && config.technologies.length > 0) {
|
|
105
|
-
const techOptions = config.technologies.map(t => `${t.name} - ${t.description}`);
|
|
106
|
-
const techNames = config.technologies.map(t => t.name);
|
|
111
|
+
const techOptions = config.technologies.map((t) => `${t.name} - ${t.description}`);
|
|
112
|
+
const techNames = config.technologies.map((t) => t.name);
|
|
107
113
|
selectedTechnologies = await promptMultiSelectWithArrows("Select your technology stack (optional):", techOptions);
|
|
108
114
|
// Convert display names back to just technology names for predefined options
|
|
109
|
-
selectedTechnologies = selectedTechnologies.map(sel => {
|
|
115
|
+
selectedTechnologies = selectedTechnologies.map((sel) => {
|
|
110
116
|
const idx = techOptions.indexOf(sel);
|
|
111
117
|
return idx >= 0 ? techNames[idx] : sel;
|
|
112
118
|
});
|
|
@@ -120,10 +126,10 @@ export async function init(args) {
|
|
|
120
126
|
// Step 4: Select skills if available for this language
|
|
121
127
|
const availableSkills = getSkillsForLanguage(selectedKey);
|
|
122
128
|
if (availableSkills.length > 0) {
|
|
123
|
-
const skillOptions = availableSkills.map(s => `${s.name} - ${s.description}`);
|
|
129
|
+
const skillOptions = availableSkills.map((s) => `${s.name} - ${s.description}`);
|
|
124
130
|
const selectedSkillNames = await promptMultiSelectWithArrows("Select AI coding rules/skills to enable (optional):", skillOptions);
|
|
125
131
|
// Convert selected display names to SkillConfig objects
|
|
126
|
-
selectedSkills = selectedSkillNames.map(sel => {
|
|
132
|
+
selectedSkills = selectedSkillNames.map((sel) => {
|
|
127
133
|
const idx = skillOptions.indexOf(sel);
|
|
128
134
|
const skill = availableSkills[idx];
|
|
129
135
|
return {
|
|
@@ -134,18 +140,52 @@ export async function init(args) {
|
|
|
134
140
|
};
|
|
135
141
|
});
|
|
136
142
|
if (selectedSkills.length > 0) {
|
|
137
|
-
console.log(`\nSelected skills: ${selectedSkills.map(s => s.name).join(", ")}`);
|
|
143
|
+
console.log(`\nSelected skills: ${selectedSkills.map((s) => s.name).join(", ")}`);
|
|
138
144
|
}
|
|
139
145
|
else {
|
|
140
146
|
console.log("\nNo skills selected.");
|
|
141
147
|
}
|
|
142
148
|
}
|
|
149
|
+
// Step 5: Select chat responder presets (optional)
|
|
150
|
+
const setupResponders = await promptConfirm("\nWould you like to set up chat responders?", false);
|
|
151
|
+
if (setupResponders) {
|
|
152
|
+
// First, ask if they want a bundle or individual presets
|
|
153
|
+
const selectionType = await promptSelectWithArrows("How would you like to configure responders?", [
|
|
154
|
+
"Use a preset bundle (recommended)",
|
|
155
|
+
"Select individual presets",
|
|
156
|
+
"Skip - configure later",
|
|
157
|
+
]);
|
|
158
|
+
if (selectionType === "Use a preset bundle (recommended)") {
|
|
159
|
+
const bundleOptions = getBundleDisplayOptions();
|
|
160
|
+
const selectedBundle = await promptSelectWithArrows("Select a responder bundle:", bundleOptions);
|
|
161
|
+
const bundleId = displayOptionToBundleId(selectedBundle);
|
|
162
|
+
if (bundleId) {
|
|
163
|
+
selectedResponders = bundleToRespondersConfig(bundleId);
|
|
164
|
+
console.log(`\nConfigured responders from bundle: ${Object.keys(selectedResponders).join(", ")}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (selectionType === "Select individual presets") {
|
|
168
|
+
const presetOptions = getPresetDisplayOptions();
|
|
169
|
+
const selectedPresets = await promptMultiSelectWithArrows("Select responder presets to enable:", presetOptions);
|
|
170
|
+
// Convert display options back to preset IDs
|
|
171
|
+
const presetIds = selectedPresets
|
|
172
|
+
.map(displayOptionToPresetId)
|
|
173
|
+
.filter((id) => id !== undefined);
|
|
174
|
+
if (presetIds.length > 0) {
|
|
175
|
+
selectedResponders = presetsToRespondersConfig(presetIds);
|
|
176
|
+
console.log(`\nConfigured responders: ${Object.keys(selectedResponders).join(", ")}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.log("\nSkipping responders - you can configure them later in config.json");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
143
183
|
// Allow custom commands for "none" language
|
|
144
184
|
checkCommand = config.checkCommand;
|
|
145
185
|
testCommand = config.testCommand;
|
|
146
186
|
if (selectedKey === "none") {
|
|
147
|
-
checkCommand = await promptInput("\nEnter your type/build check command: ") || checkCommand;
|
|
148
|
-
testCommand = await promptInput("Enter your test command: ") || testCommand;
|
|
187
|
+
checkCommand = (await promptInput("\nEnter your type/build check command: ")) || checkCommand;
|
|
188
|
+
testCommand = (await promptInput("Enter your test command: ")) || testCommand;
|
|
149
189
|
}
|
|
150
190
|
}
|
|
151
191
|
const finalConfig = {
|
|
@@ -154,7 +194,9 @@ export async function init(args) {
|
|
|
154
194
|
testCommand,
|
|
155
195
|
};
|
|
156
196
|
// Generate image name from directory name
|
|
157
|
-
const projectName = basename(cwd)
|
|
197
|
+
const projectName = basename(cwd)
|
|
198
|
+
.toLowerCase()
|
|
199
|
+
.replace(/[^a-z0-9-]/g, "-");
|
|
158
200
|
const imageName = `ralph-${projectName}`;
|
|
159
201
|
// Generate macOS development actions for Swift + SwiftUI projects
|
|
160
202
|
const macOsActions = {};
|
|
@@ -197,6 +239,14 @@ export async function init(args) {
|
|
|
197
239
|
// CLI configuration
|
|
198
240
|
cli: cliConfig,
|
|
199
241
|
cliProvider: selectedCliProviderKey,
|
|
242
|
+
// LLM providers for chat responders (empty by default, uses defaults from env vars)
|
|
243
|
+
// Example:
|
|
244
|
+
// llmProviders: {
|
|
245
|
+
// "claude": { type: "anthropic", model: "claude-sonnet-4-20250514" },
|
|
246
|
+
// "gpt4": { type: "openai", model: "gpt-4o" },
|
|
247
|
+
// "local": { type: "ollama", model: "llama3", baseUrl: "http://localhost:11434" }
|
|
248
|
+
// }
|
|
249
|
+
llmProviders: {},
|
|
200
250
|
// Optional fields with defaults/empty values for discoverability
|
|
201
251
|
notifyCommand: "",
|
|
202
252
|
technologies: selectedTechnologies.length > 0 ? selectedTechnologies : [],
|
|
@@ -244,6 +294,17 @@ export async function init(args) {
|
|
|
244
294
|
botToken: "",
|
|
245
295
|
allowedChatIds: [],
|
|
246
296
|
},
|
|
297
|
+
// Chat responders - handle incoming messages based on triggers
|
|
298
|
+
// Special "default" responder handles messages that don't match any trigger
|
|
299
|
+
// Trigger patterns: "@name" for mentions, "keyword" for prefix matching
|
|
300
|
+
// Use "ralph init" to select from preset bundles, or configure manually:
|
|
301
|
+
// responders: {
|
|
302
|
+
// "default": { type: "llm", provider: "anthropic", systemPrompt: "You are a helpful assistant for {{project}}." },
|
|
303
|
+
// "qa": { type: "llm", trigger: "@qa", provider: "anthropic", systemPrompt: "Answer questions about the codebase." },
|
|
304
|
+
// "code": { type: "claude-code", trigger: "@code" },
|
|
305
|
+
// "lint": { type: "cli", trigger: "!lint", command: "npm run lint" }
|
|
306
|
+
// }
|
|
307
|
+
responders: selectedResponders,
|
|
247
308
|
},
|
|
248
309
|
// Daemon configuration for sandbox-to-host communication
|
|
249
310
|
daemon: {
|
|
@@ -332,7 +393,7 @@ docker/.config-hash
|
|
|
332
393
|
const swiftProjectName = basename(cwd)
|
|
333
394
|
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
334
395
|
.split(" ")
|
|
335
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
396
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
336
397
|
.join("") || "App";
|
|
337
398
|
if (!existsSync(genXcodePath)) {
|
|
338
399
|
writeFileSync(genXcodePath, generateGenXcodeScript(swiftProjectName));
|
package/dist/commands/listen.js
CHANGED
|
@@ -190,7 +190,9 @@ async function processMessage(message, messagesPath, debug) {
|
|
|
190
190
|
proc.unref();
|
|
191
191
|
respondToMessage(messagesPath, message.id, {
|
|
192
192
|
success: true,
|
|
193
|
-
output: message.args?.length
|
|
193
|
+
output: message.args?.length
|
|
194
|
+
? `Ralph run started (category: ${message.args[0]})`
|
|
195
|
+
: "Ralph run started",
|
|
194
196
|
});
|
|
195
197
|
break;
|
|
196
198
|
}
|
package/dist/commands/notify.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRunningInContainer } from "../utils/config.js";
|
|
2
2
|
import { sendNotification } from "../utils/notification.js";
|
|
3
3
|
import { loadConfig } from "../utils/config.js";
|
|
4
|
-
import { getMessagesPath, sendMessage, waitForResponse
|
|
4
|
+
import { getMessagesPath, sendMessage, waitForResponse } from "../utils/message-queue.js";
|
|
5
5
|
import { existsSync } from "fs";
|
|
6
6
|
/**
|
|
7
7
|
* Send a notification - works both inside and outside containers.
|
package/dist/commands/once.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { existsSync, appendFileSync, mkdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer } from "../utils/config.js";
|
|
4
|
+
import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer, } from "../utils/config.js";
|
|
5
5
|
import { resolvePromptVariables, getCliProviders } from "../templates/prompts.js";
|
|
6
6
|
import { getStreamJsonParser } from "../utils/stream-json.js";
|
|
7
7
|
import { sendNotificationWithDaemonEvents } from "../utils/notification.js";
|
|
@@ -58,10 +58,7 @@ export async function once(args) {
|
|
|
58
58
|
// Use yoloArgs from config if available, otherwise default to Claude's --dangerously-skip-permissions
|
|
59
59
|
const yoloArgs = cliConfig.yoloArgs ?? ["--dangerously-skip-permissions"];
|
|
60
60
|
const promptArgs = cliConfig.promptArgs ?? ["-p"];
|
|
61
|
-
const cliArgs = [
|
|
62
|
-
...(cliConfig.args ?? []),
|
|
63
|
-
...yoloArgs,
|
|
64
|
-
];
|
|
61
|
+
const cliArgs = [...(cliConfig.args ?? []), ...yoloArgs];
|
|
65
62
|
// Build the prompt value based on whether fileArgs is configured
|
|
66
63
|
// fileArgs (e.g., ["--read"] for Aider) means files are passed as separate arguments
|
|
67
64
|
// Otherwise, use @file syntax embedded in the prompt (Claude Code style)
|
|
@@ -98,7 +95,7 @@ export async function once(args) {
|
|
|
98
95
|
}
|
|
99
96
|
cliArgs.push(...promptArgs, promptValue);
|
|
100
97
|
if (debug) {
|
|
101
|
-
console.log(`[debug] ${cliConfig.command} ${cliArgs.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}\n`);
|
|
98
|
+
console.log(`[debug] ${cliConfig.command} ${cliArgs.map((a) => (a.includes(" ") ? `"${a}"` : a)).join(" ")}\n`);
|
|
102
99
|
if (jsonLogPath) {
|
|
103
100
|
console.log(`[debug] Saving raw JSON to: ${jsonLogPath}\n`);
|
|
104
101
|
}
|
|
@@ -106,7 +103,12 @@ export async function once(args) {
|
|
|
106
103
|
// Create provider-specific stream-json parser
|
|
107
104
|
const streamJsonParser = getStreamJsonParser(config.cliProvider, debug);
|
|
108
105
|
// Notification options for this run
|
|
109
|
-
const notifyOptions = {
|
|
106
|
+
const notifyOptions = {
|
|
107
|
+
command: config.notifyCommand,
|
|
108
|
+
debug,
|
|
109
|
+
daemonConfig: config.daemon,
|
|
110
|
+
chatConfig: config.chat,
|
|
111
|
+
};
|
|
110
112
|
return new Promise((resolve, reject) => {
|
|
111
113
|
let output = ""; // Accumulate output for PRD complete detection
|
|
112
114
|
if (streamJsonEnabled) {
|
|
@@ -181,7 +183,10 @@ export async function once(args) {
|
|
|
181
183
|
if (code !== 0) {
|
|
182
184
|
console.error(`\n${cliConfig.command} exited with code ${code}`);
|
|
183
185
|
const errorMessage = `Iteration failed with exit code ${code}`;
|
|
184
|
-
await sendNotificationWithDaemonEvents("error", `Ralph: ${errorMessage}`, {
|
|
186
|
+
await sendNotificationWithDaemonEvents("error", `Ralph: ${errorMessage}`, {
|
|
187
|
+
...notifyOptions,
|
|
188
|
+
errorMessage,
|
|
189
|
+
});
|
|
185
190
|
}
|
|
186
191
|
else if (output.includes("<promise>COMPLETE</promise>")) {
|
|
187
192
|
await sendNotificationWithDaemonEvents("prd_complete", undefined, notifyOptions);
|
|
@@ -210,7 +215,10 @@ export async function once(args) {
|
|
|
210
215
|
if (code !== 0) {
|
|
211
216
|
console.error(`\n${cliConfig.command} exited with code ${code}`);
|
|
212
217
|
const errorMessage = `Iteration failed with exit code ${code}`;
|
|
213
|
-
await sendNotificationWithDaemonEvents("error", `Ralph: ${errorMessage}`, {
|
|
218
|
+
await sendNotificationWithDaemonEvents("error", `Ralph: ${errorMessage}`, {
|
|
219
|
+
...notifyOptions,
|
|
220
|
+
errorMessage,
|
|
221
|
+
});
|
|
214
222
|
}
|
|
215
223
|
else if (output.includes("<promise>COMPLETE</promise>")) {
|
|
216
224
|
await sendNotificationWithDaemonEvents("prd_complete", undefined, notifyOptions);
|
package/dist/commands/prd.js
CHANGED
|
@@ -234,7 +234,10 @@ export function parseListArgs(args) {
|
|
|
234
234
|
else if (args[i] === "--passes" || args[i] === "--passed") {
|
|
235
235
|
passesFilter = true;
|
|
236
236
|
}
|
|
237
|
-
else if (args[i] === "--no-passes" ||
|
|
237
|
+
else if (args[i] === "--no-passes" ||
|
|
238
|
+
args[i] === "--no-passed" ||
|
|
239
|
+
args[i] === "--not-passed" ||
|
|
240
|
+
args[i] === "--not-passes") {
|
|
238
241
|
passesFilter = false;
|
|
239
242
|
}
|
|
240
243
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, unlinkSync, appendFileSync, mkdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer } from "../utils/config.js";
|
|
4
|
+
import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer, } from "../utils/config.js";
|
|
5
5
|
import { resolvePromptVariables, getCliProviders } from "../templates/prompts.js";
|
|
6
|
-
import { validatePrd, smartMerge, readPrdFile, writePrd, expandPrdFileReferences } from "../utils/prd-validator.js";
|
|
6
|
+
import { validatePrd, smartMerge, readPrdFile, writePrd, expandPrdFileReferences, } from "../utils/prd-validator.js";
|
|
7
7
|
import { getStreamJsonParser } from "../utils/stream-json.js";
|
|
8
8
|
import { sendNotificationWithDaemonEvents } from "../utils/notification.js";
|
|
9
9
|
const CATEGORIES = ["ui", "feature", "bugfix", "setup", "development", "testing", "docs"];
|
|
@@ -32,10 +32,10 @@ function createFilteredPrd(prdPath, baseDir, category) {
|
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
const items = parsed;
|
|
35
|
-
let filteredItems = items.filter(item => item.passes === false);
|
|
35
|
+
let filteredItems = items.filter((item) => item.passes === false);
|
|
36
36
|
// Apply category filter if specified
|
|
37
37
|
if (category) {
|
|
38
|
-
filteredItems = filteredItems.filter(item => item.category === category);
|
|
38
|
+
filteredItems = filteredItems.filter((item) => item.category === category);
|
|
39
39
|
}
|
|
40
40
|
// Expand @{filepath} references in description and steps
|
|
41
41
|
const expandedItems = expandPrdFileReferences(filteredItems, baseDir);
|
|
@@ -44,7 +44,7 @@ function createFilteredPrd(prdPath, baseDir, category) {
|
|
|
44
44
|
writeFileSync(tempPath, JSON.stringify(expandedItems, null, 2));
|
|
45
45
|
return {
|
|
46
46
|
tempPath,
|
|
47
|
-
hasIncomplete: filteredItems.length > 0
|
|
47
|
+
hasIncomplete: filteredItems.length > 0,
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
@@ -88,7 +88,7 @@ function syncPassesFromTasks(tasksPath, prdPath) {
|
|
|
88
88
|
for (const task of tasks) {
|
|
89
89
|
if (task.passes === true) {
|
|
90
90
|
// Find matching item in prd by description
|
|
91
|
-
const match = prd.find(item => item.description === task.description ||
|
|
91
|
+
const match = prd.find((item) => item.description === task.description ||
|
|
92
92
|
item.description.includes(task.description) ||
|
|
93
93
|
task.description.includes(item.description));
|
|
94
94
|
if (match && !match.passes) {
|
|
@@ -116,9 +116,7 @@ async function runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig
|
|
|
116
116
|
let jsonLogPath;
|
|
117
117
|
let lineBuffer = ""; // Buffer for incomplete JSON lines
|
|
118
118
|
// Build CLI arguments: config args + yolo args + model args + prompt args
|
|
119
|
-
const cliArgs = [
|
|
120
|
-
...(cliConfig.args ?? []),
|
|
121
|
-
];
|
|
119
|
+
const cliArgs = [...(cliConfig.args ?? [])];
|
|
122
120
|
// Only add yolo args when running in a container
|
|
123
121
|
// Use yoloArgs from config if available, otherwise default to Claude's --dangerously-skip-permissions
|
|
124
122
|
if (sandboxed) {
|
|
@@ -163,7 +161,7 @@ async function runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig
|
|
|
163
161
|
}
|
|
164
162
|
cliArgs.push(...promptArgs, promptValue);
|
|
165
163
|
if (debug) {
|
|
166
|
-
console.log(`[debug] ${cliConfig.command} ${cliArgs.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}\n`);
|
|
164
|
+
console.log(`[debug] ${cliConfig.command} ${cliArgs.map((a) => (a.includes(" ") ? `"${a}"` : a)).join(" ")}\n`);
|
|
167
165
|
if (jsonLogPath) {
|
|
168
166
|
console.log(`[debug] Saving raw JSON to: ${jsonLogPath}\n`);
|
|
169
167
|
}
|
|
@@ -254,7 +252,7 @@ async function runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig
|
|
|
254
252
|
* Sleep for the specified number of milliseconds.
|
|
255
253
|
*/
|
|
256
254
|
function sleep(ms) {
|
|
257
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
255
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
258
256
|
}
|
|
259
257
|
/**
|
|
260
258
|
* Formats elapsed time in a human-readable format.
|
|
@@ -299,14 +297,14 @@ function countPrdItems(prdPath, category) {
|
|
|
299
297
|
const items = parsed;
|
|
300
298
|
let filteredItems = items;
|
|
301
299
|
if (category) {
|
|
302
|
-
filteredItems = items.filter(item => item.category === category);
|
|
300
|
+
filteredItems = items.filter((item) => item.category === category);
|
|
303
301
|
}
|
|
304
|
-
const complete = filteredItems.filter(item => item.passes === true).length;
|
|
305
|
-
const incomplete = filteredItems.filter(item => item.passes === false).length;
|
|
302
|
+
const complete = filteredItems.filter((item) => item.passes === true).length;
|
|
303
|
+
const incomplete = filteredItems.filter((item) => item.passes === false).length;
|
|
306
304
|
return {
|
|
307
305
|
total: filteredItems.length,
|
|
308
306
|
complete,
|
|
309
|
-
incomplete
|
|
307
|
+
incomplete,
|
|
310
308
|
};
|
|
311
309
|
}
|
|
312
310
|
/**
|
|
@@ -340,7 +338,7 @@ function validateAndRecoverPrd(prdPath, validPrd) {
|
|
|
340
338
|
console.log("\x1b[32mRecovered: restored valid PRD structure.\x1b[0m");
|
|
341
339
|
}
|
|
342
340
|
if (mergeResult.warnings.length > 0) {
|
|
343
|
-
mergeResult.warnings.forEach(w => console.log(` \x1b[33m${w}\x1b[0m`));
|
|
341
|
+
mergeResult.warnings.forEach((w) => console.log(` \x1b[33m${w}\x1b[0m`));
|
|
344
342
|
}
|
|
345
343
|
return { recovered: true, itemsUpdated: mergeResult.itemsUpdated };
|
|
346
344
|
}
|
|
@@ -434,13 +432,15 @@ export async function run(args) {
|
|
|
434
432
|
// Only use provider's streamJsonArgs if defined, otherwise empty array (no special args)
|
|
435
433
|
// This allows providers without JSON streaming to still have output displayed
|
|
436
434
|
const streamJsonArgs = providerConfig?.streamJsonArgs ?? [];
|
|
437
|
-
const streamJson = streamJsonConfig?.enabled
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
435
|
+
const streamJson = streamJsonConfig?.enabled
|
|
436
|
+
? {
|
|
437
|
+
enabled: true,
|
|
438
|
+
saveRawJson: streamJsonConfig.saveRawJson !== false, // default true
|
|
439
|
+
outputDir: config.docker?.asciinema?.outputDir || ".recordings",
|
|
440
|
+
args: streamJsonArgs,
|
|
441
|
+
parser: getStreamJsonParser(config.cliProvider, debug),
|
|
442
|
+
}
|
|
443
|
+
: undefined;
|
|
444
444
|
// Progress tracking: stop only if no tasks complete after N iterations
|
|
445
445
|
const MAX_ITERATIONS_WITHOUT_PROGRESS = 3;
|
|
446
446
|
// Get requested iteration count (may be adjusted dynamically)
|
|
@@ -615,6 +615,7 @@ export async function run(args) {
|
|
|
615
615
|
command: config.notifyCommand,
|
|
616
616
|
debug,
|
|
617
617
|
daemonConfig: config.daemon,
|
|
618
|
+
chatConfig: config.chat,
|
|
618
619
|
});
|
|
619
620
|
break;
|
|
620
621
|
}
|
|
@@ -629,6 +630,7 @@ export async function run(args) {
|
|
|
629
630
|
command: config.notifyCommand,
|
|
630
631
|
debug,
|
|
631
632
|
daemonConfig: config.daemon,
|
|
633
|
+
chatConfig: config.chat,
|
|
632
634
|
taskName,
|
|
633
635
|
});
|
|
634
636
|
}
|
|
@@ -664,7 +666,13 @@ export async function run(args) {
|
|
|
664
666
|
console.log("Check the PRD and task definitions for issues.");
|
|
665
667
|
// Send notification about stopped run
|
|
666
668
|
const stoppedMessage = `No progress after ${MAX_ITERATIONS_WITHOUT_PROGRESS} iterations. ${progressCounts.incomplete} tasks remaining.`;
|
|
667
|
-
await sendNotificationWithDaemonEvents("run_stopped", `Ralph: Run stopped - ${stoppedMessage}`, {
|
|
669
|
+
await sendNotificationWithDaemonEvents("run_stopped", `Ralph: Run stopped - ${stoppedMessage}`, {
|
|
670
|
+
command: config.notifyCommand,
|
|
671
|
+
debug,
|
|
672
|
+
daemonConfig: config.daemon,
|
|
673
|
+
chatConfig: config.chat,
|
|
674
|
+
errorMessage: stoppedMessage,
|
|
675
|
+
});
|
|
668
676
|
break;
|
|
669
677
|
}
|
|
670
678
|
}
|
|
@@ -684,7 +692,13 @@ export async function run(args) {
|
|
|
684
692
|
console.error("Please check your CLI configuration and try again.");
|
|
685
693
|
// Send notification about error
|
|
686
694
|
const errorMessage = `CLI failed ${consecutiveFailures} times with exit code ${exitCode}. Check configuration.`;
|
|
687
|
-
await sendNotificationWithDaemonEvents("error", `Ralph: ${errorMessage}`, {
|
|
695
|
+
await sendNotificationWithDaemonEvents("error", `Ralph: ${errorMessage}`, {
|
|
696
|
+
command: config.notifyCommand,
|
|
697
|
+
debug,
|
|
698
|
+
daemonConfig: config.daemon,
|
|
699
|
+
chatConfig: config.chat,
|
|
700
|
+
errorMessage,
|
|
701
|
+
});
|
|
688
702
|
break;
|
|
689
703
|
}
|
|
690
704
|
console.log("Continuing to next iteration...");
|
|
@@ -729,6 +743,7 @@ export async function run(args) {
|
|
|
729
743
|
command: config.notifyCommand,
|
|
730
744
|
debug,
|
|
731
745
|
daemonConfig: config.daemon,
|
|
746
|
+
chatConfig: config.chat,
|
|
732
747
|
});
|
|
733
748
|
break;
|
|
734
749
|
}
|
package/dist/commands/slack.js
CHANGED
|
@@ -106,12 +106,12 @@ async function createSlackApp(configToken, appName) {
|
|
|
106
106
|
const response = await fetch("https://slack.com/api/apps.manifest.create", {
|
|
107
107
|
method: "POST",
|
|
108
108
|
headers: {
|
|
109
|
-
|
|
109
|
+
Authorization: `Bearer ${configToken}`,
|
|
110
110
|
"Content-Type": "application/json",
|
|
111
111
|
},
|
|
112
112
|
body: JSON.stringify({ manifest: JSON.stringify(manifest) }),
|
|
113
113
|
});
|
|
114
|
-
const data = await response.json();
|
|
114
|
+
const data = (await response.json());
|
|
115
115
|
if (!data.ok) {
|
|
116
116
|
console.error(`\nError creating Slack app: ${data.error}`);
|
|
117
117
|
if (data.errors) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"presets": {
|
|
3
|
+
"qa": {
|
|
4
|
+
"name": "Q&A Assistant",
|
|
5
|
+
"description": "LLM responder optimized for answering questions about the codebase",
|
|
6
|
+
"type": "llm",
|
|
7
|
+
"trigger": "@qa",
|
|
8
|
+
"provider": "anthropic",
|
|
9
|
+
"systemPrompt": "You are a knowledgeable Q&A assistant for the {{project}} project. Your role is to:\n\n1. Answer questions accurately and concisely about the codebase, its architecture, and functionality\n2. Explain how specific features, components, or modules work\n3. Help developers understand code patterns, conventions, and best practices used in the project\n4. Clarify dependencies, APIs, and integration points\n5. Provide context about why certain design decisions were made\n\nKeep responses focused and practical. If you're unsure about something, say so rather than guessing. Reference specific files, functions, or code sections when helpful.",
|
|
10
|
+
"timeout": 60000,
|
|
11
|
+
"maxLength": 2000
|
|
12
|
+
},
|
|
13
|
+
"reviewer": {
|
|
14
|
+
"name": "Code Reviewer",
|
|
15
|
+
"description": "LLM responder for code review feedback and suggestions",
|
|
16
|
+
"type": "llm",
|
|
17
|
+
"trigger": "@review",
|
|
18
|
+
"provider": "anthropic",
|
|
19
|
+
"systemPrompt": "You are an expert code reviewer for the {{project}} project. Your role is to:\n\n1. Review code changes, diffs, or code snippets shared with you\n2. Identify potential bugs, security issues, and edge cases\n3. Suggest improvements for readability, maintainability, and performance\n4. Check for adherence to project conventions and best practices\n5. Highlight what's done well alongside areas for improvement\n\nProvide constructive, actionable feedback. Prioritize critical issues over minor style preferences. When suggesting changes, explain the reasoning. Be specific about file locations and line numbers when possible.",
|
|
20
|
+
"timeout": 90000,
|
|
21
|
+
"maxLength": 2500
|
|
22
|
+
},
|
|
23
|
+
"architect": {
|
|
24
|
+
"name": "Architecture Advisor",
|
|
25
|
+
"description": "LLM responder for architecture and design discussions",
|
|
26
|
+
"type": "llm",
|
|
27
|
+
"trigger": "@arch",
|
|
28
|
+
"provider": "anthropic",
|
|
29
|
+
"systemPrompt": "You are a software architecture advisor for the {{project}} project. Your role is to:\n\n1. Discuss high-level system design and architectural decisions\n2. Evaluate trade-offs between different approaches (scalability, maintainability, performance)\n3. Recommend design patterns and architectural styles appropriate for the use case\n4. Help plan major refactors or new feature implementations\n5. Identify potential technical debt and suggest mitigation strategies\n\nConsider the existing codebase constraints when making recommendations. Provide clear reasoning for architectural choices. Focus on practical, incremental improvements rather than complete rewrites. Draw diagrams in ASCII when helpful.",
|
|
30
|
+
"timeout": 120000,
|
|
31
|
+
"maxLength": 3000
|
|
32
|
+
},
|
|
33
|
+
"explain": {
|
|
34
|
+
"name": "Code Explainer",
|
|
35
|
+
"description": "LLM responder focused on explaining code in detail",
|
|
36
|
+
"type": "llm",
|
|
37
|
+
"trigger": "@explain",
|
|
38
|
+
"provider": "anthropic",
|
|
39
|
+
"systemPrompt": "You are a code explanation specialist for the {{project}} project. Your role is to:\n\n1. Break down complex code into understandable parts\n2. Explain the purpose and behavior of functions, classes, and modules\n3. Trace data flow and control flow through the codebase\n4. Describe how different components interact with each other\n5. Provide context about the 'why' behind implementation choices\n\nTailor explanations to the apparent experience level of the questioner. Use analogies and examples when helpful. Walk through code step-by-step when explaining algorithms or complex logic. Be thorough but avoid unnecessary jargon.",
|
|
40
|
+
"timeout": 60000,
|
|
41
|
+
"maxLength": 2500
|
|
42
|
+
},
|
|
43
|
+
"code": {
|
|
44
|
+
"name": "Code Editor",
|
|
45
|
+
"description": "Claude Code responder for making file modifications",
|
|
46
|
+
"type": "claude-code",
|
|
47
|
+
"trigger": "@code",
|
|
48
|
+
"timeout": 300000,
|
|
49
|
+
"maxLength": 2000
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"bundles": {
|
|
53
|
+
"standard": {
|
|
54
|
+
"name": "Standard Bundle",
|
|
55
|
+
"description": "Common responders for development workflow (qa, reviewer, code)",
|
|
56
|
+
"presets": ["qa", "reviewer", "code"]
|
|
57
|
+
},
|
|
58
|
+
"full": {
|
|
59
|
+
"name": "Full Bundle",
|
|
60
|
+
"description": "All available responders",
|
|
61
|
+
"presets": ["qa", "reviewer", "architect", "explain", "code"]
|
|
62
|
+
},
|
|
63
|
+
"minimal": {
|
|
64
|
+
"name": "Minimal Bundle",
|
|
65
|
+
"description": "Just the essentials (qa, code)",
|
|
66
|
+
"presets": ["qa", "code"]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { help } from "./commands/help.js";
|
|
|
6
6
|
import { init } from "./commands/init.js";
|
|
7
7
|
import { once } from "./commands/once.js";
|
|
8
8
|
import { run } from "./commands/run.js";
|
|
9
|
-
import { prd, prdAdd, prdList, prdStatus, prdToggle, prdClean, parseListArgs } from "./commands/prd.js";
|
|
9
|
+
import { prd, prdAdd, prdList, prdStatus, prdToggle, prdClean, parseListArgs, } from "./commands/prd.js";
|
|
10
10
|
import { docker } from "./commands/docker.js";
|
|
11
11
|
import { prompt } from "./commands/prompt.js";
|
|
12
12
|
import { fixPrd } from "./commands/fix-prd.js";
|
|
@@ -14,7 +14,35 @@ export declare class DiscordChatClient implements ChatClient {
|
|
|
14
14
|
private client;
|
|
15
15
|
private onCommand;
|
|
16
16
|
private onMessage;
|
|
17
|
+
private responderMatcher;
|
|
18
|
+
private respondersConfig;
|
|
19
|
+
private botUserId;
|
|
17
20
|
constructor(settings: DiscordSettings, debug?: boolean);
|
|
21
|
+
/**
|
|
22
|
+
* Initialize responder matching from config.
|
|
23
|
+
*/
|
|
24
|
+
private initializeResponders;
|
|
25
|
+
/**
|
|
26
|
+
* Execute a responder and return the result.
|
|
27
|
+
*/
|
|
28
|
+
private executeResponder;
|
|
29
|
+
/**
|
|
30
|
+
* Handle a message that might match a responder.
|
|
31
|
+
* Returns true if a responder was matched and executed.
|
|
32
|
+
*/
|
|
33
|
+
private handleResponderMessage;
|
|
34
|
+
/**
|
|
35
|
+
* Check if the bot is mentioned in a message.
|
|
36
|
+
*/
|
|
37
|
+
private isBotMentioned;
|
|
38
|
+
/**
|
|
39
|
+
* Remove bot mention from message text.
|
|
40
|
+
*/
|
|
41
|
+
private removeBotMention;
|
|
42
|
+
/**
|
|
43
|
+
* Check if this is a DM (direct message) channel.
|
|
44
|
+
*/
|
|
45
|
+
private isDMChannel;
|
|
18
46
|
/**
|
|
19
47
|
* Check if a guild ID is allowed.
|
|
20
48
|
*/
|