@mindstudio-ai/remy 0.1.141 → 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
 
@@ -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
@@ -300,7 +300,7 @@ function loadPlanStatus() {
300
300
  if (status === "pending") {
301
301
  return `
302
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.
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
304
  </pending_plan>`;
305
305
  }
306
306
  if (status === "approved") {
@@ -1323,45 +1323,6 @@ async function listRecursive(dir) {
1323
1323
  return results;
1324
1324
  }
1325
1325
 
1326
- // src/tools/spec/clearSyncStatus.ts
1327
- var clearSyncStatusTool = {
1328
- clearable: false,
1329
- definition: {
1330
- name: "clearSyncStatus",
1331
- description: "Clear the sync status flags after syncing spec and code. Call this after finishing a sync operation.",
1332
- inputSchema: {
1333
- type: "object",
1334
- properties: {}
1335
- }
1336
- },
1337
- async execute() {
1338
- return "ok";
1339
- }
1340
- };
1341
-
1342
- // src/tools/spec/presentSyncPlan.ts
1343
- var presentSyncPlanTool = {
1344
- clearable: false,
1345
- definition: {
1346
- name: "presentSyncPlan",
1347
- 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.",
1348
- inputSchema: {
1349
- type: "object",
1350
- properties: {
1351
- content: {
1352
- type: "string",
1353
- description: "Markdown plan describing what changed and what will be updated."
1354
- }
1355
- },
1356
- required: ["content"]
1357
- }
1358
- },
1359
- streaming: {},
1360
- async execute() {
1361
- return "approved";
1362
- }
1363
- };
1364
-
1365
1326
  // src/tools/spec/presentPublishPlan.ts
1366
1327
  var presentPublishPlanTool = {
1367
1328
  clearable: false,
@@ -1416,6 +1377,47 @@ ${content}`;
1416
1377
  }
1417
1378
  };
1418
1379
 
1380
+ // src/tools/spec/updatePlanStatus.ts
1381
+ import fs10 from "fs/promises";
1382
+ var PLAN_FILE2 = ".remy-plan.md";
1383
+ var updatePlanStatusTool = {
1384
+ clearable: false,
1385
+ definition: {
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.',
1388
+ inputSchema: {
1389
+ type: "object",
1390
+ properties: {
1391
+ status: {
1392
+ type: "string",
1393
+ enum: ["approved", "rejected"],
1394
+ description: "The new plan status."
1395
+ }
1396
+ },
1397
+ required: ["status"]
1398
+ }
1399
+ },
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.";
1418
+ }
1419
+ };
1420
+
1419
1421
  // src/tools/common/setProjectOnboardingState.ts
1420
1422
  var setProjectOnboardingStateTool = {
1421
1423
  clearable: false,
@@ -1576,7 +1578,7 @@ var confirmDestructiveActionTool = {
1576
1578
  clearable: false,
1577
1579
  definition: {
1578
1580
  name: "confirmDestructiveAction",
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.",
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.",
1580
1582
  inputSchema: {
1581
1583
  type: "object",
1582
1584
  properties: {
@@ -1793,7 +1795,7 @@ var compactConversationTool = {
1793
1795
  };
1794
1796
 
1795
1797
  // src/tools/code/readFile.ts
1796
- import fs10 from "fs/promises";
1798
+ import fs11 from "fs/promises";
1797
1799
  var DEFAULT_MAX_LINES2 = 500;
1798
1800
  function isBinary(buffer) {
1799
1801
  const sample = buffer.subarray(0, 8192);
@@ -1830,7 +1832,7 @@ var readFileTool = {
1830
1832
  },
1831
1833
  async execute(input) {
1832
1834
  try {
1833
- const buffer = await fs10.readFile(input.path);
1835
+ const buffer = await fs11.readFile(input.path);
1834
1836
  if (isBinary(buffer)) {
1835
1837
  const size = buffer.length;
1836
1838
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -1864,7 +1866,7 @@ var readFileTool = {
1864
1866
  };
1865
1867
 
1866
1868
  // src/tools/code/writeFile.ts
1867
- import fs11 from "fs/promises";
1869
+ import fs12 from "fs/promises";
1868
1870
  import path6 from "path";
1869
1871
  var writeFileTool = {
1870
1872
  clearable: true,
@@ -1902,7 +1904,7 @@ var writeFileTool = {
1902
1904
  lastNewlineCount = newlineCount;
1903
1905
  const lastNewline = partial.content.lastIndexOf("\n");
1904
1906
  const completeContent = partial.content.substring(0, lastNewline + 1);
1905
- const oldContent = await fs11.readFile(partial.path, "utf-8").catch(() => "");
1907
+ const oldContent = await fs12.readFile(partial.path, "utf-8").catch(() => "");
1906
1908
  return `Writing ${partial.path} (${newlineCount} lines)
1907
1909
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1908
1910
  }
@@ -1911,13 +1913,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1911
1913
  async execute(input) {
1912
1914
  const release = await acquireFileLock(input.path);
1913
1915
  try {
1914
- await fs11.mkdir(path6.dirname(input.path), { recursive: true });
1916
+ await fs12.mkdir(path6.dirname(input.path), { recursive: true });
1915
1917
  let oldContent = null;
1916
1918
  try {
1917
- oldContent = await fs11.readFile(input.path, "utf-8");
1919
+ oldContent = await fs12.readFile(input.path, "utf-8");
1918
1920
  } catch {
1919
1921
  }
1920
- await fs11.writeFile(input.path, input.content, "utf-8");
1922
+ await fs12.writeFile(input.path, input.content, "utf-8");
1921
1923
  const lineCount = input.content.split("\n").length;
1922
1924
  const label = oldContent !== null ? "Wrote" : "Created";
1923
1925
  return `${label} ${input.path} (${lineCount} lines)
@@ -1931,7 +1933,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
1931
1933
  };
1932
1934
 
1933
1935
  // src/tools/code/editFile/index.ts
1934
- import fs12 from "fs/promises";
1936
+ import fs13 from "fs/promises";
1935
1937
 
1936
1938
  // src/tools/code/editFile/_helpers.ts
1937
1939
  function buildLineOffsets(content) {
@@ -2044,7 +2046,7 @@ var editFileTool = {
2044
2046
  async execute(input) {
2045
2047
  const release = await acquireFileLock(input.path);
2046
2048
  try {
2047
- const content = await fs12.readFile(input.path, "utf-8");
2049
+ const content = await fs13.readFile(input.path, "utf-8");
2048
2050
  const { old_string, new_string, replace_all } = input;
2049
2051
  const occurrences = findOccurrences(content, old_string);
2050
2052
  if (replace_all) {
@@ -2060,7 +2062,7 @@ var editFileTool = {
2060
2062
  new_string
2061
2063
  );
2062
2064
  }
2063
- await fs12.writeFile(input.path, updated, "utf-8");
2065
+ await fs13.writeFile(input.path, updated, "utf-8");
2064
2066
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2065
2067
  ${unifiedDiff(input.path, content, updated)}`;
2066
2068
  }
@@ -2071,7 +2073,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2071
2073
  old_string.length,
2072
2074
  new_string
2073
2075
  );
2074
- await fs12.writeFile(input.path, updated, "utf-8");
2076
+ await fs13.writeFile(input.path, updated, "utf-8");
2075
2077
  return `Updated ${input.path}
2076
2078
  ${unifiedDiff(input.path, content, updated)}`;
2077
2079
  }
@@ -2087,7 +2089,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2087
2089
  flex.matchedText.length,
2088
2090
  new_string
2089
2091
  );
