@launch11/srgical 0.0.7 → 0.0.8

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 CHANGED
@@ -31,7 +31,7 @@ This repo currently ships the foundation for:
31
31
  Prints the installed version with release-note links instead of only echoing the semver.
32
32
  - `srgical doctor`
33
33
  Reports the active plan, plan readiness, execution state, auto-run state, and which supported agents are available
34
- locally.
34
+ locally, along with any cached AI advice for the selected plan.
35
35
  - `srgical about`
36
36
  Shows package details, release links, and the currently supported agent adapters.
37
37
  - `srgical changelog`
@@ -40,7 +40,8 @@ This repo currently ships the foundation for:
40
40
  Creates a local `.srgical/` planning pack from built-in templates, with `--plan <id>` for named plans.
41
41
  - `srgical studio`
42
42
  Opens a full-screen planning studio where you can switch between named plans, inspect readiness with `/readiness`,
43
- inspect supported tools with `/agents`, and explicitly trigger pack writes, single-step execution, or `/auto`.
43
+ inspect supported tools with `/agents`, refresh AI guidance with `/advice`, and explicitly trigger pack writes,
44
+ single-step execution, or `/auto`.
44
45
  - `srgical run-next`
45
46
  Replays the generated next-agent prompt through the active agent, with `--plan <id>` for plan targeting,
46
47
  `--dry-run` for safe preview, `--agent <id>` for a one-run override, and `--auto` for bounded multi-step execution.
@@ -168,10 +169,19 @@ Inside the studio, the footer is intentionally minimal:
168
169
  - `PgUp/PgDn` scrolls the transcript
169
170
  - `/agents` chooses the current tool
170
171
  - `/help` shows the full command set
172
+ - `/quit` exits the studio
171
173
 
172
174
  The composer is now multiline with a minimum two-line visible input area. `Enter` sends, while `Shift+Enter`,
173
175
  `Alt+Enter`, or `Ctrl+J` inserts a newline when the terminal exposes those keys distinctly.
174
176
 
177
+ The studio can also ask the active agent for an AI assessment of the current planning state. Run `/advice` to cache a
178
+ plain-English summary of:
179
+
180
+ - the problem statement the agent believes you are solving,
181
+ - whether the current plan state is clear or still fuzzy,
182
+ - what research or repo truth still needs to be gathered,
183
+ - and the best next move right now.
184
+
175
185
  ## Current Claude Caveat
176
186
 
177
187
  Claude support is real, but it is not treated as interchangeable with Codex. The current non-interactive Claude path
@@ -91,6 +91,12 @@ function renderPlanDetailLines(state) {
91
91
  if (state.autoRun) {
92
92
  lines.push(`Auto run detail: attempted ${state.autoRun.stepsAttempted}${state.autoRun.maxSteps ? `/${state.autoRun.maxSteps}` : ""}, stop reason ${state.autoRun.stopReason ?? "none"}`);
93
93
  }
94
+ if (state.advice) {
95
+ lines.push(`AI advice: ${state.advice.problemStatement}`, ` Clarity: ${state.advice.clarity}`, ` Assessment: ${state.advice.stateAssessment}`, ` Research: ${state.advice.researchNeeded.length > 0 ? state.advice.researchNeeded.join(", ") : "none"}`, ` Next: ${state.advice.nextAction}`);
96
+ }
97
+ else {
98
+ lines.push("AI advice: none cached yet (run `/advice` in studio to generate guidance).");
99
+ }
94
100
  return lines;
95
101
  }
