@mindstudio-ai/remy 0.1.140 → 0.1.142

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
@@ -105,16 +105,13 @@ Available only when `onboardingState` is `onboardingFinished`.
105
105
 
106
106
  | Tool | Description |
107
107
  |------|-------------|
108
- | `clearSyncStatus` | Clear sync flags after syncing spec and code |
109
- | `presentSyncPlan` | Present a markdown sync plan to the user for approval (streams content) |
110
108
  | `presentPublishPlan` | Present a publish changelog for user approval (streams content) |
111
- | `presentPlan` | Present an implementation plan for user approval (streams content) |
112
109
 
113
110
  ### Tool Streaming
114
111
 
115
112
  Tools can opt into streaming via a `streaming` config on the tool definition:
116
113
 
117
- - **Content streaming** (writeSpec, writeFile, presentSyncPlan, presentPublishPlan, presentPlan): Streams `tool_input_delta` events with progressive content as the LLM generates tool arguments. Tools can provide a `transform` function to customize the streamed output (e.g., writeSpec/writeFile compute a progressive diff).
114
+ - **Content streaming** (writeSpec, writeFile, presentPublishPlan): Streams `tool_input_delta` events with progressive content as the LLM generates tool arguments. Tools can provide a `transform` function to customize the streamed output (e.g., writeSpec/writeFile compute a progressive diff).
118
115
  - **Input streaming** (promptUser): Streams progressive `tool_start` events with `partial: true` as structured input (like a questions array) builds up.
119
116
  - **No streaming** (all other tools): `tool_start` fires once when the complete tool arguments are available.
120
117
 
@@ -157,10 +154,7 @@ Some tools are resolved by the sandbox rather than executed locally. Remy emits
157
154
  - `setProjectOnboardingState` — advances the onboarding flow
158
155
  - `setProjectName` — sets the project name
159
156
  - `confirmDestructiveAction` — renders a confirmation dialog
160
- - `clearSyncStatus` — clears sync dirty flags and updates git sync ref
161
- - `presentSyncPlan` — renders a full-screen markdown plan for user approval
162
157
  - `presentPublishPlan` — renders a full-screen changelog for user approval
163
- - `presentPlan` — renders a full-screen implementation plan for user approval
164
158
 
165
159
  ### Project Structure
166
160
 
@@ -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.
@@ -1,23 +1,12 @@
1
1
  ---
2
- trigger: sync
2
+ trigger: sync
3
3
  ---
4
4
 
5
5
  This is an automated action triggered by the user pressing "Sync" in the editor.
6
6
 
7
- The user has manually edited files since the last sync. The `refs/sync-point` git ref marks the last known-good sync state. It's created using a temporary git index that captures the full working tree (including unstaged changes) as a tree object so it represents exactly what the files looked like at sync time, not just what was committed.
7
+ Review the spec files in `src/` and the corresponding code in `dist/`. Identify any differences where one side has changes that the other doesn't reflect. Write a sync plan with `writePlan` describing what changed and what you intend to update. Write it for a human: describe changes in plain language ("renamed the greeting field", "added a note about error handling"), not as a list of file paths and code diffs.
8
8
 
9
- To see what the user changed, run: `git diff refs/sync-point -- src/ dist/`
10
-
11
- This compares the sync-point tree against the current working tree. Do not add `HEAD` or any other ref — the command as written diffs directly against the working tree, which is what you want.
12
-
13
- In the diff output: lines prefixed with `-` are what was in the file at last sync. Lines prefixed with `+` are the user's current edits. Sync should bring the other side in line with the `+` side.
14
-
15
- Analyze the changes and write a sync plan with `presentSyncPlan` — a clear markdown summary of what changed and what you intend to update. Write it for a human: describe changes in plain language ("renamed the greeting field", "added a note about error handling"), not as a list of file paths and code diffs. Reference specific code or file paths only when it helps clarity. The user will review and approve before you make changes.
16
-
17
- If approved:
9
+ If the plan is approved:
18
10
  - If spec files (`src/`) changed, update the corresponding code in `dist/` to match
