@mindstudio-ai/remy 0.1.140 → 0.1.141

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,5 @@
1
+ ---
2
+ trigger: approvePlan
3
+ ---
4
+
5
+ The user has approved the implementation plan in .remy-plan.md. Proceed with implementing the plan. Delete .remy-plan.md when all planned work is complete.
@@ -8,7 +8,7 @@ First, review the specific item and think about how it fits with the existing sp
8
8
 
9
9
  Then, ask the user any clarifying questions about anything that is ambiguous or requires additional input. Consult the team for any design work, architecture review, or SDK guidance - even if they're just quick questions (that's what the team is there for - they want to help and feel valuable!). Additive feature work is the most fun to build, but make sure you take a step back first and consider any technical debt or organization implications - we need to keep the codebase clean, tidy, bug-free, and easy/intuitive to manage. When adding new features, this might mean extracting shared helpers to separate files, breaking apart components into multiple files, making new folders, etc. - Don't go overboard, but also don't keep adding to one giant file until it ends up unmanageable. Consider organization and structure when building the plan.
10
10
 
11
- Then, put together a plan to build out the feature. Present the plan to the user for their approval.
11
+ Then, put together a plan to build out the feature. Write the plan with `writePlan` for the user's approval.
12
12
 
13
13
  When they've approved the plan, be sure to update the spec first - remember, the spec is the source of truth about the product. Then, build everything in one turn, using the spec as the master plan.
14
14
 
@@ -8,4 +8,4 @@ First, explore the project and get a sense of what has built.
8
8
 
9
9
  Read specific files and trace paths, don't just guess at how something works based on partial information. When you are finished exploring, identiy any high-impact areas for cleaning up the code. This can include things like directory organization, splitting things into separate files to make the project easier to scan, breaking up large components or screens, deduplicating copy-pasted code, removing dead code, and anything else that will leave the project more robust, reliable, and easier to scan/work on for developers. Do not optimize for the sake of optimizing, only focus on reducing real technical debt and leaving the product better and a more pleasant experience for other developers working on it.
10
10
 
11
- When you have a plan, run it by the `codeSanityCheck` to get a second set of eyes. Then, present the plan to the user. The technical detail is important so the user has a sense of what the plan entails, but remember that the user is not very technical, so it is equally important to help them understand the why behind any refactors.
11
+ When you have a plan, run it by the `codeSanityCheck` to get a second set of eyes. Then, write the plan with `writePlan` for the user to review. The technical detail is important so the user has a sense of what the plan entails, but remember that the user is not very technical, so it is equally important to help them understand the why behind any refactors.
@@ -0,0 +1,5 @@
1
+ ---
2
+ trigger: rejectPlan
3
+ ---
4
+
5
+ The user has rejected the implementation plan. Acknowledge and continue the conversation.
package/dist/headless.js CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/headless.ts
8
8
  import { createInterface } from "readline";
9
- import { writeFileSync } from "fs";
9
+ import { writeFileSync, readFileSync, unlinkSync } from "fs";
10
10
 
11
11
  // src/logger.ts
12
12
  import fs from "fs";
@@ -292,6 +292,28 @@ function parseFrontmatter(filePath) {
292
292
  return { name: "", description: "", type: "" };
293
293
  }
294
294
  }
