@launch11/srgical 0.0.1

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,271 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectClaude = detectClaude;
7
+ exports.requestPlannerReply = requestPlannerReply;
8
+ exports.writePlanningPack = writePlanningPack;
9
+ exports.runNextPrompt = runNextPrompt;
10
+ exports.setClaudeRuntimeForTesting = setClaudeRuntimeForTesting;
11
+ exports.resetClaudeRuntimeForTesting = resetClaudeRuntimeForTesting;
12
+ const node_child_process_1 = require("node:child_process");
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_os_1 = __importDefault(require("node:os"));
15
+ const node_path_1 = __importDefault(require("node:path"));
16
+ const local_pack_1 = require("./local-pack");
17
+ const planning_epochs_1 = require("./planning-epochs");
18
+ const prompts_1 = require("./prompts");
19
+ const FOLLOW_APPENDED_PROMPT_QUERY = "Follow the appended system prompt exactly, do the work in the current working directory, and return only the final response.";
20
+ const CLAUDE_WRITE_ALLOW_TOOLS = ["Bash", "Read", "Edit", "Write"];
21
+ const CLAUDE_INSTALL_HINT = "install Claude Code CLI to enable";
22
+ let claudeCommandPromise;
23
+ let forcedClaudeCommand = null;
24
+ let spawnAndCaptureImpl = spawnAndCaptureBase;
25
+ async function detectClaude() {
26
+ try {
27
+ const command = await resolveClaudeCommand();
28
+ const version = await spawnAndCaptureImpl(command, ["--version"], process.cwd());
29
+ return {
30
+ available: true,
31
+ command,
32
+ version: version.stdout.trim()
33
+ };
34
+ }
35
+ catch (error) {
36
+ return {
37
+ available: false,
38
+ command: process.platform === "win32" ? "claude.exe" : "claude",
39
+ error: normalizeClaudeDetectionError(error)
40
+ };
41
+ }
42
+ }
43
+ async function requestPlannerReply(workspaceRoot, messages) {
44
+ const result = await runClaudeExec({
45
+ cwd: workspaceRoot,
46
+ prompt: (0, prompts_1.buildPlannerPrompt)(messages, workspaceRoot),
47
+ permissionMode: "plan",
48
+ maxTurns: 4
49
+ });
50
+ return result.lastMessage.trim();
51
+ }
52
+ async function writePlanningPack(workspaceRoot, messages) {
53
+ const planningEpoch = await (0, planning_epochs_1.preparePlanningPackForWrite)(workspaceRoot);
54
+ const claudeStatus = await detectClaude();
55
+ if (!claudeStatus.available) {
56
+ return appendPlanningEpochSummary(planningEpoch, await (0, local_pack_1.writePlanningPackFallback)(workspaceRoot, messages, claudeStatus.error ?? "Claude Code CLI is unavailable", "Claude Code"));
57
+ }
58
+ try {
59
+ const result = await runClaudeExec({
60
+ cwd: workspaceRoot,
61
+ prompt: await (0, prompts_1.buildPackWriterPrompt)(messages, workspaceRoot),
62
+ permissionMode: "acceptEdits",
63
+ allowedTools: CLAUDE_WRITE_ALLOW_TOOLS,
64
+ maxTurns: 24
65
+ });
66
+ return appendPlanningEpochSummary(planningEpoch, result.lastMessage.trim());
67
+ }
68
+ catch (error) {
69
+ if (isClaudeUnavailableError(error)) {
70
+ const message = error instanceof Error ? error.message : "Claude Code CLI is unavailable";
71
+ return appendPlanningEpochSummary(planningEpoch, await (0, local_pack_1.writePlanningPackFallback)(workspaceRoot, messages, message, "Claude Code"));
72
+ }
73
+ const message = error instanceof Error ? error.message : String(error);
74
+ const epochSummary = (0, planning_epochs_1.formatPlanningEpochSummary)(planningEpoch);
75
+ if (epochSummary) {
76
+ throw new Error(`${epochSummary}\n${message}`);
77
+ }
78
+ throw error;
79
+ }
80
+ }
81
+ async function runNextPrompt(workspaceRoot, prompt) {
82
+ const result = await runClaudeExec({
83
+ cwd: workspaceRoot,
84
+ prompt,
85
+ permissionMode: "acceptEdits",
86
+ allowedTools: CLAUDE_WRITE_ALLOW_TOOLS,
87
+ maxTurns: 24
88
+ });
89
+ return result.lastMessage.trim();
90
+ }
91
+ function setClaudeRuntimeForTesting(options) {
92
+ if (Object.prototype.hasOwnProperty.call(options, "command")) {
93
+ forcedClaudeCommand = options.command ?? null;
94
+ claudeCommandPromise = undefined;
95
+ }
96
+ if (options.spawnAndCapture) {
97
+ spawnAndCaptureImpl = options.spawnAndCapture;
98
+ }
99
+ }
100
+ function resetClaudeRuntimeForTesting() {
101
+ forcedClaudeCommand = null;
102
+ claudeCommandPromise = undefined;
103
+ spawnAndCaptureImpl = spawnAndCaptureBase;
104
+ }
105
+ async function runClaudeExec(options) {
106
+ const tempDir = await (0, promises_1.mkdtemp)(node_path_1.default.join(node_os_1.default.tmpdir(), "srgical-claude-"));
107
+ const promptFile = node_path_1.default.join(tempDir, "prompt.txt");
108
+ const command = await resolveClaudeCommand();
109
+ const args = ["-p", "--output-format", "text", "--permission-mode", options.permissionMode];
110
+ await (0, promises_1.writeFile)(promptFile, options.prompt, "utf8");
111
+ args.push("--append-system-prompt-file", promptFile);
112
+ if (options.allowedTools && options.allowedTools.length > 0) {
113
+ const settingsFile = node_path_1.default.join(tempDir, "settings.json");
114
+ await (0, promises_1.writeFile)(settingsFile, JSON.stringify({
115
+ permissions: {
116
+ allow: options.allowedTools
117
+ }
118
+ }, null, 2), "utf8");
119
+ args.push("--settings", settingsFile);
120
+ }
121
+ if (options.maxTurns) {
122
+ args.push("--max-turns", String(options.maxTurns));
123
+ }
124
+ args.push("--no-session-persistence", FOLLOW_APPENDED_PROMPT_QUERY);
125
+ try {
126
+ const result = await spawnAndCaptureImpl(command, args, options.cwd);
127
+ return {
128
+ stdout: result.stdout,
129
+ stderr: result.stderr,
130
+ lastMessage: result.stdout.trim()
131
+ };
132
+ }
133
+ finally {
134
+ await (0, promises_1.rm)(tempDir, { recursive: true, force: true });
135
+ }
136
+ }
137
+ async function resolveClaudeCommand() {
138
+ if (forcedClaudeCommand) {
139
+ return forcedClaudeCommand;
140
+ }
141
+ if (!claudeCommandPromise) {
142
+ claudeCommandPromise = loadClaudeCommand();
143
+ }
144
+ return claudeCommandPromise;
145
+ }
146
+ async function loadClaudeCommand() {
147
+ if (process.platform !== "win32") {
148
+ return "claude";
149
+ }
150
+ const result = await spawnAndCaptureImpl("where.exe", ["claude"], process.cwd());
151
+ const matches = result.stdout
152
+ .split(/\r?\n/)
153
+ .map((line) => line.trim())
154
+ .filter(Boolean);
155
+ const shim = matches.find((line) => {
156
+ const lower = line.toLowerCase();
157
+ return lower.endsWith(".cmd") || lower.endsWith(".bat");
158
+ });
159
+ if (shim) {
160
+ return shim;
161
+ }
162
+ const executable = matches.find((line) => line.toLowerCase().endsWith(".exe"));
163
+ if (executable) {
164
+ return executable;
165
+ }
166
+ for (const candidate of matches) {
167
+ const siblingShim = await resolveSiblingShim(candidate);
168
+ if (siblingShim) {
169
+ return siblingShim;
170
+ }
171
+ }
172
+ if (matches.length > 0) {
173
+ return matches[0];
174
+ }
175
+ throw new Error("Unable to resolve a Claude executable path.");
176
+ }
177
+ function spawnAndCaptureBase(command, args, cwd, stdinText) {
178
+ return new Promise((resolve, reject) => {
179
+ const spec = buildSpawnSpec(command, args, cwd);
180
+ const child = (0, node_child_process_1.spawn)(spec.command, spec.args, spec.options);
181
+ let stdout = "";
182
+ let stderr = "";
183
+ child.stdout.on("data", (chunk) => {
184
+ stdout += chunk.toString();
185
+ });
186
+ child.stderr.on("data", (chunk) => {
187
+ stderr += chunk.toString();
188
+ });
189
+ child.on("error", reject);
190
+ child.on("close", (code) => {
191
+ if (code === 0) {
192
+ resolve({ stdout, stderr });
193
+ return;
194
+ }
195
+ reject(new Error(stderr.trim() || stdout.trim() || `${command} exited with code ${code}`));
196
+ });
197
+ if (stdinText) {
198
+ child.stdin.write(stdinText);
199
+ }
200
+ child.stdin.end();
201
+ });
202
+ }
203
+ function buildSpawnSpec(command, args, cwd) {
204
+ const lower = command.toLowerCase();
205
+ if (lower.endsWith(".cmd") || lower.endsWith(".bat")) {
206
+ const commandLine = [quoteForShell(command), ...args.map(quoteForShell)].join(" ");
207
+ return {
208
+ command: commandLine,
209
+ args: [],
210
+ options: {
211
+ cwd,
212
+ env: process.env,
213
+ shell: true
214
+ }
215
+ };
216
+ }
217
+ return {
218
+ command,
219
+ args,
220
+ options: {
221
+ cwd,
222
+ env: process.env
223
+ }
224
+ };
225
+ }
226
+ async function resolveSiblingShim(candidate) {
227
+ const extension = node_path_1.default.extname(candidate);
228
+ if (extension) {
229
+ return null;
230
+ }
231
+ for (const suffix of [".cmd", ".bat", ".exe"]) {
232
+ const sibling = `${candidate}${suffix}`;
233
+ try {
234
+ await (0, promises_1.access)(sibling);
235
+ return sibling;
236
+ }
237
+ catch {
238
+ continue;
239
+ }
240
+ }
241
+ return null;
242
+ }
243
+ function quoteForShell(value) {
244
+ if (/^[A-Za-z0-9_:\\/.=-]+$/.test(value)) {
245
+ return value;
246
+ }
247
+ const escaped = value.replace(/"/g, '""');
248
+ return `"${escaped}"`;
249
+ }
250
+ function normalizeClaudeDetectionError(error) {
251
+ const message = error instanceof Error ? error.message : "Failed to run claude";
252
+ const normalized = message.toLowerCase();
253
+ if (normalized.includes("could not find files for the given pattern") ||
254
+ normalized.includes("unable to resolve a claude executable path") ||
255
+ normalized.includes("'claude' is not recognized") ||
256
+ normalized.includes("enoent")) {
257
+ return CLAUDE_INSTALL_HINT;
258
+ }
259
+ return message;
260
+ }
261
+ function isClaudeUnavailableError(error) {
262
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
263
+ return (message.includes("unable to resolve a claude executable path") ||
264
+ message.includes("'claude' is not recognized") ||
265
+ message.includes("enoent") ||
266
+ message.includes("failed to run claude"));
267
+ }
268
+ function appendPlanningEpochSummary(preparation, summary) {
269
+ const epochSummary = (0, planning_epochs_1.formatPlanningEpochSummary)(preparation);
270
+ return [epochSummary, summary].filter(Boolean).join("\n");
271
+ }
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectCodex = detectCodex;
7
+ exports.requestPlannerReply = requestPlannerReply;
8
+ exports.writePlanningPack = writePlanningPack;
9
+ exports.runNextPrompt = runNextPrompt;
10
+ exports.setCodexRuntimeForTesting = setCodexRuntimeForTesting;
11
+ exports.resetCodexRuntimeForTesting = resetCodexRuntimeForTesting;
12
+ const node_child_process_1 = require("node:child_process");
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_os_1 = __importDefault(require("node:os"));
15
+ const node_path_1 = __importDefault(require("node:path"));
16
+ const local_pack_1 = require("./local-pack");
17
+ const planning_epochs_1 = require("./planning-epochs");
18
+ const prompts_1 = require("./prompts");
19
+ let codexCommandPromise;
20
+ let forcedCodexCommand = null;
21
+ let spawnAndCaptureImpl = spawnAndCaptureBase;
22
+ async function detectCodex() {
23
+ try {
24
+ const command = await resolveCodexCommand();
25
+ const version = await spawnAndCaptureImpl(command, ["--version"], process.cwd());
26
+ return {
27
+ available: true,
28
+ command,
29
+ version: version.stdout.trim()
30
+ };
31
+ }
32
+ catch (error) {
33
+ return {
34
+ available: false,
35
+ command: process.platform === "win32" ? "codex.exe" : "codex",
36
+ error: error instanceof Error ? error.message : "Failed to run codex"
37
+ };
38
+ }
39
+ }
40
+ async function requestPlannerReply(workspaceRoot, messages) {
41
+ const result = await runCodexExec({
42
+ cwd: workspaceRoot,
43
+ prompt: (0, prompts_1.buildPlannerPrompt)(messages, workspaceRoot),
44
+ allowWrite: false,
45
+ skipGitRepoCheck: true,
46
+ ephemeral: true
47
+ });
48
+ return result.lastMessage.trim();
49
+ }
50
+ async function writePlanningPack(workspaceRoot, messages) {
51
+ const planningEpoch = await (0, planning_epochs_1.preparePlanningPackForWrite)(workspaceRoot);
52
+ const codexStatus = await detectCodex();
53
+ if (!codexStatus.available) {
54
+ return appendPlanningEpochSummary(planningEpoch, await (0, local_pack_1.writePlanningPackFallback)(workspaceRoot, messages, codexStatus.error ?? "Codex is unavailable", "Codex"));
55
+ }
56
+ try {
57
+ const result = await runCodexExec({
58
+ cwd: workspaceRoot,
59
+ prompt: await (0, prompts_1.buildPackWriterPrompt)(messages, workspaceRoot),
60
+ allowWrite: true,
61
+ skipGitRepoCheck: true,
62
+ ephemeral: false
63
+ });
64
+ return appendPlanningEpochSummary(planningEpoch, result.lastMessage.trim());
65
+ }
66
+ catch (error) {
67
+ if (isCodexUnavailableError(error)) {
68
+ const message = error instanceof Error ? error.message : "Codex is unavailable";
69
+ return appendPlanningEpochSummary(planningEpoch, await (0, local_pack_1.writePlanningPackFallback)(workspaceRoot, messages, message, "Codex"));
70
+ }
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ const epochSummary = (0, planning_epochs_1.formatPlanningEpochSummary)(planningEpoch);
73
+ if (epochSummary) {
74
+ throw new Error(`${epochSummary}\n${message}`);
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+ async function runNextPrompt(workspaceRoot, prompt) {
80
+ const result = await runCodexExec({
81
+ cwd: workspaceRoot,
82
+ prompt,
83
+ allowWrite: true,
84
+ skipGitRepoCheck: true,
85
+ ephemeral: false
86
+ });
87
+ return result.lastMessage.trim();
88
+ }
89
+ function setCodexRuntimeForTesting(options) {
90
+ if (Object.prototype.hasOwnProperty.call(options, "command")) {
91
+ forcedCodexCommand = options.command ?? null;
92
+ codexCommandPromise = undefined;
93
+ }
94
+ if (options.spawnAndCapture) {
95
+ spawnAndCaptureImpl = options.spawnAndCapture;
96
+ }
97
+ }
98
+ function resetCodexRuntimeForTesting() {
99
+ forcedCodexCommand = null;
100
+ codexCommandPromise = undefined;
101
+ spawnAndCaptureImpl = spawnAndCaptureBase;
102
+ }
103
+ async function runCodexExec(options) {
104
+ const tempDir = await (0, promises_1.mkdtemp)(node_path_1.default.join(node_os_1.default.tmpdir(), "srgical-codex-"));
105
+ const outputFile = node_path_1.default.join(tempDir, "last-message.txt");
106
+ const args = ["exec", "--color", "never", "-o", outputFile];
107
+ const command = await resolveCodexCommand();
108
+ if (options.allowWrite) {
109
+ args.push("--full-auto");
110
+ }
111
+ else {
112
+ args.push("-s", "read-only");
113
+ }
114
+ if (options.skipGitRepoCheck) {
115
+ args.push("--skip-git-repo-check");
116
+ }
117
+ if (options.ephemeral) {
118
+ args.push("--ephemeral");
119
+ }
120
+ args.push("-");
121
+ try {
122
+ const result = await spawnAndCaptureImpl(command, args, options.cwd, options.prompt);
123
+ const lastMessage = await (0, promises_1.readFile)(outputFile, "utf8");
124
+ return {
125
+ stdout: result.stdout,
126
+ stderr: result.stderr,
127
+ lastMessage
128
+ };
129
+ }
130
+ finally {
131
+ await (0, promises_1.rm)(tempDir, { recursive: true, force: true });
132
+ }
133
+ }
134
+ async function resolveCodexCommand() {
135
+ if (forcedCodexCommand) {
136
+ return forcedCodexCommand;
137
+ }
138
+ if (!codexCommandPromise) {
139
+ codexCommandPromise = loadCodexCommand();
140
+ }
141
+ return codexCommandPromise;
142
+ }
143
+ async function loadCodexCommand() {
144
+ if (process.platform !== "win32") {
145
+ return "codex";
146
+ }
147
+ const result = await spawnAndCaptureImpl("where.exe", ["codex"], process.cwd());
148
+ const matches = result.stdout
149
+ .split(/\r?\n/)
150
+ .map((line) => line.trim())
151
+ .filter(Boolean);
152
+ const shim = matches.find((line) => {
153
+ const lower = line.toLowerCase();
154
+ return lower.endsWith(".cmd") || lower.endsWith(".bat");
155
+ });
156
+ if (shim) {
157
+ return shim;
158
+ }
159
+ const executable = matches.find((line) => line.toLowerCase().endsWith(".exe"));
160
+ if (executable) {
161
+ return executable;
162
+ }
163
+ for (const candidate of matches) {
164
+ const siblingShim = await resolveSiblingShim(candidate);
165
+ if (siblingShim) {
166
+ return siblingShim;
167
+ }
168
+ }
169
+ if (matches.length > 0) {
170
+ return matches[0];
171
+ }
172
+ throw new Error("Unable to resolve a Codex executable path.");
173
+ }
174
+ function spawnAndCaptureBase(command, args, cwd, stdinText) {
175
+ return new Promise((resolve, reject) => {
176
+ const spec = buildSpawnSpec(command, args, cwd);
177
+ const child = (0, node_child_process_1.spawn)(spec.command, spec.args, spec.options);
178
+ let stdout = "";
179
+ let stderr = "";
180
+ child.stdout.on("data", (chunk) => {
181
+ stdout += chunk.toString();
182
+ });
183
+ child.stderr.on("data", (chunk) => {
184
+ stderr += chunk.toString();
185
+ });
186
+ child.on("error", reject);
187
+ child.on("close", (code) => {
188
+ if (code === 0) {
189
+ resolve({ stdout, stderr });
190
+ return;
191
+ }
192
+ reject(new Error(stderr.trim() || stdout.trim() || `${command} exited with code ${code}`));
193
+ });
194
+ if (stdinText) {
195
+ child.stdin.write(stdinText);
196
+ }
197
+ child.stdin.end();
198
+ });
199
+ }
200
+ function buildSpawnSpec(command, args, cwd) {
201
+ const lower = command.toLowerCase();
202
+ if (lower.endsWith(".cmd") || lower.endsWith(".bat")) {
203
+ const commandLine = [quoteForShell(command), ...args.map(quoteForShell)].join(" ");
204
+ return {
205
+ command: commandLine,
206
+ args: [],
207
+ options: {
208
+ cwd,
209
+ env: process.env,
210
+ shell: true
211
+ }
212
+ };
213
+ }
214
+ return {
215
+ command,
216
+ args,
217
+ options: {
218
+ cwd,
219
+ env: process.env
220
+ }
221
+ };
222
+ }
223
+ async function resolveSiblingShim(candidate) {
224
+ const extension = node_path_1.default.extname(candidate);
225
+ if (extension) {
226
+ return null;
227
+ }
228
+ for (const suffix of [".cmd", ".bat", ".exe"]) {
229
+ const sibling = `${candidate}${suffix}`;
230
+ try {
231
+ await (0, promises_1.access)(sibling);
232
+ return sibling;
233
+ }
234
+ catch {
235
+ continue;
236
+ }
237
+ }
238
+ return null;
239
+ }
240
+ function quoteForShell(value) {
241
+ if (/^[A-Za-z0-9_:\\/.=-]+$/.test(value)) {
242
+ return value;
243
+ }
244
+ const escaped = value.replace(/"/g, '""');
245
+ return `"${escaped}"`;
246
+ }
247
+ function isCodexUnavailableError(error) {
248
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
249
+ return (message.includes("unable to resolve a codex executable path") ||
250
+ message.includes("'codex' is not recognized") ||
251
+ message.includes("enoent") ||
252
+ message.includes("failed to run codex"));
253
+ }
254
+ function appendPlanningEpochSummary(preparation, summary) {
255
+ const epochSummary = (0, planning_epochs_1.formatPlanningEpochSummary)(preparation);
256
+ return [epochSummary, summary].filter(Boolean).join("\n");
257
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderExecutionStepLines = renderExecutionStepLines;
4
+ exports.hasQueuedNextStep = hasQueuedNextStep;
5
+ exports.formatNoQueuedNextStepMessage = formatNoQueuedNextStepMessage;
6
+ exports.formatStepLabel = formatStepLabel;
7
+ exports.renderDryRunPreview = renderDryRunPreview;
8
+ exports.formatExecutionFailureMessage = formatExecutionFailureMessage;
9
+ const agent_1 = require("./agent");
10
+ const PROMPT_PREVIEW_LINE_LIMIT = 18;
11
+ const PROMPT_PREVIEW_CHAR_LIMIT = 1200;
12
+ function renderExecutionStepLines(nextStepSummary, nextRecommended) {
13
+ if (!nextStepSummary) {
14
+ return [
15
+ "Next step summary: unavailable.",
16
+ nextRecommended
17
+ ? `Tracker points to \`${nextRecommended}\`, but its table row could not be summarized.`
18
+ : "Tracker does not currently expose a next recommended step."
19
+ ];
20
+ }
21
+ const lines = [
22
+ `Next step: ${nextStepSummary.id}${nextStepSummary.phase ? ` (${nextStepSummary.phase})` : ""}`,
23
+ `Scope: ${nextStepSummary.scope || "unknown"}`,
24
+ `Acceptance: ${nextStepSummary.acceptance || "unknown"}`
25
+ ];
26
+ if (nextStepSummary.notes) {
27
+ lines.push(`Notes: ${nextStepSummary.notes}`);
28
+ }
29
+ return lines;
30
+ }
31
+ function hasQueuedNextStep(nextRecommended) {
32
+ return Boolean(nextRecommended);
33
+ }
34
+ function formatNoQueuedNextStepMessage(source) {
35
+ return [
36
+ "No next recommended step is currently queued in `.srgical/03-detailed-implementation-plan.md`.",
37
+ source === "run-next"
38
+ ? "Run `srgical studio` to queue more work or update the tracker before executing again."
39
+ : "Use the planning studio to queue more work or update the tracker before running execution again."
40
+ ].join("\n");
41
+ }
42
+ function formatStepLabel(nextStepSummary, nextRecommended) {
43
+ if (nextStepSummary) {
44
+ return `\`${nextStepSummary.id}\`${nextStepSummary.phase ? ` (${nextStepSummary.phase})` : ""}`;
45
+ }
46
+ if (nextRecommended) {
47
+ return `\`${nextRecommended}\``;
48
+ }
49
+ return null;
50
+ }
51
+ function renderDryRunPreview(prompt, nextStepSummary, nextRecommended) {
52
+ const lines = [
53
+ "Execution dry run:",
54
+ ...renderExecutionStepLines(nextStepSummary, nextRecommended),
55
+ "",
56
+ ...buildPromptPreviewLines(prompt),
57
+ "",
58
+ `Dry run only: ${(0, agent_1.getPrimaryAgentAdapter)().label} was not invoked and no execution state or run log was updated.`
59
+ ];
60
+ return lines;
61
+ }
62
+ function formatExecutionFailureMessage(errorMessage, nextStepSummary, nextRecommended, source) {
63
+ const lines = [
64
+ `Execution failed${formatStepLabel(nextStepSummary, nextRecommended) ? ` for ${formatStepLabel(nextStepSummary, nextRecommended)}` : ""}.`,
65
+ `Reason: ${errorMessage}`,
66
+ "",
67
+ "Recovery:",
68
+ "- Review `.srgical/04-next-agent-prompt.md` and `.srgical/03-detailed-implementation-plan.md`.",
69
+ "- Inspect `.srgical/execution-state.json` for the latest recorded failure summary."
70
+ ];
71
+ if (source === "run-next") {
72
+ lines.push("- Inspect `.srgical/execution-log.md` for the durable run history.");
73
+ }
74
+ lines.push("- Preview safely with `srgical run-next --dry-run` or `/preview` in the studio.");
75
+ return lines.join("\n");
76
+ }
77
+ function buildPromptPreviewLines(prompt) {
78
+ const promptLines = prompt.split(/\r?\n/);
79
+ const preview = promptLines.slice(0, PROMPT_PREVIEW_LINE_LIMIT).join("\n");
80
+ const trimmedPreview = preview.length <= PROMPT_PREVIEW_CHAR_LIMIT ? preview : `${preview.slice(0, PROMPT_PREVIEW_CHAR_LIMIT).trimEnd()}...`;
81
+ return [
82
+ `Prompt preview: first ${Math.min(PROMPT_PREVIEW_LINE_LIMIT, promptLines.length)} of ${promptLines.length} lines.`,
83
+ "",
84
+ trimmedPreview,
85
+ ...(promptLines.length > PROMPT_PREVIEW_LINE_LIMIT ? ["", "... [preview truncated]"] : [])
86
+ ];
87
+ }