19
11
  - If code files (`dist/`) changed, update the corresponding spec in `src/` to match
20
12
  - If both changed, reconcile — spec is the source of truth for intent, but respect code changes that add implementation detail
21
- - When all files are synced, call `clearSyncStatus`
22
-
23
- If dismissed, acknowledge and do nothing.
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. When the user approves the plan (via chat or any other signal), call updatePlanStatus with status "approved" before beginning any implementation work.
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
 
@@ -1301,34 +1323,18 @@ async function listRecursive(dir) {
1301
1323
  return results;
1302
1324
  }
1303
1325
 
1304
- // src/tools/spec/clearSyncStatus.ts
1305
- var clearSyncStatusTool = {
1306
- clearable: false,
1307
- definition: {
1308
- name: "clearSyncStatus",
1309
- description: "Clear the sync status flags after syncing spec and code. Call this after finishing a sync operation.",
1310
- inputSchema: {
1311
- type: "object",
1312
- properties: {}
1313
- }
1314
- },
1315
- async execute() {
1316
- return "ok";
1317
- }
1318
- };
1319
-
1320
- // src/tools/spec/presentSyncPlan.ts
1321
- var presentSyncPlanTool = {
1326
+ // src/tools/spec/presentPublishPlan.ts
1327
+ var presentPublishPlanTool = {
1322
1328
  clearable: false,
1323
1329
  definition: {
1324
- name: "presentSyncPlan",
1325
- description: "Present a structured sync plan to the user for approval. Write a clear markdown summary of what changed and what you intend to update. The user will see this in a full-screen view and can approve or dismiss. Call this BEFORE making any sync edits.",
1330
+ name: "presentPublishPlan",
1331
+ description: "Present a publish changelog to the user for approval. Write a clear markdown summary of what changed since the last deploy. The user will see this in a full-screen view and can approve or dismiss. Call this BEFORE committing or pushing.",
1326
1332
  inputSchema: {
1327
1333
  type: "object",
1328
1334
  properties: {
1329
1335
  content: {
1330
1336
  type: "string",
1331
- description: "Markdown plan describing what changed and what will be updated."
1337
+ description: "Markdown changelog describing what changed and what will be deployed."
1332
1338
  }
1333
1339
  },
1334
1340
  required: ["content"]
@@ -1340,49 +1346,75 @@ var presentSyncPlanTool = {
1340
1346
  }
1341
1347
  };
1342
1348
 
1343
- // src/tools/spec/presentPublishPlan.ts
1344
- var presentPublishPlanTool = {
1349
+ // src/tools/spec/writePlan.ts
1350
+ import fs9 from "fs/promises";
1351
+ var PLAN_FILE = ".remy-plan.md";
1352
+ var writePlanTool = {
1345
1353
  clearable: false,
1346
1354
  definition: {
1347
- name: "presentPublishPlan",
1348
- description: "Present a publish changelog to the user for approval. Write a clear markdown summary of what changed since the last deploy. The user will see this in a full-screen view and can approve or dismiss. Call this BEFORE committing or pushing.",
1355
+ name: "writePlan",
1356
+ 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.",
1349
1357
  inputSchema: {
1350
1358
  type: "object",
1351
1359
  properties: {
1352
1360
  content: {
1353
1361
  type: "string",
1354
- description: "Markdown changelog describing what changed and what will be deployed."
1362
+ description: "Markdown plan describing what you intend to do."
1355
1363
  }
1356
1364
  },
1357
1365
  required: ["content"]
1358
1366
  }
1359
1367
  },
1360
- streaming: {},
1361
- async execute() {
1362
- return "approved";
1368
+ async execute(input) {
1369
+ const content = input.content;
1370
+ const file = `---
1371
+ status: pending
1372
+ ---
1373
+
1374
+ ${content}`;
1375
+ await fs9.writeFile(PLAN_FILE, file, "utf-8");
1376
+ return "Plan written to .remy-plan.md. Waiting for user approval.";
1363
1377
  }
1364
1378
  };
1365
1379
 
1366
- // src/tools/spec/presentPlan.ts
1367
- var presentPlanTool = {
1380
+ // src/tools/spec/updatePlanStatus.ts
1381
+ import fs10 from "fs/promises";
1382
+ var PLAN_FILE2 = ".remy-plan.md";
1383
+ var updatePlanStatusTool = {
1368
1384
  clearable: false,
1369
1385
  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.",
1386
+ name: "updatePlanStatus",
1387
+ description: 'Update the status of the current implementation plan. Use when the user approves or rejects the plan via chat (e.g. "looks good, go ahead" or "scrap it"). Approving sets the plan to active so you can begin implementation. Rejecting deletes the plan.',
1372
1388
  inputSchema: {
1373
1389
  type: "object",
1374
1390
  properties: {
1375
- content: {
1391
+ status: {
1376
1392
  type: "string",
1377
- description: "Markdown plan describing what you intend to do."
1393
+ enum: ["approved", "rejected"],
1394
+ description: "The new plan status."
1378
1395
  }
1379
1396
  },
1380
- required: ["content"]
1397
+ required: ["status"]
1381
1398
  }
1382
1399
  },
1383
- streaming: {},
1384
- async execute() {
1385
- return "approved";
1400
+ async execute(input) {
1401
+ const status = input.status;
1402
+ let content;
1403
+ try {
1404
+ content = await fs10.readFile(PLAN_FILE2, "utf-8");
1405
+ } catch {
1406
+ return "No plan file found.";
1407
+ }
1408
+ if (status === "rejected") {
1409
+ await fs10.unlink(PLAN_FILE2);
1410
+ return "Plan rejected and removed.";
1411
+ }
1412
+ await fs10.writeFile(
1413
+ PLAN_FILE2,
1414
+ content.replace(/^status:\s*\w+/m, `status: ${status}`),
1415
+ "utf-8"
1416
+ );
1417
+ return "Plan approved. Proceeding with implementation.";
1386
1418
  }
1387
1419
  };
1388
1420
 
@@ -1546,7 +1578,7 @@ var confirmDestructiveActionTool = {
1546
1578
  clearable: false,
1547
1579
  definition: {
1548
1580
  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.",
1581
+ 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 presentPublishPlan or writePlan (those already include approval). Do not use before onboarding state transitions.",
1550
1582
  inputSchema: {
1551
1583
  type: "object",
1552
1584
  properties: {
@@ -1763,7 +1795,7 @@ var compactConversationTool = {
1763
1795
  };
1764
1796
 
1765
1797
  // src/tools/code/readFile.ts
1766
- import fs9 from "fs/promises";
1798
+ import fs11 from "fs/promises";
1767
1799
  var DEFAULT_MAX_LINES2 = 500;
1768
1800
  function isBinary(buffer) {
1769
1801
  const sample = buffer.subarray(0, 8192);
@@ -1800,7 +1832,7 @@ var readFileTool = {
1800
1832
  },
1801
1833
  async execute(input) {
1802
1834
  try {
1803
- const buffer = await fs9.readFile(input.path);
1835
+ const buffer = await fs11.readFile(input.path);
1804
1836
  if (isBinary(buffer)) {
1805
1837
  const size = buffer.length;
1806
1838
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -1834,7 +1866,7 @@ var readFileTool = {
1834
1866
  };
1835
1867
 
1836
1868
  // src/tools/code/writeFile.ts
1837
- import fs10 from "fs/promises";
1869
+ import fs12 from "fs/promises";
1838
1870
  import path6 from "path";
1839
1871
  var writeFileTool = {
1840
1872
  clearable: true,
@@ -1872,7 +1904,7 @@ var writeFileTool = {
1872
1904
  lastNewlineCount = newlineCount;
1873
1905
  const lastNewline = partial.content.lastIndexOf("\n");
1874
1906
  const completeContent = partial.content.substring(0, lastNewline + 1);
1875
- const oldContent = await fs10.readFile(partial.path, "utf-8").catch(() => "");
1907
+ const oldContent = await fs12.readFile(partial.path, "utf-8").catch(() => "");
1876
1908
  return `Writing ${partial.path} (${newlineCount} lines)
1877
1909
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1878
1910
  }
@@ -1881,13 +1913,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1881
1913
  async execute(input) {
1882
1914
  const release = await acquireFileLock(input.path);
1883
1915
  try {
1884
- await fs10.mkdir(path6.dirname(input.path), { recursive: true });
1916
+ await fs12.mkdir(path6.dirname(input.path), { recursive: true });
1885
1917
  let oldContent = null;
1886
1918
  try {
1887
- oldContent = await fs10.readFile(input.path, "utf-8");
1919
+ oldContent = await fs12.readFile(input.path, "utf-8");
1888
1920
  } catch {
1889
1921
  }
1890
- await fs10.writeFile(input.path, input.content, "utf-8");
1922
+ await fs12.writeFile(input.path, input.content, "utf-8");
1891
1923
  const lineCount = input.content.split("\n").length;
1892
1924
  const label = oldContent !== null ? "Wrote" : "Created";
1893
1925
  return `${label} ${input.path} (${lineCount} lines)
@@ -1901,7 +1933,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
1901
1933
  };
1902
1934
 
1903
1935
  // src/tools/code/editFile/index.ts
1904
- import fs11 from "fs/promises";
1936
+ import fs13 from "fs/promises";
1905
1937
 
1906
1938
  // src/tools/code/editFile/_helpers.ts
1907
1939
  function buildLineOffsets(content) {
@@ -2014,7 +2046,7 @@ var editFileTool = {
2014
2046
  async execute(input) {
2015
2047
  const release = await acquireFileLock(input.path);
2016
2048
  try {
2017
- const content = await fs11.readFile(input.path, "utf-8");
2049
+ const content = await fs13.readFile(input.path, "utf-8");
2018
2050
  const { old_string, new_string, replace_all } = input;
2019
2051
  const occurrences = findOccurrences(content, old_string);
2020
2052
  if (replace_all) {
@@ -2030,7 +2062,7 @@ var editFileTool = {
2030
2062
  new_string
2031
2063
  );
2032
2064
  }
2033
- await fs11.writeFile(input.path, updated, "utf-8");
2065
+ await fs13.writeFile(input.path, updated, "utf-8");
2034
2066
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2035
2067
  ${unifiedDiff(input.path, content, updated)}`;
2036
2068
  }
@@ -2041,7 +2073,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2041
2073
  old_string.length,
2042
2074
  new_string
2043
2075
  );
2044
- await fs11.writeFile(input.path, updated, "utf-8");
2076
+ await fs13.writeFile(input.path, updated, "utf-8");
2045
2077
  return `Updated ${input.path}
2046
2078
  ${unifiedDiff(input.path, content, updated)}`;
2047
2079
  }
@@ -2057,7 +2089,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2057
2089
  flex.matchedText.length,
2058
2090
  new_string
2059
2091
  );
2060
- await fs11.writeFile(input.path, updated, "utf-8");
2092
+ await fs13.writeFile(input.path, updated, "utf-8");
2061
2093
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2062
2094
  ${unifiedDiff(input.path, content, updated)}`;
2063
2095
  }
@@ -2268,12 +2300,12 @@ var globTool = {
2268
2300
  };
2269
2301
 
2270
2302
  // src/tools/code/listDir.ts
2271
- import fs12 from "fs/promises";
2303
+ import fs14 from "fs/promises";
2272
2304
  import path7 from "path";
2273
2305
  var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
2274
2306
  var MAX_CHILDREN = 15;
2275
2307
  async function readAndSort(dirPath) {
2276
- const entries = await fs12.readdir(dirPath, { withFileTypes: true });
2308
+ const entries = await fs14.readdir(dirPath, { withFileTypes: true });
2277
2309
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2278
2310
  if (a.isDirectory() && !b.isDirectory()) {
2279
2311
  return -1;
@@ -2314,7 +2346,7 @@ function formatSize(bytes) {
2314
2346
  }
2315
2347
  async function formatFile(dirPath, name, indent) {
2316
2348
  try {
2317
- const stat = await fs12.stat(path7.join(dirPath, name));
2349
+ const stat = await fs14.stat(path7.join(dirPath, name));
2318
2350
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
2319
2351
  } catch {
2320
2352
  return `${indent}${name}`;
@@ -3381,11 +3413,11 @@ var BROWSER_TOOLS = [
3381
3413
  var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
3382
3414
 
3383
3415
  // src/subagents/browserAutomation/prompt.ts
3384
- import fs13 from "fs";
3416
+ import fs15 from "fs";
3385
3417
  var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
3386
3418
  function getBrowserAutomationPrompt() {
3387
3419
  try {
3388
- const appSpec = fs13.readFileSync("src/app.md", "utf-8").trim();
3420
+ const appSpec = fs15.readFileSync("src/app.md", "utf-8").trim();
3389
3421
  return `${BASE_PROMPT}
3390
3422
 
3391
3423
  <!-- cache_breakpoint -->
@@ -4227,12 +4259,12 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
4227
4259
  }
4228
4260
 
4229
4261
  // src/subagents/common/context.ts
4230
- import fs14 from "fs";
4262
+ import fs16 from "fs";
4231
4263
  import path8 from "path";
4232
4264
  function walkMdFiles2(dir, skip) {
4233
4265
  const files = [];
4234
4266
  try {
4235
- for (const entry of fs14.readdirSync(dir, { withFileTypes: true })) {
4267
+ for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
4236
4268
  const full = path8.join(dir, entry.name);
4237
4269
  if (entry.isDirectory()) {
4238
4270
  if (!skip?.has(entry.name)) {
@@ -4248,7 +4280,7 @@ function walkMdFiles2(dir, skip) {
4248
4280
  }
4249
4281
  function parseFrontmatter2(filePath) {
4250
4282
  try {
4251
- const content = fs14.readFileSync(filePath, "utf-8");
4283
+ const content = fs16.readFileSync(filePath, "utf-8");
4252
4284
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4253
4285
  if (!match) {
4254
4286
  return {};
@@ -4294,7 +4326,7 @@ function loadRoadmapIndex() {
4294
4326
  const parts = [];
4295
4327
  try {
4296
4328
  const indexJson = JSON.parse(
4297
- fs14.readFileSync("src/roadmap/index.json", "utf-8")
4329
+ fs16.readFileSync("src/roadmap/index.json", "utf-8")
4298
4330
  );
4299
4331
  if (indexJson.lanes?.length > 0) {
4300
4332
  const laneLines = indexJson.lanes.map(
@@ -4397,7 +4429,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
4397
4429
  }
4398
4430
 
4399
4431
  // src/subagents/designExpert/data/sampleCache.ts
4400
- import fs15 from "fs";
4432
+ import fs17 from "fs";
4401
4433
  var SAMPLE_FILE = ".remy-design-sample.json";
4402
4434
  var cached = null;
4403
4435
  function generateIndices(poolSize, sampleSize) {
@@ -4411,14 +4443,14 @@ function generateIndices(poolSize, sampleSize) {
4411
4443
  }
4412
4444
  function load() {
4413
4445
  try {
4414
- return JSON.parse(fs15.readFileSync(SAMPLE_FILE, "utf-8"));
4446
+ return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
4415
4447
  } catch {
4416
4448
  return null;
4417
4449
  }
4418
4450
  }
4419
4451
  function save(indices) {
4420
4452
  try {
4421
- fs15.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4453
+ fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4422
4454
  } catch {
4423
4455
  }
4424
4456
  }
@@ -4747,7 +4779,7 @@ var VISION_TOOLS = [
4747
4779
  ];
4748
4780
 
4749
4781
  // src/subagents/productVision/executor.ts
4750
- import fs16 from "fs";
4782
+ import fs18 from "fs";
4751
4783
  import path9 from "path";
4752
4784
  var ROADMAP_DIR = "src/roadmap";
4753
4785
  var PITCH_DECK_SHELL = readAsset(
@@ -4762,13 +4794,13 @@ async function executeVisionTool(name, input, context) {
4762
4794
  case "writeFile": {
4763
4795
  const filePath = resolve(input.path);
4764
4796
  try {
4765
- fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
4797
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
4766
4798
  let oldContent = null;
4767
4799
  try {
4768
- oldContent = fs16.readFileSync(filePath, "utf-8");
4800
+ oldContent = fs18.readFileSync(filePath, "utf-8");
4769
4801
  } catch {
4770
4802
  }
4771
- fs16.writeFileSync(filePath, input.content, "utf-8");
4803
+ fs18.writeFileSync(filePath, input.content, "utf-8");
4772
4804
  const lineCount = input.content.split("\n").length;
4773
4805
  const label = oldContent !== null ? "Wrote" : "Created";
4774
4806
  return `${label} ${filePath} (${lineCount} lines)
@@ -4780,11 +4812,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
4780
4812
  case "deleteFile": {
4781
4813
  const filePath = resolve(input.path);
4782
4814
  try {
4783
- if (!fs16.existsSync(filePath)) {
4815
+ if (!fs18.existsSync(filePath)) {
4784
4816
  return `Error: ${filePath} does not exist`;
4785
4817
  }
4786
- const oldContent = fs16.readFileSync(filePath, "utf-8");
4787
- fs16.unlinkSync(filePath);
4818
+ const oldContent = fs18.readFileSync(filePath, "utf-8");
4819
+ fs18.unlinkSync(filePath);
4788
4820
  return `Deleted ${filePath}
4789
4821
  ${unifiedDiff(filePath, oldContent, "")}`;
4790
4822
  } catch (err) {
@@ -4797,8 +4829,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
4797
4829
  }
4798
4830
  const filePath = resolve("pitch.html");
4799
4831
  try {
4800
- fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
4801
- const existing = fs16.existsSync(filePath) ? fs16.readFileSync(filePath, "utf-8").trim() : "";
4832
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
4833
+ const existing = fs18.existsSync(filePath) ? fs18.readFileSync(filePath, "utf-8").trim() : "";
4802
4834
  const currentDeck = existing || PITCH_DECK_SHELL;
4803
4835
  const task = `
4804
4836
  <pitch_content>${input.task}</pitch_content>
@@ -4823,7 +4855,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
4823
4855
  /```(?:html|wireframe)\n([\s\S]*?)```/
4824
4856
  );
4825
4857
  const html = htmlMatch ? htmlMatch[1].trim() : result;
4826
- fs16.writeFileSync(filePath, html, "utf-8");
4858
+ fs18.writeFileSync(filePath, html, "utf-8");
4827
4859
  return `Pitch deck written successfully.`;
4828
4860
  } catch (err) {
4829
4861
  return `Error generating pitch deck: ${err.message}`;
@@ -5065,10 +5097,9 @@ var ALL_TOOLS = [
5065
5097
  codeSanityCheckTool,
5066
5098
  compactConversationTool,
5067
5099
  // Post-onboarding
5068
- clearSyncStatusTool,
5069
- presentSyncPlanTool,
5070
5100
  presentPublishPlanTool,
5071
- presentPlanTool,
5101
+ writePlanTool,
5102
+ updatePlanStatusTool,
5072
5103
  // Spec
5073
5104
  readSpecTool,
5074
5105
  writeSpecTool,
@@ -5110,12 +5141,12 @@ function executeTool(name, input, context) {
5110
5141
  }
5111
5142
 
5112
5143
  // src/session.ts
5113
- import fs17 from "fs";
5144
+ import fs19 from "fs";
5114
5145
  var log7 = createLogger("session");
5115
5146
  var SESSION_FILE = ".remy-session.json";
5116
5147
  function loadSession(state) {
5117
5148
  try {
5118
- const raw = fs17.readFileSync(SESSION_FILE, "utf-8");
5149
+ const raw = fs19.readFileSync(SESSION_FILE, "utf-8");
5119
5150
  const data = JSON.parse(raw);
5120
5151
  if (Array.isArray(data.messages) && data.messages.length > 0) {
5121
5152
  state.messages = sanitizeMessages(data.messages);
@@ -5164,7 +5195,7 @@ function sanitizeMessages(messages) {
5164
5195
  }
5165
5196
  function saveSession(state) {
5166
5197
  try {
5167
- fs17.writeFileSync(
5198
+ fs19.writeFileSync(
5168
5199
  SESSION_FILE,
5169
5200
  JSON.stringify({ messages: state.messages }, null, 2),
5170
5201
  "utf-8"
@@ -5177,7 +5208,7 @@ function saveSession(state) {
5177
5208
  function clearSession(state) {
5178
5209
  state.messages = [];
5179
5210
  try {
5180
- fs17.unlinkSync(SESSION_FILE);
5211
+ fs19.unlinkSync(SESSION_FILE);
5181
5212
  } catch {
5182
5213
  }
5183
5214
  }
@@ -5402,10 +5433,7 @@ function getToolCalls(blocks) {
5402
5433
  var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
5403
5434
  "promptUser",
5404
5435
  "setProjectOnboardingState",
5405
- "clearSyncStatus",
5406
- "presentSyncPlan",
5407
5436
  "presentPublishPlan",
5408
- "presentPlan",
5409
5437
  "confirmDestructiveAction",
5410
5438
  "runScenario",
5411
5439
  "runMethod",
@@ -5467,7 +5495,6 @@ async function runTurn(params) {
5467
5495
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
5468
5496
  "setProjectOnboardingState",
5469
5497
  "setProjectMetadata",
5470
- "clearSyncStatus",
5471
5498
  "editsFinished"
5472
5499
  ]);
5473
5500
  let lastCompletedTools = "";
@@ -6156,8 +6183,6 @@ ${xmlParts}
6156
6183
  const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
6157
6184
  "promptUser",
6158
6185
  "confirmDestructiveAction",
6159
- "presentPlan",
6160
- "presentSyncPlan",
6161
6186
  "presentPublishPlan"
6162
6187
  ]);
6163
6188
  function resolveExternalTool(id, name, _input) {
@@ -6374,6 +6399,23 @@ ${xmlParts}
6374
6399
  userMessage = resolved;
6375
6400
  }
6376
6401
  const isHidden = resolved !== null || !!parsed.hidden;
6402
+ const rawText = parsed.text ?? "";
6403
+ if (rawText.startsWith("@@automated::approvePlan@@")) {
6404
+ try {
6405
+ const plan = readFileSync(".remy-plan.md", "utf-8");
6406
+ writeFileSync(
6407
+ ".remy-plan.md",
6408
+ plan.replace(/^status:\s*pending/m, "status: approved"),
6409
+ "utf-8"
6410
+ );
6411
+ } catch {
6412
+ }
6413
+ } else if (rawText.startsWith("@@automated::rejectPlan@@")) {
6414
+ try {
6415
+ unlinkSync(".remy-plan.md");
6416
+ } catch {
6417
+ }
6418
+ }
6377
6419
  const onboardingState = parsed.onboardingState ?? "onboardingFinished";
6378
6420
  const system = buildSystemPrompt(
6379
6421
  onboardingState,