295
+ function loadPlanStatus() {
296
+ try {
297
+ const content = fs4.readFileSync(".remy-plan.md", "utf-8");
298
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
299
+ const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
300
+ if (status === "pending") {
301
+ return `
302
+ <pending_plan>
303
+ You have a pending implementation plan in .remy-plan.md awaiting user approval. Do NOT begin implementing the plan until the user approves it. You may continue chatting, answering questions, and revising the plan if asked. To revise, call writePlan again with updated content.
304
+ </pending_plan>`;
305
+ }
306
+ if (status === "approved") {
307
+ return `
308
+ <approved_plan>
309
+ The user has approved your implementation plan in .remy-plan.md. You may reference it during implementation. Delete the file when you have finished all planned work.
310
+ </approved_plan>`;
311
+ }
312
+ return "";
313
+ } catch {
314
+ return "";
315
+ }
316
+ }
295
317
  function loadProjectFileListing() {
296
318
  try {
297
319
  const entries = fs4.readdirSync(".", { withFileTypes: true });
@@ -421,7 +443,7 @@ ${isLspConfigured() ? `<typescript_lsp>
421
443
  </code_authoring_instructions>
422
444
 
423
445
  {{static/instructions.md}}
424
-
446
+ ${loadPlanStatus()}
425
447
  <conversation_summaries>
426
448
  Your conversation history may include <prior_conversation_summary> blocks in the user's messages. These are automated summaries of earlier messages that have been compacted to save context space. The user does not see this summary, they see the full conversation history in their UI. Treat the summary as ground truth for what happened before, but do not reference it directly to the user ("as mentioned in the summary..."). Just continue naturally as if you remember the prior work.
427
449
 
@@ -1363,12 +1385,14 @@ var presentPublishPlanTool = {
1363
1385
  }
1364
1386
  };
1365
1387
 
1366
- // src/tools/spec/presentPlan.ts
1367
- var presentPlanTool = {
1388
+ // src/tools/spec/writePlan.ts
1389
+ import fs9 from "fs/promises";
1390
+ var PLAN_FILE = ".remy-plan.md";
1391
+ var writePlanTool = {
1368
1392
  clearable: false,
1369
1393
  definition: {
1370
- name: "presentPlan",
1371
- description: "Present an implementation plan for user approval before making changes. Use this only for large, multi-step changes like new features, new interface types, or when the user explicitly asks to see a plan. Most work should be done autonomously without a plan. Write a clear markdown summary of what you intend to do in plain language \u2014 describe the changes from the user's perspective, not as a list of files and code paths. If the user rejects with feedback, revise and present again.",
1394
+ name: "writePlan",
1395
+ description: "Write an implementation plan for user approval before making changes. Use this only for large, multi-step changes like new features, new interface types, or when the user explicitly asks to see a plan. Most work should be done autonomously without a plan. Write a clear markdown summary of what you intend to do in plain language \u2014 describe the changes from the user's perspective, not as a list of files and code paths. The plan is saved to .remy-plan.md and the user can review, discuss, and approve or reject it. If the user asks for revisions, call this tool again with updated content to overwrite the plan.",
1372
1396
  inputSchema: {
1373
1397
  type: "object",
1374
1398
  properties: {
@@ -1380,9 +1404,15 @@ var presentPlanTool = {
1380
1404
  required: ["content"]
1381
1405
  }
1382
1406
  },
1383
- streaming: {},
1384
- async execute() {
1385
- return "approved";
1407
+ async execute(input) {
1408
+ const content = input.content;
1409
+ const file = `---
1410
+ status: pending
1411
+ ---
1412
+
1413
+ ${content}`;
1414
+ await fs9.writeFile(PLAN_FILE, file, "utf-8");
1415
+ return "Plan written to .remy-plan.md. Waiting for user approval.";
1386
1416
  }
1387
1417
  };
1388
1418
 
@@ -1546,7 +1576,7 @@ var confirmDestructiveActionTool = {
1546
1576
  clearable: false,
1547
1577
  definition: {
1548
1578
  name: "confirmDestructiveAction",
1549
- description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentSyncPlan, presentPublishPlan, or presentPlan (those already include approval). Do not use before onboarding state transitions.",
1579
+ description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentSyncPlan, presentPublishPlan, or writePlan (those already include approval). Do not use before onboarding state transitions.",
1550
1580
  inputSchema: {
1551
1581
  type: "object",
1552
1582
  properties: {
@@ -1763,7 +1793,7 @@ var compactConversationTool = {
1763
1793
  };
1764
1794
 
1765
1795
  // src/tools/code/readFile.ts
1766
- import fs9 from "fs/promises";
1796
+ import fs10 from "fs/promises";
1767
1797
  var DEFAULT_MAX_LINES2 = 500;
1768
1798
  function isBinary(buffer) {
1769
1799
  const sample = buffer.subarray(0, 8192);
@@ -1800,7 +1830,7 @@ var readFileTool = {
1800
1830
  },
1801
1831
  async execute(input) {
1802
1832
  try {
1803
- const buffer = await fs9.readFile(input.path);
1833
+ const buffer = await fs10.readFile(input.path);
1804
1834
  if (isBinary(buffer)) {
1805
1835
  const size = buffer.length;
1806
1836
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -1834,7 +1864,7 @@ var readFileTool = {
1834
1864
  };
1835
1865
 
1836
1866
  // src/tools/code/writeFile.ts
1837
- import fs10 from "fs/promises";
1867
+ import fs11 from "fs/promises";
1838
1868
  import path6 from "path";
1839
1869
  var writeFileTool = {
1840
1870
  clearable: true,
@@ -1872,7 +1902,7 @@ var writeFileTool = {
1872
1902
  lastNewlineCount = newlineCount;
1873
1903
  const lastNewline = partial.content.lastIndexOf("\n");
1874
1904
  const completeContent = partial.content.substring(0, lastNewline + 1);
1875
- const oldContent = await fs10.readFile(partial.path, "utf-8").catch(() => "");
1905
+ const oldContent = await fs11.readFile(partial.path, "utf-8").catch(() => "");
1876
1906
  return `Writing ${partial.path} (${newlineCount} lines)
1877
1907
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1878
1908
  }
@@ -1881,13 +1911,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1881
1911
  async execute(input) {
1882
1912
  const release = await acquireFileLock(input.path);
1883
1913
  try {
1884
- await fs10.mkdir(path6.dirname(input.path), { recursive: true });
1914
+ await fs11.mkdir(path6.dirname(input.path), { recursive: true });
1885
1915
  let oldContent = null;
1886
1916
  try {
1887
- oldContent = await fs10.readFile(input.path, "utf-8");
1917
+ oldContent = await fs11.readFile(input.path, "utf-8");
1888
1918
  } catch {
1889
1919
  }
1890
- await fs10.writeFile(input.path, input.content, "utf-8");
1920
+ await fs11.writeFile(input.path, input.content, "utf-8");
1891
1921
  const lineCount = input.content.split("\n").length;
1892
1922
  const label = oldContent !== null ? "Wrote" : "Created";
1893
1923
  return `${label} ${input.path} (${lineCount} lines)
@@ -1901,7 +1931,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
1901
1931
  };
1902
1932
 
1903
1933
  // src/tools/code/editFile/index.ts
1904
- import fs11 from "fs/promises";
1934
+ import fs12 from "fs/promises";
1905
1935
 
1906
1936
  // src/tools/code/editFile/_helpers.ts
1907
1937
  function buildLineOffsets(content) {
@@ -2014,7 +2044,7 @@ var editFileTool = {
2014
2044
  async execute(input) {
2015
2045
  const release = await acquireFileLock(input.path);
2016
2046
  try {
2017
- const content = await fs11.readFile(input.path, "utf-8");
2047
+ const content = await fs12.readFile(input.path, "utf-8");
2018
2048
  const { old_string, new_string, replace_all } = input;
2019
2049
  const occurrences = findOccurrences(content, old_string);
2020
2050
  if (replace_all) {
@@ -2030,7 +2060,7 @@ var editFileTool = {
2030
2060
  new_string
2031
2061
  );
2032
2062
  }
2033
- await fs11.writeFile(input.path, updated, "utf-8");
2063
+ await fs12.writeFile(input.path, updated, "utf-8");
2034
2064
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2035
2065
  ${unifiedDiff(input.path, content, updated)}`;
2036
2066
  }
@@ -2041,7 +2071,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2041
2071
  old_string.length,
2042
2072
  new_string
2043
2073
  );
2044
- await fs11.writeFile(input.path, updated, "utf-8");
2074
+ await fs12.writeFile(input.path, updated, "utf-8");
2045
2075
  return `Updated ${input.path}
2046
2076
  ${unifiedDiff(input.path, content, updated)}`;
2047
2077
  }
@@ -2057,7 +2087,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2057
2087
  flex.matchedText.length,
2058
2088
  new_string
2059
2089
  );
2060
- await fs11.writeFile(input.path, updated, "utf-8");
2090
+ await fs12.writeFile(input.path, updated, "utf-8");
2061
2091
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2062
2092
  ${unifiedDiff(input.path, content, updated)}`;
2063
2093
  }
@@ -2268,12 +2298,12 @@ var globTool = {
2268
2298
  };
2269
2299
 
2270
2300
  // src/tools/code/listDir.ts
2271
- import fs12 from "fs/promises";
2301
+ import fs13 from "fs/promises";
2272
2302
  import path7 from "path";
2273
2303
  var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
2274
2304
  var MAX_CHILDREN = 15;
2275
2305
  async function readAndSort(dirPath) {
2276
- const entries = await fs12.readdir(dirPath, { withFileTypes: true });
2306
+ const entries = await fs13.readdir(dirPath, { withFileTypes: true });
2277
2307
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2278
2308
  if (a.isDirectory() && !b.isDirectory()) {
2279
2309
  return -1;
@@ -2314,7 +2344,7 @@ function formatSize(bytes) {
2314
2344
  }
2315
2345
  async function formatFile(dirPath, name, indent) {
2316
2346
  try {
2317
- const stat = await fs12.stat(path7.join(dirPath, name));
2347
+ const stat = await fs13.stat(path7.join(dirPath, name));
2318
2348
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
2319
2349
  } catch {
2320
2350
  return `${indent}${name}`;
@@ -3381,11 +3411,11 @@ var BROWSER_TOOLS = [
3381
3411
  var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
3382
3412
 
3383
3413
  // src/subagents/browserAutomation/prompt.ts
3384
- import fs13 from "fs";
3414
+ import fs14 from "fs";
3385
3415
  var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
3386
3416
  function getBrowserAutomationPrompt() {
3387
3417
  try {
3388
- const appSpec = fs13.readFileSync("src/app.md", "utf-8").trim();
3418
+ const appSpec = fs14.readFileSync("src/app.md", "utf-8").trim();
3389
3419
  return `${BASE_PROMPT}