2090
- await fs12.writeFile(input.path, updated, "utf-8");
2092
+ await fs13.writeFile(input.path, updated, "utf-8");
2091
2093
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2092
2094
  ${unifiedDiff(input.path, content, updated)}`;
2093
2095
  }
@@ -2298,12 +2300,12 @@ var globTool = {
2298
2300
  };
2299
2301
 
2300
2302
  // src/tools/code/listDir.ts
2301
- import fs13 from "fs/promises";
2303
+ import fs14 from "fs/promises";
2302
2304
  import path7 from "path";
2303
2305
  var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
2304
2306
  var MAX_CHILDREN = 15;
2305
2307
  async function readAndSort(dirPath) {
2306
- const entries = await fs13.readdir(dirPath, { withFileTypes: true });
2308
+ const entries = await fs14.readdir(dirPath, { withFileTypes: true });
2307
2309
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2308
2310
  if (a.isDirectory() && !b.isDirectory()) {
2309
2311
  return -1;
@@ -2344,7 +2346,7 @@ function formatSize(bytes) {
2344
2346
  }
2345
2347
  async function formatFile(dirPath, name, indent) {
2346
2348
  try {
2347
- const stat = await fs13.stat(path7.join(dirPath, name));
2349
+ const stat = await fs14.stat(path7.join(dirPath, name));
2348
2350
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
2349
2351
  } catch {
2350
2352
  return `${indent}${name}`;
@@ -3411,11 +3413,11 @@ var BROWSER_TOOLS = [
3411
3413
  var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
3412
3414
 
3413
3415
  // src/subagents/browserAutomation/prompt.ts
3414
- import fs14 from "fs";
3416
+ import fs15 from "fs";
3415
3417
  var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
3416
3418
  function getBrowserAutomationPrompt() {
3417
3419
  try {
3418
- const appSpec = fs14.readFileSync("src/app.md", "utf-8").trim();
3420
+ const appSpec = fs15.readFileSync("src/app.md", "utf-8").trim();
3419
3421
  return `${BASE_PROMPT}
3420
3422
 
3421
3423
  <!-- cache_breakpoint -->
@@ -4257,12 +4259,12 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
4257
4259
  }
4258
4260
 
4259
4261
  // src/subagents/common/context.ts
4260
- import fs15 from "fs";
4262
+ import fs16 from "fs";
4261
4263
  import path8 from "path";
