@mindstudio-ai/remy 0.1.268 → 0.1.270

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 CHANGED
@@ -618,7 +618,6 @@ function readJsonAsset(fallback, ...segments) {
618
618
 
619
619
  // src/prompt/static/projectContext.ts
620
620
  import fs4 from "fs";
621
- import path3 from "path";
622
621
 
623
622
  // src/projectRoot.ts
624
623
  var PROJECT_ROOT = process.cwd();
@@ -629,80 +628,25 @@ function loadProjectRoot() {
629
628
  ## Project Root
630
629
  \`${PROJECT_ROOT}\`
631
630
 
632
- File paths are relative to this directory. Every tool operates here and bash commands run it it.`;
633
- }
634
- function loadProjectManifest() {
635
- try {
636
- const manifest = fs4.readFileSync("mindstudio.json", "utf-8");
637
- return `
638
- ## Project Manifest (mindstudio.json)
639
- \`\`\`json
640
- ${manifest}
641
- \`\`\``;
642
- } catch {
643
- return "";
644
- }
631
+ File paths are relative to this directory. Every tool operates here and bash commands run in it.`;
645
632
  }
646
- function loadSpecFileMetadata() {
633
+ function loadAppIdentity() {
647
634
  try {
648
- const files = walkMdFiles("src");
649
- if (files.length === 0) {
635
+ const manifest = JSON.parse(fs4.readFileSync("mindstudio.json", "utf-8"));
636
+ const name = typeof manifest.name === "string" ? manifest.name.trim() : "";
637
+ if (!name) {
650
638
  return "";
651
639
  }
652
- const entries = [];
653
- for (const filePath of files) {
654
- const { name, description, type } = parseFrontmatter(filePath);
655
- let line = `- ${filePath}`;
656
- if (name) {
657
- line += ` \u2014 "${name}"`;
658
- }
659
- if (type) {
660
- line += ` (${type})`;
661
- }
662
- if (description) {
663
- line += ` \u2014 ${description}`;
664
- }
665
- entries.push(line);
666
- }
640
+ const description = typeof manifest.description === "string" ? manifest.description.trim() : "";
667
641
  return `
668
- ## Spec Files
669
- ${entries.join("\n")}`;
642
+ ## App
643
+ "${name}"${description ? ` \u2014 ${description}` : ""}
644
+
645
+ Full manifest: \`mindstudio.json\` (read it when you need the app's structure, tables, methods, roles, auth settings, interfaces, and everything else).`;
670
646
  } catch {
671
647
  return "";
672
648
  }
673
649
  }
674
- function walkMdFiles(dir) {
675
- const results = [];
676
- try {
677
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
678
- for (const entry of entries) {
679
- const full = path3.join(dir, entry.name);
680
- if (entry.isDirectory()) {
681
- results.push(...walkMdFiles(full));
682
- } else if (entry.name.endsWith(".md")) {
683
- results.push(full);
684
- }
685
- }
686
- } catch {
687
- }
688
- return results.sort();
689
- }
690
- function parseFrontmatter(filePath) {
691
- try {
692
- const content = fs4.readFileSync(filePath, "utf-8");
693
- const match = content.match(/^---\n([\s\S]*?)\n---/);
694
- if (!match) {
695
- return { name: "", description: "", type: "" };
696
- }
697
- const fm = match[1];
698
- const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
699
- const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
700
- const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
701
- return { name, description, type };
702
- } catch {
703
- return { name: "", description: "", type: "" };
704
- }
705
- }
706
650
  function loadPlanStatus(onboardingState) {
707
651
  try {
708
652
  const content = fs4.readFileSync(".remy-plan.md", "utf-8");
@@ -731,33 +675,12 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
731
675
  return "";
732
676
  }
733
677
  }
734
- function loadProjectFileListing() {
735
- try {
736
- const entries = fs4.readdirSync(".", { withFileTypes: true });
737
- const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
738
- if (a.isDirectory() && !b.isDirectory()) {
739
- return -1;
740
- }
741
- if (!a.isDirectory() && b.isDirectory()) {
742
- return 1;
743
- }
744
- return a.name.localeCompare(b.name);
745
- }).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
746
- return `
747
- ## Project Files
748
- \`\`\`
749
- ${listing}
750
- \`\`\``;
751
- } catch {
752
- return "";
753
- }
754
- }
755
678
 
756
679
  // src/prompt/skills/catalog.ts
757
680
  import fs5 from "fs";
758
- import path4 from "path";
681
+ import path3 from "path";
759
682
  var SKILLS_DIR = assetPath("prompt", "skills");
