@mindstudio-ai/remy 0.1.99 → 0.1.101

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.
@@ -12,7 +12,9 @@ If you are building a web frontend, consult `visualDesignExpert` for guidance an
12
12
 
13
13
  Then, build everything in one turn: methods, tables, interfaces, manifest updates, and scenarios, using the spec as the master plan. Be sure to delete any unnecessary files from the "Hello World" scaffold that already exist in the project - don't forget to update the page metadata on index.html too.
14
14
 
15
- When code generation is complete, verify your work:
15
+ When code generation is complete, do a polish pass before verifying. Re-read the spec annotations and the design expert's guidance, then walk through each frontend file looking for design details that got skipped in the initial build: animations, transitions, hover states, micro-interactions, spring physics, entrance reveals, gesture handling, layout issues, and anything else. The initial build prioritizes getting everything connected and functional, but this pass closes the gap between "it works" and "it feels great." In many ways this is the most important part of the initial build, as the user's first experience of the deliverable will set their expectations for every iteration that follows. Don't mess this up.
16
+
17
+ Then verify:
16
18
  - First, run use `runScenario` to seed test data, then use `runMethod` to confirm a method works
17
19
  - If the app has a web frontend, check the browser logs to make sure there are no errors rendering it.
18
20
  - Ask the `visualDesignExpert` to take a screenshot and verity that the visual design looks correct. Fix any issues it flags - we want the user's first time seeing the finished product to truly wow them.
package/dist/headless.js CHANGED
@@ -1051,6 +1051,18 @@ function unifiedDiff(filePath, oldText, newText) {
1051
1051
  return lines.join("\n");
1052
1052
  }
1053
1053
 
1054
+ // src/tools/_helpers/fileLock.ts
1055
+ var locks = /* @__PURE__ */ new Map();
1056
+ function acquireFileLock(filePath) {
1057
+ let release;
1058
+ const next = new Promise((res) => {
1059
+ release = res;
1060
+ });
1061
+ const wait = locks.get(filePath) ?? Promise.resolve();
1062
+ locks.set(filePath, next);
1063
+ return wait.then(() => release);
1064
+ }
1065
+
1054
1066
  // src/tools/spec/writeSpec.ts
1055
1067
  var writeSpecTool = {
1056
1068
  clearable: true,
@@ -1086,6 +1098,7 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
1086
1098
  } catch (err) {
1087
1099
  return `Error: ${err.message}`;
1088
1100
  }
1101
+ const release = await acquireFileLock(input.path);
1089
1102
  try {
1090
1103
  await fs6.mkdir(path4.dirname(input.path), { recursive: true });
1091
1104
  let oldContent = null;
@@ -1100,6 +1113,8 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
1100
1113
  ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
1101
1114
  } catch (err) {
1102
1115
  return `Error writing file: ${err.message}`;
1116
+ } finally {
1117
+ release();
1103
1118
  }
1104
1119
  }
1105
1120
  };
@@ -1151,69 +1166,74 @@ var editSpecTool = {
1151
1166
  } catch (err) {
1152
1167
  return `Error: ${err.message}`;
1153
1168
  }
