@mindstudio-ai/remy 0.1.139 → 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.
- package/dist/automatedActions/approvePlan.md +5 -0
- package/dist/automatedActions/buildFromRoadmap.md +1 -1
- package/dist/automatedActions/codeCleanup.md +1 -1
- package/dist/automatedActions/rejectPlan.md +5 -0
- package/dist/headless.js +120 -52
- package/dist/index.js +139 -71
- package/dist/prompt/compiled/methods.md +40 -6
- package/dist/prompt/static/coding.md +2 -0
- package/dist/prompt/static/instructions.md +2 -2
- package/package.json +1 -1
|
@@ -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.
|
|
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,
|
|
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.
|
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/
|
|
1367
|
-
|
|
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: "
|
|
1371
|
-
description: "
|
|
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
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1914
|
+
await fs11.mkdir(path6.dirname(input.path), { recursive: true });
|
|
1885
1915
|
let oldContent = null;
|
|
1886
1916
|
try {
|
|
1887
|
-
oldContent = await
|
|
1917
|
+
oldContent = await fs11.readFile(input.path, "utf-8");
|
|
1888
1918
|
} catch {
|
|
1889
1919
|
}
|
|
1890
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}`;
|
|
@@ -2352,7 +2382,30 @@ var listDirTool = {
|
|
|
2352
2382
|
const capped = children.slice(0, MAX_CHILDREN);
|
|
2353
2383
|
for (const child of capped) {
|
|
2354
2384
|
if (child.isDirectory()) {
|
|
2355
|
-
|
|
2385
|
+
const [childDisplay, childFinalPath] = await collapsePath(
|
|
2386
|
+
finalPath,
|
|
2387
|
+
child.name
|
|
2388
|
+
);
|
|
2389
|
+
lines.push(` ${childDisplay}/`);
|
|
2390
|
+
try {
|
|
2391
|
+
const grandchildren = await readAndSort(childFinalPath);
|
|
2392
|
+
const gcCapped = grandchildren.slice(0, MAX_CHILDREN);
|
|
2393
|
+
for (const gc of gcCapped) {
|
|
2394
|
+
if (gc.isDirectory()) {
|
|
2395
|
+
lines.push(` ${gc.name}/`);
|
|
2396
|
+
} else {
|
|
2397
|
+
lines.push(
|
|
2398
|
+
await formatFile(childFinalPath, gc.name, " ")
|
|
2399
|
+
);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
if (grandchildren.length > MAX_CHILDREN) {
|
|
2403
|
+
lines.push(
|
|
2404
|
+
` ... and ${grandchildren.length - MAX_CHILDREN} more`
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
} catch {
|
|
2408
|
+
}
|
|
2356
2409
|
} else {
|
|
2357
2410
|
lines.push(await formatFile(finalPath, child.name, " "));
|
|
2358
2411
|
}
|
|
@@ -3358,11 +3411,11 @@ var BROWSER_TOOLS = [
|
|
|
3358
3411
|
var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
|
|
3359
3412
|
|
|
3360
3413
|
// src/subagents/browserAutomation/prompt.ts
|
|
3361
|
-
import
|
|
3414
|
+
import fs14 from "fs";
|
|
3362
3415
|
var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
|
|
3363
3416
|
function getBrowserAutomationPrompt() {
|
|
3364
3417
|
try {
|
|
3365
|
-
const appSpec =
|
|
3418
|
+
const appSpec = fs14.readFileSync("src/app.md", "utf-8").trim();
|
|
3366
3419
|
return `${BASE_PROMPT}
|
|
3367
3420
|
|
|
3368
3421
|
<!-- cache_breakpoint -->
|
|
@@ -4204,12 +4257,12 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
|
|
|
4204
4257
|
}
|
|
4205
4258
|
|
|
4206
4259
|
// src/subagents/common/context.ts
|
|
4207
|
-
import
|
|
4260
|
+
import fs15 from "fs";
|
|
4208
4261
|
import path8 from "path";
|
|
4209
4262
|
function walkMdFiles2(dir, skip) {
|
|
4210
4263
|
const files = [];
|
|
4211
4264
|
try {
|
|
4212
|
-
for (const entry of
|
|
4265
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
4213
4266
|
const full = path8.join(dir, entry.name);
|
|
4214
4267
|
if (entry.isDirectory()) {
|
|
4215
4268
|
if (!skip?.has(entry.name)) {
|
|
@@ -4225,7 +4278,7 @@ function walkMdFiles2(dir, skip) {
|
|
|
4225
4278
|
}
|
|
4226
4279
|
function parseFrontmatter2(filePath) {
|
|
4227
4280
|
try {
|
|
4228
|
-
const content =
|
|
4281
|
+
const content = fs15.readFileSync(filePath, "utf-8");
|
|
4229
4282
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4230
4283
|
if (!match) {
|
|
4231
4284
|
return {};
|
|
@@ -4271,7 +4324,7 @@ function loadRoadmapIndex() {
|
|
|
4271
4324
|
const parts = [];
|
|
4272
4325
|
try {
|
|
4273
4326
|
const indexJson = JSON.parse(
|
|
4274
|
-
|
|
4327
|
+
fs15.readFileSync("src/roadmap/index.json", "utf-8")
|
|
4275
4328
|
);
|
|
4276
4329
|
if (indexJson.lanes?.length > 0) {
|
|
4277
4330
|
const laneLines = indexJson.lanes.map(
|
|
@@ -4374,7 +4427,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
|
|
|
4374
4427
|
}
|
|
4375
4428
|
|
|
4376
4429
|
// src/subagents/designExpert/data/sampleCache.ts
|
|
4377
|
-
import
|
|
4430
|
+
import fs16 from "fs";
|
|
4378
4431
|
var SAMPLE_FILE = ".remy-design-sample.json";
|
|
4379
4432
|
var cached = null;
|
|
4380
4433
|
function generateIndices(poolSize, sampleSize) {
|
|
@@ -4388,14 +4441,14 @@ function generateIndices(poolSize, sampleSize) {
|
|
|
4388
4441
|
}
|
|
4389
4442
|
function load() {
|
|
4390
4443
|
try {
|
|
4391
|
-
return JSON.parse(
|
|
4444
|
+
return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
|
|
4392
4445
|
} catch {
|
|
4393
4446
|
return null;
|
|
4394
4447
|
}
|
|
4395
4448
|
}
|
|
4396
4449
|
function save(indices) {
|
|
4397
4450
|
try {
|
|
4398
|
-
|
|
4451
|
+
fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
|
|
4399
4452
|
} catch {
|
|
4400
4453
|
}
|
|
4401
4454
|
}
|
|
@@ -4724,7 +4777,7 @@ var VISION_TOOLS = [
|
|
|
4724
4777
|
];
|
|
4725
4778
|
|
|
4726
4779
|
// src/subagents/productVision/executor.ts
|
|
4727
|
-
import
|
|
4780
|
+
import fs17 from "fs";
|
|
4728
4781
|
import path9 from "path";
|
|
4729
4782
|
var ROADMAP_DIR = "src/roadmap";
|
|
4730
4783
|
var PITCH_DECK_SHELL = readAsset(
|
|
@@ -4739,13 +4792,13 @@ async function executeVisionTool(name, input, context) {
|
|
|
4739
4792
|
case "writeFile": {
|
|
4740
4793
|
const filePath = resolve(input.path);
|
|
4741
4794
|
try {
|
|
4742
|
-
|
|
4795
|
+
fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
4743
4796
|
let oldContent = null;
|
|
4744
4797
|
try {
|
|
4745
|
-
oldContent =
|
|
4798
|
+
oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
4746
4799
|
} catch {
|
|
4747
4800
|
}
|
|
4748
|
-
|
|
4801
|
+
fs17.writeFileSync(filePath, input.content, "utf-8");
|
|
4749
4802
|
const lineCount = input.content.split("\n").length;
|
|
4750
4803
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
4751
4804
|
return `${label} ${filePath} (${lineCount} lines)
|
|
@@ -4757,11 +4810,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
|
|
|
4757
4810
|
case "deleteFile": {
|
|
4758
4811
|
const filePath = resolve(input.path);
|
|
4759
4812
|
try {
|
|
4760
|
-
if (!
|
|
4813
|
+
if (!fs17.existsSync(filePath)) {
|
|
4761
4814
|
return `Error: ${filePath} does not exist`;
|
|
4762
4815
|
}
|
|
4763
|
-
const oldContent =
|
|
4764
|
-
|
|
4816
|
+
const oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
4817
|
+
fs17.unlinkSync(filePath);
|
|
4765
4818
|
return `Deleted ${filePath}
|
|
4766
4819
|
${unifiedDiff(filePath, oldContent, "")}`;
|
|
4767
4820
|
} catch (err) {
|
|
@@ -4774,8 +4827,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
|
|
|
4774
4827
|
}
|
|
4775
4828
|
const filePath = resolve("pitch.html");
|
|
4776
4829
|
try {
|
|
4777
|
-
|
|
4778
|
-
const existing =
|
|
4830
|
+
fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
4831
|
+
const existing = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8").trim() : "";
|
|
4779
4832
|
const currentDeck = existing || PITCH_DECK_SHELL;
|
|
4780
4833
|
const task = `
|
|
4781
4834
|
<pitch_content>${input.task}</pitch_content>
|
|
@@ -4800,7 +4853,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
|
|
|
4800
4853
|
/```(?:html|wireframe)\n([\s\S]*?)```/
|
|
4801
4854
|
);
|
|
4802
4855
|
const html = htmlMatch ? htmlMatch[1].trim() : result;
|
|
4803
|
-
|
|
4856
|
+
fs17.writeFileSync(filePath, html, "utf-8");
|
|
4804
4857
|
return `Pitch deck written successfully.`;
|
|
4805
4858
|
} catch (err) {
|
|
4806
4859
|
return `Error generating pitch deck: ${err.message}`;
|
|
@@ -5045,7 +5098,7 @@ var ALL_TOOLS = [
|
|
|
5045
5098
|
clearSyncStatusTool,
|
|
5046
5099
|
presentSyncPlanTool,
|
|
5047
5100
|
presentPublishPlanTool,
|
|
5048
|
-
|
|
5101
|
+
writePlanTool,
|
|
5049
5102
|
// Spec
|
|
5050
5103
|
readSpecTool,
|
|
5051
5104
|
writeSpecTool,
|
|
@@ -5087,12 +5140,12 @@ function executeTool(name, input, context) {
|
|
|
5087
5140
|
}
|
|
5088
5141
|
|
|
5089
5142
|
// src/session.ts
|
|
5090
|
-
import
|
|
5143
|
+
import fs18 from "fs";
|
|
5091
5144
|
var log7 = createLogger("session");
|
|
5092
5145
|
var SESSION_FILE = ".remy-session.json";
|
|
5093
5146
|
function loadSession(state) {
|
|
5094
5147
|
try {
|
|
5095
|
-
const raw =
|
|
5148
|
+
const raw = fs18.readFileSync(SESSION_FILE, "utf-8");
|
|
5096
5149
|
const data = JSON.parse(raw);
|
|
5097
5150
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
5098
5151
|
state.messages = sanitizeMessages(data.messages);
|
|
@@ -5141,7 +5194,7 @@ function sanitizeMessages(messages) {
|
|
|
5141
5194
|
}
|
|
5142
5195
|
function saveSession(state) {
|
|
5143
5196
|
try {
|
|
5144
|
-
|
|
5197
|
+
fs18.writeFileSync(
|
|
5145
5198
|
SESSION_FILE,
|
|
5146
5199
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
5147
5200
|
"utf-8"
|
|
@@ -5154,7 +5207,7 @@ function saveSession(state) {
|
|
|
5154
5207
|
function clearSession(state) {
|
|
5155
5208
|
state.messages = [];
|
|
5156
5209
|
try {
|
|
5157
|
-
|
|
5210
|
+
fs18.unlinkSync(SESSION_FILE);
|
|
5158
5211
|
} catch {
|
|
5159
5212
|
}
|
|
5160
5213
|
}
|
|
@@ -5382,7 +5435,6 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
|
5382
5435
|
"clearSyncStatus",
|
|
5383
5436
|
"presentSyncPlan",
|
|
5384
5437
|
"presentPublishPlan",
|
|
5385
|
-
"presentPlan",
|
|
5386
5438
|
"confirmDestructiveAction",
|
|
5387
5439
|
"runScenario",
|
|
5388
5440
|
"runMethod",
|
|
@@ -6133,7 +6185,6 @@ ${xmlParts}
|
|
|
6133
6185
|
const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
6134
6186
|
"promptUser",
|
|
6135
6187
|
"confirmDestructiveAction",
|
|
6136
|
-
"presentPlan",
|
|
6137
6188
|
"presentSyncPlan",
|
|
6138
6189
|
"presentPublishPlan"
|
|
6139
6190
|
]);
|
|
@@ -6351,6 +6402,23 @@ ${xmlParts}
|
|
|
6351
6402
|
userMessage = resolved;
|
|
6352
6403
|
}
|
|
6353
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
|
+
}
|
|
6354
6422
|
const onboardingState = parsed.onboardingState ?? "onboardingFinished";
|
|
6355
6423
|
const system = buildSystemPrompt(
|
|
6356
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/
|
|
857
|
-
|
|
858
|
-
var
|
|
859
|
-
|
|
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
|
-
|
|
862
|
+
PLAN_FILE = ".remy-plan.md";
|
|
863
|
+
writePlanTool = {
|
|
862
864
|
clearable: false,
|
|
863
865
|
definition: {
|
|
864
|
-
name: "
|
|
865
|
-
description: "
|
|
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
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1917
|
+
import fs9 from "fs";
|
|
1888
1918
|
function loadSession(state) {
|
|
1889
1919
|
try {
|
|
1890
|
-
const raw =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2186
|
+
await fs11.mkdir(path5.dirname(input.path), { recursive: true });
|
|
2157
2187
|
let oldContent = null;
|
|
2158
2188
|
try {
|
|
2159
|
-
oldContent = await
|
|
2189
|
+
oldContent = await fs11.readFile(input.path, "utf-8");
|
|
2160
2190
|
} catch {
|
|
2161
2191
|
}
|
|
2162
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2605
|
+
import fs13 from "fs/promises";
|
|
2576
2606
|
import path6 from "path";
|
|
2577
2607
|
async function readAndSort(dirPath) {
|
|
2578
|
-
const entries = await
|
|
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
|
|
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}`;
|
|
@@ -2660,7 +2690,30 @@ var init_listDir = __esm({
|
|
|
2660
2690
|
const capped = children.slice(0, MAX_CHILDREN);
|
|
2661
2691
|
for (const child of capped) {
|
|
2662
2692
|
if (child.isDirectory()) {
|
|
2663
|
-
|
|
2693
|
+
const [childDisplay, childFinalPath] = await collapsePath(
|
|
2694
|
+
finalPath,
|
|
2695
|
+
child.name
|
|
2696
|
+
);
|
|
2697
|
+
lines.push(` ${childDisplay}/`);
|
|
2698
|
+
try {
|
|
2699
|
+
const grandchildren = await readAndSort(childFinalPath);
|
|
2700
|
+
const gcCapped = grandchildren.slice(0, MAX_CHILDREN);
|
|
2701
|
+
for (const gc of gcCapped) {
|
|
2702
|
+
if (gc.isDirectory()) {
|
|
2703
|
+
lines.push(` ${gc.name}/`);
|
|
2704
|
+
} else {
|
|
2705
|
+
lines.push(
|
|
2706
|
+
await formatFile(childFinalPath, gc.name, " ")
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
if (grandchildren.length > MAX_CHILDREN) {
|
|
2711
|
+
lines.push(
|
|
2712
|
+
` ... and ${grandchildren.length - MAX_CHILDREN} more`
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
} catch {
|
|
2716
|
+
}
|
|
2664
2717
|
} else {
|
|
2665
2718
|
lines.push(await formatFile(finalPath, child.name, " "));
|
|
2666
2719
|
}
|
|
@@ -3754,10 +3807,10 @@ var init_tools = __esm({
|
|
|
3754
3807
|
});
|
|
3755
3808
|
|
|
3756
3809
|
// src/subagents/browserAutomation/prompt.ts
|
|
3757
|
-
import
|
|
3810
|
+
import fs14 from "fs";
|
|
3758
3811
|
function getBrowserAutomationPrompt() {
|
|
3759
3812
|
try {
|
|
3760
|
-
const appSpec =
|
|
3813
|
+
const appSpec = fs14.readFileSync("src/app.md", "utf-8").trim();
|
|
3761
3814
|
return `${BASE_PROMPT}
|
|
3762
3815
|
|
|
3763
3816
|
<!-- cache_breakpoint -->
|
|
@@ -4721,12 +4774,12 @@ var init_tools3 = __esm({
|
|
|
4721
4774
|
});
|
|
4722
4775
|
|
|
4723
4776
|
// src/subagents/common/context.ts
|
|
4724
|
-
import
|
|
4777
|
+
import fs15 from "fs";
|
|
4725
4778
|
import path7 from "path";
|
|
4726
4779
|
function walkMdFiles2(dir, skip) {
|
|
4727
4780
|
const files = [];
|
|
4728
4781
|
try {
|
|
4729
|
-
for (const entry of
|
|
4782
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
4730
4783
|
const full = path7.join(dir, entry.name);
|
|
4731
4784
|
if (entry.isDirectory()) {
|
|
4732
4785
|
if (!skip?.has(entry.name)) {
|
|
@@ -4742,7 +4795,7 @@ function walkMdFiles2(dir, skip) {
|
|
|
4742
4795
|
}
|
|
4743
4796
|
function parseFrontmatter2(filePath) {
|
|
4744
4797
|
try {
|
|
4745
|
-
const content =
|
|
4798
|
+
const content = fs15.readFileSync(filePath, "utf-8");
|
|
4746
4799
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4747
4800
|
if (!match) {
|
|
4748
4801
|
return {};
|
|
@@ -4788,7 +4841,7 @@ function loadRoadmapIndex() {
|
|
|
4788
4841
|
const parts = [];
|
|
4789
4842
|
try {
|
|
4790
4843
|
const indexJson = JSON.parse(
|
|
4791
|
-
|
|
4844
|
+
fs15.readFileSync("src/roadmap/index.json", "utf-8")
|
|
4792
4845
|
);
|
|
4793
4846
|
if (indexJson.lanes?.length > 0) {
|
|
4794
4847
|
const laneLines = indexJson.lanes.map(
|
|
@@ -4896,7 +4949,7 @@ var init_context = __esm({
|
|
|
4896
4949
|
});
|
|
4897
4950
|
|
|
4898
4951
|
// src/subagents/designExpert/data/sampleCache.ts
|
|
4899
|
-
import
|
|
4952
|
+
import fs16 from "fs";
|
|
4900
4953
|
function generateIndices(poolSize, sampleSize) {
|
|
4901
4954
|
const n = Math.min(sampleSize, poolSize);
|
|
4902
4955
|
const indices = Array.from({ length: poolSize }, (_, i) => i);
|
|
@@ -4908,14 +4961,14 @@ function generateIndices(poolSize, sampleSize) {
|
|
|
4908
4961
|
}
|
|
4909
4962
|
function load() {
|
|
4910
4963
|
try {
|
|
4911
|
-
return JSON.parse(
|
|
4964
|
+
return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
|
|
4912
4965
|
} catch {
|
|
4913
4966
|
return null;
|
|
4914
4967
|
}
|
|
4915
4968
|
}
|
|
4916
4969
|
function save(indices) {
|
|
4917
4970
|
try {
|
|
4918
|
-
|
|
4971
|
+
fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
|
|
4919
4972
|
} catch {
|
|
4920
4973
|
}
|
|
4921
4974
|
}
|
|
@@ -5312,7 +5365,7 @@ var init_tools4 = __esm({
|
|
|
5312
5365
|
});
|
|
5313
5366
|
|
|
5314
5367
|
// src/subagents/productVision/executor.ts
|
|
5315
|
-
import
|
|
5368
|
+
import fs17 from "fs";
|
|
5316
5369
|
import path8 from "path";
|
|
5317
5370
|
function resolve(filePath) {
|
|
5318
5371
|
return path8.join(ROADMAP_DIR, filePath);
|
|
@@ -5322,13 +5375,13 @@ async function executeVisionTool(name, input, context) {
|
|
|
5322
5375
|
case "writeFile": {
|
|
5323
5376
|
const filePath = resolve(input.path);
|
|
5324
5377
|
try {
|
|
5325
|
-
|
|
5378
|
+
fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5326
5379
|
let oldContent = null;
|
|
5327
5380
|
try {
|
|
5328
|
-
oldContent =
|
|
5381
|
+
oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
5329
5382
|
} catch {
|
|
5330
5383
|
}
|
|
5331
|
-
|
|
5384
|
+
fs17.writeFileSync(filePath, input.content, "utf-8");
|
|
5332
5385
|
const lineCount = input.content.split("\n").length;
|
|
5333
5386
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
5334
5387
|
return `${label} ${filePath} (${lineCount} lines)
|
|
@@ -5340,11 +5393,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
|
|
|
5340
5393
|
case "deleteFile": {
|
|
5341
5394
|
const filePath = resolve(input.path);
|
|
5342
5395
|
try {
|
|
5343
|
-
if (!
|
|
5396
|
+
if (!fs17.existsSync(filePath)) {
|
|
5344
5397
|
return `Error: ${filePath} does not exist`;
|
|
5345
5398
|
}
|
|
5346
|
-
const oldContent =
|
|
5347
|
-
|
|
5399
|
+
const oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
5400
|
+
fs17.unlinkSync(filePath);
|
|
5348
5401
|
return `Deleted ${filePath}
|
|
5349
5402
|
${unifiedDiff(filePath, oldContent, "")}`;
|
|
5350
5403
|
} catch (err) {
|
|
@@ -5357,8 +5410,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
|
|
|
5357
5410
|
}
|
|
5358
5411
|
const filePath = resolve("pitch.html");
|
|
5359
5412
|
try {
|
|
5360
|
-
|
|
5361
|
-
const existing =
|
|
5413
|
+
fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5414
|
+
const existing = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8").trim() : "";
|
|
5362
5415
|
const currentDeck = existing || PITCH_DECK_SHELL;
|
|
5363
5416
|
const task = `
|
|
5364
5417
|
<pitch_content>${input.task}</pitch_content>
|
|
@@ -5383,7 +5436,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
|
|
|
5383
5436
|
/```(?:html|wireframe)\n([\s\S]*?)```/
|
|
5384
5437
|
);
|
|
5385
5438
|
const html = htmlMatch ? htmlMatch[1].trim() : result;
|
|
5386
|
-
|
|
5439
|
+
fs17.writeFileSync(filePath, html, "utf-8");
|
|
5387
5440
|
return `Pitch deck written successfully.`;
|
|
5388
5441
|
} catch (err) {
|
|
5389
5442
|
return `Error generating pitch deck: ${err.message}`;
|
|
@@ -5697,7 +5750,7 @@ var init_tools6 = __esm({
|
|
|
5697
5750
|
init_clearSyncStatus();
|
|
5698
5751
|
init_presentSyncPlan();
|
|
5699
5752
|
init_presentPublishPlan();
|
|
5700
|
-
|
|
5753
|
+
init_writePlan();
|
|
5701
5754
|
init_setProjectOnboardingState();
|
|
5702
5755
|
init_promptUser();
|
|
5703
5756
|
init_confirmDestructiveAction();
|
|
@@ -5741,7 +5794,7 @@ var init_tools6 = __esm({
|
|
|
5741
5794
|
clearSyncStatusTool,
|
|
5742
5795
|
presentSyncPlanTool,
|
|
5743
5796
|
presentPublishPlanTool,
|
|
5744
|
-
|
|
5797
|
+
writePlanTool,
|
|
5745
5798
|
// Spec
|
|
5746
5799
|
readSpecTool,
|
|
5747
5800
|
writeSpecTool,
|
|
@@ -6497,7 +6550,6 @@ var init_agent = __esm({
|
|
|
6497
6550
|
"clearSyncStatus",
|
|
6498
6551
|
"presentSyncPlan",
|
|
6499
6552
|
"presentPublishPlan",
|
|
6500
|
-
"presentPlan",
|
|
6501
6553
|
"confirmDestructiveAction",
|
|
6502
6554
|
"runScenario",
|
|
6503
6555
|
"runMethod",
|
|
@@ -6509,12 +6561,12 @@ var init_agent = __esm({
|
|
|
6509
6561
|
});
|
|
6510
6562
|
|
|
6511
6563
|
// src/config.ts
|
|
6512
|
-
import
|
|
6564
|
+
import fs18 from "fs";
|
|
6513
6565
|
import path9 from "path";
|
|
6514
6566
|
import os from "os";
|
|
6515
6567
|
function loadConfigFile() {
|
|
6516
6568
|
try {
|
|
6517
|
-
const raw =
|
|
6569
|
+
const raw = fs18.readFileSync(CONFIG_PATH, "utf-8");
|
|
6518
6570
|
log9.debug("Loaded config file", { path: CONFIG_PATH });
|
|
6519
6571
|
return JSON.parse(raw);
|
|
6520
6572
|
} catch (err) {
|
|
@@ -6684,7 +6736,7 @@ __export(headless_exports, {
|
|
|
6684
6736
|
startHeadless: () => startHeadless
|
|
6685
6737
|
});
|
|
6686
6738
|
import { createInterface } from "readline";
|
|
6687
|
-
import { writeFileSync } from "fs";
|
|
6739
|
+
import { writeFileSync, readFileSync, unlinkSync } from "fs";
|
|
6688
6740
|
function emit(event, data, requestId) {
|
|
6689
6741
|
const payload = { event, ...data };
|
|
6690
6742
|
if (requestId) {
|
|
@@ -6825,7 +6877,6 @@ ${xmlParts}
|
|
|
6825
6877
|
const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
6826
6878
|
"promptUser",
|
|
6827
6879
|
"confirmDestructiveAction",
|
|
6828
|
-
"presentPlan",
|
|
6829
6880
|
"presentSyncPlan",
|
|
6830
6881
|
"presentPublishPlan"
|
|
6831
6882
|
]);
|
|
@@ -7043,6 +7094,23 @@ ${xmlParts}
|
|
|
7043
7094
|
userMessage = resolved;
|
|
7044
7095
|
}
|
|
7045
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
|
+
}
|
|
7046
7114
|
const onboardingState = parsed.onboardingState ?? "onboardingFinished";
|
|
7047
7115
|
const system = buildSystemPrompt(
|
|
7048
7116
|
onboardingState,
|
|
@@ -7240,7 +7308,7 @@ var init_headless = __esm({
|
|
|
7240
7308
|
// src/index.tsx
|
|
7241
7309
|
import { render } from "ink";
|
|
7242
7310
|
import os2 from "os";
|
|
7243
|
-
import
|
|
7311
|
+
import fs19 from "fs";
|
|
7244
7312
|
import path10 from "path";
|
|
7245
7313
|
|
|
7246
7314
|
// src/tui/App.tsx
|
|
@@ -7559,7 +7627,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
7559
7627
|
var startupLog = createLogger("startup");
|
|
7560
7628
|
function printDebugInfo(config) {
|
|
7561
7629
|
const pkg = JSON.parse(
|
|
7562
|
-
|
|
7630
|
+
fs19.readFileSync(
|
|
7563
7631
|
path10.join(import.meta.dirname, "..", "package.json"),
|
|
7564
7632
|
"utf-8"
|
|
7565
7633
|
)
|
|
@@ -245,20 +245,54 @@ export function getApprovalState(approvals: Approval[]) {
|
|
|
245
245
|
|
|
246
246
|
## Streaming
|
|
247
247
|
|
|
248
|
-
Methods can
|
|
248
|
+
Methods can push real-time updates to the frontend using `stream()`. This is the standard pattern for any method that takes more than a few seconds.
|
|
249
249
|
|
|
250
250
|
```typescript
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
import { mindstudio, stream } from '@mindstudio-ai/agent';
|
|
252
|
+
|
|
253
|
+
export async function enrichProfile(input: { name: string }) {
|
|
254
|
+
await stream('Researching...');
|
|
255
|
+
|
|
256
|
+
const { content } = await mindstudio.generateText(
|
|
257
|
+
{ message: `Find background info on ${input.name}` },
|
|
258
|
+
{ onLog: (event) => stream({ status: event.value }) },
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
await stream({ status: 'generating_image', progress: 0.5 });
|
|
262
|
+
|
|
263
|
+
const { imageUrl } = await mindstudio.generateImage(
|
|
264
|
+
{ prompt: `Professional portrait illustration of ${input.name}` },
|
|
265
|
+
{ onLog: (event) => stream({ status: event.value }) },
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
return { bio: content, imageUrl };
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Two data types:
|
|
273
|
+
- `stream('text')` sends a text token (like LLM streaming output)
|
|
274
|
+
- `stream({ ... })` sends structured data (progress, status, intermediate results)
|
|
275
|
+
|
|
276
|
+
Every SDK action accepts an `onLog` callback that emits execution progress. Pipe it through `stream()` so the frontend sees what's happening inside each action in real time. Use `stream()` directly for your own status messages between actions.
|
|
277
|
+
|
|
278
|
+
When there's no active stream (method not called with `stream: true`, CLI execution, background jobs), `stream()` is a silent no-op. Always safe to include unconditionally.
|
|
279
|
+
|
|
280
|
+
### Frontend
|
|
281
|
+
|
|
282
|
+
The frontend calls the method with `stream: true` and receives updates via `onToken`. The `text` value is accumulated (not a delta), so replace your display content each time.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const result = await api.enrichProfile(
|
|
286
|
+
{ name: 'Alice' },
|
|
254
287
|
{
|
|
255
288
|
stream: true,
|
|
256
|
-
onToken: (text) =>
|
|
289
|
+
onToken: (text) => setResponseText(text),
|
|
257
290
|
},
|
|
258
291
|
);
|
|
292
|
+
// result is the same final output you'd get without streaming
|
|
259
293
|
```
|
|
260
294
|
|
|
261
|
-
|
|
295
|
+
Use `onStreamError` for transient error handling. The method's promise still resolves with the final return value once execution completes.
|
|
262
296
|
|
|
263
297
|
## Raw Request Context (API Interface)
|
|
264
298
|
|
|
@@ -30,6 +30,8 @@ For any work involving AI models, external actions (web scraping, email, SMS), o
|
|
|
30
30
|
|
|
31
31
|
For multi-step tasks with branching logic (research, enrichment, content pipelines), use `runTask()` instead of manually chaining SDK actions. It runs an autonomous agent loop that composes actions, retries on failure, and returns structured JSON. See the task agents reference for details.
|
|
32
32
|
|
|
33
|
+
For methods that take more than a few seconds, use `stream()` from `@mindstudio-ai/agent` to push real-time progress to the frontend. Pipe `onLog` from SDK actions through `stream()` so users see what's happening. The frontend calls the method with `stream: true` and gets updates via `onToken`. See the methods reference for the full pattern.
|
|
34
|
+
|
|
33
35
|
### Auth
|
|
34
36
|
- Not every app needs auth, and even for apps that do need auth, not every screen needs auth. Think intentionally about places where auth is required. Don't make auth be the first thing a user sees - that's jarring. Only show auth at intuitive and natural moments in the user's journey - be thoughtful about how to implement auth in the UI.
|
|
35
37
|
- Frontend interfaces are always untrusted. Always enforce auth in backend methods. Use frontend auth and role information as a hint to conditionally show/hide UI to make the experience pleasant and seamless for users depending on their state, but remember to always use backend methods for gating data that is conditional on auth.
|
|
@@ -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 `
|
|
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:
|