96
102
  function renderNextStepLines(nextStepSummary, nextRecommended) {
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadPlanningAdviceState = loadPlanningAdviceState;
4
+ exports.savePlanningAdviceState = savePlanningAdviceState;
5
+ exports.normalizePlanningAdvice = normalizePlanningAdvice;
6
+ exports.parsePlanningAdviceResponse = parsePlanningAdviceResponse;
7
+ const workspace_1 = require("./workspace");
8
+ async function loadPlanningAdviceState(workspaceRoot, options = {}) {
9
+ const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot, options);
10
+ if (!(await (0, workspace_1.fileExists)(paths.adviceState))) {
11
+ return null;
12
+ }
13
+ try {
14
+ return normalizePlanningAdvice(JSON.parse(await (0, workspace_1.readText)(paths.adviceState)), paths.planId);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ async function savePlanningAdviceState(workspaceRoot, advice, options = {}) {
21
+ const paths = await (0, workspace_1.ensurePlanningDir)(workspaceRoot, options);
22
+ const payload = {
23
+ version: 1,
24
+ planId: paths.planId,
25
+ updatedAt: new Date().toISOString(),
26
+ ...advice
27
+ };
28
+ await (0, workspace_1.writeText)(paths.adviceState, JSON.stringify(payload, null, 2));
29
+ return payload;
30
+ }
31
+ function normalizePlanningAdvice(value, fallbackPlanId) {
32
+ if (!value || typeof value !== "object") {
33
+ return null;
34
+ }
35
+ const candidate = value;
36
+ const clarity = normalizeClarity(candidate.clarity);
37
+ if (candidate.version !== 1 ||
38
+ typeof candidate.problemStatement !== "string" ||
39
+ !clarity ||
40
+ typeof candidate.stateAssessment !== "string" ||
41
+ !Array.isArray(candidate.researchNeeded) ||
42
+ candidate.researchNeeded.some((item) => typeof item !== "string") ||
43
+ typeof candidate.advice !== "string" ||
44
+ typeof candidate.nextAction !== "string") {
45
+ return null;
46
+ }
47
+ return {
48
+ version: 1,
49
+ planId: typeof candidate.planId === "string" ? candidate.planId : fallbackPlanId,
50
+ updatedAt: typeof candidate.updatedAt === "string" ? candidate.updatedAt : new Date().toISOString(),
51
+ problemStatement: candidate.problemStatement.trim(),
52
+ clarity,
53
+ stateAssessment: candidate.stateAssessment.trim(),
54
+ researchNeeded: candidate.researchNeeded.map((item) => item.trim()).filter(Boolean),
55
+ advice: candidate.advice.trim(),
56
+ nextAction: candidate.nextAction.trim()
57
+ };
58
+ }
59
+ function parsePlanningAdviceResponse(raw, fallbackPlanId) {
60
+ const jsonStart = raw.indexOf("{");
61
+ const jsonEnd = raw.lastIndexOf("}");
62
+ if (jsonStart === -1 || jsonEnd === -1 || jsonEnd <= jsonStart) {
63
+ return null;
64
+ }
65
+ try {
66
+ return normalizePlanningAdvice(JSON.parse(raw.slice(jsonStart, jsonEnd + 1)), fallbackPlanId);
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ function normalizeClarity(value) {
73
+ if (value === "clear" || value === "mostly clear" || value === "still fuzzy") {
74
+ return value;
75
+ }
76
+ return null;
77
+ }
@@ -8,6 +8,7 @@ exports.detectPrimaryAgent = detectPrimaryAgent;
8
8
  exports.resolveExecutionAgent = resolveExecutionAgent;
9
9
  exports.requestPlannerReply = requestPlannerReply;
10
10
  exports.writePlanningPack = writePlanningPack;
11
+ exports.requestPlanningAdvice = requestPlanningAdvice;
11
12
  exports.runNextPrompt = runNextPrompt;
12
13
  exports.selectPrimaryAgent = selectPrimaryAgent;
13
14
  exports.resetAgentAdaptersForTesting = resetAgentAdaptersForTesting;
@@ -31,6 +32,7 @@ const codexAdapter = {
31
32
  };
32
33
  },
33
34
  requestPlannerReply: codex_1.requestPlannerReply,
35
+ requestPlanningAdvice: codex_1.requestPlanningAdvice,
34
36
  writePlanningPack: codex_1.writePlanningPack,
35
37
  runNextPrompt: codex_1.runNextPrompt
36
38
  };
@@ -49,6 +51,7 @@ const claudeAdapter = {
49
51
  };
50
52
  },
51
53
  requestPlannerReply: claude_1.requestPlannerReply,
54
+ requestPlanningAdvice: claude_1.requestPlanningAdvice,
52
55
  writePlanningPack: claude_1.writePlanningPack,
53
56
  runNextPrompt: claude_1.runNextPrompt
54
57
  };
@@ -67,6 +70,7 @@ const augmentAdapter = {
67
70
  };
68
71
  },
69
72
  requestPlannerReply: augment_1.requestPlannerReply,
73
+ requestPlanningAdvice: augment_1.requestPlanningAdvice,
70
74
  writePlanningPack: augment_1.writePlanningPack,
71
75
  runNextPrompt: augment_1.runNextPrompt
72
76
  };
@@ -127,6 +131,10 @@ async function writePlanningPack(workspaceRoot, messages, options = {}) {
127
131
  const { adapter } = await resolvePrimaryAgent(workspaceRoot, options);
128
132
  return adapter.writePlanningPack(workspaceRoot, messages, options);
129
133
  }
134
+ async function requestPlanningAdvice(workspaceRoot, messages, packState, options = {}) {
135
+ const { adapter } = await resolvePrimaryAgent(workspaceRoot, options);
136
+ return adapter.requestPlanningAdvice(workspaceRoot, messages, packState, options);
137
+ }
130
138
  async function runNextPrompt(workspaceRoot, prompt, options = {}) {
131
139
  const { adapter } = await resolveExecutionAgent(workspaceRoot, options.agentId, { planId: options.planId });
132
140
  return adapter.runNextPrompt(workspaceRoot, prompt, { planId: options.planId });
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.detectAugment = detectAugment;
7
7
  exports.requestPlannerReply = requestPlannerReply;
8
+ exports.requestPlanningAdvice = requestPlanningAdvice;
8
9
  exports.writePlanningPack = writePlanningPack;
9
10
  exports.runNextPrompt = runNextPrompt;
10
11
  exports.setAugmentRuntimeForTesting = setAugmentRuntimeForTesting;
@@ -55,6 +56,15 @@ async function requestPlannerReply(workspaceRoot, messages, _options = {}) {
55
56
  });