1154
- let originalContent;
1169
+ const release = await acquireFileLock(input.path);
1155
1170
  try {
1156
- originalContent = await fs7.readFile(input.path, "utf-8");
1157
- } catch (err) {
1158
- return `Error reading file: ${err.message}`;
1159
- }
1160
- let content = originalContent;
1161
- for (const edit of input.edits) {
1162
- let range;
1171
+ let originalContent;
1163
1172
  try {
1164
- range = resolveHeadingPath(content, edit.heading);
1173
+ originalContent = await fs7.readFile(input.path, "utf-8");
1165
1174
  } catch (err) {
1166
- const tree = getHeadingTree(content);
1167
- return `Error: ${err.message}
1175
+ return `Error reading file: ${err.message}`;
1176
+ }
1177
+ let content = originalContent;
1178
+ for (const edit of input.edits) {
1179
+ let range;
1180
+ try {
1181
+ range = resolveHeadingPath(content, edit.heading);
1182
+ } catch (err) {
1183
+ const tree = getHeadingTree(content);
1184
+ return `Error: ${err.message}
1168
1185
 
1169
1186
  Document structure:
1170
1187
  ${tree}`;
1171
- }
1172
- const lines = content.split("\n");
1173
- switch (edit.operation) {
1174
- case "replace": {
1175
- if (edit.content == null) {
1176
- return 'Error: "content" is required for replace operations.';
1177
- }
1178
- const contentLines = edit.content.split("\n");
1179
- lines.splice(
1180
- range.contentStart,
1181
- range.contentEnd - range.contentStart,
1182
- ...contentLines
1183
- );
1184
- break;
1185
1188
  }
1186
- case "insert_after": {
1187
- if (edit.content == null) {
1188
- return 'Error: "content" is required for insert_after operations.';
1189
+ const lines = content.split("\n");
1190
+ switch (edit.operation) {
1191
+ case "replace": {
1192
+ if (edit.content == null) {
1193
+ return 'Error: "content" is required for replace operations.';
1194
+ }
1195
+ const contentLines = edit.content.split("\n");
1196
+ lines.splice(
1197
+ range.contentStart,
1198
+ range.contentEnd - range.contentStart,
1199
+ ...contentLines
1200
+ );
1201
+ break;
1189
1202
  }
1190
- const contentLines = edit.content.split("\n");
1191
- lines.splice(range.contentEnd, 0, ...contentLines);
1192
- break;
1193
- }
1194
- case "insert_before": {
1195
- if (edit.content == null) {
1196
- return 'Error: "content" is required for insert_before operations.';
1203
+ case "insert_after": {
1204
+ if (edit.content == null) {
1205
+ return 'Error: "content" is required for insert_after operations.';
1206
+ }
1207
+ const contentLines = edit.content.split("\n");
1208
+ lines.splice(range.contentEnd, 0, ...contentLines);
1209
+ break;
1197
1210
  }
1198
- const contentLines = edit.content.split("\n");
1199
- lines.splice(range.startLine, 0, ...contentLines);
1200
- break;
1201
- }
1202
- case "delete": {
1203
- lines.splice(range.startLine, range.contentEnd - range.startLine);
1204
- break;
1211
+ case "insert_before": {
1212
+ if (edit.content == null) {
1213
+ return 'Error: "content" is required for insert_before operations.';
1214
+ }
1215
+ const contentLines = edit.content.split("\n");
1216
+ lines.splice(range.startLine, 0, ...contentLines);
1217
+ break;
1218
+ }
1219
+ case "delete": {
1220
+ lines.splice(range.startLine, range.contentEnd - range.startLine);
1221
+ break;
1222
+ }
1223
+ default:
1224
+ return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
1205
1225
  }
1206
- default:
1207
- return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
1226
+ content = lines.join("\n");
1208
1227
  }
1209
- content = lines.join("\n");
1210
- }
1211
- try {
1212
- await fs7.writeFile(input.path, content, "utf-8");
1213
- } catch (err) {
1214
- return `Error writing file: ${err.message}`;
1228
+ try {
1229
+ await fs7.writeFile(input.path, content, "utf-8");
1230
+ } catch (err) {
1231
+ return `Error writing file: ${err.message}`;
1232
+ }
1233
+ return unifiedDiff(input.path, originalContent, content);
1234
+ } finally {
1235
+ release();
1215
1236
  }
1216
- return unifiedDiff(input.path, originalContent, content);
1217
1237
  }
1218
1238
  };
1219
1239
 
@@ -1825,6 +1845,7 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1825
1845
  };
1826
1846
  })(),
1827
1847
  async execute(input) {
1848
+ const release = await acquireFileLock(input.path);
1828
1849
  try {
1829
1850
  await fs10.mkdir(path6.dirname(input.path), { recursive: true });
1830
1851
  let oldContent = null;
@@ -1839,6 +1860,8 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1839
1860
  ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
1840
1861
  } catch (err) {
1841
1862
  return `Error writing file: ${err.message}`;
1863
+ } finally {
1864
+ release();
1842
1865
  }
1843
1866
  }
1844
1867
  };
@@ -1955,6 +1978,7 @@ var editFileTool = {
1955
1978
  }
1956
1979
  },
1957
1980
  async execute(input) {
1981
+ const release = await acquireFileLock(input.path);
1958
1982
  try {
1959
1983
  const content = await fs11.readFile(input.path, "utf-8");
1960
1984
  const { old_string, new_string, replace_all } = input;
@@ -2006,6 +2030,8 @@ ${unifiedDiff(input.path, content, updated)}`;
2006
2030
  return `Error: old_string not found in ${input.path}. Make sure you've read the file first and copied the exact text.`;
2007
2031
  } catch (err) {
2008
2032
  return `Error editing file: ${err.message}`;
2033
+ } finally {
2034
+ release();
2009
2035
  }
2010
2036
  }
