carto-md 1.0.18 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -135,14 +135,13 @@ npx carto-md init
135
135
  # 1. Go to your project
136
136
  cd your-project
137
137
 
138
- # 2. Generate AGENTS.md (run once)
138
+ # 2. Run once like git init
139
139
  carto init
140
-
141
- # 3. Keep it live while you work
142
- carto watch
143
140
  ```
144
141
 
145
- Leave `carto watch` running in a background terminal. Every file save updates AGENTS.md automatically.
142
+ That's it. Carto installs a git hook. Every `git commit` syncs AGENTS.md automatically — no watching, no manual runs, nothing to remember.
143
+
144
+ Want live updates on every file save too? Run `carto watch` in a background terminal.
146
145
 
147
146
  ---
148
147
 
@@ -150,17 +149,17 @@ Leave `carto watch` running in a background terminal. Every file save updates AG
150
149
 
151
150
  | Command | What it does |
152
151
  |---------|-------------|
153
- | `carto init` | Detect stack, generate AGENTS.md, install git hook |
154
- | `carto watch` | Watch files, update AGENTS.md on every save |
155
- | `carto sync` | One-time refresh, no watcher |
152
+ | `carto init` | Detect stack, generate AGENTS.md, install git hook — auto-syncs on every commit |
153
+ | `carto watch` | Live updates on every file save — optional, for between commits |
154
+ | `carto sync` | One-time manual refresh |
156
155
  | `carto impact <file>` | Show blast radius before touching a file |
157
156
  | `carto remove` | Remove AGENTS.md and .carto/ from this project |
158
157
  | `carto --version` | Show version |
159
158
 
160
159
  **When to use each:**
161
- - `init` — once, when you add Carto to a project
162
- - `watch` — every work session, leave it running
163
- - `sync` — skipped watch and need a fresh snapshot
160
+ - `init` — once per project, sets everything up
161
+ - `watch` — optional, if you want updates between commits
162
+ - `sync` — if you skipped watch and need a fresh snapshot
164
163
  - `impact` — before editing anything critical
165
164
 
166
165
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carto-md",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "The context layer for AI-native development.",
5
5
  "bin": {
6
6
  "carto": "src/cli/index.js"
@@ -64,8 +64,8 @@ function discoverForFramework(projectRoot, framework, ignoreFn) {
64
64
  if (['plumber', 'shiny', 'r-generic'].includes(framework)) {
65
65
  const rFiles = findFilesRecursive(projectRoot, ['.r'], R_IGNORE, ignoreFn)
66
66
  .filter(f => {
67
- const base = path.basename(f);
68
- return !base.startsWith('test_') && !base.startsWith('test-') && !base.endsWith('_test.R');
67
+ const lbase = path.basename(f).toLowerCase();
68
+ return !lbase.startsWith('test_') && !lbase.startsWith('test-') && !lbase.endsWith('_test.r');
69
69
  });
70
70
 
71
71
  if (rFiles.length <= MAX_FILES_TOTAL) {
@@ -20,6 +20,10 @@ const fs = require('fs');
20
20
  * from app.module import X (local package — resolved if file exists)
21
21
  * import .module (relative)
22
22
  *
23
+ * R patterns:
24
+ * library(pkg) / require(pkg) (package name recorded as-is)
25
+ * source("./file.R") (resolved if file exists)
26
+ *
23
27
  * Only includes paths that resolve to actual files in the project.
24
28
  * Skips: node_modules, non-code files, anything that doesn't resolve.
25
29
  */
@@ -33,6 +37,8 @@ function extractImports(content, filePath, projectRoot) {
33
37
  rawImports = extractJSImports(content);
34
38
  } else if (ext === '.py') {
35
39
  rawImports = extractPythonImports(content, filePath, projectRoot);
40
+ } else if (ext === '.r') {
41
+ return extractRImports(content, filePath, projectRoot);
36
42
  }
37
43
 
38
44
  // Resolve and deduplicate
@@ -72,6 +78,32 @@ function extractJSImports(content) {
72
78
  return imports;
73
79
  }
74
80
 
81
+ /**
82
+ * Extract imports from R content.
83
+ * library(pkg) / require(pkg) → package name recorded directly.
84
+ * source("./file.R") → resolved relative path if the file exists.
85
+ */
86
+ function extractRImports(content, filePath, projectRoot) {
87
+ const results = new Set();
88
+ const fileDir = path.dirname(filePath);
89
+
90
+ const pkgRe = /(?:library|require)\s*\(\s*["']?(\w[\w.]+)["']?\s*\)/g;
91
+ let m;
92
+ while ((m = pkgRe.exec(content)) !== null) {
93
+ results.add(m[1]);
94
+ }
95
+
96
+ const sourceRe = /source\s*\(\s*["']([^"']+)["']\s*\)/g;
97
+ while ((m = sourceRe.exec(content)) !== null) {
98
+ const abs = path.resolve(fileDir, m[1]);
99
+ if (fs.existsSync(abs)) {
100
+ results.add(path.relative(projectRoot, abs));
101
+ }
102
+ }
103
+
104
+ return [...results].sort();
105
+ }
106
+
75
107
  /**
76
108
  * Extract import paths from Python content. Relative imports only.
77
109
  */
@@ -201,6 +201,25 @@ function extractModels(content) {
201
201
  models.push({ className, fields });
202
202
  }
203
203
 
204
+ const s7Re = /^(\w+)\s*<-\s*(?:S7::)?new_class\s*\(\s*(?:name\s*=\s*)?["'](\w+)["']/gm;
205
+ while ((m = s7Re.exec(collapsed)) !== null) {
206
+ const className = m[2];
207
+ const propsIdx = collapsed.indexOf('properties', m.index);
208
+ if (propsIdx === -1 || propsIdx > m.index + 400) continue;
209
+ const listIdx = collapsed.indexOf('list(', propsIdx);
210
+ if (listIdx === -1) continue;
211
+ const openPos = listIdx + 4;
212
+ const closePos = findBalancedEnd(collapsed, openPos);
213
+ const propsContent = collapsed.slice(openPos + 1, closePos);
214
+ const fields = [];
215
+ const propRe = /(\w+)\s*=\s*(?:S7::)?class_(\w+)/g;
216
+ let pm;
217
+ while ((pm = propRe.exec(propsContent)) !== null) {
218
+ fields.push({ name: pm[1], type: pm[2] });
219
+ }
220
+ models.push({ className, fields });
221
+ }
222
+
204
223
  return models;
205
224
  }
206
225