56
57
  return result.lastMessage.trim();
57
58
  }
59
+ async function requestPlanningAdvice(workspaceRoot, messages, packState, options = {}) {
60
+ const result = await runAugmentExec({
61
+ cwd: workspaceRoot,
62
+ prompt: await (0, prompts_1.buildAdvicePrompt)(messages, workspaceRoot, packState, options),
63
+ askMode: true,
64
+ maxTurns: 4
65
+ });
66
+ return result.lastMessage.trim();
67
+ }
58
68
  async function writePlanningPack(workspaceRoot, messages, options = {}) {
59
69
  const planningEpoch = await (0, planning_epochs_1.preparePlanningPackForWrite)(workspaceRoot, options);
60
70
  const augmentStatus = await detectAugment();
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.detectClaude = detectClaude;
7
7
  exports.requestPlannerReply = requestPlannerReply;
8
+ exports.requestPlanningAdvice = requestPlanningAdvice;
8
9
  exports.writePlanningPack = writePlanningPack;
9
10
  exports.runNextPrompt = runNextPrompt;
10
11
  exports.setClaudeRuntimeForTesting = setClaudeRuntimeForTesting;
@@ -49,6 +50,15 @@ async function requestPlannerReply(workspaceRoot, messages, _options = {}) {
49
50
  });
50
51
  return result.lastMessage.trim();
51
52
  }
53
+ async function requestPlanningAdvice(workspaceRoot, messages, packState, options = {}) {
54
+ const result = await runClaudeExec({
55
+ cwd: workspaceRoot,
56
+ prompt: await (0, prompts_1.buildAdvicePrompt)(messages, workspaceRoot, packState, options),
57
+ permissionMode: "plan",
58
+ maxTurns: 4
59
+ });
60
+ return result.lastMessage.trim();
61
+ }
52
62
  async function writePlanningPack(workspaceRoot, messages, options = {}) {
53
63
  const planningEpoch = await (0, planning_epochs_1.preparePlanningPackForWrite)(workspaceRoot, options);
54
64
  const claudeStatus = await detectClaude();
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.detectCodex = detectCodex;
7
7
  exports.requestPlannerReply = requestPlannerReply;
8
+ exports.requestPlanningAdvice = requestPlanningAdvice;
8
9
  exports.writePlanningPack = writePlanningPack;
9
10
  exports.runNextPrompt = runNextPrompt;
10
11
  exports.setCodexRuntimeForTesting = setCodexRuntimeForTesting;
@@ -47,6 +48,16 @@ async function requestPlannerReply(workspaceRoot, messages, _options = {}) {
47
48
  });
48
49
  return result.lastMessage.trim();
49
50
  }