2011
2037
  };
package/dist/index.js CHANGED
@@ -502,6 +502,24 @@ var init_diff = __esm({
502
502
  }
503
503
  });
504
504
 
505
+ // src/tools/_helpers/fileLock.ts
506
+ function acquireFileLock(filePath) {
507
+ let release;
508
+ const next = new Promise((res) => {
509
+ release = res;
510
+ });
511
+ const wait = locks.get(filePath) ?? Promise.resolve();
512
+ locks.set(filePath, next);
513
+ return wait.then(() => release);
514
+ }
515
+ var locks;
516
+ var init_fileLock = __esm({
517
+ "src/tools/_helpers/fileLock.ts"() {
518
+ "use strict";
519
+ locks = /* @__PURE__ */ new Map();
520
+ }
521
+ });
522
+
505
523
  // src/tools/spec/writeSpec.ts
506
524
  import fs3 from "fs/promises";
507
525
  import path from "path";
@@ -511,6 +529,7 @@ var init_writeSpec = __esm({
511
529
  "use strict";
512
530
  init_helpers();
513
531
  init_diff();
532
+ init_fileLock();
514
533
  writeSpecTool = {
515
534
  clearable: true,
516
535
  definition: {
@@ -545,6 +564,7 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
545
564
  } catch (err) {
546
565
  return `Error: ${err.message}`;
547
566
  }
567
+ const release = await acquireFileLock(input.path);
548
568
  try {
549
569
  await fs3.mkdir(path.dirname(input.path), { recursive: true });
550
570
  let oldContent = null;
@@ -559,6 +579,8 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
559
579
  ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
560
580
  } catch (err) {
561
581
  return `Error writing file: ${err.message}`;
582
+ } finally {
583
+ release();
562
584
  }
563
585
  }
564
586
  };
@@ -573,6 +595,7 @@ var init_editSpec = __esm({
573
595
  "use strict";
574
596
  init_helpers();
575
597
  init_diff();
598
+ init_fileLock();
576
599
  editSpecTool = {
577
600
  clearable: true,
578
601
  definition: {
@@ -618,69 +641,74 @@ var init_editSpec = __esm({
618
641
  } catch (err) {
619
642
  return `Error: ${err.message}`;
620
643
  }
621
- let originalContent;
644
+ const release = await acquireFileLock(input.path);
622
645
  try {
623
- originalContent = await fs4.readFile(input.path, "utf-8");
624
- } catch (err) {
625
- return `Error reading file: ${err.message}`;
626
- }
627
- let content = originalContent;
628
- for (const edit of input.edits) {
629
- let range;
646
+ let originalContent;
630
647
  try {
631
- range = resolveHeadingPath(content, edit.heading);
648
+ originalContent = await fs4.readFile(input.path, "utf-8");
632
649
  } catch (err) {
633
- const tree = getHeadingTree(content);
634
- return `Error: ${err.message}
650
+ return `Error reading file: ${err.message}`;
651
+ }
652
+ let content = originalContent;
653
+ for (const edit of input.edits) {
654
+ let range;
655
+ try {
656
+ range = resolveHeadingPath(content, edit.heading);
657
+ } catch (err) {
658
+ const tree = getHeadingTree(content);
659
+ return `Error: ${err.message}
635
660
 
636
661
  Document structure:
637
662
  ${tree}`;
638
- }
639
- const lines = content.split("\n");
640
- switch (edit.operation) {
641
- case "replace": {
642
- if (edit.content == null) {
643
- return 'Error: "content" is required for replace operations.';
644
- }
645
- const contentLines = edit.content.split("\n");
646
- lines.splice(
647
- range.contentStart,
648
- range.contentEnd - range.contentStart,
649
- ...contentLines
650
- );
651
- break;
652
663
  }
653
- case "insert_after": {
654
- if (edit.content == null) {
655
- return 'Error: "content" is required for insert_after operations.';
664
+ const lines = content.split("\n");
665
+ switch (edit.operation) {
666
+ case "replace": {
667
+ if (edit.content == null) {
668
+ return 'Error: "content" is required for replace operations.';
669
+ }
670
+ const contentLines = edit.content.split("\n");
671
+ lines.splice(
672
+ range.contentStart,
673
+ range.contentEnd - range.contentStart,
674
+ ...contentLines
675
+ );
676
+ break;
656
677
  }
657
- const contentLines = edit.content.split("\n");
658
- lines.splice(range.contentEnd, 0, ...contentLines);
659
- break;
660
- }
661
- case "insert_before": {
662
- if (edit.content == null) {
663
- return 'Error: "content" is required for insert_before operations.';
678
+ case "insert_after": {
679
+ if (edit.content == null) {
680
+ return 'Error: "content" is required for insert_after operations.';
681
+ }
682
+ const contentLines = edit.content.split("\n");
683
+ lines.splice(range.contentEnd, 0, ...contentLines);
684
+ break;
664
685
  }
665
- const contentLines = edit.content.split("\n");
666
- lines.splice(range.startLine, 0, ...contentLines);
667
- break;
668
- }
669
- case "delete": {
670
- lines.splice(range.startLine, range.contentEnd - range.startLine);
671
- break;
686
+ case "insert_before": {
687
+ if (edit.content == null) {
688
+ return 'Error: "content" is required for insert_before operations.';
689
+ }
690
+ const contentLines = edit.content.split("\n");
691
+ lines.splice(range.startLine, 0, ...contentLines);
692
+ break;
693
+ }
694
+ case "delete": {
695
+ lines.splice(range.startLine, range.contentEnd - range.startLine);
696
+ break;
697
+ }
698
+ default:
699
+ return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
672
700
  }
673
- default:
674
- return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
701
+ content = lines.join("\n");
675
702
  }
676
- content = lines.join("\n");
677
- }
678
- try {
679
- await fs4.writeFile(input.path, content, "utf-8");
680
- } catch (err) {
681
- return `Error writing file: ${err.message}`;
703
+ try {
704
+ await fs4.writeFile(input.path, content, "utf-8");
705
+ } catch (err) {
706
+ return `Error writing file: ${err.message}`;
707
+ }
708
+ return unifiedDiff(input.path, originalContent, content);
709
+ } finally {
710
+ release();
682
711
  }
683
- return unifiedDiff(input.path, originalContent, content);
684
712
  }
685
713
  };
686
714
  }
@@ -1335,6 +1363,7 @@ var init_writeFile = __esm({
1335
1363
  "src/tools/code/writeFile.ts"() {
1336
1364
  "use strict";
1337
1365
  init_diff();
1366
+ init_fileLock();
1338
1367
  writeFileTool = {
1339
1368
  clearable: true,
1340
1369
  definition: {
@@ -1378,6 +1407,7 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1378
1407
  };
1379
1408
  })(),
1380
1409
  async execute(input) {
1410
+ const release = await acquireFileLock(input.path);
1381
1411
  try {
1382
1412
  await fs7.mkdir(path3.dirname(input.path), { recursive: true });
1383
1413
  let oldContent = null;
@@ -1392,6 +1422,8 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1392
1422
  ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
1393
1423
  } catch (err) {
1394
1424
  return `Error writing file: ${err.message}`;
1425
+ } finally {
1426
+ release();
1395
1427
  }
1396
1428
  }
1397
1429
  };
@@ -1489,6 +1521,7 @@ var init_editFile = __esm({
1489
1521
  "src/tools/code/editFile/index.ts"() {
1490
1522
  "use strict";
1491
1523
  init_diff();
1524
+ init_fileLock();
1492
1525
  init_helpers2();
1493
1526
  editFileTool = {
1494
1527
  clearable: true,
@@ -1519,6 +1552,7 @@ var init_editFile = __esm({
1519
1552
  }
1520
1553
  },
1521
1554
  async execute(input) {
1555
+ const release = await acquireFileLock(input.path);
1522
1556
  try {
1523
1557
  const content = await fs8.readFile(input.path, "utf-8");
1524
1558
  const { old_string, new_string, replace_all } = input;
@@ -1570,6 +1604,8 @@ ${unifiedDiff(input.path, content, updated)}`;
1570
1604
  return `Error: old_string not found in ${input.path}. Make sure you've read the file first and copied the exact text.`;
1571
1605
  } catch (err) {
1572
1606
  return `Error editing file: ${err.message}`;
1607
+ } finally {
1608
+ release();
1573
1609
  }
1574
1610
  }
1575
1611
  };
@@ -62,7 +62,7 @@ export const Users = db.defineTable<{
62
62
 
63
63
  - **`email` / `phone`** — read-only from code. Writing via `update()` or `push()` throws a `MindStudioError`. Use the auth API to change a user's email or phone.
64
64
  - **`roles`** — read/write from both code and the dashboard. `Users.update(userId, { roles: ['admin'] })` works and syncs to the platform. Dashboard role changes sync back to the table.
65
- - All other columns are fully the developer's.
65
+ - All other columns are fully the developer's. When auth creates a user row, only the managed columns (email/phone, roles) are populated. All user-defined columns start as null until the user completes onboarding — type them as optional and guard against null.
66
66
 
67
67
  ## Frontend Auth (Interface SDK)
68
68
 
@@ -129,6 +129,7 @@ await auth.logout(); // clears session
129
129
 
130
130
  ```typescript
131
131
  auth.phone.countries // ~180 countries with { code, dialCode, name, flag }
132
+ // Key selects by country code (US, CA, BB), not dial code — multiple countries share +1
132
133
  auth.phone.detectCountry() // guess from timezone, e.g. 'US'
133
134
  auth.phone.toE164('5551234567', 'US') // '+15551234567'
134
135
  auth.phone.format('+15551234567') // '+1 (555) 123-4567'
@@ -25,7 +25,7 @@ Remember: users care about look and feel as much as (and often more than) underl
25
25
 
26
26
  Write specs in natural, human language. Describe what the app does the way you'd explain it to a colleague. The spec renders with annotations hidden is a human-forward document that anyone can read. The spec with annotations visible is the agent-forward document that drives code generation. Keep the prose clean and readable — the user should never see raw CSS, code, or technical values in the prose. Write "square corners on all cards" not `border-radius: 0`. Write "no shadows" not `box-shadow: none`. Technical specifics belong in annotations.
27
27
 
28
- When the design expert provides specific implementation details — layout structure, CSS values, spacing, font sizes, rotation angles, shadow definitions, animation timings, or things to pay special attention to or watch out for — capture them as annotations on the relevant prose. The design expert's recommendations are precise and intentional; don't summarize them into vague language. The prose describes the intent, the annotations preserve the exact values the coder needs:
28
+ When the design expert provides specific implementation details — layout structure, CSS values, spacing, font sizes, rotation angles, shadow definitions, animation timings, or things to pay special attention to or watch out for — it is critical that you capture them in full within the spec. The design expert's recommendations are precise and intentional; don't summarize them into vague language. The prose describes the intent, the annotations preserve the exact values the coder needs:
29
29
 
30
30
  ```markdown
31
31
  Cards float at varied angles with [rounded corners]{border-radius: 24px} on a pure black background.
@@ -37,6 +37,8 @@ box-shadow: 0 8px 32px rgba(0,0,0,0.3) for floating depth
37
37
 
38
38
  When you have image URLs (from the design expert), embed them directly in the spec using markdown image syntax. Write descriptive alt text that captures what the image actually depicts (this helps accessibility and helps the coding agent understand the image without loading it). Use the surrounding prose to explain the design intent — what the image is for, how it should be used in the layout, and why it was chosen.
39
39
 
40
+ When the design expert provides wireframes, include them directly in the spec for future reference.
41
+
40
42
  ```markdown
41
43
  ### Hero Section
42
44
 
@@ -43,7 +43,7 @@ For any work involving AI models, external actions (web scraping, email, SMS), o
43
43
  - Use container queries for components that need to adapt to their container rather than the viewport.
44
44
 
45
45
  ### State Management
46
- - Calls to methods introduce latency. When building web frontends that load data from methods, consider front-loading as much data as you can in a single API request - e.g., when possible, load a large data object into a central store and use that to render sub-screens in an app, rather than an API call on every screen.
46
+ - Calls to methods introduce latency. When building web frontends that load data from methods, front-load as much data as you can in a single API request - e.g., when possible, load a large data object into a central store and use that to render sub-screens in an app, rather than an API call on every screen. User experience and perceived speed/performance are far more valuable than normalization and good REST API design.
47
47
 
48
48
  ### Dependencies
49
49
  Before installing a package you haven't used in this project, do a quick web search to confirm it's still the best option. The JavaScript ecosystem moves fast — the package you remember from training may have been superseded by something smaller, faster, or better maintained. A 10-second search beats debugging a deprecated library.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.99",
3
+ "version": "0.1.101",
4
4
  "description": "MindStudio coding agent",
5
5
  "repository": {
6
6
  "type": "git",