760
- function parseFrontmatter2(content) {
683
+ function parseFrontmatter(content) {
761
684
  const match = content.match(/^---\n([\s\S]*?)\n---/);
762
685
  if (!match) {
763
686
  return {};
@@ -780,9 +703,9 @@ function loadCatalog() {
780
703
  }
781
704
  const skills = [];
782
705
  for (const file of files.sort()) {
783
- const full = path4.join(SKILLS_DIR, file);
706
+ const full = path3.join(SKILLS_DIR, file);
784
707
  const id = file.replace(/\.md$/, "");
785
- const fields = parseFrontmatter2(fs5.readFileSync(full, "utf-8"));
708
+ const fields = parseFrontmatter(fs5.readFileSync(full, "utf-8"));
786
709
  if (!fields.name || !fields.what || !fields.when) {
787
710
  continue;
788
711
  }
@@ -836,13 +759,7 @@ function resolveIncludes(template) {
836
759
  );
837
760
  return result.replace(/\n{3,}/g, "\n\n").trim();
838
761
  }
839
- function buildSystemPrompt(onboardingState, viewContext) {
840
- const projectContext = [
841
- loadProjectRoot(),
842
- loadProjectManifest(),
843
- loadSpecFileMetadata(),
844
- loadProjectFileListing()
845
- ].filter(Boolean).join("\n");
762
+ function buildSystemPrompt(onboardingState) {
846
763
  const now = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
847
764
  month: "long",
848
765
  day: "numeric",
@@ -851,8 +768,6 @@ function buildSystemPrompt(onboardingState, viewContext) {
851
768
  const template = `
852
769
  {{static/identity.md}}
853
770
 
854
- Current date: ${now}
855
-
856
771
  <platform_docs>
857
772
  <platform>
858
773
  {{compiled/platform.md}}
@@ -882,21 +797,9 @@ Current date: ${now}
882
797
  {{compiled/design.md}}
883
798
  </design>
884
799
 
885
- <app_files>
886
- {{compiled/files.md}}
887
- </app_files>
888
-
889
800
  <interfaces>
890
801
  {{compiled/interfaces.md}}
891
802
  </interfaces>
892
-
893
- <scenarios>
894
- {{compiled/scenarios.md}}
895
- </scenarios>
896
-
897
- <secrets>
898
- {{compiled/secrets.md}}
899
- </secrets>
900
803
  </platform_docs>
901
804
 
902
805
  ${loadSkillsCatalog()}
@@ -909,12 +812,12 @@ ${loadSkillsCatalog()}
909
812
  {{compiled/msfm.md}}
910
813
  </mindstudio_flavored_markdown_spec_docs>
911
814
 
912
- <intake_mode_instructions>
815
+ ${onboardingState === "intake" ? `<intake_mode_instructions>
913
816
  {{static/intake.md}}
914
- </intake_mode_instructions>
817
+ </intake_mode_instructions>` : ""}
915
818
 
916
819
  <spec_authoring_instructions>
917
- {{static/authoring.md}}
820
+ ${onboardingState !== void 0 && onboardingState !== "onboardingFinished" ? "{{static/authoring.md}}" : "{{static/spec-maintenance.md}}"}
918
821
  </spec_authoring_instructions>
919
822
 
920
823
  <team>
@@ -932,11 +835,11 @@ ${loadSkillsCatalog()}
932
835
  <conversation_summaries>
933
836
  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.
934
837
 
935
- 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.
838
+ Tool results generally persist in the conversation until a compaction summarizes them. In rare cases very old tool results may be trimmed when the conversation nears the context limit. Either way, if you need something from an earlier tool call and can't see it anymore, just re-read the file or re-run the query.
936
839
  </conversation_summaries>
937
840
 
938
841
  <project_onboarding>
939
- New projects progress through three onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
842
+ New projects progress through four onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
940
843
 
941
844
  - **intake**: Gathering requirements. The project has scaffold code (a "hello world" starter) but it's not the user's app yet. Focus on understanding what they want to build, not on the existing code. Intake ends with a plan proposal via writePlan.
942
845
  - **building**: The user approved the initial plan. The agent is writing the spec and building the app. This can take a while and involves heavy tool use (spec authoring, design expert consultation, code generation, verification, polishing).
@@ -944,24 +847,21 @@ New projects progress through three onboarding states. The user might skip this
944
847
  - **onboardingFinished**: The project is built and ready. Full development mode with all tools available. From here on, keep spec and code in sync as changes are made.
945
848
  </project_onboarding>
946
849
 
850
+ ${loadProjectRoot()}
851
+
852
+ ${renderOrgContextBlock()}
853
+
947
854
  {{static/instructions.md}}
948
855
 
949
856
  <!-- cache_breakpoint -->
950
857
 
858
+ Current date: ${now}
859
+
951
860
  <current_project_onboarding_state>
952
861
  ${onboardingState ?? "onboardingFinished"}
953
862
  </current_project_onboarding_state>
954
863
 
955
- <project_context>
956
- ${projectContext}
957
- </project_context>
958
-
959
- ${renderOrgContextBlock()}
960
-
961
- <view_context>
962
- The user is currently in ${viewContext?.mode ?? "code"} mode.
963
- ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
964
- </view_context>
864
+ ${loadAppIdentity()}
965
865
 
966
866
  ${loadPlanStatus(onboardingState)}
967
867
  `;
@@ -1100,7 +1000,7 @@ var readSpecTool = {
1100
1000
 
1101
1001
  // src/tools/spec/writeSpec.ts
1102
1002
  import fs7 from "fs/promises";
1103
- import path5 from "path";
1003
+ import path4 from "path";
1104
1004
 
1105
1005
  // src/tools/_helpers/diff.ts
1106
1006
  var CONTEXT_LINES = 3;
@@ -1189,7 +1089,7 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
1189
1089
  }
1190
1090
  const release = await acquireFileLock(input.path);
1191
1091
  try {
1192
- await fs7.mkdir(path5.dirname(input.path), { recursive: true });
1092
+ await fs7.mkdir(path4.dirname(input.path), { recursive: true });
1193
1093
  let oldContent = null;
1194
1094
  try {
1195
1095
  oldContent = await fs7.readFile(input.path, "utf-8");
@@ -1394,7 +1294,7 @@ var editSpecTool = {
1394
1294
 
1395
1295
  // src/tools/spec/listSpecFiles.ts
1396
1296
  import fs9 from "fs/promises";
1397
- import path6 from "path";
1297
+ import path5 from "path";
1398
1298
  var listSpecFilesTool = {
1399
1299
  definition: {
1400
1300
  name: "listSpecFiles",
@@ -1433,7 +1333,7 @@ async function listRecursive(dir) {
1433
1333
  return a.name.localeCompare(b.name);
1434
1334
  });
1435
1335
  for (const entry of entries) {
1436
- const fullPath = path6.join(dir, entry.name);
1336
+ const fullPath = path5.join(dir, entry.name);
1437
1337
  if (entry.isDirectory()) {
1438
1338
  results.push(`${fullPath}/`);
1439
1339
  results.push(...await listRecursive(fullPath));
@@ -1466,8 +1366,33 @@ var presentPublishPlanTool = {
1466
1366
  }
1467
1367
  };
1468
1368
 
1369
+ // src/atomicWrite.ts
1370
+ import fs10 from "fs";
1371
+ import fsp from "fs/promises";
1372
+ var writeFileAtomicSync = (file, data) => {
1373
+ const tmp = `${file}.${process.pid}.tmp`;
1374
+ const fd2 = fs10.openSync(tmp, "w");
1375
+ try {
1376
+ fs10.writeSync(fd2, data);
1377
+ fs10.fsyncSync(fd2);
1378
+ } finally {
1379
+ fs10.closeSync(fd2);
1380
+ }
1381
+ fs10.renameSync(tmp, file);
1382
+ };
1383
+ var writeFileAtomic = async (file, data) => {
1384
+ const tmp = `${file}.${process.pid}.tmp`;
1385
+ const handle = await fsp.open(tmp, "w");
1386
+ try {
1387
+ await handle.writeFile(data);
1388
+ await handle.sync();
1389
+ } finally {
1390
+ await handle.close();
1391
+ }
1392
+ await fsp.rename(tmp, file);
1393
+ };
1394
+
1469
1395
  // src/tools/spec/writePlan.ts
1470
- import fs10 from "fs/promises";
1471
1396
  var PLAN_FILE = ".remy-plan.md";
1472
1397
  var writePlanTool = {
1473
1398
  definition: {
@@ -1491,7 +1416,7 @@ status: pending
1491
1416
  ---
1492
1417
 
1493
1418
  ${content}`;
1494
- await fs10.writeFile(PLAN_FILE, file, "utf-8");
1419
+ await writeFileAtomic(PLAN_FILE, file);
1495
1420
  return "Plan written to .remy-plan.md. Waiting for user approval.";
1496
1421
  }
1497
1422
  };
@@ -1527,13 +1452,13 @@ var updatePlanStatusTool = {
1527
1452
  return "No plan file found.";
1528
1453
  }
1529
1454
  if (status === "rejected") {
1530
- await fs11.unlink(PLAN_FILE2);
1455
+ await fs11.unlink(PLAN_FILE2).catch(() => {
1456
+ });
1531
1457
  return "Plan rejected and removed.";
1532
1458
  }
1533
- await fs11.writeFile(
1459
+ await writeFileAtomic(
1534
1460
  PLAN_FILE2,
1535
- content.replace(/^status:\s*\w+/m, `status: ${status}`),
1536
- "utf-8"
1461
+ content.replace(/^status:\s*\w+/m, `status: ${status}`)
1537
1462
  );
1538
1463
  return "Plan approved. Proceeding with implementation.";
1539
1464
  }
@@ -2271,7 +2196,7 @@ var readFileTool = {
2271
2196
 
2272
2197
  // src/tools/code/writeFile.ts
2273
2198
  import fs14 from "fs/promises";
2274
- import path7 from "path";
2199
+ import path6 from "path";
2275
2200
  var writeFileTool = {
2276
2201
  definition: {
2277
2202
  name: "writeFile",
@@ -2316,7 +2241,7 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2316
2241
  async execute(input) {
2317
2242
  const release = await acquireFileLock(input.path);
2318
2243
  try {
2319
- await fs14.mkdir(path7.dirname(input.path), { recursive: true });
2244
+ await fs14.mkdir(path6.dirname(input.path), { recursive: true });
2320
2245
  let oldContent = null;
2321
2246
  try {
2322
2247
  oldContent = await fs14.readFile(input.path, "utf-8");
@@ -2425,7 +2350,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2425
2350
 
2426
2351
  // src/tools/code/bash.ts
2427
2352
  import { spawn as spawn2 } from "child_process";
2428
- import path8 from "path";
2353
+ import path7 from "path";
2429
2354
  var DEFAULT_TIMEOUT_MS = 12e4;
2430
2355
  var DEFAULT_MAX_LINES2 = 500;
2431
2356
  var MAX_OUTPUT_BYTES = 3e4;
@@ -2463,7 +2388,7 @@ var bashTool = {
2463
2388
  const child = spawn2("sh", ["-c", input.command], {
2464
2389
  // Pinned rather than inherited. `undefined` here means "wherever the
2465
2390
  // process happens to be", which is the project root only by luck.
2466
- cwd: input.cwd ? path8.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
2391
+ cwd: input.cwd ? path7.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
2467
2392
  env: { ...process.env, FORCE_COLOR: "1" }
2468
2393
  });
2469
2394
  let output = "";
@@ -2707,7 +2632,7 @@ var globTool = {
2707
2632
 
2708
2633
  // src/tools/code/listDir.ts
2709
2634
  import fs16 from "fs/promises";
2710
- import path9 from "path";
2635
+ import path8 from "path";
2711
2636
  var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
2712
2637
  var MAX_CHILDREN = 15;
2713
2638
  async function readAndSort(dirPath) {
@@ -2724,7 +2649,7 @@ async function readAndSort(dirPath) {
2724
2649
  }
2725
2650
  async function collapsePath(basePath, name) {
2726
2651
  let display = name;
2727
- let current = path9.join(basePath, name);
2652
+ let current = path8.join(basePath, name);
2728
2653
  for (; ; ) {
2729
2654
  let children;
2730
2655
  try {
@@ -2734,7 +2659,7 @@ async function collapsePath(basePath, name) {
2734
2659
  }
2735
2660
  if (children.length === 1 && children[0].isDirectory()) {
2736
2661
  display += "/" + children[0].name;
2737
- current = path9.join(current, children[0].name);
2662
+ current = path8.join(current, children[0].name);
2738
2663
  } else {
2739
2664
  break;
2740
2665
  }
@@ -2752,7 +2677,7 @@ function formatSize(bytes) {
2752
2677
  }
2753
2678
  async function formatFile(dirPath, name, indent) {
2754
2679
  try {
2755
- const stat2 = await fs16.stat(path9.join(dirPath, name));
2680
+ const stat2 = await fs16.stat(path8.join(dirPath, name));
2756
2681
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat2.size)}`;
2757
2682
  } catch {
2758
2683
  return `${indent}${name}`;
@@ -3063,7 +2988,8 @@ var CONTENT_TYPES = {
3063
2988
  ".jpg": "image/jpeg",
3064
2989
  ".jpeg": "image/jpeg",
3065
2990
  ".gif": "image/gif",
3066
- ".webp": "image/webp"
2991
+ ".webp": "image/webp",
2992
+ ".svg": "image/svg+xml"
3067
2993
  };
3068
2994
  var hosted = /* @__PURE__ */ new Map();
3069
2995
  function isFetchableUrl(ref) {
@@ -3215,7 +3141,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3215
3141
  let onLog;
3216
3142
  let model;
3217
3143
  let apiConfig;
3218
- let path14;
3144
+ let path13;
3219
3145
  let fullPage = true;
3220
3146
  let width;
3221
3147
  let height;
@@ -3223,7 +3149,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3223
3149
  if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
3224
3150
  prompt = promptOrOptions.prompt;
3225
3151
  existingImage = promptOrOptions.image;
3226
- path14 = promptOrOptions.path;
3152
+ path13 = promptOrOptions.path;
3227
3153
  if (promptOrOptions.fullPage !== void 0) {
3228
3154
  fullPage = promptOrOptions.fullPage;
3229
3155
  }
@@ -3247,7 +3173,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3247
3173
  const ssResult = await sidecarRequest(
3248
3174
  fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
3249
3175
  {
3250
- ...path14 ? { path: path14 } : {},
3176
+ ...path13 ? { path: path13 } : {},
3251
3177
  ...width != null ? { width } : {},
3252
3178
  ...height != null ? { height } : {},
3253
3179
  ...format ? { format } : {}
@@ -3670,7 +3596,8 @@ async function runSubAgent(config) {
3670
3596
  background,
3671
3597
  acquireLock,
3672
3598
  onBackgroundComplete,
3673
- captureArtifacts
3599
+ captureArtifacts,
3600
+ cachePolicy = "run"
3674
3601
  } = config;
3675
3602
  const artifacts = {};
3676
3603
  const bgAbort = background ? new AbortController() : null;
@@ -3765,6 +3692,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3765
3692
  system: fullSystem,
3766
3693
  messages: cleanMessagesForApi(messages),
3767
3694
  tools: tools2,
3695
+ cachePolicy,
3768
3696
  signal
3769
3697
  },
3770
3698
  {
@@ -4250,15 +4178,15 @@ var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
4250
4178
 
4251
4179
  // src/subagents/common/context.ts
4252
4180
  import fs17 from "fs";
4253
- import path10 from "path";
4254
- function walkMdFiles2(dir, skip) {
4181
+ import path9 from "path";
4182
+ function walkMdFiles(dir, skip) {
4255
4183
  const files = [];
4256
4184
  try {
4257
4185
  for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
4258
- const full = path10.join(dir, entry.name);
4186
+ const full = path9.join(dir, entry.name);
4259
4187
  if (entry.isDirectory()) {
4260
4188
  if (!skip?.has(entry.name)) {
4261
- files.push(...walkMdFiles2(full, skip));
4189
+ files.push(...walkMdFiles(full, skip));
4262
4190
  }
4263
4191
  } else if (entry.name.endsWith(".md")) {
4264
4192
  files.push(full);
@@ -4268,7 +4196,7 @@ function walkMdFiles2(dir, skip) {
4268
4196
  }
4269
4197
  return files.sort();
4270
4198
  }
4271
- function parseFrontmatter3(filePath) {
4199
+ function parseFrontmatter2(filePath) {
4272
4200
  try {
4273
4201
  const content = fs17.readFileSync(filePath, "utf-8");
4274
4202
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -4290,12 +4218,12 @@ function parseFrontmatter3(filePath) {
4290
4218
  }
4291
4219
  }
4292
4220
  function loadSpecIndex() {
4293
- const files = walkMdFiles2("src", /* @__PURE__ */ new Set(["roadmap"]));
4221
+ const files = walkMdFiles("src", /* @__PURE__ */ new Set(["roadmap"]));
4294
4222
  if (files.length === 0) {
4295
4223
  return "";
4296
4224
  }
4297
4225
  const lines = files.map((f) => {
4298
- const fm = parseFrontmatter3(f);
4226
+ const fm = parseFrontmatter2(f);
4299
4227
  let line = `- ${f}`;
4300
4228
  if (fm.name) {
4301
4229
  line += ` \u2014 "${fm.name}"`;
@@ -4333,10 +4261,10 @@ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
4333
4261
  }
4334
4262
  } catch {
4335
4263
  }
4336
- const files = walkMdFiles2("src/roadmap");
4264
+ const files = walkMdFiles("src/roadmap");
4337
4265
  if (files.length > 0) {
4338
4266
  const lines = files.map((f) => {
4339
- const fm = parseFrontmatter3(f);
4267
+ const fm = parseFrontmatter2(f);
4340
4268
  let line = `- ${f}`;
4341
4269
  if (fm.name) {
4342
4270
  line += ` \u2014 "${fm.name}"`;
@@ -5331,7 +5259,7 @@ function load() {
5331
5259
  }
5332
5260
  function save(indices) {
5333
5261
  try {
5334
- fs18.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
5262
+ writeFileAtomicSync(SAMPLE_FILE, JSON.stringify(indices));
5335
5263
  } catch {
5336
5264
  }
5337
5265
  }
@@ -5582,6 +5510,8 @@ async function runDesignExpert(opts, context) {
5582
5510
  }),
5583
5511
  task: opts.task,
5584
5512
  history: history.length > 0 ? history : void 0,
5513
+ // History-carrying thread re-sent across parent turns — 1h-TTL profile.
5514
+ cachePolicy: "conversation",
5585
5515
  tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
5586
5516
  externalTools: /* @__PURE__ */ new Set(),
5587
5517
  executeTool: (name, input, toolCallId, onLog, sams) => {
@@ -5705,14 +5635,14 @@ var VISION_TOOLS = [
5705
5635
 
5706
5636
  // src/subagents/productVision/executor.ts
5707
5637
  import fs19 from "fs";
5708
- import path11 from "path";
5638
+ import path10 from "path";
5709
5639
  var ROADMAP_DIR = "src/roadmap";
5710
5640
  var PITCH_DECK_SHELL = readAsset(
5711
5641
  "subagents/productVision",
5712
5642
  "pitch-deck-shell.html"
5713
5643
  );
5714
5644
  function resolve2(filePath) {
5715
- return path11.join(ROADMAP_DIR, filePath);
5645
+ return path10.join(ROADMAP_DIR, filePath);
5716
5646
  }
5717
5647
  async function executeVisionTool(name, input, context) {
5718
5648
  switch (name) {
@@ -5849,6 +5779,8 @@ var productVisionTool = {
5849
5779
  system: getProductVisionPrompt(),
5850
5780
  task: input.task,
5851
5781
  history: history.length > 0 ? history : void 0,
5782
+ // History-carrying thread re-sent across parent turns — 1h-TTL profile.
5783
+ cachePolicy: "conversation",
5852
5784
  tools: VISION_TOOLS,
5853
5785
  externalTools: /* @__PURE__ */ new Set(),
5854
5786
  executeTool: (name, input2, toolCallId, _onLog, sams) => {
@@ -6316,7 +6248,7 @@ var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
6316
6248
  "runAutomatedBrowserTest",
6317
6249
  "askMindStudioSdk"
6318
6250
  ]);
6319
- function getToolDefinitions(_onboardingState) {
6251
+ function getToolDefinitions() {
6320
6252
  return ALL_TOOLS.map((t) => t.definition);
6321
6253
  }
6322
6254
  function getToolByName(name) {
@@ -6700,7 +6632,11 @@ Write the summary of the conversation above, following your instructions.`;
6700
6632
  messages: [{ role: "user", content: userContent }],
6701
6633
  // Always empty. With a toolset available the model picks `tool_use` over
6702
6634
  // producing a summary, leaving summaryText empty.
6703
- tools: []
6635
+ tools: [],
6636
+ // Each summary call carries a unique 100-200KB chunk that is never
6637
+ // re-read (parallel siblings can't read each other's in-flight writes
6638
+ // either) — a cache write here is pure waste.
6639
+ cachePolicy: "oneshot"
6704
6640
  })) {
6705
6641
  if (event.type === "text") {
6706
6642
  summaryText += event.text;
@@ -6732,15 +6668,10 @@ Write the summary of the conversation above, following your instructions.`;
6732
6668
 
6733
6669
  // src/session.ts
6734
6670
  import fs21 from "fs";
6735
- import path12 from "path";
6671
+ import path11 from "path";
6736
6672
  var log10 = createLogger("session");
6737
6673
  var SESSION_FILE = ".remy-session.json";
6738
6674
  var ARCHIVE_DIR = ".logs/sessions";
6739
- function writeFileAtomicSync(file, data) {
6740
- const tmp = `${file}.tmp`;
6741
- fs21.writeFileSync(tmp, data, "utf-8");
6742
- fs21.renameSync(tmp, file);
6743
- }
6744
6675
  var ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
6745
6676
  var RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
6746
6677
  var ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
@@ -6769,6 +6700,12 @@ function loadSession(state) {
6769
6700
  return true;
6770
6701
  }
6771
6702
  } catch {
6703
+ try {
6704
+ const quarantine = `${SESSION_FILE}.corrupt-${Date.now()}`;
6705
+ fs21.renameSync(SESSION_FILE, quarantine);
6706
+ log10.warn(`Session file unreadable \u2014 quarantined to ${quarantine}`);
6707
+ } catch {
6708
+ }
6772
6709
  }
6773
6710
  return false;
6774
6711
  }
@@ -6831,17 +6768,17 @@ function archiveMessages(messages, label, models) {
6831
6768
  fs21.mkdirSync(ARCHIVE_DIR, { recursive: true });
6832
6769
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6833
6770
  const count = messages.length;
6834
- let dest = path12.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
6771
+ let dest = path11.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
6835
6772
  let n = 1;
6836
6773
  while (fs21.existsSync(dest)) {
6837
- dest = path12.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
6774
+ dest = path11.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
6838
6775
  }
6839
6776
  const payload = { messages };
6840
6777
  if (models && Object.keys(models).length > 0) {
6841
6778
  payload.models = models;
6842
6779
  }
6843
6780
  writeFileAtomicSync(dest, JSON.stringify(payload));
6844
- archiveCountCache.set(path12.basename(dest), count);
6781
+ archiveCountCache.set(path11.basename(dest), count);
6845
6782
  log10.info("Session archived", { label, dest, messageCount: count });
6846
6783
  pruneArchives();
6847
6784
  return dest;
@@ -6854,7 +6791,7 @@ function pruneArchives() {
6854
6791
  }
6855
6792
  const archives = entries.map((name) => ({
6856
6793
  name,
6857
- size: fs21.statSync(path12.join(ARCHIVE_DIR, name)).size
6794
+ size: fs21.statSync(path11.join(ARCHIVE_DIR, name)).size
6858
6795
  })).sort(
6859
6796
  (a, b) => archiveSortKey(b.name).localeCompare(archiveSortKey(a.name))
6860
6797
  );
@@ -6872,7 +6809,7 @@ function pruneArchives() {
6872
6809
  let freed = 0;
6873
6810
  for (let i = cut; i < archives.length; i++) {
6874
6811
  try {
6875
- fs21.unlinkSync(path12.join(ARCHIVE_DIR, archives[i].name));
6812
+ fs21.unlinkSync(path11.join(ARCHIVE_DIR, archives[i].name));
6876
6813
  freed += archives[i].size;
6877
6814
  removed++;
6878
6815
  } catch {
@@ -6896,7 +6833,7 @@ function parseArchive(name) {
6896
6833
  return cached3;
6897
6834
  }
6898
6835
  try {
6899
- const raw = fs21.readFileSync(path12.join(ARCHIVE_DIR, name), "utf-8");
6836
+ const raw = fs21.readFileSync(path11.join(ARCHIVE_DIR, name), "utf-8");
6900
6837
  const data = JSON.parse(raw);
6901
6838
  const messages = Array.isArray(data?.messages) ? data.messages : [];
6902
6839
  archiveCountCache.set(name, messages.length);
@@ -7177,7 +7114,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
7177
7114
 
7178
7115
  // src/brandExtraction/index.ts
7179
7116
  import fs22 from "fs";
7180
- import path13 from "path";
7117
+ import path12 from "path";
7181
7118
  import { createHash } from "crypto";
7182
7119
  var log12 = createLogger("brandExtraction");
7183
7120
  var EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
@@ -7201,18 +7138,18 @@ async function runExtraction(apiConfig, model) {
7201
7138
  return brand;
7202
7139
  }
7203
7140
  function isDedicatedBrandFile(filePath) {
7204
- if (filePath.split(path13.sep).includes("@brand")) {
7141
+ if (filePath.split(path12.sep).includes("@brand")) {
7205
7142
  return true;
7206
7143
  }
7207
- const { type } = parseFrontmatter4(filePath);
7144
+ const { type } = parseFrontmatter3(filePath);
7208
7145
  return type.startsWith("design/color") || type.startsWith("design/typography");
7209
7146
  }
7210
7147
  function isBrandRelevant(filePath) {
7211
- return filePath === path13.join("src", "app.md") || isDedicatedBrandFile(filePath);
7148
+ return filePath === path12.join("src", "app.md") || isDedicatedBrandFile(filePath);
7212
7149
  }
7213
7150
  function computeInputHash() {
7214
7151
  const entries = [];
7215
- for (const filePath of walkMdFiles3("src")) {
7152
+ for (const filePath of walkMdFiles2("src")) {
7216
7153
  if (isBrandRelevant(filePath)) {
7217
7154
  entries.push({ path: filePath, content: readSafe(filePath) });
7218
7155
  }
@@ -7253,14 +7190,14 @@ function readBrandManifest() {
7253
7190
  return "";
7254
7191
  }
7255
7192
  }
7256
- function walkMdFiles3(dir) {
7193
+ function walkMdFiles2(dir) {
7257
7194
  const results = [];
7258
7195
  try {
7259
7196
  const entries = fs22.readdirSync(dir, { withFileTypes: true });
7260
7197
  for (const entry of entries) {
7261
- const full = path13.join(dir, entry.name);
7198
+ const full = path12.join(dir, entry.name);
7262
7199
  if (entry.isDirectory()) {
7263
- results.push(...walkMdFiles3(full));
7200
+ results.push(...walkMdFiles2(full));
7264
7201
  } else if (entry.name.endsWith(".md")) {
7265
7202
  results.push(full);
7266
7203
  }
@@ -7269,7 +7206,7 @@ function walkMdFiles3(dir) {
7269
7206
  }
7270
7207
  return results.sort();
7271
7208
  }
7272
- function parseFrontmatter4(filePath) {
7209
+ function parseFrontmatter3(filePath) {
7273
7210
  try {
7274
7211
  const content = fs22.readFileSync(filePath, "utf-8");
7275
7212
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -7298,7 +7235,10 @@ async function extractBrand(apiConfig, model) {
7298
7235
  subAgentId: "brandExtractor",
7299
7236
  system: EXTRACT_PROMPT,
7300
7237
  messages: [{ role: "user", content: corpus }],
7301
- tools: []
7238
+ tools: [],
7239
+ // One call per extraction over a corpus that changes with every spec
7240
+ // edit — never re-read, so a cache write is pure waste.
7241
+ cachePolicy: "oneshot"
7302
7242
  })) {
7303
7243
  if (event.type === "text") {
7304
7244
  responseText += event.text;
@@ -7337,7 +7277,7 @@ async function extractBrand(apiConfig, model) {
7337
7277
  var HEAD_SLICE_CHARS = 2e3;
7338
7278
  var BRAND_CORPUS_CHAR_LIMIT = 24e5;
7339
7279
  function buildCorpus() {
7340
- const all = walkMdFiles3("src");
7280
+ const all = walkMdFiles2("src");
7341
7281
  const ordered = [
7342
7282
  ...all.filter(isBrandRelevant),
7343
7283
  ...all.filter((f) => !isBrandRelevant(f))
@@ -7480,11 +7420,9 @@ function pickFont(raw) {
7480
7420
  return out;
7481
7421
  }
7482
7422
  function persistBrand(brand, inputHash) {
7483
- const tmp = `${BRAND_FILE}.tmp`;
7484
- fs22.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7485
- fs22.renameSync(tmp, BRAND_FILE);
7423
+ writeFileAtomicSync(BRAND_FILE, JSON.stringify(brand, null, 2));
7486
7424
  const cache = { inputHash, generatedAt: Date.now() };
7487
- fs22.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7425
+ writeFileAtomicSync(CACHE_FILE, JSON.stringify(cache, null, 2));
7488
7426
  }
7489
7427
  function readCache() {
7490
7428
  try {
@@ -7825,7 +7763,7 @@ async function runTurn(params) {
7825
7763
  toolRegistry,
7826
7764
  onBackgroundComplete
7827
7765
  } = params;
7828
- const tools2 = getToolDefinitions(onboardingState);
7766
+ const tools2 = getToolDefinitions();
7829
7767
  const buildModelOverride = buildModel ? filterModelPicks({ parent: buildModel }).parent : void 0;
7830
7768
  const baseline = resolveModel("parent", state.models, model);
7831
7769
  const parentModel = buildModelOverride ?? baseline;
@@ -8033,6 +7971,9 @@ async function runTurn(params) {
8033
7971
  system,
8034
7972
  messages: cleanMessagesForApi(state.messages),
8035
7973
  tools: tools2,
7974
+ // The parent is one long-lived conversation re-read across turns
7975
+ // with multi-minute gaps — the 1h-TTL profile.
7976
+ cachePolicy: "conversation",
8036
7977
  signal
8037
7978
  },
8038
7979
  {
@@ -8569,16 +8510,15 @@ ${lines.join("\n")}`;
8569
8510
  }
8570
8511
 
8571
8512
  // src/headless/planFile.ts
8572
- import { readFileSync, writeFileSync, unlinkSync } from "fs";
8513
+ import { readFileSync, unlinkSync } from "fs";
8573
8514
  var PLAN_FILE3 = ".remy-plan.md";
8574
8515
  function applyPlanFileSideEffect(rawText) {
8575
8516
  if (hasSentinel(rawText, "approvePlan") || hasSentinel(rawText, "approveInitialPlan")) {
8576
8517
  try {
8577
8518
  const plan = readFileSync(PLAN_FILE3, "utf-8");
8578
- writeFileSync(
8519
+ writeFileAtomicSync(
8579
8520
  PLAN_FILE3,
8580
- plan.replace(/^status:\s*pending/m, "status: approved"),
8581
- "utf-8"
8521
+ plan.replace(/^status:\s*pending/m, "status: approved")
8582
8522
  );
8583
8523
  } catch {
8584
8524
  }
@@ -8591,7 +8531,7 @@ function applyPlanFileSideEffect(rawText) {
8591
8531
  }
8592
8532
 
8593
8533
  // src/headless/stats.ts
8594
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
8534
+ import { readFileSync as readFileSync2 } from "fs";
8595
8535
  var STATS_FILE = ".remy-stats.json";
8596
8536
  function createSessionStats() {
8597
8537
  return {
@@ -8628,7 +8568,7 @@ function loadPassiveResults() {
8628
8568
  }
8629
8569
  function writeStats(stats, queue, passiveResults) {
8630
8570
  try {
8631
- writeFileSync2(
8571
+ writeFileAtomicSync(
8632
8572
  STATS_FILE,
8633
8573
  JSON.stringify({
8634
8574
  ...stats,
@@ -8857,9 +8797,7 @@ var HeadlessSession = class {
8857
8797
  name: "compactConversation",
8858
8798
  input: {},
8859
8799
  startedAt: Date.now(),
8860
- background: true,
8861
- uiOnly: true,
8862
- result: "Compaction started in the background."
8800
+ uiOnly: true
8863
8801
  }
8864
8802
  ]
8865
8803
  });
@@ -8868,15 +8806,7 @@ var HeadlessSession = class {
8868
8806
  type: "tool_start",
8869
8807
  id,
8870
8808
  name: "compactConversation",
8871
- input: {},
8872
- background: true
8873
- });
8874
- this.onEvent({
8875
- type: "tool_done",
8876
- id,
8877
- name: "compactConversation",
8878
- result: "Compaction started in the background.",
8879
- isError: false
8809
+ input: {}
8880
8810
  });
8881
8811
  }
8882
8812
  } else {
@@ -8885,11 +8815,31 @@ var HeadlessSession = class {
8885
8815
  if (this.syntheticCompactionId) {
8886
8816
  const id = this.syntheticCompactionId;
8887
8817
  this.syntheticCompactionId = null;
8888
- this.onBackgroundComplete(
8818
+ const result = event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? []);
8819
+ const isError = !!event.error;
8820
+ for (let i = this.state.messages.length - 1; i >= 0; i--) {
8821
+ const msg = this.state.messages[i];
8822
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
8823
+ continue;
8824
+ }
8825
+ const block = msg.content.find(
8826
+ (b) => b.type === "tool" && b.id === id
8827
+ );
8828
+ if (block && block.type === "tool") {
8829
+ block.result = result;
8830
+ block.isError = isError;
8831
+ block.completedAt = Date.now();
8832
+ saveSession(this.state);
8833
+ break;
8834
+ }
8835
+ }
8836
+ this.onEvent({
8837
+ type: "tool_done",
8889
8838
  id,
8890
- "compactConversation",
8891
- event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? [])
8892
- );
8839
+ name: "compactConversation",
8840
+ result,
8841
+ isError
8842
+ });
8893
8843
  }
8894
8844
  this.sessionStats.compactionInProgress = false;
8895
8845
  if (!event.error) {
@@ -9350,10 +9300,7 @@ var HeadlessSession = class {
9350
9300
  const buildModel = hasSentinel(rawText, "approvePlan") ? parsed.buildModel : void 0;
9351
9301
  const onboardingState = parsed.onboardingState ?? "onboardingFinished";
9352
9302
  this.currentOnboardingState = onboardingState;
9353
- const system = buildSystemPrompt(
9354
- onboardingState,
9355
- parsed.viewContext
9356
- );
9303
+ const system = buildSystemPrompt(onboardingState);
9357
9304
  if (resolved?.next && !fromChain) {
9358
9305
  for (const step of getActionChain(resolved.next)) {
9359
9306
  this.queue.push({
@@ -9430,13 +9377,12 @@ var HeadlessSession = class {
9430
9377
  }
9431
9378
  const onboardingState = batch.find((b) => b.command.onboardingState !== void 0)?.command.onboardingState ?? this.currentOnboardingState ?? "onboardingFinished";
9432
9379
  this.currentOnboardingState = onboardingState;
9433
- const viewContext = [...batch].reverse().find((b) => b.command.viewContext !== void 0)?.command.viewContext;
9434
9380
  await this.executeTurn({
9435
9381
  entries,
9436
9382
  requestId: primaryRid,
9437
9383
  absorbedRids,
9438
9384
  onboardingState,
9439
- system: buildSystemPrompt(onboardingState, viewContext)
9385
+ system: buildSystemPrompt(onboardingState)
9440
9386
  });
9441
9387
  }
9442
9388
  /**