4262
4264
  function walkMdFiles2(dir, skip) {
4263
4265
  const files = [];
4264
4266
  try {
4265
- for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
4267
+ for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
4266
4268
  const full = path8.join(dir, entry.name);
4267
4269
  if (entry.isDirectory()) {
4268
4270
  if (!skip?.has(entry.name)) {
@@ -4278,7 +4280,7 @@ function walkMdFiles2(dir, skip) {
4278
4280
  }
4279
4281
  function parseFrontmatter2(filePath) {
4280
4282
  try {
4281
- const content = fs15.readFileSync(filePath, "utf-8");
4283
+ const content = fs16.readFileSync(filePath, "utf-8");
4282
4284
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4283
4285
  if (!match) {
4284
4286
  return {};
@@ -4324,7 +4326,7 @@ function loadRoadmapIndex() {
4324
4326
  const parts = [];
4325
4327
  try {
4326
4328
  const indexJson = JSON.parse(
4327
- fs15.readFileSync("src/roadmap/index.json", "utf-8")
4329
+ fs16.readFileSync("src/roadmap/index.json", "utf-8")
4328
4330
  );
4329
4331
  if (indexJson.lanes?.length > 0) {
4330
4332
  const laneLines = indexJson.lanes.map(
@@ -4427,7 +4429,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
4427
4429
  }
4428
4430
 
4429
4431
  // src/subagents/designExpert/data/sampleCache.ts
4430
- import fs16 from "fs";
4432
+ import fs17 from "fs";
4431
4433
  var SAMPLE_FILE = ".remy-design-sample.json";
4432
4434
  var cached = null;
4433
4435
  function generateIndices(poolSize, sampleSize) {
@@ -4441,14 +4443,14 @@ function generateIndices(poolSize, sampleSize) {
4441
4443
  }
4442
4444
  function load() {
4443
4445
  try {
4444
- return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
4446
+ return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
4445
4447
  } catch {
4446
4448
  return null;
4447
4449
  }
4448
4450
  }
4449
4451
  function save(indices) {
4450
4452
  try {
4451
- fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4453
+ fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4452
4454
  } catch {
4453
4455
  }
4454
4456
  }
@@ -4777,7 +4779,7 @@ var VISION_TOOLS = [
4777
4779
  ];
4778
4780
 
4779
4781
  // src/subagents/productVision/executor.ts
4780
- import fs17 from "fs";
4782
+ import fs18 from "fs";
4781
4783
  import path9 from "path";
4782
4784
  var ROADMAP_DIR = "src/roadmap";
4783
4785
  var PITCH_DECK_SHELL = readAsset(
@@ -4792,13 +4794,13 @@ async function executeVisionTool(name, input, context) {
4792
4794
  case "writeFile": {
4793
4795
  const filePath = resolve(input.path);
4794
4796
  try {
4795
- fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
4797
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
4796
4798
  let oldContent = null;
4797
4799
  try {
4798
- oldContent = fs17.readFileSync(filePath, "utf-8");
4800
+ oldContent = fs18.readFileSync(filePath, "utf-8");
4799
4801
  } catch {
4800
4802
  }
4801
- fs17.writeFileSync(filePath, input.content, "utf-8");
4803
+ fs18.writeFileSync(filePath, input.content, "utf-8");
4802
4804
  const lineCount = input.content.split("\n").length;
4803
4805
  const label = oldContent !== null ? "Wrote" : "Created";
4804
4806
  return `${label} ${filePath} (${lineCount} lines)
@@ -4810,11 +4812,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
4810
4812
  case "deleteFile": {
4811
4813
  const filePath = resolve(input.path);
4812
4814
  try {
4813
- if (!fs17.existsSync(filePath)) {
4815
+ if (!fs18.existsSync(filePath)) {
4814
4816
  return `Error: ${filePath} does not exist`;
4815
4817
  }
4816
- const oldContent = fs17.readFileSync(filePath, "utf-8");
4817
- fs17.unlinkSync(filePath);
4818
+ const oldContent = fs18.readFileSync(filePath, "utf-8");
4819
+ fs18.unlinkSync(filePath);
4818
4820
  return `Deleted ${filePath}
4819
4821
  ${unifiedDiff(filePath, oldContent, "")}`;
4820
4822
  } catch (err) {
@@ -4827,8 +4829,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
4827
4829
  }
4828
4830
  const filePath = resolve("pitch.html");
4829
4831
  try {
4830
- fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
4831
- const existing = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8").trim() : "";
4832
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
4833
+ const existing = fs18.existsSync(filePath) ? fs18.readFileSync(filePath, "utf-8").trim() : "";
4832
4834
  const currentDeck = existing || PITCH_DECK_SHELL;
4833
4835
  const task = `
4834
4836
  <pitch_content>${input.task}</pitch_content>
@@ -4853,7 +4855,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
4853
4855
  /```(?:html|wireframe)\n([\s\S]*?)```/
4854
4856
  );
4855
4857
  const html = htmlMatch ? htmlMatch[1].trim() : result;
4856
- fs17.writeFileSync(filePath, html, "utf-8");
4858
+ fs18.writeFileSync(filePath, html, "utf-8");
4857
4859
  return `Pitch deck written successfully.`;
4858
4860
  } catch (err) {
4859
4861
  return `Error generating pitch deck: ${err.message}`;
@@ -5095,10 +5097,9 @@ var ALL_TOOLS = [
5095
5097
  codeSanityCheckTool,
5096
5098
  compactConversationTool,
5097
5099
  // Post-onboarding
5098
- clearSyncStatusTool,
5099
- presentSyncPlanTool,
5100
5100
  presentPublishPlanTool,
5101
5101
  writePlanTool,
5102
+ updatePlanStatusTool,
5102
5103
  // Spec
5103
5104
  readSpecTool,
5104
5105
  writeSpecTool,
@@ -5140,12 +5141,12 @@ function executeTool(name, input, context) {
5140
5141
  }
5141
5142
 
5142
5143
  // src/session.ts
5143
- import fs18 from "fs";
5144
+ import fs19 from "fs";
5144
5145
  var log7 = createLogger("session");
5145
5146
  var SESSION_FILE = ".remy-session.json";
5146
5147
  function loadSession(state) {
5147
5148
  try {
5148
- const raw = fs18.readFileSync(SESSION_FILE, "utf-8");
5149
+ const raw = fs19.readFileSync(SESSION_FILE, "utf-8");
5149
5150
  const data = JSON.parse(raw);
5150
5151
  if (Array.isArray(data.messages) && data.messages.length > 0) {
5151
5152
  state.messages = sanitizeMessages(data.messages);
@@ -5194,7 +5195,7 @@ function sanitizeMessages(messages) {
5194
5195
  }
5195
5196
  function saveSession(state) {
5196
5197
  try {
5197
- fs18.writeFileSync(
5198
+ fs19.writeFileSync(
5198
5199
  SESSION_FILE,
5199
5200
  JSON.stringify({ messages: state.messages }, null, 2),
5200
5201
  "utf-8"
@@ -5207,7 +5208,7 @@ function saveSession(state) {
5207
5208
  function clearSession(state) {
5208
5209
  state.messages = [];
5209
5210
  try {
5210
- fs18.unlinkSync(SESSION_FILE);
5211
+ fs19.unlinkSync(SESSION_FILE);
5211
5212
  } catch {
5212
5213
  }
5213
5214
  }
@@ -5432,8 +5433,6 @@ function getToolCalls(blocks) {
5432
5433
  var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
5433
5434
  "promptUser",
5434
5435
  "setProjectOnboardingState",
5435
- "clearSyncStatus",
5436
- "presentSyncPlan",
5437
5436
  "presentPublishPlan",
5438
5437
  "confirmDestructiveAction",
5439
5438
  "runScenario",
@@ -5496,7 +5495,6 @@ async function runTurn(params) {
5496
5495
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
5497
5496
  "setProjectOnboardingState",
5498
5497
  "setProjectMetadata",
5499
- "clearSyncStatus",
5500
5498
  "editsFinished"
5501
5499
  ]);
5502
5500
  let lastCompletedTools = "";
@@ -6185,7 +6183,6 @@ ${xmlParts}
6185
6183
  const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
6186
6184
  "promptUser",
6187
6185
  "confirmDestructiveAction",
6188
- "presentSyncPlan",
6189
6186
  "presentPublishPlan"
6190
6187
  ]);
6191
6188
  function resolveExternalTool(id, name, _input) {
package/dist/index.js CHANGED
@@ -773,57 +773,6 @@ var init_listSpecFiles = __esm({
773
773
  }
774
774
  });
775
775
 
776
- // src/tools/spec/clearSyncStatus.ts
777
- var clearSyncStatusTool;
778
- var init_clearSyncStatus = __esm({
779
- "src/tools/spec/clearSyncStatus.ts"() {
780
- "use strict";
781
- clearSyncStatusTool = {
782
- clearable: false,
783
- definition: {
784
- name: "clearSyncStatus",
785
- description: "Clear the sync status flags after syncing spec and code. Call this after finishing a sync operation.",
786
- inputSchema: {
787
- type: "object",
788
- properties: {}
789
- }
790
- },
791
- async execute() {
792
- return "ok";
793
- }
794
- };
795
- }
796
- });
797
-
798
- // src/tools/spec/presentSyncPlan.ts
799
- var presentSyncPlanTool;
800
- var init_presentSyncPlan = __esm({
801
- "src/tools/spec/presentSyncPlan.ts"() {
802
- "use strict";
803
- presentSyncPlanTool = {
804
- clearable: false,
805
- definition: {
806
- name: "presentSyncPlan",
807
- 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.",
808
- inputSchema: {
809
- type: "object",
810
- properties: {
811
- content: {
812
- type: "string",
813
- description: "Markdown plan describing what changed and what will be updated."
814
- }
815
- },
816
- required: ["content"]
817
- }
818
- },
819
- streaming: {},
820
- async execute() {
821
- return "approved";
822
- }
823
- };
824
- }
825
- });
826
-
827
776
  // src/tools/spec/presentPublishPlan.ts
828
777
  var presentPublishPlanTool;
829
778
  var init_presentPublishPlan = __esm({
@@ -890,6 +839,53 @@ ${content}`;
890
839
  }
891
840
  });
892
841
 
842
+ // src/tools/spec/updatePlanStatus.ts
843
+ import fs7 from "fs/promises";
844
+ var PLAN_FILE2, updatePlanStatusTool;
845
+ var init_updatePlanStatus = __esm({
846
+ "src/tools/spec/updatePlanStatus.ts"() {
847
+ "use strict";
848
+ PLAN_FILE2 = ".remy-plan.md";
849
+ updatePlanStatusTool = {
850
+ clearable: false,
851
+ definition: {
852
+ name: "updatePlanStatus",
853
+ 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.',
854
+ inputSchema: {
855
+ type: "object",
856
+ properties: {
857
+ status: {
858
+ type: "string",
859
+ enum: ["approved", "rejected"],
860
+ description: "The new plan status."
861
+ }
862
+ },
863
+ required: ["status"]
864
+ }
865
+ },
866
+ async execute(input) {
867
+ const status = input.status;
868
+ let content;
869
+ try {
870
+ content = await fs7.readFile(PLAN_FILE2, "utf-8");
871
+ } catch {
872
+ return "No plan file found.";
873
+ }
874
+ if (status === "rejected") {
875
+ await fs7.unlink(PLAN_FILE2);
876
+ return "Plan rejected and removed.";
877
+ }
878
+ await fs7.writeFile(
879
+ PLAN_FILE2,
880
+ content.replace(/^status:\s*\w+/m, `status: ${status}`),
881
+ "utf-8"
882
+ );
883
+ return "Plan approved. Proceeding with implementation.";
884
+ }
885
+ };
886
+ }
887
+ });
888
+
893
889
  // src/tools/common/setProjectOnboardingState.ts
894
890
  var setProjectOnboardingStateTool;
895
891
  var init_setProjectOnboardingState = __esm({
@@ -1066,7 +1062,7 @@ var init_confirmDestructiveAction = __esm({
1066
1062
  clearable: false,
1067
1063
  definition: {
1068
1064
  name: "confirmDestructiveAction",
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.",
1065
+ 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.",
1070
1066
  inputSchema: {
1071
1067
  type: "object",
1072
1068
  properties: {
@@ -1287,12 +1283,12 @@ var init_setProjectMetadata = __esm({
1287
1283
  });
1288
1284
 
1289
1285
  // src/assets.ts
1290
- import fs7 from "fs";
1286
+ import fs8 from "fs";
1291
1287
  import path3 from "path";
1292
1288
  function findRoot(start) {
1293
1289
  let dir = start;
1294
1290
  while (dir !== path3.dirname(dir)) {
1295
- if (fs7.existsSync(path3.join(dir, "package.json"))) {
1291
+ if (fs8.existsSync(path3.join(dir, "package.json"))) {
1296
1292
  return dir;
1297
1293
  }
1298
1294
  dir = path3.dirname(dir);
@@ -1305,7 +1301,7 @@ function assetPath(...segments) {
1305
1301
  function readAsset(...segments) {
1306
1302
  const full = assetPath(...segments);
1307
1303
  try {
1308
- return fs7.readFileSync(full, "utf-8").trim();
1304
+ return fs8.readFileSync(full, "utf-8").trim();
1309
1305
  } catch {
1310
1306
  throw new Error(`Required asset missing: ${full}`);
1311
1307
  }
@@ -1313,7 +1309,7 @@ function readAsset(...segments) {
1313
1309
  function readJsonAsset(fallback, ...segments) {
1314
1310
  const full = assetPath(...segments);
1315
1311
  try {
1316
- return JSON.parse(fs7.readFileSync(full, "utf-8"));
1312
+ return JSON.parse(fs8.readFileSync(full, "utf-8"));
1317
1313
  } catch {
1318
1314
  return fallback;
1319
1315
  }
@@ -1325,7 +1321,7 @@ var init_assets = __esm({
1325
1321
  ROOT = findRoot(
1326
1322
  import.meta.dirname ?? path3.dirname(new URL(import.meta.url).pathname)
1327
1323
  );
1328
- ASSETS_BASE = fs7.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
1324
+ ASSETS_BASE = fs8.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
1329
1325
  }
1330
1326
  });
1331
1327
 
@@ -1614,12 +1610,12 @@ var init_lsp = __esm({
1614
1610
  });
1615
1611
 
1616
1612
  // src/prompt/static/projectContext.ts
1617
- import fs8 from "fs";
1613
+ import fs9 from "fs";
1618
1614
  import path4 from "path";
1619
1615
  function loadProjectInstructions() {
1620
1616
  for (const file of AGENT_INSTRUCTION_FILES) {
1621
1617
  try {
1622
- const content = fs8.readFileSync(file, "utf-8").trim();
1618
+ const content = fs9.readFileSync(file, "utf-8").trim();
1623
1619
  if (content) {
1624
1620
  return `
1625
1621
  ## Project Instructions (${file})
@@ -1632,7 +1628,7 @@ ${content}`;
1632
1628
  }
1633
1629
  function loadProjectManifest() {
1634
1630
  try {
1635
- const manifest = fs8.readFileSync("mindstudio.json", "utf-8");
1631
+ const manifest = fs9.readFileSync("mindstudio.json", "utf-8");
1636
1632
  return `
1637
1633
  ## Project Manifest (mindstudio.json)
1638
1634
  \`\`\`json
@@ -1673,7 +1669,7 @@ ${entries.join("\n")}`;
1673
1669
  function walkMdFiles(dir) {
1674
1670
  const results = [];
1675
1671
  try {
1676
- const entries = fs8.readdirSync(dir, { withFileTypes: true });
1672
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
1677
1673
  for (const entry of entries) {
1678
1674
  const full = path4.join(dir, entry.name);
1679
1675
  if (entry.isDirectory()) {
@@ -1688,7 +1684,7 @@ function walkMdFiles(dir) {
1688
1684
  }
1689
1685
  function parseFrontmatter(filePath) {
1690
1686
  try {
1691
- const content = fs8.readFileSync(filePath, "utf-8");
1687
+ const content = fs9.readFileSync(filePath, "utf-8");
1692
1688
  const match = content.match(/^---\n([\s\S]*?)\n---/);
1693
1689
  if (!match) {
1694
1690
  return { name: "", description: "", type: "" };
@@ -1704,13 +1700,13 @@ function parseFrontmatter(filePath) {
1704
1700
  }
1705
1701
  function loadPlanStatus() {
1706
1702
  try {
1707
- const content = fs8.readFileSync(".remy-plan.md", "utf-8");
1703
+ const content = fs9.readFileSync(".remy-plan.md", "utf-8");
1708
1704
  const match = content.match(/^---\n([\s\S]*?)\n---/);
1709
1705
  const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
1710
1706
  if (status === "pending") {
1711
1707
  return `
1712
1708
  <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.
1709
+ 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.
1714
1710
  </pending_plan>`;
1715
1711
  }
1716
1712
  if (status === "approved") {
@@ -1726,7 +1722,7 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
1726
1722
  }
1727
1723
  function loadProjectFileListing() {
1728
1724
  try {
1729
- const entries = fs8.readdirSync(".", { withFileTypes: true });
1725
+ const entries = fs9.readdirSync(".", { withFileTypes: true });
1730
1726
  const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
1731
1727
  if (a.isDirectory() && !b.isDirectory()) {
1732
1728
  return -1;
@@ -1914,10 +1910,10 @@ var init_prompt = __esm({
1914
1910
  });
1915
1911
 
1916
1912
  // src/session.ts
1917
- import fs9 from "fs";
1913
+ import fs10 from "fs";
1918
1914
  function loadSession(state) {
1919
1915
  try {
1920
- const raw = fs9.readFileSync(SESSION_FILE, "utf-8");
1916
+ const raw = fs10.readFileSync(SESSION_FILE, "utf-8");
1921
1917
  const data = JSON.parse(raw);
1922
1918
  if (Array.isArray(data.messages) && data.messages.length > 0) {
1923
1919
  state.messages = sanitizeMessages(data.messages);
@@ -1966,7 +1962,7 @@ function sanitizeMessages(messages) {
1966
1962
  }
1967
1963
  function saveSession(state) {
1968
1964
  try {
1969
- fs9.writeFileSync(
1965
+ fs10.writeFileSync(
1970
1966
  SESSION_FILE,
1971
1967
  JSON.stringify({ messages: state.messages }, null, 2),
1972
1968
  "utf-8"
@@ -1979,7 +1975,7 @@ function saveSession(state) {
1979
1975
  function clearSession(state) {
1980
1976
  state.messages = [];
1981
1977
  try {
1982
- fs9.unlinkSync(SESSION_FILE);
1978
+ fs10.unlinkSync(SESSION_FILE);
1983
1979
  } catch {
1984
1980
  }
1985
1981
  }
@@ -2053,7 +2049,7 @@ var init_compactConversation = __esm({
2053
2049
  });
2054
2050
 
2055
2051
  // src/tools/code/readFile.ts
2056
- import fs10 from "fs/promises";
2052
+ import fs11 from "fs/promises";
2057
2053
  function isBinary(buffer) {
2058
2054
  const sample = buffer.subarray(0, 8192);
2059
2055
  for (let i = 0; i < sample.length; i++) {
@@ -2094,7 +2090,7 @@ var init_readFile = __esm({
2094
2090
  },
2095
2091
  async execute(input) {
2096
2092
  try {
2097
- const buffer = await fs10.readFile(input.path);
2093
+ const buffer = await fs11.readFile(input.path);
2098
2094
  if (isBinary(buffer)) {
2099
2095
  const size = buffer.length;
2100
2096
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -2130,7 +2126,7 @@ var init_readFile = __esm({
2130
2126
  });
2131
2127
 
2132
2128
  // src/tools/code/writeFile.ts
2133
- import fs11 from "fs/promises";
2129
+ import fs12 from "fs/promises";
2134
2130
  import path5 from "path";
2135
2131
  var writeFileTool;
2136
2132
  var init_writeFile = __esm({
@@ -2174,7 +2170,7 @@ var init_writeFile = __esm({
2174
2170
  lastNewlineCount = newlineCount;
2175
2171
  const lastNewline = partial.content.lastIndexOf("\n");
2176
2172
  const completeContent = partial.content.substring(0, lastNewline + 1);
2177
- const oldContent = await fs11.readFile(partial.path, "utf-8").catch(() => "");
2173
+ const oldContent = await fs12.readFile(partial.path, "utf-8").catch(() => "");
2178
2174
  return `Writing ${partial.path} (${newlineCount} lines)
2179
2175
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2180
2176
  }
@@ -2183,13 +2179,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2183
2179
  async execute(input) {
2184
2180
  const release = await acquireFileLock(input.path);
2185
2181
  try {
2186
- await fs11.mkdir(path5.dirname(input.path), { recursive: true });
2182
+ await fs12.mkdir(path5.dirname(input.path), { recursive: true });
2187
2183
  let oldContent = null;
2188
2184
  try {
2189
- oldContent = await fs11.readFile(input.path, "utf-8");
2185
+ oldContent = await fs12.readFile(input.path, "utf-8");
2190
2186
  } catch {
2191
2187
  }
2192
- await fs11.writeFile(input.path, input.content, "utf-8");
2188
+ await fs12.writeFile(input.path, input.content, "utf-8");
2193
2189
  const lineCount = input.content.split("\n").length;
2194
2190
  const label = oldContent !== null ? "Wrote" : "Created";
2195
2191
  return `${label} ${input.path} (${lineCount} lines)
@@ -2289,7 +2285,7 @@ var init_helpers2 = __esm({
2289
2285
  });
2290
2286
 
2291
2287
  // src/tools/code/editFile/index.ts
2292
- import fs12 from "fs/promises";
2288
+ import fs13 from "fs/promises";
2293
2289
  var editFileTool;
2294
2290
  var init_editFile = __esm({
2295
2291
  "src/tools/code/editFile/index.ts"() {
@@ -2328,7 +2324,7 @@ var init_editFile = __esm({
2328
2324
  async execute(input) {
2329
2325
  const release = await acquireFileLock(input.path);
2330
2326
  try {
2331
- const content = await fs12.readFile(input.path, "utf-8");
2327
+ const content = await fs13.readFile(input.path, "utf-8");
2332
2328
  const { old_string, new_string, replace_all } = input;
2333
2329
  const occurrences = findOccurrences(content, old_string);
2334
2330
  if (replace_all) {
@@ -2344,7 +2340,7 @@ var init_editFile = __esm({
2344
2340
  new_string
2345
2341
  );
2346
2342
  }
2347
- await fs12.writeFile(input.path, updated, "utf-8");
2343
+ await fs13.writeFile(input.path, updated, "utf-8");
2348
2344
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2349
2345
  ${unifiedDiff(input.path, content, updated)}`;
2350
2346
  }
@@ -2355,7 +2351,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2355
2351
  old_string.length,
2356
2352
  new_string
2357
2353
  );
2358
- await fs12.writeFile(input.path, updated, "utf-8");
2354
+ await fs13.writeFile(input.path, updated, "utf-8");
2359
2355
  return `Updated ${input.path}
2360
2356
  ${unifiedDiff(input.path, content, updated)}`;
2361
2357
  }
@@ -2371,7 +2367,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2371
2367
  flex.matchedText.length,
2372
2368
  new_string
2373
2369
  );
2374
- await fs12.writeFile(input.path, updated, "utf-8");
2370
+ await fs13.writeFile(input.path, updated, "utf-8");
2375
2371
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2376
2372
  ${unifiedDiff(input.path, content, updated)}`;
2377
2373
  }
@@ -2602,10 +2598,10 @@ var init_glob = __esm({
2602
2598
  });
2603
2599
 
2604
2600
  // src/tools/code/listDir.ts
2605
- import fs13 from "fs/promises";
2601
+ import fs14 from "fs/promises";
2606
2602
  import path6 from "path";
2607
2603
  async function readAndSort(dirPath) {
2608
- const entries = await fs13.readdir(dirPath, { withFileTypes: true });
2604
+ const entries = await fs14.readdir(dirPath, { withFileTypes: true });
2609
2605
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2610
2606
  if (a.isDirectory() && !b.isDirectory()) {
2611
2607
  return -1;
@@ -2646,7 +2642,7 @@ function formatSize(bytes) {
2646
2642
  }
2647
2643
  async function formatFile(dirPath, name, indent) {
2648
2644
  try {
2649
- const stat = await fs13.stat(path6.join(dirPath, name));
2645
+ const stat = await fs14.stat(path6.join(dirPath, name));
2650
2646
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
2651
2647
  } catch {
2652
2648
  return `${indent}${name}`;
@@ -3807,10 +3803,10 @@ var init_tools = __esm({
3807
3803
  });
3808
3804
 
3809
3805
  // src/subagents/browserAutomation/prompt.ts
3810
- import fs14 from "fs";
3806
+ import fs15 from "fs";
3811
3807
  function getBrowserAutomationPrompt() {
3812
3808
  try {
3813
- const appSpec = fs14.readFileSync("src/app.md", "utf-8").trim();
3809
+ const appSpec = fs15.readFileSync("src/app.md", "utf-8").trim();
3814
3810
  return `${BASE_PROMPT}
3815
3811
 
3816
3812
  <!-- cache_breakpoint -->
@@ -4774,12 +4770,12 @@ var init_tools3 = __esm({
4774
4770
  });
4775
4771
 
4776
4772
  // src/subagents/common/context.ts
4777
- import fs15 from "fs";
4773
+ import fs16 from "fs";
4778
4774
  import path7 from "path";
4779
4775
  function walkMdFiles2(dir, skip) {
4780
4776
  const files = [];
4781
4777
  try {
4782
- for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
4778
+ for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
4783
4779
  const full = path7.join(dir, entry.name);
4784
4780
  if (entry.isDirectory()) {
4785
4781
  if (!skip?.has(entry.name)) {
@@ -4795,7 +4791,7 @@ function walkMdFiles2(dir, skip) {
4795
4791
  }
4796
4792
  function parseFrontmatter2(filePath) {
4797
4793
  try {
4798
- const content = fs15.readFileSync(filePath, "utf-8");
4794
+ const content = fs16.readFileSync(filePath, "utf-8");
4799
4795
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4800
4796
  if (!match) {
4801
4797
  return {};
@@ -4841,7 +4837,7 @@ function loadRoadmapIndex() {
4841
4837
  const parts = [];
4842
4838
  try {
4843
4839
  const indexJson = JSON.parse(
4844
- fs15.readFileSync("src/roadmap/index.json", "utf-8")
4840
+ fs16.readFileSync("src/roadmap/index.json", "utf-8")
4845
4841
  );
4846
4842
  if (indexJson.lanes?.length > 0) {
4847
4843
  const laneLines = indexJson.lanes.map(
@@ -4949,7 +4945,7 @@ var init_context = __esm({
4949
4945
  });
4950
4946
 
4951
4947
  // src/subagents/designExpert/data/sampleCache.ts
4952
- import fs16 from "fs";
4948
+ import fs17 from "fs";
4953
4949
  function generateIndices(poolSize, sampleSize) {
4954
4950
  const n = Math.min(sampleSize, poolSize);
4955
4951
  const indices = Array.from({ length: poolSize }, (_, i) => i);
@@ -4961,14 +4957,14 @@ function generateIndices(poolSize, sampleSize) {
4961
4957
  }
4962
4958
  function load() {
4963
4959
  try {
4964
- return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
4960
+ return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
4965
4961
  } catch {
4966
4962
  return null;
4967
4963
  }
4968
4964
  }
4969
4965
  function save(indices) {
4970
4966
  try {
4971
- fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4967
+ fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4972
4968
  } catch {
4973
4969
  }
4974
4970
  }
@@ -5365,7 +5361,7 @@ var init_tools4 = __esm({
5365
5361
  });
5366
5362
 
5367
5363
  // src/subagents/productVision/executor.ts
5368
- import fs17 from "fs";
5364
+ import fs18 from "fs";
5369
5365
  import path8 from "path";
5370
5366
  function resolve(filePath) {
5371
5367
  return path8.join(ROADMAP_DIR, filePath);
@@ -5375,13 +5371,13 @@ async function executeVisionTool(name, input, context) {
5375
5371
  case "writeFile": {
5376
5372
  const filePath = resolve(input.path);
5377
5373
  try {
5378
- fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
5374
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5379
5375
  let oldContent = null;
5380
5376
  try {
5381
- oldContent = fs17.readFileSync(filePath, "utf-8");
5377
+ oldContent = fs18.readFileSync(filePath, "utf-8");
5382
5378
  } catch {
5383
5379
  }
5384
- fs17.writeFileSync(filePath, input.content, "utf-8");
5380
+ fs18.writeFileSync(filePath, input.content, "utf-8");
5385
5381
  const lineCount = input.content.split("\n").length;
5386
5382
  const label = oldContent !== null ? "Wrote" : "Created";
5387
5383
  return `${label} ${filePath} (${lineCount} lines)
@@ -5393,11 +5389,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
5393
5389
  case "deleteFile": {
5394
5390
  const filePath = resolve(input.path);
5395
5391
  try {
5396
- if (!fs17.existsSync(filePath)) {
5392
+ if (!fs18.existsSync(filePath)) {
5397
5393
  return `Error: ${filePath} does not exist`;
5398
5394
  }
5399
- const oldContent = fs17.readFileSync(filePath, "utf-8");
5400
- fs17.unlinkSync(filePath);
5395
+ const oldContent = fs18.readFileSync(filePath, "utf-8");
5396
+ fs18.unlinkSync(filePath);
5401
5397
  return `Deleted ${filePath}
5402
5398
  ${unifiedDiff(filePath, oldContent, "")}`;
5403
5399
  } catch (err) {
@@ -5410,8 +5406,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
5410
5406
  }
5411
5407
  const filePath = resolve("pitch.html");
5412
5408
  try {
5413
- fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
5414
- const existing = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8").trim() : "";
5409
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5410
+ const existing = fs18.existsSync(filePath) ? fs18.readFileSync(filePath, "utf-8").trim() : "";
5415
5411
  const currentDeck = existing || PITCH_DECK_SHELL;
5416
5412
  const task = `
5417
5413
  <pitch_content>${input.task}</pitch_content>
@@ -5436,7 +5432,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
5436
5432
  /```(?:html|wireframe)\n([\s\S]*?)```/
5437
5433
  );
5438
5434
  const html = htmlMatch ? htmlMatch[1].trim() : result;
5439
- fs17.writeFileSync(filePath, html, "utf-8");
5435
+ fs18.writeFileSync(filePath, html, "utf-8");
5440
5436
  return `Pitch deck written successfully.`;
5441
5437
  } catch (err) {
5442
5438
  return `Error generating pitch deck: ${err.message}`;
@@ -5747,10 +5743,9 @@ var init_tools6 = __esm({
5747
5743
  init_writeSpec();
5748
5744
  init_editSpec();
5749
5745
  init_listSpecFiles();
5750
- init_clearSyncStatus();
5751
- init_presentSyncPlan();
5752
5746
  init_presentPublishPlan();
5753
5747
  init_writePlan();
5748
+ init_updatePlanStatus();
5754
5749
  init_setProjectOnboardingState();
5755
5750
  init_promptUser();
5756
5751
  init_confirmDestructiveAction();
@@ -5791,10 +5786,9 @@ var init_tools6 = __esm({
5791
5786
  codeSanityCheckTool,
5792
5787
  compactConversationTool,
5793
5788
  // Post-onboarding
5794
- clearSyncStatusTool,
5795
- presentSyncPlanTool,
5796
5789
  presentPublishPlanTool,
5797
5790
  writePlanTool,
5791
+ updatePlanStatusTool,
5798
5792
  // Spec
5799
5793
  readSpecTool,
5800
5794
  writeSpecTool,
@@ -6088,7 +6082,6 @@ async function runTurn(params) {
6088
6082
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
6089
6083
  "setProjectOnboardingState",
6090
6084
  "setProjectMetadata",
6091
- "clearSyncStatus",
6092
6085
  "editsFinished"
6093
6086
  ]);
6094
6087
  let lastCompletedTools = "";
@@ -6547,8 +6540,6 @@ var init_agent = __esm({
6547
6540
  EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
6548
6541
  "promptUser",
6549
6542
  "setProjectOnboardingState",
6550
- "clearSyncStatus",
6551
- "presentSyncPlan",
6552
6543
  "presentPublishPlan",
6553
6544
  "confirmDestructiveAction",
6554
6545
  "runScenario",
@@ -6561,12 +6552,12 @@ var init_agent = __esm({
6561
6552
  });
6562
6553
 
6563
6554
  // src/config.ts
6564
- import fs18 from "fs";
6555
+ import fs19 from "fs";
6565
6556
  import path9 from "path";
6566
6557
  import os from "os";
6567
6558
  function loadConfigFile() {
6568
6559
  try {
6569
- const raw = fs18.readFileSync(CONFIG_PATH, "utf-8");
6560
+ const raw = fs19.readFileSync(CONFIG_PATH, "utf-8");
6570
6561
  log9.debug("Loaded config file", { path: CONFIG_PATH });
6571
6562
  return JSON.parse(raw);
6572
6563
  } catch (err) {
@@ -6877,7 +6868,6 @@ ${xmlParts}
6877
6868
  const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
6878
6869
  "promptUser",
6879
6870
  "confirmDestructiveAction",
6880
- "presentSyncPlan",
6881
6871
  "presentPublishPlan"
6882
6872
  ]);
6883
6873
  function resolveExternalTool(id, name, _input) {
@@ -7308,7 +7298,7 @@ var init_headless = __esm({
7308
7298
  // src/index.tsx
7309
7299
  import { render } from "ink";
7310
7300
  import os2 from "os";
7311
- import fs19 from "fs";
7301
+ import fs20 from "fs";
7312
7302
  import path10 from "path";
7313
7303
 
7314
7304
  // src/tui/App.tsx
@@ -7627,7 +7617,7 @@ for (let i = 0; i < args.length; i++) {
7627
7617
  var startupLog = createLogger("startup");
7628
7618
  function printDebugInfo(config) {
7629
7619
  const pkg = JSON.parse(
7630
- fs19.readFileSync(
7620
+ fs20.readFileSync(
7631
7621
  path10.join(import.meta.dirname, "..", "package.json"),
7632
7622
  "utf-8"
7633
7623
  )
@@ -365,3 +365,14 @@ Consult the `visualDesignExpert` to help you work through authentication at a hi
365
365
  **The overall login page:** This is a branding moment. Use the app's full visual identity — colors, typography, any logos, hero imagery, or illustration. A centered card on a branded background is a classic pattern. Don't make it look like a generic SaaS login template. The login page must feel like it belongs to this specific app. Consult the `visualDesignExpert` for guidance on how to really make this shine.
366
366
 
367
367
  **Post-login transition:** After successful verification, the transition into the app should feel seamless and instant. Avoid a blank loading screen — if data needs to load, show the app shell with skeleton states. Always make sure the user has a way of logging out.
368
+
369
+ ## Testing Auth in Development
370
+
371
+ Auth works the same in dev/preview as in production — real verification codes are sent to real email addresses and phone numbers. There are two test bypasses:
372
+
373
+ - **Email:** `remy@mindstudio.ai` — verification code is always `123456`
374
+ - **Phone:** any `555` number (e.g. `+15551234567`) — verification code is always `123456`
375
+
376
+ All other emails and phone numbers receive real codes. There is no dev-mode bypass, no fake code, and no way to skip verification. When testing auth flows in the preview, use one of the test bypasses above or a real email/phone.
377
+
378
+ Browser automation tools (screenshots, automated browser tests) handle their own auth sessions. Scenarios seed database data but do not create browser auth sessions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.141",
3
+ "version": "0.1.142",
4
4
  "description": "MindStudio coding agent",
5
5
  "repository": {
6
6
  "type": "git",