@mindstudio-ai/remy 0.1.109 → 0.1.111
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/headless.js +137 -37
- package/dist/index.js +154 -53
- package/dist/prompt/compiled/design.md +1 -0
- package/dist/prompt/compiled/interfaces.md +2 -2
- package/dist/prompt/compiled/sdk-actions.md +1 -0
- package/dist/prompt/sources/llms.txt +1 -0
- package/dist/prompt/static/instructions.md +1 -1
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -414,7 +414,7 @@ ${isLspConfigured() ? `<typescript_lsp>
|
|
|
414
414
|
<conversation_summaries>
|
|
415
415
|
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.
|
|
416
416
|
|
|
417
|
-
Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query
|
|
417
|
+
Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query.
|
|
418
418
|
</conversation_summaries>
|
|
419
419
|
|
|
420
420
|
<project_onboarding>
|
|
@@ -2235,11 +2235,62 @@ var globTool = {
|
|
|
2235
2235
|
|
|
2236
2236
|
// src/tools/code/listDir.ts
|
|
2237
2237
|
import fs12 from "fs/promises";
|
|
2238
|
+
import path7 from "path";
|
|
2239
|
+
var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
2240
|
+
var MAX_CHILDREN = 15;
|
|
2241
|
+
async function readAndSort(dirPath) {
|
|
2242
|
+
const entries = await fs12.readdir(dirPath, { withFileTypes: true });
|
|
2243
|
+
return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
|
|
2244
|
+
if (a.isDirectory() && !b.isDirectory()) {
|
|
2245
|
+
return -1;
|
|
2246
|
+
}
|
|
2247
|
+
if (!a.isDirectory() && b.isDirectory()) {
|
|
2248
|
+
return 1;
|
|
2249
|
+
}
|
|
2250
|
+
return a.name.localeCompare(b.name);
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
async function collapsePath(basePath, name) {
|
|
2254
|
+
let display = name;
|
|
2255
|
+
let current = path7.join(basePath, name);
|
|
2256
|
+
for (; ; ) {
|
|
2257
|
+
let children;
|
|
2258
|
+
try {
|
|
2259
|
+
children = await readAndSort(current);
|
|
2260
|
+
} catch {
|
|
2261
|
+
break;
|
|
2262
|
+
}
|
|
2263
|
+
if (children.length === 1 && children[0].isDirectory()) {
|
|
2264
|
+
display += "/" + children[0].name;
|
|
2265
|
+
current = path7.join(current, children[0].name);
|
|
2266
|
+
} else {
|
|
2267
|
+
break;
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
return [display, current];
|
|
2271
|
+
}
|
|
2272
|
+
function formatSize(bytes) {
|
|
2273
|
+
if (bytes < 1e3) {
|
|
2274
|
+
return `${bytes} B`;
|
|
2275
|
+
}
|
|
2276
|
+
if (bytes < 1e6) {
|
|
2277
|
+
return `${(bytes / 1e3).toFixed(1)} kB`;
|
|
2278
|
+
}
|
|
2279
|
+
return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
2280
|
+
}
|
|
2281
|
+
async function formatFile(dirPath, name, indent) {
|
|
2282
|
+
try {
|
|
2283
|
+
const stat = await fs12.stat(path7.join(dirPath, name));
|
|
2284
|
+
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
|
|
2285
|
+
} catch {
|
|
2286
|
+
return `${indent}${name}`;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2238
2289
|
var listDirTool = {
|
|
2239
2290
|
clearable: true,
|
|
2240
2291
|
definition: {
|
|
2241
2292
|
name: "listDir",
|
|
2242
|
-
description: "List the contents of a directory. Shows
|
|
2293
|
+
description: "List the contents of a directory with one level of subdirectory expansion. Shows file sizes and collapses single-child directory chains (a/b/c/ shown as one entry). Use this for a quick overview of a directory's structure. For finding files across the whole project, use glob instead.",
|
|
2243
2294
|
inputSchema: {
|
|
2244
2295
|
type: "object",
|
|
2245
2296
|
properties: {
|
|
@@ -2253,16 +2304,34 @@ var listDirTool = {
|
|
|
2253
2304
|
async execute(input) {
|
|
2254
2305
|
const dirPath = input.path || ".";
|
|
2255
2306
|
try {
|
|
2256
|
-
const entries = await
|
|
2257
|
-
const lines =
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2307
|
+
const entries = await readAndSort(dirPath);
|
|
2308
|
+
const lines = [];
|
|
2309
|
+
for (const entry of entries) {
|
|
2310
|
+
if (entry.isDirectory()) {
|
|
2311
|
+
const [displayName, finalPath] = await collapsePath(
|
|
2312
|
+
dirPath,
|
|
2313
|
+
entry.name
|
|
2314
|
+
);
|
|
2315
|
+
lines.push(`${displayName}/`);
|
|
2316
|
+
try {
|
|
2317
|
+
const children = await readAndSort(finalPath);
|
|
2318
|
+
const capped = children.slice(0, MAX_CHILDREN);
|
|
2319
|
+
for (const child of capped) {
|
|
2320
|
+
if (child.isDirectory()) {
|
|
2321
|
+
lines.push(` ${child.name}/`);
|
|
2322
|
+
} else {
|
|
2323
|
+
lines.push(await formatFile(finalPath, child.name, " "));
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (children.length > MAX_CHILDREN) {
|
|
2327
|
+
lines.push(` ... and ${children.length - MAX_CHILDREN} more`);
|
|
2328
|
+
}
|
|
2329
|
+
} catch {
|
|
2330
|
+
}
|
|
2331
|
+
} else {
|
|
2332
|
+
lines.push(await formatFile(dirPath, entry.name, ""));
|
|
2263
2333
|
}
|
|
2264
|
-
|
|
2265
|
-
}).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
|
|
2334
|
+
}
|
|
2266
2335
|
return lines.join("\n") || "(empty directory)";
|
|
2267
2336
|
} catch (err) {
|
|
2268
2337
|
return `Error listing directory: ${err.message}`;
|
|
@@ -2455,26 +2524,44 @@ async function analyzeImage(params) {
|
|
|
2455
2524
|
var SCREENSHOT_ANALYSIS_PROMPT = `Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).
|
|
2456
2525
|
|
|
2457
2526
|
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
2527
|
+
var TEXT_WRAP_DISCLAIMER = `Note: ignore text wrapping issues. Screenshots occasionally show text wrapping onto an extra line compared to the live page \u2014 most noticeable in buttons, badges, and headings. This is a known limitation of SVG foreignObject rendering used the DOM-to-image capture library that took the screenshot. The browser's SVG renderer computes slightly wider text metrics than the HTML layout engine, so text that fits on one line in the live DOM can overflow by a fraction of a pixel in the capture - this is not a real issue.`;
|
|
2528
|
+
function buildScreenshotAnalysisPrompt(opts) {
|
|
2529
|
+
let p = opts?.prompt || SCREENSHOT_ANALYSIS_PROMPT;
|
|
2530
|
+
if (opts?.styleMap) {
|
|
2531
|
+
p += `
|
|
2532
|
+
|
|
2533
|
+
The following styleMap describes the computed layout state at the moment of capture. Use it to verify typography, spacing, overflow, and element dimensions \u2014 it is more accurate than visual estimation from the image.
|
|
2534
|
+
|
|
2535
|
+
<style_map>
|
|
2536
|
+
${opts.styleMap}
|
|
2537
|
+
</style_map>`;
|
|
2538
|
+
}
|
|
2539
|
+
p += `
|
|
2540
|
+
|
|
2541
|
+
${TEXT_WRAP_DISCLAIMER}`;
|
|
2542
|
+
return p;
|
|
2543
|
+
}
|
|
2458
2544
|
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
2459
2545
|
let prompt;
|
|
2460
2546
|
let existingUrl;
|
|
2461
2547
|
let onLog;
|
|
2462
|
-
let
|
|
2548
|
+
let path10;
|
|
2463
2549
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2464
2550
|
prompt = promptOrOptions.prompt;
|
|
2465
2551
|
existingUrl = promptOrOptions.imageUrl;
|
|
2466
|
-
|
|
2552
|
+
path10 = promptOrOptions.path;
|
|
2467
2553
|
onLog = promptOrOptions.onLog;
|
|
2468
2554
|
} else {
|
|
2469
2555
|
prompt = promptOrOptions;
|
|
2470
2556
|
}
|
|
2471
2557
|
let url;
|
|
2558
|
+
let styleMap;
|
|
2472
2559
|
if (existingUrl) {
|
|
2473
2560
|
url = existingUrl;
|
|
2474
2561
|
} else {
|
|
2475
2562
|
const ssResult = await sidecarRequest(
|
|
2476
2563
|
"/screenshot-full-page",
|
|
2477
|
-
|
|
2564
|
+
path10 ? { path: path10 } : void 0,
|
|
2478
2565
|
{ timeout: 12e4 }
|
|
2479
2566
|
);
|
|
2480
2567
|
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
@@ -2483,20 +2570,21 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2483
2570
|
`No URL in sidecar response. The browser may not be ready yet. Response: ${JSON.stringify(ssResult)}`
|
|
2484
2571
|
);
|
|
2485
2572
|
}
|
|
2573
|
+
styleMap = ssResult?.styleMap;
|
|
2486
2574
|
}
|
|
2487
2575
|
if (prompt === false) {
|
|
2488
2576
|
return url;
|
|
2489
2577
|
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2578
|
+
const analysisPrompt = buildScreenshotAnalysisPrompt({
|
|
2579
|
+
prompt: prompt || void 0,
|
|
2580
|
+
styleMap
|
|
2581
|
+
});
|
|
2494
2582
|
const analysis = await analyzeImage({
|
|
2495
2583
|
prompt: analysisPrompt,
|
|
2496
2584
|
imageUrl: url,
|
|
2497
2585
|
onLog
|
|
2498
2586
|
});
|
|
2499
|
-
return JSON.stringify({ url, analysis });
|
|
2587
|
+
return JSON.stringify({ url, analysis, ...styleMap ? { styleMap } : {} });
|
|
2500
2588
|
}
|
|
2501
2589
|
|
|
2502
2590
|
// src/tools/code/screenshot.ts
|
|
@@ -3281,7 +3369,9 @@ var browserAutomationTool = {
|
|
|
3281
3369
|
stepType: "analyzeImage",
|
|
3282
3370
|
step: {
|
|
3283
3371
|
imageUrl: s.result.url,
|
|
3284
|
-
prompt:
|
|
3372
|
+
prompt: buildScreenshotAnalysisPrompt({
|
|
3373
|
+
styleMap: s.result.styleMap
|
|
3374
|
+
})
|
|
3285
3375
|
}
|
|
3286
3376
|
}));
|
|
3287
3377
|
const batchResult = await runCli(
|
|
@@ -3460,10 +3550,6 @@ __export(analyzeImage_exports, {
|
|
|
3460
3550
|
definition: () => definition4,
|
|
3461
3551
|
execute: () => execute4
|
|
3462
3552
|
});
|
|
3463
|
-
var DEFAULT_PROMPT = `
|
|
3464
|
-
Describe everything visible in this image \u2014 every element, its position, its size relative to the frame, its colors, its content. Be comprehensive, thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).
|
|
3465
|
-
|
|
3466
|
-
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
3467
3553
|
var definition4 = {
|
|
3468
3554
|
clearable: true,
|
|
3469
3555
|
name: "analyzeImage",
|
|
@@ -3485,11 +3571,9 @@ var definition4 = {
|
|
|
3485
3571
|
};
|
|
3486
3572
|
async function execute4(input, onLog) {
|
|
3487
3573
|
const imageUrl = input.imageUrl;
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
Note: ignore text wrapping issues. Screenshots occasionally show text wrapping onto an extra line compared to the live page \u2014 most noticeable in buttons, badges, and headings. This is a known limitation of SVG foreignObject rendering used the DOM-to-image capture library that took the screenshot. The browser's SVG renderer computes slightly wider text metrics than the HTML layout engine, so text that fits on one line in the live DOM can overflow by a fraction of a pixel in the capture - this is not a real issue.
|
|
3492
|
-
`;
|
|
3574
|
+
const prompt = buildScreenshotAnalysisPrompt({
|
|
3575
|
+
prompt: input.prompt
|
|
3576
|
+
});
|
|
3493
3577
|
const analysis = await analyzeImage({
|
|
3494
3578
|
prompt,
|
|
3495
3579
|
imageUrl,
|
|
@@ -3538,13 +3622,26 @@ async function execute5(input, onLog, context) {
|
|
|
3538
3622
|
return `Error: browser navigation completed but no screenshot URL was returned. Agent output: ${result}`;
|
|
3539
3623
|
}
|
|
3540
3624
|
const url = urlMatch[0];
|
|
3541
|
-
|
|
3625
|
+
let styleMap;
|
|
3626
|
+
try {
|
|
3627
|
+
const parsed = JSON.parse(result);
|
|
3628
|
+
styleMap = parsed?.styleMap;
|
|
3629
|
+
} catch {
|
|
3630
|
+
}
|
|
3631
|
+
const analysisPrompt = buildScreenshotAnalysisPrompt({
|
|
3632
|
+
prompt: input.prompt,
|
|
3633
|
+
styleMap
|
|
3634
|
+
});
|
|
3542
3635
|
const analysis = await analyzeImage({
|
|
3543
3636
|
prompt: analysisPrompt,
|
|
3544
3637
|
imageUrl: url,
|
|
3545
3638
|
onLog
|
|
3546
3639
|
});
|
|
3547
|
-
return JSON.stringify({
|
|
3640
|
+
return JSON.stringify({
|
|
3641
|
+
url,
|
|
3642
|
+
analysis,
|
|
3643
|
+
...styleMap ? { styleMap } : {}
|
|
3644
|
+
});
|
|
3548
3645
|
} catch (err) {
|
|
3549
3646
|
return `Error taking interactive screenshot: ${err.message}`;
|
|
3550
3647
|
}
|
|
@@ -3824,12 +3921,12 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
|
|
|
3824
3921
|
|
|
3825
3922
|
// src/subagents/common/context.ts
|
|
3826
3923
|
import fs14 from "fs";
|
|
3827
|
-
import
|
|
3924
|
+
import path8 from "path";
|
|
3828
3925
|
function walkMdFiles2(dir, skip) {
|
|
3829
3926
|
const files = [];
|
|
3830
3927
|
try {
|
|
3831
3928
|
for (const entry of fs14.readdirSync(dir, { withFileTypes: true })) {
|
|
3832
|
-
const full =
|
|
3929
|
+
const full = path8.join(dir, entry.name);
|
|
3833
3930
|
if (entry.isDirectory()) {
|
|
3834
3931
|
if (!skip?.has(entry.name)) {
|
|
3835
3932
|
files.push(...walkMdFiles2(full, skip));
|
|
@@ -4309,7 +4406,7 @@ var VISION_TOOLS = [
|
|
|
4309
4406
|
|
|
4310
4407
|
// src/subagents/productVision/executor.ts
|
|
4311
4408
|
import fs16 from "fs";
|
|
4312
|
-
import
|
|
4409
|
+
import path9 from "path";
|
|
4313
4410
|
var ROADMAP_DIR = "src/roadmap";
|
|
4314
4411
|
function formatRequires(requires) {
|
|
4315
4412
|
return requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
|
|
@@ -4325,7 +4422,7 @@ async function executeVisionTool(name, input) {
|
|
|
4325
4422
|
requires,
|
|
4326
4423
|
body
|
|
4327
4424
|
} = input;
|
|
4328
|
-
const filePath =
|
|
4425
|
+
const filePath = path9.join(ROADMAP_DIR, `${slug}.md`);
|
|
4329
4426
|
try {
|
|
4330
4427
|
fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
4331
4428
|
const oldContent = fs16.existsSync(filePath) ? fs16.readFileSync(filePath, "utf-8") : "";
|
|
@@ -4351,7 +4448,7 @@ ${unifiedDiff(filePath, oldContent, content)}`;
|
|
|
4351
4448
|
}
|
|
4352
4449
|
case "updateRoadmapItem": {
|
|
4353
4450
|
const { slug } = input;
|
|
4354
|
-
const filePath =
|
|
4451
|
+
const filePath = path9.join(ROADMAP_DIR, `${slug}.md`);
|
|
4355
4452
|
try {
|
|
4356
4453
|
if (!fs16.existsSync(filePath)) {
|
|
4357
4454
|
return `Error: ${filePath} does not exist`;
|
|
@@ -4419,7 +4516,7 @@ ${unifiedDiff(filePath, oldContent, content)}`;
|
|
|
4419
4516
|
}
|
|
4420
4517
|
case "deleteRoadmapItem": {
|
|
4421
4518
|
const { slug } = input;
|
|
4422
|
-
const filePath =
|
|
4519
|
+
const filePath = path9.join(ROADMAP_DIR, `${slug}.md`);
|
|
4423
4520
|
try {
|
|
4424
4521
|
if (!fs16.existsSync(filePath)) {
|
|
4425
4522
|
return `Error: ${filePath} does not exist`;
|
|
@@ -6044,6 +6141,9 @@ ${xmlParts}
|
|
|
6044
6141
|
if (pending) {
|
|
6045
6142
|
pendingTools.delete(id);
|
|
6046
6143
|
pending.resolve(result);
|
|
6144
|
+
} else if (!running) {
|
|
6145
|
+
log10.info("Late tool_result while idle, dismissing", { id });
|
|
6146
|
+
emit("completed", { success: true }, requestId);
|
|
6047
6147
|
} else {
|
|
6048
6148
|
earlyResults.set(id, result);
|
|
6049
6149
|
}
|
package/dist/index.js
CHANGED
|
@@ -1829,15 +1829,66 @@ var init_glob = __esm({
|
|
|
1829
1829
|
|
|
1830
1830
|
// src/tools/code/listDir.ts
|
|
1831
1831
|
import fs9 from "fs/promises";
|
|
1832
|
-
|
|
1832
|
+
import path4 from "path";
|
|
1833
|
+
async function readAndSort(dirPath) {
|
|
1834
|
+
const entries = await fs9.readdir(dirPath, { withFileTypes: true });
|
|
1835
|
+
return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
|
|
1836
|
+
if (a.isDirectory() && !b.isDirectory()) {
|
|
1837
|
+
return -1;
|
|
1838
|
+
}
|
|
1839
|
+
if (!a.isDirectory() && b.isDirectory()) {
|
|
1840
|
+
return 1;
|
|
1841
|
+
}
|
|
1842
|
+
return a.name.localeCompare(b.name);
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
async function collapsePath(basePath, name) {
|
|
1846
|
+
let display = name;
|
|
1847
|
+
let current = path4.join(basePath, name);
|
|
1848
|
+
for (; ; ) {
|
|
1849
|
+
let children;
|
|
1850
|
+
try {
|
|
1851
|
+
children = await readAndSort(current);
|
|
1852
|
+
} catch {
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
if (children.length === 1 && children[0].isDirectory()) {
|
|
1856
|
+
display += "/" + children[0].name;
|
|
1857
|
+
current = path4.join(current, children[0].name);
|
|
1858
|
+
} else {
|
|
1859
|
+
break;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
return [display, current];
|
|
1863
|
+
}
|
|
1864
|
+
function formatSize(bytes) {
|
|
1865
|
+
if (bytes < 1e3) {
|
|
1866
|
+
return `${bytes} B`;
|
|
1867
|
+
}
|
|
1868
|
+
if (bytes < 1e6) {
|
|
1869
|
+
return `${(bytes / 1e3).toFixed(1)} kB`;
|
|
1870
|
+
}
|
|
1871
|
+
return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
1872
|
+
}
|
|
1873
|
+
async function formatFile(dirPath, name, indent) {
|
|
1874
|
+
try {
|
|
1875
|
+
const stat = await fs9.stat(path4.join(dirPath, name));
|
|
1876
|
+
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
|
|
1877
|
+
} catch {
|
|
1878
|
+
return `${indent}${name}`;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
var EXCLUDE, MAX_CHILDREN, listDirTool;
|
|
1833
1882
|
var init_listDir = __esm({
|
|
1834
1883
|
"src/tools/code/listDir.ts"() {
|
|
1835
1884
|
"use strict";
|
|
1885
|
+
EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
1886
|
+
MAX_CHILDREN = 15;
|
|
1836
1887
|
listDirTool = {
|
|
1837
1888
|
clearable: true,
|
|
1838
1889
|
definition: {
|
|
1839
1890
|
name: "listDir",
|
|
1840
|
-
description: "List the contents of a directory. Shows
|
|
1891
|
+
description: "List the contents of a directory with one level of subdirectory expansion. Shows file sizes and collapses single-child directory chains (a/b/c/ shown as one entry). Use this for a quick overview of a directory's structure. For finding files across the whole project, use glob instead.",
|
|
1841
1892
|
inputSchema: {
|
|
1842
1893
|
type: "object",
|
|
1843
1894
|
properties: {
|
|
@@ -1851,16 +1902,34 @@ var init_listDir = __esm({
|
|
|
1851
1902
|
async execute(input) {
|
|
1852
1903
|
const dirPath = input.path || ".";
|
|
1853
1904
|
try {
|
|
1854
|
-
const entries = await
|
|
1855
|
-
const lines =
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1905
|
+
const entries = await readAndSort(dirPath);
|
|
1906
|
+
const lines = [];
|
|
1907
|
+
for (const entry of entries) {
|
|
1908
|
+
if (entry.isDirectory()) {
|
|
1909
|
+
const [displayName, finalPath] = await collapsePath(
|
|
1910
|
+
dirPath,
|
|
1911
|
+
entry.name
|
|
1912
|
+
);
|
|
1913
|
+
lines.push(`${displayName}/`);
|
|
1914
|
+
try {
|
|
1915
|
+
const children = await readAndSort(finalPath);
|
|
1916
|
+
const capped = children.slice(0, MAX_CHILDREN);
|
|
1917
|
+
for (const child of capped) {
|
|
1918
|
+
if (child.isDirectory()) {
|
|
1919
|
+
lines.push(` ${child.name}/`);
|
|
1920
|
+
} else {
|
|
1921
|
+
lines.push(await formatFile(finalPath, child.name, " "));
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
if (children.length > MAX_CHILDREN) {
|
|
1925
|
+
lines.push(` ... and ${children.length - MAX_CHILDREN} more`);
|
|
1926
|
+
}
|
|
1927
|
+
} catch {
|
|
1928
|
+
}
|
|
1929
|
+
} else {
|
|
1930
|
+
lines.push(await formatFile(dirPath, entry.name, ""));
|
|
1861
1931
|
}
|
|
1862
|
-
|
|
1863
|
-
}).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
|
|
1932
|
+
}
|
|
1864
1933
|
return lines.join("\n") || "(empty directory)";
|
|
1865
1934
|
} catch (err) {
|
|
1866
1935
|
return `Error listing directory: ${err.message}`;
|
|
@@ -2154,26 +2223,43 @@ var init_analyzeImage = __esm({
|
|
|
2154
2223
|
});
|
|
2155
2224
|
|
|
2156
2225
|
// src/tools/_helpers/screenshot.ts
|
|
2226
|
+
function buildScreenshotAnalysisPrompt(opts) {
|
|
2227
|
+
let p = opts?.prompt || SCREENSHOT_ANALYSIS_PROMPT;
|
|
2228
|
+
if (opts?.styleMap) {
|
|
2229
|
+
p += `
|
|
2230
|
+
|
|
2231
|
+
The following styleMap describes the computed layout state at the moment of capture. Use it to verify typography, spacing, overflow, and element dimensions \u2014 it is more accurate than visual estimation from the image.
|
|
2232
|
+
|
|
2233
|
+
<style_map>
|
|
2234
|
+
${opts.styleMap}
|
|
2235
|
+
</style_map>`;
|
|
2236
|
+
}
|
|
2237
|
+
p += `
|
|
2238
|
+
|
|
2239
|
+
${TEXT_WRAP_DISCLAIMER}`;
|
|
2240
|
+
return p;
|
|
2241
|
+
}
|
|
2157
2242
|
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
2158
2243
|
let prompt;
|
|
2159
2244
|
let existingUrl;
|
|
2160
2245
|
let onLog;
|
|
2161
|
-
let
|
|
2246
|
+
let path11;
|
|
2162
2247
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2163
2248
|
prompt = promptOrOptions.prompt;
|
|
2164
2249
|
existingUrl = promptOrOptions.imageUrl;
|
|
2165
|
-
|
|
2250
|
+
path11 = promptOrOptions.path;
|
|
2166
2251
|
onLog = promptOrOptions.onLog;
|
|
2167
2252
|
} else {
|
|
2168
2253
|
prompt = promptOrOptions;
|
|
2169
2254
|
}
|
|
2170
2255
|
let url;
|
|
2256
|
+
let styleMap;
|
|
2171
2257
|
if (existingUrl) {
|
|
2172
2258
|
url = existingUrl;
|
|
2173
2259
|
} else {
|
|
2174
2260
|
const ssResult = await sidecarRequest(
|
|
2175
2261
|
"/screenshot-full-page",
|
|
2176
|
-
|
|
2262
|
+
path11 ? { path: path11 } : void 0,
|
|
2177
2263
|
{ timeout: 12e4 }
|
|
2178
2264
|
);
|
|
2179
2265
|
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
@@ -2182,22 +2268,23 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2182
2268
|
`No URL in sidecar response. The browser may not be ready yet. Response: ${JSON.stringify(ssResult)}`
|
|
2183
2269
|
);
|
|
2184
2270
|
}
|
|
2271
|
+
styleMap = ssResult?.styleMap;
|
|
2185
2272
|
}
|
|
2186
2273
|
if (prompt === false) {
|
|
2187
2274
|
return url;
|
|
2188
2275
|
}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2276
|
+
const analysisPrompt = buildScreenshotAnalysisPrompt({
|
|
2277
|
+
prompt: prompt || void 0,
|
|
2278
|
+
styleMap
|
|
2279
|
+
});
|
|
2193
2280
|
const analysis = await analyzeImage({
|
|
2194
2281
|
prompt: analysisPrompt,
|
|
2195
2282
|
imageUrl: url,
|
|
2196
2283
|
onLog
|
|
2197
2284
|
});
|
|
2198
|
-
return JSON.stringify({ url, analysis });
|
|
2285
|
+
return JSON.stringify({ url, analysis, ...styleMap ? { styleMap } : {} });
|
|
2199
2286
|
}
|
|
2200
|
-
var SCREENSHOT_ANALYSIS_PROMPT;
|
|
2287
|
+
var SCREENSHOT_ANALYSIS_PROMPT, TEXT_WRAP_DISCLAIMER;
|
|
2201
2288
|
var init_screenshot = __esm({
|
|
2202
2289
|
"src/tools/_helpers/screenshot.ts"() {
|
|
2203
2290
|
"use strict";
|
|
@@ -2206,6 +2293,7 @@ var init_screenshot = __esm({
|
|
|
2206
2293
|
SCREENSHOT_ANALYSIS_PROMPT = `Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).
|
|
2207
2294
|
|
|
2208
2295
|
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
2296
|
+
TEXT_WRAP_DISCLAIMER = `Note: ignore text wrapping issues. Screenshots occasionally show text wrapping onto an extra line compared to the live page \u2014 most noticeable in buttons, badges, and headings. This is a known limitation of SVG foreignObject rendering used the DOM-to-image capture library that took the screenshot. The browser's SVG renderer computes slightly wider text metrics than the HTML layout engine, so text that fits on one line in the live DOM can overflow by a fraction of a pixel in the capture - this is not a real issue.`;
|
|
2209
2297
|
}
|
|
2210
2298
|
});
|
|
2211
2299
|
|
|
@@ -2917,19 +3005,19 @@ var init_tools = __esm({
|
|
|
2917
3005
|
|
|
2918
3006
|
// src/assets.ts
|
|
2919
3007
|
import fs10 from "fs";
|
|
2920
|
-
import
|
|
3008
|
+
import path5 from "path";
|
|
2921
3009
|
function findRoot(start) {
|
|
2922
3010
|
let dir = start;
|
|
2923
|
-
while (dir !==
|
|
2924
|
-
if (fs10.existsSync(
|
|
3011
|
+
while (dir !== path5.dirname(dir)) {
|
|
3012
|
+
if (fs10.existsSync(path5.join(dir, "package.json"))) {
|
|
2925
3013
|
return dir;
|
|
2926
3014
|
}
|
|
2927
|
-
dir =
|
|
3015
|
+
dir = path5.dirname(dir);
|
|
2928
3016
|
}
|
|
2929
3017
|
return start;
|
|
2930
3018
|
}
|
|
2931
3019
|
function assetPath(...segments) {
|
|
2932
|
-
return
|
|
3020
|
+
return path5.join(ASSETS_BASE, ...segments);
|
|
2933
3021
|
}
|
|
2934
3022
|
function readAsset(...segments) {
|
|
2935
3023
|
const full = assetPath(...segments);
|
|
@@ -2952,9 +3040,9 @@ var init_assets = __esm({
|
|
|
2952
3040
|
"src/assets.ts"() {
|
|
2953
3041
|
"use strict";
|
|
2954
3042
|
ROOT = findRoot(
|
|
2955
|
-
import.meta.dirname ??
|
|
3043
|
+
import.meta.dirname ?? path5.dirname(new URL(import.meta.url).pathname)
|
|
2956
3044
|
);
|
|
2957
|
-
ASSETS_BASE = fs10.existsSync(
|
|
3045
|
+
ASSETS_BASE = fs10.existsSync(path5.join(ROOT, "dist", "prompt")) ? path5.join(ROOT, "dist") : path5.join(ROOT, "src");
|
|
2958
3046
|
}
|
|
2959
3047
|
});
|
|
2960
3048
|
|
|
@@ -3085,7 +3173,9 @@ var init_browserAutomation = __esm({
|
|
|
3085
3173
|
stepType: "analyzeImage",
|
|
3086
3174
|
step: {
|
|
3087
3175
|
imageUrl: s.result.url,
|
|
3088
|
-
prompt:
|
|
3176
|
+
prompt: buildScreenshotAnalysisPrompt({
|
|
3177
|
+
styleMap: s.result.styleMap
|
|
3178
|
+
})
|
|
3089
3179
|
}
|
|
3090
3180
|
}));
|
|
3091
3181
|
const batchResult = await runCli(
|
|
@@ -3290,11 +3380,9 @@ __export(analyzeImage_exports, {
|
|
|
3290
3380
|
});
|
|
3291
3381
|
async function execute4(input, onLog) {
|
|
3292
3382
|
const imageUrl = input.imageUrl;
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
Note: ignore text wrapping issues. Screenshots occasionally show text wrapping onto an extra line compared to the live page \u2014 most noticeable in buttons, badges, and headings. This is a known limitation of SVG foreignObject rendering used the DOM-to-image capture library that took the screenshot. The browser's SVG renderer computes slightly wider text metrics than the HTML layout engine, so text that fits on one line in the live DOM can overflow by a fraction of a pixel in the capture - this is not a real issue.
|
|
3297
|
-
`;
|
|
3383
|
+
const prompt = buildScreenshotAnalysisPrompt({
|
|
3384
|
+
prompt: input.prompt
|
|
3385
|
+
});
|
|
3298
3386
|
const analysis = await analyzeImage({
|
|
3299
3387
|
prompt,
|
|
3300
3388
|
imageUrl,
|
|
@@ -3302,15 +3390,12 @@ async function execute4(input, onLog) {
|
|
|
3302
3390
|
});
|
|
3303
3391
|
return JSON.stringify({ url: imageUrl, analysis });
|
|
3304
3392
|
}
|
|
3305
|
-
var
|
|
3393
|
+
var definition4;
|
|
3306
3394
|
var init_analyzeImage2 = __esm({
|
|
3307
3395
|
"src/subagents/designExpert/tools/analyzeImage.ts"() {
|
|
3308
3396
|
"use strict";
|
|
3309
3397
|
init_analyzeImage();
|
|
3310
|
-
|
|
3311
|
-
Describe everything visible in this image \u2014 every element, its position, its size relative to the frame, its colors, its content. Be comprehensive, thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).
|
|
3312
|
-
|
|
3313
|
-
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
3398
|
+
init_screenshot();
|
|
3314
3399
|
definition4 = {
|
|
3315
3400
|
clearable: true,
|
|
3316
3401
|
name: "analyzeImage",
|
|
@@ -3351,13 +3436,26 @@ async function execute5(input, onLog, context) {
|
|
|
3351
3436
|
return `Error: browser navigation completed but no screenshot URL was returned. Agent output: ${result}`;
|
|
3352
3437
|
}
|
|
3353
3438
|
const url = urlMatch[0];
|
|
3354
|
-
|
|
3439
|
+
let styleMap;
|
|
3440
|
+
try {
|
|
3441
|
+
const parsed = JSON.parse(result);
|
|
3442
|
+
styleMap = parsed?.styleMap;
|
|
3443
|
+
} catch {
|
|
3444
|
+
}
|
|
3445
|
+
const analysisPrompt = buildScreenshotAnalysisPrompt({
|
|
3446
|
+
prompt: input.prompt,
|
|
3447
|
+
styleMap
|
|
3448
|
+
});
|
|
3355
3449
|
const analysis = await analyzeImage({
|
|
3356
3450
|
prompt: analysisPrompt,
|
|
3357
3451
|
imageUrl: url,
|
|
3358
3452
|
onLog
|
|
3359
3453
|
});
|
|
3360
|
-
return JSON.stringify({
|
|
3454
|
+
return JSON.stringify({
|
|
3455
|
+
url,
|
|
3456
|
+
analysis,
|
|
3457
|
+
...styleMap ? { styleMap } : {}
|
|
3458
|
+
});
|
|
3361
3459
|
} catch (err) {
|
|
3362
3460
|
return `Error taking interactive screenshot: ${err.message}`;
|
|
3363
3461
|
}
|
|
@@ -3710,12 +3808,12 @@ var init_tools2 = __esm({
|
|
|
3710
3808
|
|
|
3711
3809
|
// src/subagents/common/context.ts
|
|
3712
3810
|
import fs12 from "fs";
|
|
3713
|
-
import
|
|
3811
|
+
import path6 from "path";
|
|
3714
3812
|
function walkMdFiles(dir, skip) {
|
|
3715
3813
|
const files = [];
|
|
3716
3814
|
try {
|
|
3717
3815
|
for (const entry of fs12.readdirSync(dir, { withFileTypes: true })) {
|
|
3718
|
-
const full =
|
|
3816
|
+
const full = path6.join(dir, entry.name);
|
|
3719
3817
|
if (entry.isDirectory()) {
|
|
3720
3818
|
if (!skip?.has(entry.name)) {
|
|
3721
3819
|
files.push(...walkMdFiles(full, skip));
|
|
@@ -4263,7 +4361,7 @@ var init_tools3 = __esm({
|
|
|
4263
4361
|
|
|
4264
4362
|
// src/subagents/productVision/executor.ts
|
|
4265
4363
|
import fs14 from "fs";
|
|
4266
|
-
import
|
|
4364
|
+
import path7 from "path";
|
|
4267
4365
|
function formatRequires(requires) {
|
|
4268
4366
|
return requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
|
|
4269
4367
|
}
|
|
@@ -4278,7 +4376,7 @@ async function executeVisionTool(name, input) {
|
|
|
4278
4376
|
requires,
|
|
4279
4377
|
body
|
|
4280
4378
|
} = input;
|
|
4281
|
-
const filePath =
|
|
4379
|
+
const filePath = path7.join(ROADMAP_DIR, `${slug}.md`);
|
|
4282
4380
|
try {
|
|
4283
4381
|
fs14.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
4284
4382
|
const oldContent = fs14.existsSync(filePath) ? fs14.readFileSync(filePath, "utf-8") : "";
|
|
@@ -4304,7 +4402,7 @@ ${unifiedDiff(filePath, oldContent, content)}`;
|
|
|
4304
4402
|
}
|
|
4305
4403
|
case "updateRoadmapItem": {
|
|
4306
4404
|
const { slug } = input;
|
|
4307
|
-
const filePath =
|
|
4405
|
+
const filePath = path7.join(ROADMAP_DIR, `${slug}.md`);
|
|
4308
4406
|
try {
|
|
4309
4407
|
if (!fs14.existsSync(filePath)) {
|
|
4310
4408
|
return `Error: ${filePath} does not exist`;
|
|
@@ -4372,7 +4470,7 @@ ${unifiedDiff(filePath, oldContent, content)}`;
|
|
|
4372
4470
|
}
|
|
4373
4471
|
case "deleteRoadmapItem": {
|
|
4374
4472
|
const { slug } = input;
|
|
4375
|
-
const filePath =
|
|
4473
|
+
const filePath = path7.join(ROADMAP_DIR, `${slug}.md`);
|
|
4376
4474
|
try {
|
|
4377
4475
|
if (!fs14.existsSync(filePath)) {
|
|
4378
4476
|
return `Error: ${filePath} does not exist`;
|
|
@@ -5592,7 +5690,7 @@ var init_agent = __esm({
|
|
|
5592
5690
|
|
|
5593
5691
|
// src/prompt/static/projectContext.ts
|
|
5594
5692
|
import fs16 from "fs";
|
|
5595
|
-
import
|
|
5693
|
+
import path8 from "path";
|
|
5596
5694
|
function loadProjectInstructions() {
|
|
5597
5695
|
for (const file of AGENT_INSTRUCTION_FILES) {
|
|
5598
5696
|
try {
|
|
@@ -5652,7 +5750,7 @@ function walkMdFiles2(dir) {
|
|
|
5652
5750
|
try {
|
|
5653
5751
|
const entries = fs16.readdirSync(dir, { withFileTypes: true });
|
|
5654
5752
|
for (const entry of entries) {
|
|
5655
|
-
const full =
|
|
5753
|
+
const full = path8.join(dir, entry.name);
|
|
5656
5754
|
if (entry.isDirectory()) {
|
|
5657
5755
|
results.push(...walkMdFiles2(full));
|
|
5658
5756
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -5828,7 +5926,7 @@ ${isLspConfigured() ? `<typescript_lsp>
|
|
|
5828
5926
|
<conversation_summaries>
|
|
5829
5927
|
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.
|
|
5830
5928
|
|
|
5831
|
-
Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query
|
|
5929
|
+
Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query.
|
|
5832
5930
|
</conversation_summaries>
|
|
5833
5931
|
|
|
5834
5932
|
<project_onboarding>
|
|
@@ -5864,7 +5962,7 @@ var init_prompt4 = __esm({
|
|
|
5864
5962
|
|
|
5865
5963
|
// src/config.ts
|
|
5866
5964
|
import fs17 from "fs";
|
|
5867
|
-
import
|
|
5965
|
+
import path9 from "path";
|
|
5868
5966
|
import os from "os";
|
|
5869
5967
|
function loadConfigFile() {
|
|
5870
5968
|
try {
|
|
@@ -5905,7 +6003,7 @@ var init_config = __esm({
|
|
|
5905
6003
|
"use strict";
|
|
5906
6004
|
init_logger();
|
|
5907
6005
|
log7 = createLogger("config");
|
|
5908
|
-
CONFIG_PATH =
|
|
6006
|
+
CONFIG_PATH = path9.join(
|
|
5909
6007
|
os.homedir(),
|
|
5910
6008
|
".mindstudio-local-tunnel",
|
|
5911
6009
|
"config.json"
|
|
@@ -6688,6 +6786,9 @@ ${xmlParts}
|
|
|
6688
6786
|
if (pending) {
|
|
6689
6787
|
pendingTools.delete(id);
|
|
6690
6788
|
pending.resolve(result);
|
|
6789
|
+
} else if (!running) {
|
|
6790
|
+
log10.info("Late tool_result while idle, dismissing", { id });
|
|
6791
|
+
emit("completed", { success: true }, requestId);
|
|
6691
6792
|
} else {
|
|
6692
6793
|
earlyResults.set(id, result);
|
|
6693
6794
|
}
|
|
@@ -6819,7 +6920,7 @@ var init_headless = __esm({
|
|
|
6819
6920
|
import { render } from "ink";
|
|
6820
6921
|
import os2 from "os";
|
|
6821
6922
|
import fs18 from "fs";
|
|
6822
|
-
import
|
|
6923
|
+
import path10 from "path";
|
|
6823
6924
|
|
|
6824
6925
|
// src/tui/App.tsx
|
|
6825
6926
|
import { useState as useState2, useCallback, useRef } from "react";
|
|
@@ -7138,7 +7239,7 @@ var startupLog = createLogger("startup");
|
|
|
7138
7239
|
function printDebugInfo(config) {
|
|
7139
7240
|
const pkg = JSON.parse(
|
|
7140
7241
|
fs18.readFileSync(
|
|
7141
|
-
|
|
7242
|
+
path10.join(import.meta.dirname, "..", "package.json"),
|
|
7142
7243
|
"utf-8"
|
|
7143
7244
|
)
|
|
7144
7245
|
);
|
|
@@ -25,6 +25,7 @@ Interfaces run fullscreen in the user's browser or a wrapped webview mobile app.
|
|
|
25
25
|
- **No long scrolling pages.** Use structured layouts: cards, split panes, steppers, tabs, grouped sections that fit the viewport. The interface should feel like an award winning iOS or macOS app, not a document.
|
|
26
26
|
- **On mobile**, scrolling may be necessary, but use sticky headers, fixed CTAs, and anchored navigation to keep key actions within reach. Always use "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" to make sure apps feel like apps.
|
|
27
27
|
- Think of every screen as something the user opens, uses, and closes — not something they read.
|
|
28
|
+
- Pay attention to details that will make things feel app-like - set user-select: none, use motion, use iOS/macOS design language and patterns.
|
|
28
29
|
|
|
29
30
|
## Layout Stability
|
|
30
31
|
|
|
@@ -251,7 +251,7 @@ The human-readable spec. Frontmatter contains structured fields; the prose body
|
|
|
251
251
|
```yaml
|
|
252
252
|
---
|
|
253
253
|
name: Todo Assistant
|
|
254
|
-
model: {"model": "claude-4-5-haiku", "temperature": 0.5, "maxResponseTokens":
|
|
254
|
+
model: {"model": "claude-4-5-haiku", "temperature": 0.5, "maxResponseTokens": 16000}
|
|
255
255
|
description: Conversational agent that helps users manage their to-do list.
|
|
256
256
|
---
|
|
257
257
|
```
|
|
@@ -282,7 +282,7 @@ dist/interfaces/agent/
|
|
|
282
282
|
"agent": {
|
|
283
283
|
"model": "claude-4-5-haiku",
|
|
284
284
|
"temperature": 0.5,
|
|
285
|
-
"maxTokens":
|
|
285
|
+
"maxTokens": 16000,
|
|
286
286
|
"systemPrompt": "system.md",
|
|
287
287
|
"tools": [
|
|
288
288
|
{ "method": "create-todo", "description": "tools/createTodo.md" },
|
|
@@ -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.
|
|
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. Delete the notes file when your work is done.
|
|
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:
|