3390
3420
 
3391
3421
  <!-- cache_breakpoint -->
@@ -4227,12 +4257,12 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
4227
4257
  }
4228
4258
 
4229
4259
  // src/subagents/common/context.ts
4230
- import fs14 from "fs";
4260
+ import fs15 from "fs";
4231
4261
  import path8 from "path";
4232
4262
  function walkMdFiles2(dir, skip) {
4233
4263
  const files = [];
4234
4264
  try {
4235
- for (const entry of fs14.readdirSync(dir, { withFileTypes: true })) {
4265
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
4236
4266
  const full = path8.join(dir, entry.name);
4237
4267
  if (entry.isDirectory()) {
4238
4268
  if (!skip?.has(entry.name)) {
@@ -4248,7 +4278,7 @@ function walkMdFiles2(dir, skip) {
4248
4278
  }
4249
4279
  function parseFrontmatter2(filePath) {
4250
4280
  try {
4251
- const content = fs14.readFileSync(filePath, "utf-8");
4281
+ const content = fs15.readFileSync(filePath, "utf-8");
4252
4282
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4253
4283
  if (!match) {
4254
4284
  return {};
@@ -4294,7 +4324,7 @@ function loadRoadmapIndex() {
4294
4324
  const parts = [];
4295
4325
  try {
4296
4326
  const indexJson = JSON.parse(
4297
- fs14.readFileSync("src/roadmap/index.json", "utf-8")
4327
+ fs15.readFileSync("src/roadmap/index.json", "utf-8")
4298
4328
  );
4299
4329
  if (indexJson.lanes?.length > 0) {
4300
4330
  const laneLines = indexJson.lanes.map(
@@ -4397,7 +4427,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
4397
4427
  }
4398
4428
 
4399
4429
  // src/subagents/designExpert/data/sampleCache.ts
4400
- import fs15 from "fs";
4430
+ import fs16 from "fs";
4401
4431
  var SAMPLE_FILE = ".remy-design-sample.json";
4402
4432
  var cached = null;
4403
4433
  function generateIndices(poolSize, sampleSize) {
@@ -4411,14 +4441,14 @@ function generateIndices(poolSize, sampleSize) {
4411
4441
  }
4412
4442
  function load() {
4413
4443
  try {
4414
- return JSON.parse(fs15.readFileSync(SAMPLE_FILE, "utf-8"));
4444
+ return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
4415
4445
  } catch {
4416
4446
  return null;
4417
4447
  }
4418
4448
  }
4419
4449
  function save(indices) {
4420
4450
  try {
4421
- fs15.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4451
+ fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4422
4452
  } catch {
4423
4453
  }
4424
4454
  }
@@ -4747,7 +4777,7 @@ var VISION_TOOLS = [
4747
4777
  ];
4748
4778
 
4749
4779
  // src/subagents/productVision/executor.ts
4750
- import fs16 from "fs";
4780
+ import fs17 from "fs";
4751
4781
  import path9 from "path";
4752
4782
  var ROADMAP_DIR = "src/roadmap";
4753
4783
  var PITCH_DECK_SHELL = readAsset(
@@ -4762,13 +4792,13 @@ async function executeVisionTool(name, input, context) {
4762
4792
  case "writeFile": {
4763
4793
  const filePath = resolve(input.path);
4764
4794
  try {
4765
- fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
4795
+ fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
4766
4796
  let oldContent = null;
4767
4797
  try {
4768
- oldContent = fs16.readFileSync(filePath, "utf-8");
4798
+ oldContent = fs17.readFileSync(filePath, "utf-8");
4769
4799
  } catch {
4770
4800
  }
4771
- fs16.writeFileSync(filePath, input.content, "utf-8");
4801
+ fs17.writeFileSync(filePath, input.content, "utf-8");
4772
4802
  const lineCount = input.content.split("\n").length;
4773
4803
  const label = oldContent !== null ? "Wrote" : "Created";
4774
4804
  return `${label} ${filePath} (${lineCount} lines)
@@ -4780,11 +4810,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
4780
4810
  case "deleteFile": {
4781
4811
  const filePath = resolve(input.path);
4782
4812
  try {
4783
- if (!fs16.existsSync(filePath)) {
4813
+ if (!fs17.existsSync(filePath)) {
4784
4814
  return `Error: ${filePath} does not exist`;
4785
4815
  }
4786
- const oldContent = fs16.readFileSync(filePath, "utf-8");
4787
- fs16.unlinkSync(filePath);
4816
+ const oldContent = fs17.readFileSync(filePath, "utf-8");
4817
+ fs17.unlinkSync(filePath);
4788
4818
  return `Deleted ${filePath}
4789
4819
  ${unifiedDiff(filePath, oldContent, "")}`;
4790
4820
  } catch (err) {
@@ -4797,8 +4827,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
4797
4827
  }
4798
4828
  const filePath = resolve("pitch.html");
4799
4829
  try {
4800
- fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
4801
- const existing = fs16.existsSync(filePath) ? fs16.readFileSync(filePath, "utf-8").trim() : "";
4830
+ fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
4831
+ const existing = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8").trim() : "";
4802
4832
  const currentDeck = existing || PITCH_DECK_SHELL;
4803
4833
  const task = `
4804
4834
  <pitch_content>${input.task}</pitch_content>
@@ -4823,7 +4853,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
4823
4853
  /```(?:html|wireframe)\n([\s\S]*?)```/
4824
4854
  );
4825
4855
  const html = htmlMatch ? htmlMatch[1].trim() : result;
4826
- fs16.writeFileSync(filePath, html, "utf-8");
4856
+ fs17.writeFileSync(filePath, html, "utf-8");
4827
4857
  return `Pitch deck written successfully.`;
4828
4858
  } catch (err) {
4829
4859
  return `Error generating pitch deck: ${err.message}`;
@@ -5068,7 +5098,7 @@ var ALL_TOOLS = [
5068
5098
  clearSyncStatusTool,
5069
5099
  presentSyncPlanTool,
5070
5100
  presentPublishPlanTool,
5071
- presentPlanTool,
5101
+ writePlanTool,
5072
5102
  // Spec
5073
5103
  readSpecTool,
5074
5104
  writeSpecTool,
@@ -5110,12 +5140,12 @@ function executeTool(name, input, context) {
5110
5140
  }
5111
5141
 
5112
5142
  // src/session.ts
5113
- import fs17 from "fs";
5143
+ import fs18 from "fs";
5114
5144
  var log7 = createLogger("session");
5115
5145
  var SESSION_FILE = ".remy-session.json";
5116
5146
  function loadSession(state) {
5117
5147
  try {
5118
- const raw = fs17.readFileSync(SESSION_FILE, "utf-8");
5148
+ const raw = fs18.readFileSync(SESSION_FILE, "utf-8");
5119
5149
  const data = JSON.parse(raw);
5120
5150
  if (Array.isArray(data.messages) && data.messages.length > 0) {
5121
5151
  state.messages = sanitizeMessages(data.messages);
@@ -5164,7 +5194,7 @@ function sanitizeMessages(messages) {
5164
5194
  }
5165
5195
  function saveSession(state) {
5166
5196
  try {
5167
- fs17.writeFileSync(
5197
+ fs18.writeFileSync(
5168
5198
  SESSION_FILE,
5169
5199
  JSON.stringify({ messages: state.messages }, null, 2),
5170
5200
  "utf-8"
@@ -5177,7 +5207,7 @@ function saveSession(state) {
5177
5207
  function clearSession(state) {
5178
5208
  state.messages = [];
5179
5209
  try {
5180
- fs17.unlinkSync(SESSION_FILE);
5210
+ fs18.unlinkSync(SESSION_FILE);
5181
5211
  } catch {
5182
5212
  }
5183
5213
  }
@@ -5405,7 +5435,6 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
5405
5435
  "clearSyncStatus",
5406
5436
  "presentSyncPlan",
5407
5437
  "presentPublishPlan",
5408
- "presentPlan",
5409
5438
  "confirmDestructiveAction",
5410
5439
  "runScenario",
5411
5440
  "runMethod",
@@ -6156,7 +6185,6 @@ ${xmlParts}
6156
6185
  const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
6157
6186
  "promptUser",
6158
6187
  "confirmDestructiveAction",
6159
- "presentPlan",
6160
6188
  "presentSyncPlan",
6161
6189
  "presentPublishPlan"
6162
6190
  ]);
@@ -6374,6 +6402,23 @@ ${xmlParts}
6374
6402
  userMessage = resolved;
6375
6403
  }
6376
6404
  const isHidden = resolved !== null || !!parsed.hidden;
6405
+ const rawText = parsed.text ?? "";
6406
+ if (rawText.startsWith("@@automated::approvePlan@@")) {
6407
+ try {
6408
+ const plan = readFileSync(".remy-plan.md", "utf-8");
6409
+ writeFileSync(
6410
+ ".remy-plan.md",
6411
+ plan.replace(/^status:\s*pending/m, "status: approved"),
6412
+ "utf-8"
6413
+ );
6414
+ } catch {
6415
+ }
6416
+ } else if (rawText.startsWith("@@automated::rejectPlan@@")) {
6417
+ try {
6418
+ unlinkSync(".remy-plan.md");
6419
+ } catch {
6420
+ }
6421
+ }
6377
6422
  const onboardingState = parsed.onboardingState ?? "onboardingFinished";
6378
6423
  const system = buildSystemPrompt(
6379
6424
  onboardingState,
package/dist/index.js CHANGED
@@ -853,16 +853,18 @@ var init_presentPublishPlan = __esm({
853
853
  }
854
854
  });
855
855
 
856
- // src/tools/spec/presentPlan.ts
857
- var presentPlanTool;
858
- var init_presentPlan = __esm({
859
- "src/tools/spec/presentPlan.ts"() {
856
+ // src/tools/spec/writePlan.ts
857
+ import fs6 from "fs/promises";
858
+ var PLAN_FILE, writePlanTool;
859
+ var init_writePlan = __esm({
860
+ "src/tools/spec/writePlan.ts"() {
860
861
  "use strict";
861
- presentPlanTool = {
862
+ PLAN_FILE = ".remy-plan.md";
863
+ writePlanTool = {
862
864
  clearable: false,
863
865
  definition: {
864
- name: "presentPlan",
865
- description: "Present an implementation plan for user approval before making changes. Use this only for large, multi-step changes like new features, new interface types, or when the user explicitly asks to see a plan. Most work should be done autonomously without a plan. Write a clear markdown summary of what you intend to do in plain language \u2014 describe the changes from the user's perspective, not as a list of files and code paths. If the user rejects with feedback, revise and present again.",
866
+ name: "writePlan",
867
+ description: "Write an implementation plan for user approval before making changes. Use this only for large, multi-step changes like new features, new interface types, or when the user explicitly asks to see a plan. Most work should be done autonomously without a plan. Write a clear markdown summary of what you intend to do in plain language \u2014 describe the changes from the user's perspective, not as a list of files and code paths. The plan is saved to .remy-plan.md and the user can review, discuss, and approve or reject it. If the user asks for revisions, call this tool again with updated content to overwrite the plan.",
866
868
  inputSchema: {
867
869
  type: "object",
868
870
  properties: {
@@ -874,9 +876,15 @@ var init_presentPlan = __esm({
874
876
  required: ["content"]
875
877
  }
876
878
  },
877
- streaming: {},
878
- async execute() {
879
- return "approved";
879
+ async execute(input) {
880
+ const content = input.content;
881
+ const file = `---
882
+ status: pending
883
+ ---
884
+
885
+ ${content}`;
886
+ await fs6.writeFile(PLAN_FILE, file, "utf-8");
887
+ return "Plan written to .remy-plan.md. Waiting for user approval.";
880
888
  }
881
889
  };
882
890
  }
@@ -1058,7 +1066,7 @@ var init_confirmDestructiveAction = __esm({
1058
1066
  clearable: false,
1059
1067
  definition: {
1060
1068
  name: "confirmDestructiveAction",
1061
- description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentSyncPlan, presentPublishPlan, or presentPlan (those already include approval). Do not use before onboarding state transitions.",
1069
+ description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentSyncPlan, presentPublishPlan, or writePlan (those already include approval). Do not use before onboarding state transitions.",
1062
1070
  inputSchema: {
1063
1071
  type: "object",
1064
1072
  properties: {
@@ -1279,12 +1287,12 @@ var init_setProjectMetadata = __esm({
1279
1287
  });
1280
1288
 
1281
1289
  // src/assets.ts
1282
- import fs6 from "fs";
1290
+ import fs7 from "fs";
1283
1291
  import path3 from "path";
1284
1292
  function findRoot(start) {
1285
1293
  let dir = start;
1286
1294
  while (dir !== path3.dirname(dir)) {
1287
- if (fs6.existsSync(path3.join(dir, "package.json"))) {
1295
+ if (fs7.existsSync(path3.join(dir, "package.json"))) {
1288
1296
  return dir;
1289
1297
  }
1290
1298
  dir = path3.dirname(dir);
@@ -1297,7 +1305,7 @@ function assetPath(...segments) {
1297
1305
  function readAsset(...segments) {
1298
1306
  const full = assetPath(...segments);
1299
1307
  try {
1300
- return fs6.readFileSync(full, "utf-8").trim();
1308
+ return fs7.readFileSync(full, "utf-8").trim();
1301
1309
  } catch {
1302
1310
  throw new Error(`Required asset missing: ${full}`);
1303
1311
  }
@@ -1305,7 +1313,7 @@ function readAsset(...segments) {
1305
1313
  function readJsonAsset(fallback, ...segments) {
1306
1314
  const full = assetPath(...segments);
1307
1315
  try {
1308
- return JSON.parse(fs6.readFileSync(full, "utf-8"));
1316
+ return JSON.parse(fs7.readFileSync(full, "utf-8"));
1309
1317
  } catch {
1310
1318
  return fallback;
1311
1319
  }
@@ -1317,7 +1325,7 @@ var init_assets = __esm({
1317
1325
  ROOT = findRoot(
1318
1326
  import.meta.dirname ?? path3.dirname(new URL(import.meta.url).pathname)
1319
1327
  );
1320
- ASSETS_BASE = fs6.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
1328
+ ASSETS_BASE = fs7.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
1321
1329
  }
1322
1330
  });
1323
1331
 
@@ -1606,12 +1614,12 @@ var init_lsp = __esm({
1606
1614
  });
1607
1615
 
1608
1616
  // src/prompt/static/projectContext.ts
1609
- import fs7 from "fs";
1617
+ import fs8 from "fs";
1610
1618
  import path4 from "path";
1611
1619
  function loadProjectInstructions() {
1612
1620
  for (const file of AGENT_INSTRUCTION_FILES) {
1613
1621
  try {
1614
- const content = fs7.readFileSync(file, "utf-8").trim();
1622
+ const content = fs8.readFileSync(file, "utf-8").trim();
1615
1623
  if (content) {
1616
1624
  return `
1617
1625
  ## Project Instructions (${file})
@@ -1624,7 +1632,7 @@ ${content}`;
1624
1632
  }
1625
1633
  function loadProjectManifest() {
1626
1634
  try {
1627
- const manifest = fs7.readFileSync("mindstudio.json", "utf-8");
1635
+ const manifest = fs8.readFileSync("mindstudio.json", "utf-8");
1628
1636
  return `
1629
1637
  ## Project Manifest (mindstudio.json)
1630
1638
  \`\`\`json
@@ -1665,7 +1673,7 @@ ${entries.join("\n")}`;
1665
1673
  function walkMdFiles(dir) {
1666
1674
  const results = [];
1667
1675
  try {
1668
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
1676
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
1669
1677
  for (const entry of entries) {
1670
1678
  const full = path4.join(dir, entry.name);
1671
1679
  if (entry.isDirectory()) {
@@ -1680,7 +1688,7 @@ function walkMdFiles(dir) {
1680
1688
  }
1681
1689
  function parseFrontmatter(filePath) {
1682
1690
  try {
1683
- const content = fs7.readFileSync(filePath, "utf-8");
1691
+ const content = fs8.readFileSync(filePath, "utf-8");
1684
1692
  const match = content.match(/^---\n([\s\S]*?)\n---/);
1685
1693
  if (!match) {
1686
1694
  return { name: "", description: "", type: "" };
@@ -1694,9 +1702,31 @@ function parseFrontmatter(filePath) {
1694
1702
  return { name: "", description: "", type: "" };
1695
1703
  }
1696
1704
  }
1705
+ function loadPlanStatus() {
1706
+ try {
1707
+ const content = fs8.readFileSync(".remy-plan.md", "utf-8");
1708
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
1709
+ const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
1710
+ if (status === "pending") {
1711
+ return `
1712
+ <pending_plan>
1713
+ You have a pending implementation plan in .remy-plan.md awaiting user approval. Do NOT begin implementing the plan until the user approves it. You may continue chatting, answering questions, and revising the plan if asked. To revise, call writePlan again with updated content.
1714
+ </pending_plan>`;
1715
+ }
1716
+ if (status === "approved") {
1717
+ return `
1718
+ <approved_plan>
1719
+ The user has approved your implementation plan in .remy-plan.md. You may reference it during implementation. Delete the file when you have finished all planned work.
1720
+ </approved_plan>`;
1721
+ }
1722
+ return "";
1723
+ } catch {
1724
+ return "";
1725
+ }
1726
+ }
1697
1727
  function loadProjectFileListing() {
1698
1728
  try {
1699
- const entries = fs7.readdirSync(".", { withFileTypes: true });
1729
+ const entries = fs8.readdirSync(".", { withFileTypes: true });
1700
1730
  const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
1701
1731
  if (a.isDirectory() && !b.isDirectory()) {
1702
1732
  return -1;
@@ -1845,7 +1875,7 @@ ${isLspConfigured() ? `<typescript_lsp>
1845
1875
  </code_authoring_instructions>
1846
1876
 
1847
1877
  {{static/instructions.md}}
1848
-
1878
+ ${loadPlanStatus()}
1849
1879
  <conversation_summaries>
1850
1880
  Your conversation history may include <prior_conversation_summary> blocks in the user's messages. These are automated summaries of earlier messages that have been compacted to save context space. The user does not see this summary, they see the full conversation history in their UI. Treat the summary as ground truth for what happened before, but do not reference it directly to the user ("as mentioned in the summary..."). Just continue naturally as if you remember the prior work.
1851
1881
 
@@ -1884,10 +1914,10 @@ var init_prompt = __esm({
1884
1914
  });
1885
1915
 
1886
1916
  // src/session.ts
1887
- import fs8 from "fs";
1917
+ import fs9 from "fs";
1888
1918
  function loadSession(state) {
1889
1919
  try {
1890
- const raw = fs8.readFileSync(SESSION_FILE, "utf-8");
1920
+ const raw = fs9.readFileSync(SESSION_FILE, "utf-8");
1891
1921
  const data = JSON.parse(raw);
1892
1922
  if (Array.isArray(data.messages) && data.messages.length > 0) {
1893
1923
  state.messages = sanitizeMessages(data.messages);
@@ -1936,7 +1966,7 @@ function sanitizeMessages(messages) {
1936
1966
  }
1937
1967
  function saveSession(state) {
1938
1968
  try {
1939
- fs8.writeFileSync(
1969
+ fs9.writeFileSync(
1940
1970
  SESSION_FILE,
1941
1971
  JSON.stringify({ messages: state.messages }, null, 2),
1942
1972
  "utf-8"
@@ -1949,7 +1979,7 @@ function saveSession(state) {
1949
1979
  function clearSession(state) {
1950
1980
  state.messages = [];
1951
1981
  try {
1952
- fs8.unlinkSync(SESSION_FILE);
1982
+ fs9.unlinkSync(SESSION_FILE);
1953
1983
  } catch {
1954
1984
  }
1955
1985
  }
@@ -2023,7 +2053,7 @@ var init_compactConversation = __esm({
2023
2053
  });
2024
2054
 
2025
2055
  // src/tools/code/readFile.ts
2026
- import fs9 from "fs/promises";
2056
+ import fs10 from "fs/promises";
2027
2057
  function isBinary(buffer) {
2028
2058
  const sample = buffer.subarray(0, 8192);
2029
2059
  for (let i = 0; i < sample.length; i++) {
@@ -2064,7 +2094,7 @@ var init_readFile = __esm({
2064
2094
  },
2065
2095
  async execute(input) {
2066
2096
  try {
2067
- const buffer = await fs9.readFile(input.path);
2097
+ const buffer = await fs10.readFile(input.path);
2068
2098
  if (isBinary(buffer)) {
2069
2099
  const size = buffer.length;
2070
2100
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -2100,7 +2130,7 @@ var init_readFile = __esm({
2100
2130
  });
2101
2131
 
2102
2132
  // src/tools/code/writeFile.ts
2103
- import fs10 from "fs/promises";
2133
+ import fs11 from "fs/promises";
2104
2134
  import path5 from "path";
2105
2135
  var writeFileTool;
2106
2136
  var init_writeFile = __esm({
@@ -2144,7 +2174,7 @@ var init_writeFile = __esm({
2144
2174
  lastNewlineCount = newlineCount;
2145
2175
  const lastNewline = partial.content.lastIndexOf("\n");
2146
2176
  const completeContent = partial.content.substring(0, lastNewline + 1);
2147
- const oldContent = await fs10.readFile(partial.path, "utf-8").catch(() => "");
2177
+ const oldContent = await fs11.readFile(partial.path, "utf-8").catch(() => "");
2148
2178
  return `Writing ${partial.path} (${newlineCount} lines)
2149
2179
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2150
2180
  }
@@ -2153,13 +2183,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2153
2183
  async execute(input) {
2154
2184
  const release = await acquireFileLock(input.path);
2155
2185
  try {
2156
- await fs10.mkdir(path5.dirname(input.path), { recursive: true });
2186
+ await fs11.mkdir(path5.dirname(input.path), { recursive: true });
2157
2187
  let oldContent = null;
2158
2188
  try {
2159
- oldContent = await fs10.readFile(input.path, "utf-8");
2189
+ oldContent = await fs11.readFile(input.path, "utf-8");
2160
2190
  } catch {
2161
2191
  }
2162
- await fs10.writeFile(input.path, input.content, "utf-8");
2192
+ await fs11.writeFile(input.path, input.content, "utf-8");
2163
2193
  const lineCount = input.content.split("\n").length;
2164
2194
  const label = oldContent !== null ? "Wrote" : "Created";
2165
2195
  return `${label} ${input.path} (${lineCount} lines)
@@ -2259,7 +2289,7 @@ var init_helpers2 = __esm({
2259
2289
  });
2260
2290
 
2261
2291
  // src/tools/code/editFile/index.ts
2262
- import fs11 from "fs/promises";
2292
+ import fs12 from "fs/promises";
2263
2293
  var editFileTool;
2264
2294
  var init_editFile = __esm({
2265
2295
  "src/tools/code/editFile/index.ts"() {
@@ -2298,7 +2328,7 @@ var init_editFile = __esm({
2298
2328
  async execute(input) {
2299
2329
  const release = await acquireFileLock(input.path);
2300
2330
  try {
2301
- const content = await fs11.readFile(input.path, "utf-8");
2331
+ const content = await fs12.readFile(input.path, "utf-8");
2302
2332
  const { old_string, new_string, replace_all } = input;
2303
2333
  const occurrences = findOccurrences(content, old_string);
2304
2334
  if (replace_all) {
@@ -2314,7 +2344,7 @@ var init_editFile = __esm({
2314
2344
  new_string
2315
2345
  );
2316
2346
  }
2317
- await fs11.writeFile(input.path, updated, "utf-8");
2347
+ await fs12.writeFile(input.path, updated, "utf-8");
2318
2348
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2319
2349
  ${unifiedDiff(input.path, content, updated)}`;
2320
2350
  }
@@ -2325,7 +2355,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2325
2355
  old_string.length,
2326
2356
  new_string
2327
2357
  );
2328
- await fs11.writeFile(input.path, updated, "utf-8");
2358
+ await fs12.writeFile(input.path, updated, "utf-8");
2329
2359
  return `Updated ${input.path}
2330
2360
  ${unifiedDiff(input.path, content, updated)}`;
2331
2361
  }
@@ -2341,7 +2371,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2341
2371
  flex.matchedText.length,
2342
2372
  new_string
2343
2373
  );
2344
- await fs11.writeFile(input.path, updated, "utf-8");
2374
+ await fs12.writeFile(input.path, updated, "utf-8");
2345
2375
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2346
2376
  ${unifiedDiff(input.path, content, updated)}`;
2347
2377
  }
@@ -2572,10 +2602,10 @@ var init_glob = __esm({
2572
2602
  });
2573
2603
 
2574
2604
  // src/tools/code/listDir.ts
2575
- import fs12 from "fs/promises";
2605
+ import fs13 from "fs/promises";
2576
2606
  import path6 from "path";
2577
2607
  async function readAndSort(dirPath) {
2578
- const entries = await fs12.readdir(dirPath, { withFileTypes: true });
2608
+ const entries = await fs13.readdir(dirPath, { withFileTypes: true });
2579
2609
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2580
2610
  if (a.isDirectory() && !b.isDirectory()) {
2581
2611
  return -1;
@@ -2616,7 +2646,7 @@ function formatSize(bytes) {
2616
2646
  }
2617
2647
  async function formatFile(dirPath, name, indent) {
2618
2648
  try {
2619
- const stat = await fs12.stat(path6.join(dirPath, name));
2649
+ const stat = await fs13.stat(path6.join(dirPath, name));
2620
2650
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
2621
2651
  } catch {
2622
2652
  return `${indent}${name}`;
@@ -3777,10 +3807,10 @@ var init_tools = __esm({
3777
3807
  });
3778
3808
 
3779
3809
  // src/subagents/browserAutomation/prompt.ts
3780
- import fs13 from "fs";
3810
+ import fs14 from "fs";
3781
3811
  function getBrowserAutomationPrompt() {
3782
3812
  try {
3783
- const appSpec = fs13.readFileSync("src/app.md", "utf-8").trim();
3813
+ const appSpec = fs14.readFileSync("src/app.md", "utf-8").trim();
3784
3814
  return `${BASE_PROMPT}
3785
3815
 
3786
3816
  <!-- cache_breakpoint -->
@@ -4744,12 +4774,12 @@ var init_tools3 = __esm({
4744
4774
  });
4745
4775
 
4746
4776
  // src/subagents/common/context.ts
4747
- import fs14 from "fs";
4777
+ import fs15 from "fs";
4748
4778
  import path7 from "path";
4749
4779
  function walkMdFiles2(dir, skip) {
4750
4780
  const files = [];
4751
4781
  try {
4752
- for (const entry of fs14.readdirSync(dir, { withFileTypes: true })) {
4782
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
4753
4783
  const full = path7.join(dir, entry.name);
4754
4784
  if (entry.isDirectory()) {
4755
4785
  if (!skip?.has(entry.name)) {
@@ -4765,7 +4795,7 @@ function walkMdFiles2(dir, skip) {
4765
4795
  }
4766
4796
  function parseFrontmatter2(filePath) {
4767
4797
  try {
4768
- const content = fs14.readFileSync(filePath, "utf-8");
4798
+ const content = fs15.readFileSync(filePath, "utf-8");
4769
4799
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4770
4800
  if (!match) {
4771
4801
  return {};
@@ -4811,7 +4841,7 @@ function loadRoadmapIndex() {
4811
4841
  const parts = [];
4812
4842
  try {
4813
4843
  const indexJson = JSON.parse(
4814
- fs14.readFileSync("src/roadmap/index.json", "utf-8")
4844
+ fs15.readFileSync("src/roadmap/index.json", "utf-8")
4815
4845
  );
4816
4846
  if (indexJson.lanes?.length > 0) {
4817
4847
  const laneLines = indexJson.lanes.map(
@@ -4919,7 +4949,7 @@ var init_context = __esm({
4919
4949
  });
4920
4950
 
4921
4951
  // src/subagents/designExpert/data/sampleCache.ts
4922
- import fs15 from "fs";
4952
+ import fs16 from "fs";
4923
4953
  function generateIndices(poolSize, sampleSize) {
4924
4954
  const n = Math.min(sampleSize, poolSize);
4925
4955
  const indices = Array.from({ length: poolSize }, (_, i) => i);
@@ -4931,14 +4961,14 @@ function generateIndices(poolSize, sampleSize) {
4931
4961
  }
4932
4962
  function load() {
4933
4963
  try {
4934
- return JSON.parse(fs15.readFileSync(SAMPLE_FILE, "utf-8"));
4964
+ return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
4935
4965
  } catch {
4936
4966
  return null;
4937
4967
  }
4938
4968
  }
4939
4969
  function save(indices) {
4940
4970
  try {
4941
- fs15.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4971
+ fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4942
4972
  } catch {
4943
4973
  }
4944
4974
  }
@@ -5335,7 +5365,7 @@ var init_tools4 = __esm({
5335
5365
  });
5336
5366
 
5337
5367
  // src/subagents/productVision/executor.ts
5338
- import fs16 from "fs";
5368
+ import fs17 from "fs";
5339
5369
  import path8 from "path";
5340
5370
  function resolve(filePath) {
5341
5371
  return path8.join(ROADMAP_DIR, filePath);
@@ -5345,13 +5375,13 @@ async function executeVisionTool(name, input, context) {
5345
5375
  case "writeFile": {
5346
5376
  const filePath = resolve(input.path);
5347
5377
  try {
5348
- fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
5378
+ fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
5349
5379
  let oldContent = null;
5350
5380
  try {
5351
- oldContent = fs16.readFileSync(filePath, "utf-8");
5381
+ oldContent = fs17.readFileSync(filePath, "utf-8");
5352
5382
  } catch {
5353
5383
  }
5354
- fs16.writeFileSync(filePath, input.content, "utf-8");
5384
+ fs17.writeFileSync(filePath, input.content, "utf-8");
5355
5385
  const lineCount = input.content.split("\n").length;
5356
5386
  const label = oldContent !== null ? "Wrote" : "Created";
5357
5387
  return `${label} ${filePath} (${lineCount} lines)
@@ -5363,11 +5393,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
5363
5393
  case "deleteFile": {
5364
5394
  const filePath = resolve(input.path);
5365
5395
  try {
5366
- if (!fs16.existsSync(filePath)) {
5396
+ if (!fs17.existsSync(filePath)) {
5367
5397
  return `Error: ${filePath} does not exist`;
5368
5398
  }
5369
- const oldContent = fs16.readFileSync(filePath, "utf-8");
5370
- fs16.unlinkSync(filePath);
5399
+ const oldContent = fs17.readFileSync(filePath, "utf-8");
5400
+ fs17.unlinkSync(filePath);
5371
5401
  return `Deleted ${filePath}
5372
5402
  ${unifiedDiff(filePath, oldContent, "")}`;
5373
5403
  } catch (err) {
@@ -5380,8 +5410,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
5380
5410
  }
5381
5411
  const filePath = resolve("pitch.html");
5382
5412
  try {
5383
- fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
5384
- const existing = fs16.existsSync(filePath) ? fs16.readFileSync(filePath, "utf-8").trim() : "";
5413
+ fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
5414
+ const existing = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8").trim() : "";
5385
5415
  const currentDeck = existing || PITCH_DECK_SHELL;
5386
5416
  const task = `
5387
5417
  <pitch_content>${input.task}</pitch_content>
@@ -5406,7 +5436,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
5406
5436
  /```(?:html|wireframe)\n([\s\S]*?)```/
5407
5437
  );
5408
5438
  const html = htmlMatch ? htmlMatch[1].trim() : result;
5409
- fs16.writeFileSync(filePath, html, "utf-8");
5439
+ fs17.writeFileSync(filePath, html, "utf-8");
5410
5440
  return `Pitch deck written successfully.`;
5411
5441
  } catch (err) {
5412
5442
  return `Error generating pitch deck: ${err.message}`;
@@ -5720,7 +5750,7 @@ var init_tools6 = __esm({
5720
5750
  init_clearSyncStatus();
5721
5751
  init_presentSyncPlan();
5722
5752
  init_presentPublishPlan();
5723
- init_presentPlan();
5753
+ init_writePlan();
5724
5754
  init_setProjectOnboardingState();
5725
5755
  init_promptUser();
5726
5756
  init_confirmDestructiveAction();
@@ -5764,7 +5794,7 @@ var init_tools6 = __esm({
5764
5794
  clearSyncStatusTool,
5765
5795
  presentSyncPlanTool,
5766
5796
  presentPublishPlanTool,
5767
- presentPlanTool,
5797
+ writePlanTool,
5768
5798
  // Spec
5769
5799
  readSpecTool,
5770
5800
  writeSpecTool,
@@ -6520,7 +6550,6 @@ var init_agent = __esm({
6520
6550
  "clearSyncStatus",
6521
6551
  "presentSyncPlan",
6522
6552
  "presentPublishPlan",
6523
- "presentPlan",
6524
6553
  "confirmDestructiveAction",
6525
6554
  "runScenario",
6526
6555
  "runMethod",
@@ -6532,12 +6561,12 @@ var init_agent = __esm({
6532
6561
  });
6533
6562
 
6534
6563
  // src/config.ts
6535
- import fs17 from "fs";
6564
+ import fs18 from "fs";
6536
6565
  import path9 from "path";
6537
6566
  import os from "os";
6538
6567
  function loadConfigFile() {
6539
6568
  try {
6540
- const raw = fs17.readFileSync(CONFIG_PATH, "utf-8");
6569
+ const raw = fs18.readFileSync(CONFIG_PATH, "utf-8");
6541
6570
  log9.debug("Loaded config file", { path: CONFIG_PATH });
6542
6571
  return JSON.parse(raw);
6543
6572
  } catch (err) {
@@ -6707,7 +6736,7 @@ __export(headless_exports, {
6707
6736
  startHeadless: () => startHeadless
6708
6737
  });
6709
6738
  import { createInterface } from "readline";
6710
- import { writeFileSync } from "fs";
6739
+ import { writeFileSync, readFileSync, unlinkSync } from "fs";
6711
6740
  function emit(event, data, requestId) {
6712
6741
  const payload = { event, ...data };
6713
6742
  if (requestId) {
@@ -6848,7 +6877,6 @@ ${xmlParts}
6848
6877
  const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
6849
6878
  "promptUser",
6850
6879
  "confirmDestructiveAction",
6851
- "presentPlan",
6852
6880
  "presentSyncPlan",
6853
6881
  "presentPublishPlan"
6854
6882
  ]);
@@ -7066,6 +7094,23 @@ ${xmlParts}
7066
7094
  userMessage = resolved;
7067
7095
  }
7068
7096
  const isHidden = resolved !== null || !!parsed.hidden;
7097
+ const rawText = parsed.text ?? "";
7098
+ if (rawText.startsWith("@@automated::approvePlan@@")) {
7099
+ try {
7100
+ const plan = readFileSync(".remy-plan.md", "utf-8");
7101
+ writeFileSync(
7102
+ ".remy-plan.md",
7103
+ plan.replace(/^status:\s*pending/m, "status: approved"),
7104
+ "utf-8"
7105
+ );
7106
+ } catch {
7107
+ }
7108
+ } else if (rawText.startsWith("@@automated::rejectPlan@@")) {
7109
+ try {
7110
+ unlinkSync(".remy-plan.md");
7111
+ } catch {
7112
+ }
7113
+ }
7069
7114
  const onboardingState = parsed.onboardingState ?? "onboardingFinished";
7070
7115
  const system = buildSystemPrompt(
7071
7116
  onboardingState,
@@ -7263,7 +7308,7 @@ var init_headless = __esm({
7263
7308
  // src/index.tsx
7264
7309
  import { render } from "ink";
7265
7310
  import os2 from "os";
7266
- import fs18 from "fs";
7311
+ import fs19 from "fs";
7267
7312
  import path10 from "path";
7268
7313
 
7269
7314
  // src/tui/App.tsx
@@ -7582,7 +7627,7 @@ for (let i = 0; i < args.length; i++) {
7582
7627
  var startupLog = createLogger("startup");
7583
7628
  function printDebugInfo(config) {
7584
7629
  const pkg = JSON.parse(
7585
- fs18.readFileSync(
7630
+ fs19.readFileSync(
7586
7631
  path10.join(import.meta.dirname, "..", "package.json"),
7587
7632
  "utf-8"
7588
7633
  )
@@ -9,7 +9,7 @@
9
9
  - Always keep the spec up to date after making changes to the code, especially when adding features or building things from the roadmap.
10
10
  - Change only what the task requires. Match existing styles. Keep solutions simple.
11
11
  - Read files before editing them. Understand the context before making changes.
12
- - When the user asks you to make a change, execute it fully — all steps, no pausing for confirmation. Use `confirmDestructiveAction` to gate before destructive or irreversible actions (e.g., deleting data, resetting the database). For large changes that touch many files or involve significant design decisions, use `presentPlan` to get user approval first — but only when the scope genuinely warrants it or the user asks to see a plan. Most work should be done autonomously.
12
+ - When the user asks you to make a change, execute it fully — all steps, no pausing for confirmation. Use `confirmDestructiveAction` to gate before destructive or irreversible actions (e.g., deleting data, resetting the database). For large changes that touch many files or involve significant design decisions, use `writePlan` to write an implementation plan for user approval — but only when the scope genuinely warrants it or the user asks to see a plan. The plan is saved to `.remy-plan.md` and the user can review, discuss, and refine it before approving. Do not begin implementation until the plan is approved. Most work should be done autonomously without a plan.
13
13
  - Work with what you already know. If you've read a file in this session, use what you learned rather than reading it again. If a subagent already researched something, use its findings. Every tool call costs time; prefer acting on information you have over re-gathering it.
14
14
  - When multiple tool calls are independent, make them all in a single turn. Reading three files, writing two methods, or running a scenario while taking a screenshot: batch them instead of doing one per turn.
15
15
  - After two failed attempts at the same approach, tell the user what's going wrong.
@@ -17,7 +17,7 @@
17
17
  - Pushing to main branch will trigger a deploy. The user presses the publish button in the interface to request publishing.
18
18
 
19
19
  ### Build Notes
20
- For complex tasks — especially an initial buildout from a spec or making multiple changes in a single turn — write a `.remy-notes.md` scratchpad in the project root. Use it to track progress: a checklist of what's been built and what's remaining. Do not include implementation details or other decisions in the notes - it is solely for keeping track of tasks. Read the spec files directly when you need design details, implementation decisions, or other reference materials - never write them to the notes file. Delete the notes file when your work is done.
20
+ For complex tasks — especially an initial buildout from a spec or making multiple changes in a single turn — write a `.remy-notes.md` scratchpad in the project root. Use it to track progress: a checklist of what's been built and what's remaining. Do not include implementation details or other decisions in the notes - it is solely for keeping track of tasks. Read the spec files directly when you need design details, implementation decisions, or other reference materials - never write them to the notes file. Delete the notes file when your work is done. When implementing an approved plan, `.remy-plan.md` serves as your reference. Delete it when all planned work is complete.
21
21
 
22
22
  ## Communication
23
23
  The user can already see your tool calls, so most of your work is visible without narration. Focus text output on three things:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.140",
3
+ "version": "0.1.141",
4
4
  "description": "MindStudio coding agent",
5
5
  "repository": {
6
6
  "type": "git",