@kynetic-ai/spec 0.8.0 → 0.9.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.
- package/dist/cli/commands/ralph.d.ts +48 -0
- package/dist/cli/commands/ralph.d.ts.map +1 -1
- package/dist/cli/commands/ralph.js +268 -47
- package/dist/cli/commands/ralph.js.map +1 -1
- package/dist/cli/commands/session/commands.d.ts.map +1 -1
- package/dist/cli/commands/session/commands.js +8 -0
- package/dist/cli/commands/session/commands.js.map +1 -1
- package/dist/cli/commands/session/compact.d.ts +13 -0
- package/dist/cli/commands/session/compact.d.ts.map +1 -0
- package/dist/cli/commands/session/compact.js +207 -0
- package/dist/cli/commands/session/compact.js.map +1 -0
- package/dist/cli/commands/session/log.d.ts +2 -0
- package/dist/cli/commands/session/log.d.ts.map +1 -1
- package/dist/cli/commands/session/log.js +12 -2
- package/dist/cli/commands/session/log.js.map +1 -1
- package/dist/parser/skill-render.d.ts +14 -0
- package/dist/parser/skill-render.d.ts.map +1 -1
- package/dist/parser/skill-render.js +14 -4
- package/dist/parser/skill-render.js.map +1 -1
- package/dist/sessions/store.d.ts +57 -0
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +337 -9
- package/dist/sessions/store.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/marketplace.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/plugins/kspec/skills/create-workflow/SKILL.md +10 -0
- package/plugin/plugins/kspec/skills/plan/SKILL.md +10 -0
- package/plugin/plugins/kspec/skills/review/SKILL.md +2 -0
- package/plugin/plugins/kspec/skills/task-work/SKILL.md +10 -0
- package/plugin/plugins/kspec/skills/triage-automation/SKILL.md +1 -0
- package/templates/skills/create-workflow/SKILL.md +10 -0
- package/templates/skills/plan/SKILL.md +10 -0
- package/templates/skills/review/SKILL.md +2 -0
- package/templates/skills/task-work/SKILL.md +10 -0
- package/templates/skills/triage-automation/SKILL.md +1 -0
|
@@ -5,5 +5,53 @@
|
|
|
5
5
|
* Uses session event storage for full audit trail and streaming output.
|
|
6
6
|
*/
|
|
7
7
|
import type { Command } from "commander";
|
|
8
|
+
import { type LoadedSkill } from "../../parser/index.js";
|
|
9
|
+
type RalphPromptPlatform = "claude-code" | "codex" | "unknown";
|
|
10
|
+
type SkillOrigin = LoadedSkill["origin"];
|
|
11
|
+
/**
|
|
12
|
+
* Map adapter IDs to prompt rendering platforms.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPromptPlatformForAdapter(adapterId: string): RalphPromptPlatform;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve configured skill invocation string for a specific platform.
|
|
17
|
+
* Supports portable {skill:<id>} syntax and legacy literal strings.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveRalphSkillInvocation(invocation: string, platform: RalphPromptPlatform, skillOrigins: Map<string, SkillOrigin>): string;
|
|
20
|
+
type AdapterValidationRunner = (command: string, args: string[], options: {
|
|
21
|
+
encoding: "utf-8";
|
|
22
|
+
stdio: "pipe";
|
|
23
|
+
}) => {
|
|
24
|
+
status: number | null;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Check whether an adapter package appears to be installed and executable.
|
|
28
|
+
* Uses multiple non-installing probes because CLIs differ on supported flags.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isAdapterPackageAvailable(adapterPackage: string, runner?: AdapterValidationRunner): boolean;
|
|
31
|
+
interface TerminalRunResult {
|
|
32
|
+
stdout: string;
|
|
33
|
+
stderr: string;
|
|
34
|
+
exitCode: number;
|
|
35
|
+
stdout_path?: string;
|
|
36
|
+
stderr_path?: string;
|
|
37
|
+
stdout_bytes: number;
|
|
38
|
+
stderr_bytes: number;
|
|
39
|
+
preview_truncated: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface TerminalRunOptions {
|
|
42
|
+
command: string;
|
|
43
|
+
cwd: string;
|
|
44
|
+
timeout: number;
|
|
45
|
+
toolCallId: string | number;
|
|
46
|
+
specDir?: string;
|
|
47
|
+
sessionId?: string;
|
|
48
|
+
previewMaxBytes?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Execute terminal/run request with bounded in-memory preview and streamed
|
|
52
|
+
* session artifacts for full stdout/stderr retention.
|
|
53
|
+
*/
|
|
54
|
+
export declare function runTerminalCommandWithArtifacts(options: TerminalRunOptions): Promise<TerminalRunResult>;
|
|
8
55
|
export declare function registerRalphCommand(program: Command): void;
|
|
56
|
+
export {};
|
|
9
57
|
//# sourceMappingURL=ralph.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ralph.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/ralph.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"ralph.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/ralph.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBzC,OAAO,EAKL,KAAK,WAAW,EAIjB,MAAM,uBAAuB,CAAC;AAiJ/B,KAAK,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/D,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AAOzC;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAUlF;AA8DD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,mBAAmB,EAC7B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GACrC,MAAM,CAeR;AAiHD,KAAK,uBAAuB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,KAC1C;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE/B;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,uBAAmC,GAC1C,OAAO,CAiBT;AAuBD,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAgED;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CAqG5B;AAqeD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA64B3D"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Uses session event storage for full audit trail and streaming output.
|
|
6
6
|
*/
|
|
7
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
+
import { createWriteStream } from "node:fs";
|
|
8
9
|
import * as fs from "node:fs/promises";
|
|
9
10
|
import { createRequire } from "node:module";
|
|
10
11
|
import * as path from "node:path";
|
|
@@ -15,9 +16,10 @@ const require = createRequire(import.meta.url);
|
|
|
15
16
|
const { version: packageVersion } = require("../../../package.json");
|
|
16
17
|
import { registerAdapter, resolveAdapter, } from "../../agents/index.js";
|
|
17
18
|
import { spawnAndInitialize } from "../../agents/spawner.js";
|
|
18
|
-
import { initContext, loadAllItems, loadAllTasks, ReferenceIndex, } from "../../parser/index.js";
|
|
19
|
+
import { initContext, loadAllItems, loadMetaContext, loadAllTasks, ReferenceIndex, } from "../../parser/index.js";
|
|
20
|
+
import { resolveSkillReferenceTokensForPlatform } from "../../parser/skill-render.js";
|
|
19
21
|
import { buildWrapUpContext, createCliRenderer, createTranslator, DEFAULT_SUBAGENT_PREFIX, DEFAULT_WRAPUP_TIMEOUT, RALPH_PROMPT_TIMEOUT, runSubagent, runWrapUpAgent, WRAPUP_AGENT_PREFIX, } from "../../ralph/index.js";
|
|
20
|
-
import { appendEvent, closeSession, createSessionWithBudget, getSessionBudgetPath, injectEnvForAdapter, isEndLoopRequested, removeEnvForAdapter, requestEndLoop, resetBudget, saveSessionContext, } from "../../sessions/index.js";
|
|
22
|
+
import { appendEvent, closeSession, createSessionWithBudget, getSessionBudgetPath, getSessionDir, injectEnvForAdapter, isEndLoopRequested, removeEnvForAdapter, requestEndLoop, resetBudget, saveSessionContext, } from "../../sessions/index.js";
|
|
21
23
|
import { errors } from "../../strings/index.js";
|
|
22
24
|
import { getCurrentBranch } from "../../utils/git.js";
|
|
23
25
|
import { EXIT_CODES } from "../exit-codes.js";
|
|
@@ -91,7 +93,79 @@ async function allExplicitTasksDone(ctx, scope) {
|
|
|
91
93
|
});
|
|
92
94
|
return { done, statuses };
|
|
93
95
|
}
|
|
94
|
-
|
|
96
|
+
const FALLBACK_CORE_SKILLS = new Set(["task-work", "reflect", "review"]);
|
|
97
|
+
const ADAPTER_VALIDATION_PROBES = [["--help"], ["--version"]];
|
|
98
|
+
const TERMINAL_PREVIEW_MAX_BYTES = 64 * 1024;
|
|
99
|
+
const TOOL_OUTPUT_DIR = "tool-output";
|
|
100
|
+
/**
|
|
101
|
+
* Map adapter IDs to prompt rendering platforms.
|
|
102
|
+
*/
|
|
103
|
+
export function getPromptPlatformForAdapter(adapterId) {
|
|
104
|
+
switch (adapterId) {
|
|
105
|
+
case "claude-agent-acp":
|
|
106
|
+
case "claude-code-acp":
|
|
107
|
+
return "claude-code";
|
|
108
|
+
case "codex-acp":
|
|
109
|
+
return "codex";
|
|
110
|
+
default:
|
|
111
|
+
return "unknown";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build skill origin map from meta skills.
|
|
116
|
+
*/
|
|
117
|
+
async function loadSkillOriginsForRalph(ctx) {
|
|
118
|
+
const meta = await loadMetaContext(ctx);
|
|
119
|
+
const origins = new Map();
|
|
120
|
+
for (const skill of meta.skills) {
|
|
121
|
+
origins.set(skill.id, skill.origin);
|
|
122
|
+
}
|
|
123
|
+
// Fallback for core skills frequently used by ralph, even if core skills
|
|
124
|
+
// were not loaded into project meta for any reason.
|
|
125
|
+
for (const coreSkill of FALLBACK_CORE_SKILLS) {
|
|
126
|
+
if (!origins.has(coreSkill)) {
|
|
127
|
+
origins.set(coreSkill, "core");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return origins;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Normalize legacy literal invocation syntax for a target platform.
|
|
134
|
+
* Keeps backward compatibility for existing slash-style config values.
|
|
135
|
+
*/
|
|
136
|
+
function normalizeLegacyInvocation(invocation, platform) {
|
|
137
|
+
if (platform === "codex") {
|
|
138
|
+
if (/^\/kspec:([a-z0-9][a-z0-9-]*)$/.test(invocation)) {
|
|
139
|
+
return invocation.replace(/^\/kspec:([a-z0-9][a-z0-9-]*)$/, (_m, skillId) => `$kspec-${skillId}`);
|
|
140
|
+
}
|
|
141
|
+
if (/^\/([a-z0-9][a-z0-9-]*)$/.test(invocation)) {
|
|
142
|
+
return invocation.replace(/^\/([a-z0-9][a-z0-9-]*)$/, (_m, skillId) => `$${skillId}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (platform === "claude-code") {
|
|
146
|
+
if (/^\$kspec-([a-z0-9][a-z0-9-]*)$/.test(invocation)) {
|
|
147
|
+
return invocation.replace(/^\$kspec-([a-z0-9][a-z0-9-]*)$/, (_m, skillId) => `/kspec:${skillId}`);
|
|
148
|
+
}
|
|
149
|
+
if (/^\$([a-z0-9][a-z0-9-]*)$/.test(invocation)) {
|
|
150
|
+
return invocation.replace(/^\$([a-z0-9][a-z0-9-]*)$/, (_m, skillId) => `/${skillId}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return invocation;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Resolve configured skill invocation string for a specific platform.
|
|
157
|
+
* Supports portable {skill:<id>} syntax and legacy literal strings.
|
|
158
|
+
*/
|
|
159
|
+
export function resolveRalphSkillInvocation(invocation, platform, skillOrigins) {
|
|
160
|
+
if (platform === "unknown") {
|
|
161
|
+
return invocation;
|
|
162
|
+
}
|
|
163
|
+
const tokenResolved = resolveSkillReferenceTokensForPlatform(invocation, platform, skillOrigins);
|
|
164
|
+
if (tokenResolved !== invocation) {
|
|
165
|
+
return tokenResolved;
|
|
166
|
+
}
|
|
167
|
+
return normalizeLegacyInvocation(invocation, platform);
|
|
168
|
+
}
|
|
95
169
|
// AC: @ralph-skill-delegation ac-1, ac-2, ac-3
|
|
96
170
|
function buildTaskWorkPrompt(sessionCtx, iteration, maxLoops, sessionId, skillTaskWork, focus, explicitTaskScope) {
|
|
97
171
|
const focusSection = focus
|
|
@@ -174,46 +248,182 @@ ${isFinal
|
|
|
174
248
|
Exit when reflection is complete.
|
|
175
249
|
`;
|
|
176
250
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Check whether an adapter package appears to be installed and executable.
|
|
253
|
+
* Uses multiple non-installing probes because CLIs differ on supported flags.
|
|
254
|
+
*/
|
|
255
|
+
export function isAdapterPackageAvailable(adapterPackage, runner = spawnSync) {
|
|
256
|
+
for (const probeArgs of ADAPTER_VALIDATION_PROBES) {
|
|
257
|
+
const result = runner("npx", ["--no-install", adapterPackage, ...probeArgs], {
|
|
258
|
+
encoding: "utf-8",
|
|
259
|
+
stdio: "pipe",
|
|
260
|
+
});
|
|
261
|
+
if (result.status === 0) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
182
267
|
/**
|
|
183
268
|
* Validate that the specified ACP adapter package exists.
|
|
184
|
-
* Uses npx --no-install to check both global and local node_modules.
|
|
269
|
+
* Uses npx --no-install probes to check both global and local node_modules.
|
|
185
270
|
*
|
|
186
271
|
* @throws {Error} Never throws - exits process with code 3 if validation fails
|
|
187
272
|
*/
|
|
188
273
|
function validateAdapter(adapterPackage) {
|
|
189
|
-
|
|
190
|
-
// This checks both global and local node_modules, handles scoped packages
|
|
191
|
-
const result = spawnSync("npx", ["--no-install", adapterPackage, "--version"], {
|
|
192
|
-
encoding: "utf-8",
|
|
193
|
-
stdio: "pipe",
|
|
194
|
-
});
|
|
195
|
-
if (result.status !== 0) {
|
|
274
|
+
if (!isAdapterPackageAvailable(adapterPackage)) {
|
|
196
275
|
error(`Adapter package not found: ${adapterPackage}. Install with: npm install -g ${adapterPackage}`);
|
|
197
276
|
process.exit(EXIT_CODES.NOT_FOUND);
|
|
198
277
|
}
|
|
199
278
|
}
|
|
279
|
+
function sanitizeToolCallId(toolCallId) {
|
|
280
|
+
const raw = String(toolCallId).trim();
|
|
281
|
+
if (!raw) {
|
|
282
|
+
return "tool-call";
|
|
283
|
+
}
|
|
284
|
+
return raw.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
285
|
+
}
|
|
286
|
+
function updateStreamPreview(state, chunk, maxPreviewBytes) {
|
|
287
|
+
state.bytes += chunk.length;
|
|
288
|
+
const remaining = maxPreviewBytes - state.previewBytes;
|
|
289
|
+
if (remaining <= 0) {
|
|
290
|
+
state.truncated = true;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (chunk.length > remaining) {
|
|
294
|
+
state.previewParts.push(chunk.subarray(0, remaining).toString("utf-8"));
|
|
295
|
+
state.previewBytes += remaining;
|
|
296
|
+
state.truncated = true;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
state.previewParts.push(chunk.toString("utf-8"));
|
|
300
|
+
state.previewBytes += chunk.length;
|
|
301
|
+
}
|
|
302
|
+
function closeStream(stream) {
|
|
303
|
+
if (!stream) {
|
|
304
|
+
return Promise.resolve();
|
|
305
|
+
}
|
|
306
|
+
return new Promise((resolve, reject) => {
|
|
307
|
+
const onError = (err) => {
|
|
308
|
+
stream.off("finish", onFinish);
|
|
309
|
+
reject(err);
|
|
310
|
+
};
|
|
311
|
+
const onFinish = () => {
|
|
312
|
+
stream.off("error", onError);
|
|
313
|
+
resolve();
|
|
314
|
+
};
|
|
315
|
+
stream.once("error", onError);
|
|
316
|
+
stream.once("finish", onFinish);
|
|
317
|
+
stream.end();
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Execute terminal/run request with bounded in-memory preview and streamed
|
|
322
|
+
* session artifacts for full stdout/stderr retention.
|
|
323
|
+
*/
|
|
324
|
+
export async function runTerminalCommandWithArtifacts(options) {
|
|
325
|
+
const previewMaxBytes = options.previewMaxBytes ?? TERMINAL_PREVIEW_MAX_BYTES;
|
|
326
|
+
const shouldWriteArtifacts = Boolean(options.specDir && options.sessionId);
|
|
327
|
+
let stdoutPath;
|
|
328
|
+
let stderrPath;
|
|
329
|
+
if (shouldWriteArtifacts) {
|
|
330
|
+
const outputDir = path.join(getSessionDir(options.specDir, options.sessionId), TOOL_OUTPUT_DIR);
|
|
331
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
332
|
+
const safeToolCallId = sanitizeToolCallId(options.toolCallId);
|
|
333
|
+
stdoutPath = path.join(outputDir, `${safeToolCallId}.stdout.log`);
|
|
334
|
+
stderrPath = path.join(outputDir, `${safeToolCallId}.stderr.log`);
|
|
335
|
+
}
|
|
336
|
+
const stdoutState = {
|
|
337
|
+
bytes: 0,
|
|
338
|
+
previewBytes: 0,
|
|
339
|
+
previewParts: [],
|
|
340
|
+
truncated: false,
|
|
341
|
+
stream: stdoutPath ? createWriteStream(stdoutPath) : undefined,
|
|
342
|
+
};
|
|
343
|
+
const stderrState = {
|
|
344
|
+
bytes: 0,
|
|
345
|
+
previewBytes: 0,
|
|
346
|
+
previewParts: [],
|
|
347
|
+
truncated: false,
|
|
348
|
+
stream: stderrPath ? createWriteStream(stderrPath) : undefined,
|
|
349
|
+
};
|
|
350
|
+
return await new Promise((resolve, reject) => {
|
|
351
|
+
let settled = false;
|
|
352
|
+
const child = spawn(options.command, [], {
|
|
353
|
+
cwd: options.cwd,
|
|
354
|
+
shell: true,
|
|
355
|
+
timeout: options.timeout,
|
|
356
|
+
});
|
|
357
|
+
const finalize = async (exitCode, errorMessage) => {
|
|
358
|
+
if (settled) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
settled = true;
|
|
362
|
+
if (errorMessage) {
|
|
363
|
+
const errChunk = Buffer.from(errorMessage, "utf-8");
|
|
364
|
+
stderrState.stream?.write(errChunk);
|
|
365
|
+
updateStreamPreview(stderrState, errChunk, previewMaxBytes);
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
await Promise.all([
|
|
369
|
+
closeStream(stdoutState.stream),
|
|
370
|
+
closeStream(stderrState.stream),
|
|
371
|
+
]);
|
|
372
|
+
}
|
|
373
|
+
catch (streamErr) {
|
|
374
|
+
reject(streamErr);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
resolve({
|
|
378
|
+
stdout: stdoutState.previewParts.join(""),
|
|
379
|
+
stderr: stderrState.previewParts.join(""),
|
|
380
|
+
exitCode,
|
|
381
|
+
stdout_path: stdoutPath,
|
|
382
|
+
stderr_path: stderrPath,
|
|
383
|
+
stdout_bytes: stdoutState.bytes,
|
|
384
|
+
stderr_bytes: stderrState.bytes,
|
|
385
|
+
preview_truncated: stdoutState.truncated || stderrState.truncated,
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
child.stdout?.on("data", (data) => {
|
|
389
|
+
const chunk = Buffer.isBuffer(data)
|
|
390
|
+
? data
|
|
391
|
+
: Buffer.from(String(data), "utf-8");
|
|
392
|
+
stdoutState.stream?.write(chunk);
|
|
393
|
+
updateStreamPreview(stdoutState, chunk, previewMaxBytes);
|
|
394
|
+
});
|
|
395
|
+
child.stderr?.on("data", (data) => {
|
|
396
|
+
const chunk = Buffer.isBuffer(data)
|
|
397
|
+
? data
|
|
398
|
+
: Buffer.from(String(data), "utf-8");
|
|
399
|
+
stderrState.stream?.write(chunk);
|
|
400
|
+
updateStreamPreview(stderrState, chunk, previewMaxBytes);
|
|
401
|
+
});
|
|
402
|
+
child.on("close", (code) => {
|
|
403
|
+
void finalize(code ?? 1);
|
|
404
|
+
});
|
|
405
|
+
child.on("error", (err) => {
|
|
406
|
+
void finalize(1, err.message);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
}
|
|
200
410
|
// ─── Tool Request Handler ────────────────────────────────────────────────────
|
|
201
411
|
/**
|
|
202
412
|
* Handle tool requests from ACP agent.
|
|
203
413
|
* Implements file operations, terminal commands, and permission handling.
|
|
204
414
|
*/
|
|
205
|
-
async function handleRequest(client, id, method, params,
|
|
415
|
+
async function handleRequest(client, id, method, params, options) {
|
|
206
416
|
try {
|
|
207
417
|
switch (method) {
|
|
208
418
|
case "session/request_permission": {
|
|
209
419
|
const p = params;
|
|
210
420
|
// In yolo mode, auto-approve all permissions
|
|
211
421
|
// In normal mode, would need to implement permission UI
|
|
212
|
-
const
|
|
213
|
-
if (yolo) {
|
|
422
|
+
const permissionOptions = p.options || [];
|
|
423
|
+
if (options.yolo) {
|
|
214
424
|
// Find an "allow" option (prefer allow_always, then allow_once)
|
|
215
|
-
const allowOption =
|
|
216
|
-
|
|
425
|
+
const allowOption = permissionOptions.find((o) => o.kind === "allow_always") ||
|
|
426
|
+
permissionOptions.find((o) => o.kind === "allow_once");
|
|
217
427
|
if (allowOption) {
|
|
218
428
|
client.respondPermission(id, {
|
|
219
429
|
outcome: { outcome: "selected", optionId: allowOption.optionId },
|
|
@@ -250,26 +460,13 @@ async function handleRequest(client, id, method, params, yolo) {
|
|
|
250
460
|
const command = p.command;
|
|
251
461
|
const cwd = p.cwd || process.cwd();
|
|
252
462
|
const timeout = p.timeout || 60000;
|
|
253
|
-
const result = await
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
let stderr = "";
|
|
261
|
-
child.stdout?.on("data", (data) => {
|
|
262
|
-
stdout += data.toString();
|
|
263
|
-
});
|
|
264
|
-
child.stderr?.on("data", (data) => {
|
|
265
|
-
stderr += data.toString();
|
|
266
|
-
});
|
|
267
|
-
child.on("close", (code) => {
|
|
268
|
-
resolve({ stdout, stderr, exitCode: code ?? 1 });
|
|
269
|
-
});
|
|
270
|
-
child.on("error", (err) => {
|
|
271
|
-
resolve({ stdout, stderr: err.message, exitCode: 1 });
|
|
272
|
-
});
|
|
463
|
+
const result = await runTerminalCommandWithArtifacts({
|
|
464
|
+
command,
|
|
465
|
+
cwd,
|
|
466
|
+
timeout,
|
|
467
|
+
toolCallId: id,
|
|
468
|
+
specDir: options.specDir,
|
|
469
|
+
sessionId: options.sessionId,
|
|
273
470
|
});
|
|
274
471
|
// Using generic respond() since this is a custom method
|
|
275
472
|
client.respond(id, result);
|
|
@@ -488,12 +685,16 @@ async function processPendingReviewTasks(ctx, adapter, pendingReviewTasks, optio
|
|
|
488
685
|
const result = await runSubagent(adapter, subagentCtx, {
|
|
489
686
|
timeout: options.subagentTimeout,
|
|
490
687
|
outputPrefix: DEFAULT_SUBAGENT_PREFIX,
|
|
491
|
-
skillName:
|
|
688
|
+
skillName: options.prReviewSkillName,
|
|
492
689
|
}, {
|
|
493
690
|
yolo: options.yolo,
|
|
494
691
|
cwd: options.cwd,
|
|
495
692
|
extraArgs: options.autoApproveArgs,
|
|
496
|
-
handleRequest: (client, reqId, method, params) => handleRequest(client, reqId, method, params,
|
|
693
|
+
handleRequest: (client, reqId, method, params) => handleRequest(client, reqId, method, params, {
|
|
694
|
+
yolo: options.yolo,
|
|
695
|
+
specDir: options.specDir,
|
|
696
|
+
sessionId: options.sessionId,
|
|
697
|
+
}),
|
|
497
698
|
});
|
|
498
699
|
if (result.timedOut) {
|
|
499
700
|
// AC: @ralph-subagent-spawning ac-9
|
|
@@ -724,6 +925,12 @@ export function registerRalphCommand(program) {
|
|
|
724
925
|
process.exit(EXIT_CODES.VALIDATION_FAILED);
|
|
725
926
|
}
|
|
726
927
|
}
|
|
928
|
+
const skillOrigins = await loadSkillOriginsForRalph(ctx);
|
|
929
|
+
const workerPromptPlatform = getPromptPlatformForAdapter(workerAdapterId);
|
|
930
|
+
const reviewerPromptPlatform = getPromptPlatformForAdapter(reviewerAdapterId);
|
|
931
|
+
const workerTaskWorkSkill = resolveRalphSkillInvocation(ctx.config.ralph.skills.task_work, workerPromptPlatform, skillOrigins);
|
|
932
|
+
const workerReflectSkill = resolveRalphSkillInvocation(ctx.config.ralph.skills.reflect, workerPromptPlatform, skillOrigins);
|
|
933
|
+
const reviewerPrReviewSkill = resolveRalphSkillInvocation(ctx.config.ralph.skills.pr_review, reviewerPromptPlatform, skillOrigins);
|
|
727
934
|
const taskScopeInfo = explicitTaskScope
|
|
728
935
|
? `, tasks=${explicitTaskScope.refs.join(",")}`
|
|
729
936
|
: "";
|
|
@@ -874,8 +1081,11 @@ export function registerRalphCommand(program) {
|
|
|
874
1081
|
maxRetries,
|
|
875
1082
|
maxFailures,
|
|
876
1083
|
cwd: process.cwd(),
|
|
1084
|
+
specDir,
|
|
1085
|
+
sessionId,
|
|
877
1086
|
subagentTimeout: subagentTimeout * 60 * 1000,
|
|
878
1087
|
autoApproveArgs: reviewerAutoApproveArgs,
|
|
1088
|
+
prReviewSkillName: reviewerPrReviewSkill,
|
|
879
1089
|
}, failureTracker);
|
|
880
1090
|
consecutiveFailures = failureTracker.count;
|
|
881
1091
|
if (!continueLoop) {
|
|
@@ -929,8 +1139,8 @@ export function registerRalphCommand(program) {
|
|
|
929
1139
|
const iterationStartTime = new Date();
|
|
930
1140
|
// Build prompts - task-work first, then reflect
|
|
931
1141
|
// AC: @cli-ralph ac-21 - Include explicit task scope in prompt
|
|
932
|
-
const taskWorkPrompt = buildTaskWorkPrompt(currentCtx, iteration, maxLoops, sessionId,
|
|
933
|
-
const reflectPrompt = buildReflectPrompt(iteration, maxLoops, sessionId,
|
|
1142
|
+
const taskWorkPrompt = buildTaskWorkPrompt(currentCtx, iteration, maxLoops, sessionId, workerTaskWorkSkill, options.focus, explicitTaskScope);
|
|
1143
|
+
const reflectPrompt = buildReflectPrompt(iteration, maxLoops, sessionId, workerReflectSkill);
|
|
934
1144
|
// AC: @cli-ralph ac-21
|
|
935
1145
|
// AC: @ralph-per-role-adapters ac-10
|
|
936
1146
|
if (options.dryRun) {
|
|
@@ -942,6 +1152,9 @@ export function registerRalphCommand(program) {
|
|
|
942
1152
|
console.log(` max-retries: ${maxRetries}`);
|
|
943
1153
|
console.log(` max-failures: ${maxFailures}`);
|
|
944
1154
|
console.log(` restart-every: ${restartEvery === 0 ? "never" : restartEvery}`);
|
|
1155
|
+
console.log(` worker-task-work-skill: ${workerTaskWorkSkill}`);
|
|
1156
|
+
console.log(` worker-reflect-skill: ${workerReflectSkill}`);
|
|
1157
|
+
console.log(` reviewer-pr-review-skill: ${reviewerPrReviewSkill}`);
|
|
945
1158
|
if (explicitTaskScope) {
|
|
946
1159
|
console.log(` explicit-tasks: ${explicitTaskScope.refs.join(", ")}`);
|
|
947
1160
|
}
|
|
@@ -1017,7 +1230,11 @@ export function registerRalphCommand(program) {
|
|
|
1017
1230
|
// Set up tool request handler
|
|
1018
1231
|
agent.client.on("request", (reqId, method, params) => {
|
|
1019
1232
|
// biome-ignore lint/style/noNonNullAssertion: agent is guaranteed to exist when callback is registered
|
|
1020
|
-
handleRequest(agent.client, reqId, method, params,
|
|
1233
|
+
handleRequest(agent.client, reqId, method, params, {
|
|
1234
|
+
yolo: options.yolo,
|
|
1235
|
+
specDir,
|
|
1236
|
+
sessionId,
|
|
1237
|
+
}).catch((err) => {
|
|
1021
1238
|
// biome-ignore lint/style/noNonNullAssertion: agent is guaranteed to exist when callback is registered
|
|
1022
1239
|
agent.client.respondError(reqId, -32000, err.message);
|
|
1023
1240
|
});
|
|
@@ -1187,7 +1404,11 @@ export function registerRalphCommand(program) {
|
|
|
1187
1404
|
yolo: options.yolo,
|
|
1188
1405
|
cwd: process.cwd(),
|
|
1189
1406
|
extraArgs: workerAutoApproveArgs,
|
|
1190
|
-
handleRequest: (client, reqId, method, params) => handleRequest(client, reqId, method, params,
|
|
1407
|
+
handleRequest: (client, reqId, method, params) => handleRequest(client, reqId, method, params, {
|
|
1408
|
+
yolo: options.yolo,
|
|
1409
|
+
specDir,
|
|
1410
|
+
sessionId,
|
|
1411
|
+
}),
|
|
1191
1412
|
}, DEFAULT_WRAPUP_TIMEOUT);
|
|
1192
1413
|
// Log wrap-up result
|
|
1193
1414
|
await appendEvent(specDir, {
|