51
+ async function requestPlanningAdvice(workspaceRoot, messages, packState, options = {}) {
52
+ const result = await runCodexExec({
53
+ cwd: workspaceRoot,
54
+ prompt: await (0, prompts_1.buildAdvicePrompt)(messages, workspaceRoot, packState, options),
55
+ allowWrite: false,
56
+ skipGitRepoCheck: true,
57
+ ephemeral: true
58
+ });
59
+ return result.lastMessage.trim();
60
+ }
50
61
  async function writePlanningPack(workspaceRoot, messages, options = {}) {
51
62
  const planningEpoch = await (0, planning_epochs_1.preparePlanningPackForWrite)(workspaceRoot, options);
52
63
  const codexStatus = await detectCodex();
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refreshPlanningAdvice = refreshPlanningAdvice;
4
+ const advice_state_1 = require("./advice-state");
5
+ const agent_1 = require("./agent");
6
+ const planning_pack_state_1 = require("./planning-pack-state");
7
+ async function refreshPlanningAdvice(workspaceRoot, messages, options = {}) {
8
+ const packState = await (0, planning_pack_state_1.readPlanningPackState)(workspaceRoot, options);
9
+ const raw = await (0, agent_1.requestPlanningAdvice)(workspaceRoot, messages, packState, options);
10
+ const parsed = (0, advice_state_1.parsePlanningAdviceResponse)(raw, packState.planId);
11
+ if (!parsed) {
12
+ throw new Error("Planning advice could not be parsed from the active agent response.");
13
+ }
14
+ return (0, advice_state_1.savePlanningAdviceState)(workspaceRoot, {
15
+ problemStatement: parsed.problemStatement,
16
+ clarity: parsed.clarity,
17
+ stateAssessment: parsed.stateAssessment,
18
+ researchNeeded: parsed.researchNeeded,
19
+ advice: parsed.advice,
20
+ nextAction: parsed.nextAction
21
+ }, options);
22
+ }
@@ -21,6 +21,7 @@ const ARCHIVED_PACK_FILE_NAMES = [
21
21
  "studio-session.json",
22
22
  "planning-state.json",
23
23
  "auto-run-state.json",
24
+ "advice-state.json",
24
25
  "execution-state.json",
25
26
  "execution-log.md"
26
27
  ];
@@ -75,6 +76,7 @@ async function resetActivePlanningPack(paths) {
75
76
  await (0, planning_state_1.savePlanningState)(paths.root, "scaffolded", { planId: paths.planId });
76
77
  await Promise.all([
77
78
  (0, promises_1.rm)(paths.autoRunState, { force: true }),
79
+ (0, promises_1.rm)(paths.adviceState, { force: true }),
78
80
  (0, promises_1.rm)(paths.executionState, { force: true }),
79
81
  (0, promises_1.rm)(paths.executionLog, { force: true })
80
82
  ]);
@@ -88,6 +90,7 @@ function listArchivablePaths(paths) {
88
90
  paths.studioSession,
89
91
  paths.planningState,
90
92
  paths.autoRunState,
93
+ paths.adviceState,
91
94
  paths.executionState,
92
95
  paths.executionLog
93
96
  ];
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readPlanningPackState = readPlanningPackState;
4
4
  exports.isExecutionStepSummary = isExecutionStepSummary;
5
5
  exports.isExecutionReadyState = isExecutionReadyState;
6
+ const advice_state_1 = require("./advice-state");
6
7
  const auto_run_state_1 = require("./auto-run-state");
7
8
  const execution_state_1 = require("./execution-state");
8
9
  const planning_state_1 = require("./planning-state");
@@ -25,9 +26,10 @@ async function readPlanningPackState(workspaceRoot, options = {}) {
25
26
  trackerReadable = false;
26
27
  }
27
28
  }
28
- const [lastExecution, planningState, autoRun, docsPresent, studioSession] = await Promise.all([
29
+ const [lastExecution, planningState, advice, autoRun, docsPresent, studioSession] = await Promise.all([
29
30
  (0, execution_state_1.loadExecutionState)(workspaceRoot, options),
30
31
  (0, planning_state_1.loadPlanningState)(workspaceRoot, options),
32
+ (0, advice_state_1.loadPlanningAdviceState)(workspaceRoot, options),
31
33
  (0, auto_run_state_1.loadAutoRunState)(workspaceRoot, options),
32
34
  countPresentDocs(paths),
33
35
  (0, studio_session_1.loadStudioSessionState)(workspaceRoot, options)
@@ -57,6 +59,7 @@ async function readPlanningPackState(workspaceRoot, options = {}) {
57
59
  planningState,
58
60
  packMode,
59
61
  readiness,
62
+ advice,
60
63
  autoRun,
61
64
  executionActivated,
62
65
  mode,
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.buildPlannerPrompt = buildPlannerPrompt;
7
7
  exports.buildPackWriterPrompt = buildPackWriterPrompt;
8
+ exports.buildAdvicePrompt = buildAdvicePrompt;
8
9
  const promises_1 = require("node:fs/promises");
9
10
  const node_path_1 = __importDefault(require("node:path"));
10
11
  const workspace_1 = require("./workspace");
@@ -71,6 +72,46 @@ ${repoTruth}
71
72
 
72
73
  Conversation transcript:
73
74
 
75
+ ${renderTranscript(messages)}
76
+ `;
77
+ }
78
+ async function buildAdvicePrompt(messages, workspaceRoot, packState, options = {}) {
79
+ const repoTruth = await buildRepoTruthSnapshot(workspaceRoot, options);
80
+ return `You are the planning advisor inside srgical.
81
+
82
+ Your job is to assess the current planning state and return only a single JSON object.
83
+
84
+ Assess the user's current problem statement, whether the repo/project state is clear enough yet, whether more research is needed, and what the best next move is.
85
+
86
+ Rules:
87
+ - Be specific to the current repository and transcript.
88
+ - Do not invent repo facts that are not supported by the transcript or repo truth snapshot.
89
+ - Prefer concise, practical advice over generic coaching.
90
+ - If the plan is still fuzzy, say so plainly.
91
+ - If more repo research is needed, name the missing area directly.
92
+ - Return valid JSON only. No markdown fences. No prose before or after the JSON.
93
+
94
+ Required JSON shape:
95
+ {
96
+ "version": 1,
97
+ "problemStatement": "string",
98
+ "clarity": "clear" | "mostly clear" | "still fuzzy",
99
+ "stateAssessment": "one short sentence",
100
+ "researchNeeded": ["string", "string"],
101
+ "advice": "one short paragraph",
102
+ "nextAction": "one concrete next move"
103
+ }
104
+
105
+ Current deterministic planning state:
106
+
107
+ ${renderPlanningStateSummary(packState)}
108
+
109
+ Repo truth snapshot:
110
+
111
+ ${repoTruth}
112
+
113
+ Conversation transcript:
114
+
74
115
  ${renderTranscript(messages)}
75
116
  `;
76
117
  }
@@ -121,6 +162,23 @@ async function buildRepoTruthSnapshot(workspaceRoot, options = {}) {
121
162
  renderNamedSnippet(".srgical/04-next-agent-prompt.md", nextPromptSnippet)
122
163
  ].join("\n");
123
164
  }
165
+ function renderPlanningStateSummary(packState) {
166
+ return [
167
+ `- planId: ${packState.planId}`,
168
+ `- packDir: ${packState.packDir}`,
169
+ `- packPresent: ${packState.packPresent}`,
170
+ `- packMode: ${packState.packMode}`,
171
+ `- mode: ${packState.mode}`,
172
+ `- docsPresent: ${packState.docsPresent}/4`,
173
+ `- readiness: ${packState.readiness.score}/${packState.readiness.total}`,
174
+ `- readinessReadyToWrite: ${packState.readiness.readyToWrite}`,
175
+ `- missingReadinessSignals: ${packState.readiness.missingLabels.join(", ") || "none"}`,
176
+ `- nextRecommended: ${packState.currentPosition.nextRecommended ?? "none queued"}`,
177
+ `- nextStepId: ${packState.nextStepSummary?.id ?? "none"}`,
178
+ `- executionActivated: ${packState.executionActivated}`,
179
+ `- autoRunStatus: ${packState.autoRun?.status ?? "idle"}`
180
+ ].join("\n");
181
+ }
124
182
  async function summarizePackageManifest(workspaceRoot) {
125
183
  const packageJsonPath = node_path_1.default.join(workspaceRoot, "package.json");
126
184
  const exists = await (0, workspace_1.fileExists)(packageJsonPath);
@@ -61,6 +61,7 @@ function getPlanningPackPaths(root, options = {}) {
61
61
  executionLog: node_path_1.default.join(dir, "execution-log.md"),
62
62
  planningState: node_path_1.default.join(dir, "planning-state.json"),
63
63
  autoRunState: node_path_1.default.join(dir, "auto-run-state.json"),
64
+ adviceState: node_path_1.default.join(dir, "advice-state.json"),
64
65
  activePlanFile: node_path_1.default.join(planningRoot, exports.ACTIVE_PLAN_FILE)
65
66
  };
66
67
  }
@@ -180,7 +181,8 @@ async function hasDefaultPlanPresence(root) {
180
181
  fileExists(paths.executionState),
181
182
  fileExists(paths.executionLog),
182
183
  fileExists(paths.planningState),
183
- fileExists(paths.autoRunState)
184
+ fileExists(paths.autoRunState),
185
+ fileExists(paths.adviceState)
184
186
  ]);
185
187
  return checks.some(Boolean);
186
188
  }
package/dist/ui/studio.js CHANGED
@@ -15,13 +15,16 @@ const auto_run_1 = require("../core/auto-run");
15
15
  const agent_1 = require("../core/agent");
16
16
  const execution_controls_1 = require("../core/execution-controls");
17
17
  const execution_state_1 = require("../core/execution-state");
18
+ const planning_advice_1 = require("../core/planning-advice");
18
19
  const planning_pack_state_1 = require("../core/planning-pack-state");
19
20
  const planning_state_1 = require("../core/planning-state");
20
21
  const studio_session_1 = require("../core/studio-session");
21
22
  const templates_1 = require("../core/templates");
22
23
  const workspace_1 = require("../core/workspace");
23
- const READY_FOOTER = " PgUp/PgDn scroll /agents choose tool /help commands ";
24
+ const READY_FOOTER = " PgUp/PgDn scroll /agents choose tool /help commands /quit exit ";
24
25
  const ACTIVITY_FRAMES = ["[ ]", "[= ]", "[== ]", "[=== ]", "[ ===]", "[ ==]", "[ =]"];
26
+ const COMPOSER_CURSOR = "{black-fg}{#ffb14a-bg} {/}";
27
+ const escapeBlessedText = blessed_1.default.helpers.escape;
25
28
  async function launchStudio(options = {}) {
26
29
  let workspace = (0, workspace_1.resolveWorkspace)(options.workspace);
27
30
  let planId = await (0, workspace_1.resolvePlanId)(workspace, options.planId);
@@ -99,14 +102,21 @@ async function launchStudio(options = {}) {
99
102
  }
100
103
  }
101
104
  });
102
- const input = blessed_1.default.textarea({
105
+ const input = blessed_1.default.box({
103
106
  bottom: 1,
104
107
  left: 0,
105
108
  width: "100%",
106
109
  height: 4,
107
- inputOnFocus: true,
108
110
  keys: true,
109
111
  mouse: true,
112
+ tags: true,
113
+ clickable: true,
114
+ padding: {
115
+ top: 0,
116
+ right: 1,
117
+ bottom: 0,
118
+ left: 1
119
+ },
110
120
  border: {
111
121
  type: "line"
112
122
  },
@@ -119,7 +129,6 @@ async function launchStudio(options = {}) {
119
129
  }
120
130
  }
121
131
  });
122
- configureComposer(input);
123
132
  const footer = blessed_1.default.box({
124
133
  bottom: 0,
125
134
  left: 0,
@@ -148,6 +157,8 @@ async function launchStudio(options = {}) {
148
157
  let trackerSummary = "loading...";
149
158
  let executionSummary = "never run";
150
159
  let autoSummary = "idle";
160
+ let adviceSummary = "run /advice for AI guidance";
161
+ let composerValue = "";
151
162
  function setSidebar(status) {
152
163
  const planningPaths = (0, workspace_1.getPlanningPackPaths)(workspace, { planId });
153
164
  sidebar.setContent([
@@ -171,6 +182,9 @@ async function launchStudio(options = {}) {
171
182
  "{bold}Auto{/bold}",
172
183
  autoSummary,
173
184
  "",
185
+ "{bold}Advice{/bold}",
186
+ adviceSummary,
187
+ "",
174
188
  "{bold}State{/bold}",
175
189
  status ?? getActivityState()
176
190
  ].join("\n"));
@@ -236,6 +250,10 @@ async function launchStudio(options = {}) {
236
250
  transcript.setContent(rendered);
237
251
  transcript.setScrollPerc(100);
238
252
  }
253
+ function renderComposer() {
254
+ const escapedValue = escapeBlessedText(composerValue);
255
+ input.setContent(`${escapedValue}${COMPOSER_CURSOR}`);
256
+ }
239
257
  function scrollTranscript(lines) {
240
258
  transcript.scroll(lines);
241
259
  screen.render();
@@ -254,6 +272,56 @@ async function launchStudio(options = {}) {
254
272
  setFooter();
255
273
  screen.render();
256
274
  }
275
+ async function submitComposer() {
276
+ const rawValue = composerValue;
277
+ const text = rawValue.trim();
278
+ composerValue = "";
279
+ renderComposer();
280
+ if (!text || (busy && text !== "/stop")) {
281
+ screen.render();
282
+ return;
283
+ }
284
+ if (text.startsWith("/")) {
285
+ await handleSlashCommand(text);
286
+ input.focus();
287
+ renderComposer();
288
+ screen.render();
289
+ return;
290
+ }
291
+ startBusy("planner");
292
+ await appendMessage({
293
+ role: "user",
294
+ content: text
295
+ });
296
+ renderTranscript();
297
+ setSidebar();
298
+ setFooter();
299
+ renderComposer();
300
+ screen.render();
301
+ try {
302
+ const reply = await (0, agent_1.requestPlannerReply)(workspace, messages, { planId });
303
+ await appendMessage({
304
+ role: "assistant",
305
+ content: reply
306
+ });
307
+ await refreshAdvice(false);
308
+ }
309
+ catch (error) {
310
+ await appendMessage({
311
+ role: "system",
312
+ content: `Planner call failed: ${error instanceof Error ? error.message : String(error)}`
313
+ });
314
+ }
315
+ finally {
316
+ stopBusy();
317
+ renderTranscript();
318
+ setSidebar();
319
+ setFooter();
320
+ renderComposer();
321
+ input.focus();
322
+ screen.render();
323
+ }
324
+ }
257
325
  async function refreshEnvironment() {
258
326
  const [storedAgentId, packState] = await Promise.all([
259
327
  (0, studio_session_1.loadStoredActiveAgentId)(workspace, { planId }),
@@ -273,9 +341,11 @@ async function launchStudio(options = {}) {
273
341
  trackerSummary = formatTrackerSummary(packState.currentPosition);
274
342
  executionSummary = formatExecutionSummary(packState.lastExecution);
275
343
  autoSummary = formatAutoSummary(packState);
344
+ adviceSummary = formatAdviceSummary(packState.advice);
276
345
  setSidebar();
277
346
  setFooter();
278
347
  renderTranscript();
348
+ renderComposer();
279
349
  screen.render();
280
350
  }
281
351
  async function switchPlan(nextPlanId) {
@@ -284,6 +354,20 @@ async function launchStudio(options = {}) {
284
354
  messages = await (0, studio_session_1.loadStudioSession)(workspace, { planId });
285
355
  await refreshEnvironment();
286
356
  }
357
+ async function refreshAdvice(showInTranscript = false) {
358
+ try {
359
+ const advice = await (0, planning_advice_1.refreshPlanningAdvice)(workspace, messages, { planId });
360
+ await refreshEnvironment();
361
+ if (showInTranscript) {
362
+ await appendSystemMessage(renderAdviceMessage(advice));
363
+ }
364
+ }
365
+ catch (error) {
366
+ if (showInTranscript) {
367
+ await appendSystemMessage(`Advice refresh failed: ${error instanceof Error ? error.message : String(error)}`);
368
+ }
369
+ }
370
+ }
287
371
  async function handleSlashCommand(command) {
288
372
  if (command === "/quit") {
289
373
  screen.destroy();
@@ -363,6 +447,17 @@ async function launchStudio(options = {}) {
363
447
  await appendSystemMessage(renderReadinessMessage(packState));
364
448
  return;
365
449
  }
450
+ if (command === "/advice") {
451
+ startBusy("planner", "refreshing AI advice...");
452
+ try {
453
+ await refreshAdvice(true);
454
+ }
455
+ finally {
456
+ stopBusy();
457
+ await refreshEnvironment();
458
+ }
459
+ return;
460
+ }
366
461
  if (command === "/stop") {
367
462
  const state = await (0, auto_run_1.requestAutoRunStop)(workspace, { planId });
368
463
  await refreshEnvironment();
@@ -404,9 +499,10 @@ async function launchStudio(options = {}) {
404
499
  "1. Talk normally to sharpen the plan against the real repo.",
405
500
  "2. Use `/plans`, `/plan`, and `/plan new <id>` to manage named planning packs in this workspace.",
406
501
  "3. Use `/readiness` to see what context is still missing before you write the pack.",
407
- "4. Run `/write` when the plan is ready to put on disk.",
408
- "5. Run `/preview` for a safe execution preview, `/run` for one execution step, or `/auto [max]` for continuous execution.",
409
- "6. Run `/stop` to stop auto mode after the current iteration.",
502
+ "4. Run `/advice` for an AI assessment of the problem statement, clarity, research gaps, and next move.",
503
+ "5. Run `/write` when the plan is ready to put on disk.",
504
+ "6. Run `/preview` for a safe execution preview, `/run` for one execution step, or `/auto [max]` for continuous execution.",
505
+ "7. Run `/stop` to stop auto mode after the current iteration.",
410
506
  "",
411
507
  "Controls:",
412
508
  "- `Enter` sends the current message or command.",
@@ -432,6 +528,7 @@ async function launchStudio(options = {}) {
432
528
  try {
433
529
  const result = await (0, agent_1.writePlanningPack)(workspace, messages, { planId });
434
530
  await (0, planning_state_1.markPlanningPackAuthored)(workspace, { planId });
531
+ await refreshAdvice(false);
435
532
  await appendSystemMessage(`Planning pack updated for \`${planId}\`. Summary:\n${result}`);
436
533
  }
437
534
  catch (error) {
@@ -459,6 +556,7 @@ async function launchStudio(options = {}) {
459
556
  planId,
460
557
  stepLabel: packState.nextStepSummary?.id ?? packState.currentPosition.nextRecommended
461
558
  });
559
+ await refreshAdvice(false);
462
560
  await appendSystemMessage(`Execution run finished. ${(0, agent_1.getPrimaryAgentAdapter)().label} summary:\n${result}`);
463
561
  }
464
562
  catch (error) {
@@ -490,6 +588,7 @@ async function launchStudio(options = {}) {
490
588
  await appendSystemMessage(line);
491
589
  }
492
590
  });
591
+ await refreshAdvice(false);
493
592
  await appendSystemMessage(`Auto mode finished: ${result.summary}`);
494
593
  }
495
594
  catch (error) {
@@ -503,46 +602,36 @@ async function launchStudio(options = {}) {
503
602
  }
504
603
  await appendSystemMessage(`Unknown command: ${command}`);
505
604
  }
506
- input.on("submit", async (value) => {
507
- const text = value.trim();
508
- input.clearValue();
509
- if (!text || (busy && text !== "/stop")) {
510
- screen.render();
605
+ input.on("click", () => {
606
+ input.focus();
607
+ screen.render();
608
+ });
609
+ input.on("keypress", async (ch, key) => {
610
+ if (key.name === "pageup" || key.name === "ppage" || key.name === "pagedown" || key.name === "npage") {
511
611
  return;
512
612
  }
513
- if (text.startsWith("/")) {
514
- await handleSlashCommand(text);
515
- input.focus();
613
+ if (key.name === "enter" && !key.shift && !key.meta && !key.ctrl) {
614
+ await submitComposer();
516
615
  return;
517
616
  }
518
- startBusy("planner");
519
- await appendMessage({
520
- role: "user",
521
- content: text
522
- });
523
- renderTranscript();
524
- setSidebar();
525
- setFooter();
526
- screen.render();
527
- try {
528
- const reply = await (0, agent_1.requestPlannerReply)(workspace, messages, { planId });
529
- await appendMessage({
530
- role: "assistant",
531
- content: reply
532
- });
617
+ if ((key.name === "enter" && (key.shift || key.meta)) || (key.ctrl && key.name === "j")) {
618
+ composerValue += "\n";
619
+ renderComposer();
620
+ screen.render();
621
+ return;
533
622
  }
534
- catch (error) {
535
- await appendMessage({
536
- role: "system",
537
- content: `Planner call failed: ${error instanceof Error ? error.message : String(error)}`
538
- });
623
+ if (key.name === "backspace") {
624
+ composerValue = removeLastCodePoint(composerValue);
625
+ renderComposer();
626
+ screen.render();
627
+ return;
539
628
  }
540
- finally {
541
- stopBusy();
542
- renderTranscript();
543
- setSidebar();
544
- setFooter();
545
- input.focus();
629
+ if (key.ctrl || key.meta) {
630
+ return;
631
+ }
632
+ if (ch && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) {
633
+ composerValue += ch;
634
+ renderComposer();
546
635
  screen.render();
547
636
  }
548
637
  });
@@ -563,8 +652,10 @@ async function launchStudio(options = {}) {
563
652
  });
564
653
  }
565
654
  renderTranscript();
655
+ renderComposer();
566
656
  setSidebar("booting...");
567
657
  setFooter(" Starting studio... ");
658
+ screen.program.hideCursor();
568
659
  screen.render();
569
660
  input.focus();
570
661
  await refreshEnvironment();
@@ -633,6 +724,17 @@ function formatAutoSummary(packState) {
633
724
  }
634
725
  return lines.join("\n");
635
726
  }
727
+ function formatAdviceSummary(advice) {
728
+ if (!advice) {
729
+ return "run /advice for AI guidance";
730
+ }
731
+ const lines = [
732
+ `problem: ${advice.problemStatement}`,
733
+ `clarity: ${advice.clarity}`,
734
+ `next: ${advice.nextAction}`
735
+ ];
736
+ return lines.join("\n");
737
+ }
636
738
  function formatAgentSummary(activeAgent, statuses) {
637
739
  return [`active: ${activeAgent.label}`, ...statuses.map((status) => formatAgentStatusLine(status, status.id === activeAgent.id))]
638
740
  .join("\n");
@@ -774,28 +876,8 @@ function deriveDisplayMode(packState) {
774
876
  }
775
877
  return packState.trackerReadable ? "Plan Written - Needs Step" : "Gathering Context";
776
878
  }
777
- function configureComposer(input) {
778
- const internals = input;
779
- const originalListener = internals._listener;
780
- if (!originalListener) {
781
- return;
782
- }
783
- internals._listener = function patchedListener(ch, key) {
784
- if (key.name === "enter" && !key.shift && !key.meta && !key.ctrl) {
785
- if (typeof internals._done === "function") {
786
- internals._done(null, internals.value ?? "");
787
- }
788
- return;
789
- }
790
- if ((key.name === "enter" && (key.shift || key.meta)) || (key.ctrl && key.name === "j")) {
791
- originalListener.call(this, "\n", { ...key, name: "enter" });
792
- return;
793
- }
794
- originalListener.call(this, ch, key);
795
- };
796
- }
797
879
  function renderStatusMessage(workspace, packState) {
798
- return [
880
+ const lines = [
799
881
  `Plan: ${packState.planId}`,
800
882
  `Planning dir: ${(0, workspace_1.getPlanningPackPaths)(workspace, { planId: packState.planId }).relativeDir}`,
801
883
  `Mode: ${packState.mode}${packState.hasFailureOverlay ? " [last run failed]" : ""}`,
@@ -804,7 +886,11 @@ function renderStatusMessage(workspace, packState) {
804
886
  `Execution activated: ${packState.executionActivated ? "yes" : "no"}`,
805
887
  `Auto mode: ${packState.autoRun?.status ?? "idle"}`,
806
888
  `Next step: ${packState.nextStepSummary?.id ?? packState.currentPosition.nextRecommended ?? "none queued"}`
807
- ].join("\n");
889
+ ];
890
+ if (packState.advice) {
891
+ lines.push(`Advice next: ${packState.advice.nextAction}`);
892
+ }
893
+ return lines.join("\n");
808
894
  }
809
895
  function renderReadinessMessage(packState) {
810
896
  return [
@@ -820,6 +906,19 @@ function renderReadinessMessage(packState) {
820
906
  : "Next: keep gathering repo truth, constraints, and the first execution slice."
821
907
  ].join("\n");
822
908
  }
909
+ function renderAdviceMessage(advice) {
910
+ return [
911
+ `Problem: ${advice.problemStatement}`,
912
+ `Clarity: ${advice.clarity}`,
913
+ `Assessment: ${advice.stateAssessment}`,
914
+ `Research: ${advice.researchNeeded.length > 0 ? advice.researchNeeded.join(" | ") : "none"}`,
915
+ `Advice: ${advice.advice}`,
916
+ `Next: ${advice.nextAction}`
917
+ ].join("\n");
918
+ }
919
+ function removeLastCodePoint(value) {
920
+ return Array.from(value).slice(0, -1).join("");
921
+ }
823
922
  function renderPlanUsageMessage(currentPlanId, paths) {
824
923
  return [
825
924
  `Current plan: ${currentPlanId}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launch11/srgical",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "A polished local-first CLI for planning and executing long AI-driven delivery sequences.",
5
5
  "files": [
6
6
  "dist",