@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.
Files changed (36) hide show
  1. package/dist/cli/commands/ralph.d.ts +48 -0
  2. package/dist/cli/commands/ralph.d.ts.map +1 -1
  3. package/dist/cli/commands/ralph.js +268 -47
  4. package/dist/cli/commands/ralph.js.map +1 -1
  5. package/dist/cli/commands/session/commands.d.ts.map +1 -1
  6. package/dist/cli/commands/session/commands.js +8 -0
  7. package/dist/cli/commands/session/commands.js.map +1 -1
  8. package/dist/cli/commands/session/compact.d.ts +13 -0
  9. package/dist/cli/commands/session/compact.d.ts.map +1 -0
  10. package/dist/cli/commands/session/compact.js +207 -0
  11. package/dist/cli/commands/session/compact.js.map +1 -0
  12. package/dist/cli/commands/session/log.d.ts +2 -0
  13. package/dist/cli/commands/session/log.d.ts.map +1 -1
  14. package/dist/cli/commands/session/log.js +12 -2
  15. package/dist/cli/commands/session/log.js.map +1 -1
  16. package/dist/parser/skill-render.d.ts +14 -0
  17. package/dist/parser/skill-render.d.ts.map +1 -1
  18. package/dist/parser/skill-render.js +14 -4
  19. package/dist/parser/skill-render.js.map +1 -1
  20. package/dist/sessions/store.d.ts +57 -0
  21. package/dist/sessions/store.d.ts.map +1 -1
  22. package/dist/sessions/store.js +337 -9
  23. package/dist/sessions/store.js.map +1 -1
  24. package/package.json +1 -1
  25. package/plugin/.claude-plugin/marketplace.json +1 -1
  26. package/plugin/.claude-plugin/plugin.json +1 -1
  27. package/plugin/plugins/kspec/skills/create-workflow/SKILL.md +10 -0
  28. package/plugin/plugins/kspec/skills/plan/SKILL.md +10 -0
  29. package/plugin/plugins/kspec/skills/review/SKILL.md +2 -0
  30. package/plugin/plugins/kspec/skills/task-work/SKILL.md +10 -0
  31. package/plugin/plugins/kspec/skills/triage-automation/SKILL.md +1 -0
  32. package/templates/skills/create-workflow/SKILL.md +10 -0
  33. package/templates/skills/plan/SKILL.md +10 -0
  34. package/templates/skills/review/SKILL.md +2 -0
  35. package/templates/skills/task-work/SKILL.md +10 -0
  36. 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;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqyBzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA42B3D"}
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
- // ─── Prompt Template ─────────────────────────────────────────────────────────
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
- // ─── Streaming Output ────────────────────────────────────────────────────────
178
- // Translator and renderer are created per-session in the action handler.
179
- // This allows the architecture to be reused by future TUI renderers.
180
- // ─── Adapter Validation ──────────────────────────────────────────────────────
181
- // AC: @ralph-adapter-validation valid-adapter-proceeds, invalid-adapter-error, validation-before-spawn
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
- // Use npx --no-install with --version to check if package exists
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, yolo) {
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 options = p.options || [];
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 = options.find((o) => o.kind === "allow_always") ||
216
- options.find((o) => o.kind === "allow_once");
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 new Promise((resolve) => {
254
- const child = spawn(command, [], {
255
- cwd,
256
- shell: true,
257
- timeout,
258
- });
259
- let stdout = "";
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: ctx.config.ralph.skills.pr_review,
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, options.yolo),
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, ctx.config.ralph.skills.task_work, options.focus, explicitTaskScope);
933
- const reflectPrompt = buildReflectPrompt(iteration, maxLoops, sessionId, ctx.config.ralph.skills.reflect);
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, options.yolo).catch((err) => {
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, options.yolo),
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, {