@glasstrace/sdk 1.10.1 → 1.11.0

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.
Files changed (39) hide show
  1. package/README.md +43 -5
  2. package/dist/{chunk-WQF7ZQOM.js → chunk-DQFGNX3H.js} +13 -8
  3. package/dist/{chunk-WQF7ZQOM.js.map → chunk-DQFGNX3H.js.map} +1 -1
  4. package/dist/{chunk-UMGZJYC4.js → chunk-FQ4SEG6Y.js} +8 -3
  5. package/dist/chunk-FQ4SEG6Y.js.map +1 -0
  6. package/dist/chunk-KOYZJN6G.js +651 -0
  7. package/dist/chunk-KOYZJN6G.js.map +1 -0
  8. package/dist/{chunk-ZBQQXVHD.js → chunk-YIEXKQYP.js} +2 -67
  9. package/dist/chunk-YIEXKQYP.js.map +1 -0
  10. package/dist/cli/init.cjs +460 -127
  11. package/dist/cli/init.cjs.map +1 -1
  12. package/dist/cli/init.js +29 -16
  13. package/dist/cli/init.js.map +1 -1
  14. package/dist/cli/mcp-add.cjs +346 -98
  15. package/dist/cli/mcp-add.cjs.map +1 -1
  16. package/dist/cli/mcp-add.js +32 -14
  17. package/dist/cli/mcp-add.js.map +1 -1
  18. package/dist/cli/status.cjs +6 -1
  19. package/dist/cli/status.cjs.map +1 -1
  20. package/dist/cli/status.js +7 -2
  21. package/dist/cli/status.js.map +1 -1
  22. package/dist/cli/uninit.cjs +6 -1
  23. package/dist/cli/uninit.cjs.map +1 -1
  24. package/dist/cli/uninit.js +2 -2
  25. package/dist/cli/upgrade-instructions.cjs +390 -113
  26. package/dist/cli/upgrade-instructions.cjs.map +1 -1
  27. package/dist/cli/upgrade-instructions.js +70 -18
  28. package/dist/cli/upgrade-instructions.js.map +1 -1
  29. package/dist/index.cjs +11 -6
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +2 -2
  32. package/dist/node-entry.cjs +11 -6
  33. package/dist/node-entry.cjs.map +1 -1
  34. package/dist/node-entry.js +2 -2
  35. package/package.json +1 -1
  36. package/dist/chunk-TJ46YOGJ.js +0 -355
  37. package/dist/chunk-TJ46YOGJ.js.map +0 -1
  38. package/dist/chunk-UMGZJYC4.js.map +0 -1
  39. package/dist/chunk-ZBQQXVHD.js.map +0 -1
@@ -12,11 +12,11 @@ import {
12
12
  unwrapCJSExport,
13
13
  unwrapExport,
14
14
  writeShutdownMarker
15
- } from "../chunk-UMGZJYC4.js";
15
+ } from "../chunk-FQ4SEG6Y.js";
16
16
  import "../chunk-RL43PU2X.js";
17
17
  import "../chunk-6RKS3DNA.js";
18
18
  import "../chunk-NB7GJE4S.js";
19
- import "../chunk-ZBQQXVHD.js";
19
+ import "../chunk-YIEXKQYP.js";
20
20
  import "../chunk-NSBPE2FW.js";
21
21
  export {
22
22
  findMatchingDelimiter,
@@ -46,33 +46,61 @@ var AGENT_RULES = [
46
46
  },
47
47
  {
48
48
  name: "codex",
49
+ // Codex 2026 default discovery is `AGENTS.override.md` → `AGENTS.md` →
50
+ // opt-in `project_doc_fallback_filenames`; `codex.md` is NOT in the
51
+ // default fallback list. Detection requires Codex-specific markers
52
+ // (`codex.md` legacy, `.codex/` config dir) — `AGENTS.md` is NOT
53
+ // included as a marker because the SDK now writes `AGENTS.md`
54
+ // broadly via the multi-target dispatcher's companion writes; if
55
+ // `AGENTS.md` were a Codex marker, every project with the SDK's
56
+ // own companion AGENTS.md would re-classify as Codex on subsequent
57
+ // detect runs and trigger unintended `.codex/config.toml` writes
58
+ // (Codex P1 + Copilot P1 review of Wave 18 PR #274). The canonical
59
+ // write destination remains AGENTS.md regardless of which marker
60
+ // classified the project.
49
61
  markers: ["codex.md", ".codex"],
50
62
  mcpConfigPath: (dir) => (0, import_node_path.join)(dir, ".codex", "config.toml"),
51
- infoFilePath: (dir) => (0, import_node_path.join)(dir, "codex.md"),
63
+ infoFilePath: (dir) => (0, import_node_path.join)(dir, "AGENTS.md"),
52
64
  cliBinary: "codex",
53
65
  registrationCommand: "npx glasstrace mcp add --agent codex"
54
66
  },
55
67
  {
56
68
  name: "gemini",
57
- markers: [".gemini"],
69
+ markers: [".gemini", "GEMINI.md"],
58
70
  mcpConfigPath: (dir) => (0, import_node_path.join)(dir, ".gemini", "settings.json"),
59
- infoFilePath: () => null,
71
+ infoFilePath: (dir) => (0, import_node_path.join)(dir, "GEMINI.md"),
60
72
  cliBinary: "gemini",
61
73
  registrationCommand: "npx glasstrace mcp add --agent gemini"
62
74
  },
63
75
  {
64
76
  name: "cursor",
77
+ // `.cursor/rules/*.mdc` is the current canonical format per Cursor's
78
+ // 2026 docs. `.cursorrules` (single file) is supported-but-deprecated
79
+ // and stays as a transitional fallback that the multi-target write
80
+ // helper writes unconditionally alongside the .mdc canonical.
65
81
  markers: [".cursor", ".cursorrules"],
66
82
  mcpConfigPath: (dir) => (0, import_node_path.join)(dir, ".cursor", "mcp.json"),
67
- infoFilePath: (dir) => (0, import_node_path.join)(dir, ".cursorrules"),
83
+ infoFilePath: (dir) => (0, import_node_path.join)(dir, ".cursor", "rules", "glasstrace.mdc"),
68
84
  cliBinary: null,
69
85
  registrationCommand: "npx glasstrace mcp add --agent cursor"
70
86
  },
71
87
  {
72
88
  name: "windsurf",
73
- markers: [".windsurfrules", ".windsurf"],
89
+ // Windsurf's current canonical workspace-rules format is
90
+ // `.windsurf/rules/*.md`. AGENTS.md is a parallel cross-tool
91
+ // mechanism Windsurf also reads BUT is NOT included as a Windsurf
92
+ // detection marker — the SDK writes `AGENTS.md` broadly via the
93
+ // multi-target dispatcher's companion writes, so treating
94
+ // `AGENTS.md` as a Windsurf marker would re-classify every
95
+ // SDK-managed project as Windsurf and cause `glasstrace uninit`
96
+ // to mutate the global `~/.codeium/windsurf/mcp_config.json` for
97
+ // non-Windsurf projects (Codex P1 + Copilot P1 review of Wave 18
98
+ // PR #274). The single-file `.windsurfrules` is the deprecated
99
+ // legacy form — recognized as a marker so legacy projects classify
100
+ // correctly, but the SDK no longer writes to it.
101
+ markers: [".windsurf", ".windsurfrules"],
74
102
  mcpConfigPath: () => (0, import_node_path.join)((0, import_node_os.homedir)(), ".codeium", "windsurf", "mcp_config.json"),
75
- infoFilePath: (dir) => (0, import_node_path.join)(dir, ".windsurfrules"),
103
+ infoFilePath: (dir) => (0, import_node_path.join)(dir, ".windsurf", "rules", "glasstrace.md"),
76
104
  cliBinary: null,
77
105
  registrationCommand: "npx glasstrace mcp add --agent windsurf"
78
106
  }
@@ -159,10 +187,7 @@ async function detectAgents(projectRoot) {
159
187
  continue;
160
188
  }
161
189
  seenAgents.add(rule.name);
162
- let infoFilePath = rule.infoFilePath(foundDir);
163
- if (infoFilePath !== null && !await pathExists(infoFilePath)) {
164
- infoFilePath = null;
165
- }
190
+ const infoFilePath = rule.infoFilePath(foundDir);
166
191
  const cliAvailable = rule.cliBinary ? await isCliAvailable(rule.cliBinary) : false;
167
192
  detected.push({
168
193
  name: rule.name,
@@ -175,13 +200,62 @@ async function detectAgents(projectRoot) {
175
200
  detected.push({
176
201
  name: "generic",
177
202
  mcpConfigPath: (0, import_node_path.join)(resolvedRoot, ".glasstrace", "mcp.json"),
178
- infoFilePath: null,
203
+ infoFilePath: (0, import_node_path.join)(resolvedRoot, "AGENTS.md"),
179
204
  cliAvailable: false,
180
205
  registrationCommand: null
181
206
  });
182
207
  return detected;
183
208
  }
184
209
 
210
+ // src/agent-detection/inject.ts
211
+ var import_promises2 = require("node:fs/promises");
212
+ var HTML_START_RE = /^<!--\s*glasstrace:mcp:start(?:\s+v=([^\s>]+))?\s*-->$/;
213
+ var HTML_END = "<!-- glasstrace:mcp:end -->";
214
+ var HASH_START_RE = /^#\s*glasstrace:mcp:start(?:\s+v=(\S+))?$/;
215
+ var HASH_END = "# glasstrace:mcp:end";
216
+ function parseStartMarkerLine(line) {
217
+ const trimmed = line.trim();
218
+ const html = HTML_START_RE.exec(trimmed);
219
+ if (html !== null) {
220
+ return { kind: "html", stamp: html[1] ?? null };
221
+ }
222
+ const hash = HASH_START_RE.exec(trimmed);
223
+ if (hash !== null) {
224
+ return { kind: "hash", stamp: hash[1] ?? null };
225
+ }
226
+ return null;
227
+ }
228
+ function isEndMarker(line) {
229
+ const trimmed = line.trim();
230
+ return trimmed === HTML_END || trimmed === HASH_END;
231
+ }
232
+ function findMarkerBoundaries(lines) {
233
+ let startIdx = -1;
234
+ for (let i = 0; i < lines.length; i++) {
235
+ if (parseStartMarkerLine(lines[i]) !== null) {
236
+ startIdx = i;
237
+ } else if (startIdx !== -1 && isEndMarker(lines[i])) {
238
+ return { startIdx, endIdx: i };
239
+ }
240
+ }
241
+ return null;
242
+ }
243
+ async function hasManagedSection(filePath) {
244
+ let content;
245
+ try {
246
+ content = await (0, import_promises2.readFile)(filePath, "utf-8");
247
+ } catch (err) {
248
+ const code = err.code;
249
+ if (code === "ENOENT") return false;
250
+ throw err;
251
+ }
252
+ return findMarkerBoundaries(content.split("\n")) !== null;
253
+ }
254
+
255
+ // src/agent-detection/inject-all-targets.ts
256
+ var import_promises3 = require("node:fs/promises");
257
+ var import_node_path2 = require("node:path");
258
+
185
259
  // src/agent-detection/agent-instruction-text.ts
186
260
  function buildAgentInstructionBody() {
187
261
  return [
@@ -205,7 +279,13 @@ function buildAgentInstructionBody() {
205
279
  "1. Start with `find_trace_candidates`. Pass whatever route or procedure name is natural \u2014 the server normalizes vocabulary and, on miss, returns close matches and a sample of routes actually present in the window.",
206
280
  "2. Take the highest-confidence candidate's `suggestedFollowups` and pass them straight to `get_trace` or `get_root_cause`.",
207
281
  "3. For side-effect bugs, read `sideEffectSummary` in the `get_trace` / `get_root_cause` response. The allowlisted fields (`templateKey`, `providerOperation`, `role`, `locale`, `timezone`, `status`, `phase`) are the ones that disambiguate payload bugs.",
208
- "4. If a tool returns empty, READ the response's `closeMatches`, `recentRoutesSample`, and `recoveryActions` before pivoting to source. Empty results carry `notAbsenceProof: true` \u2014 they are never proof the bug did not occur.",
282
+ "4. If a tool returns empty, READ the response's empty-result envelope before pivoting to source \u2014 each field disambiguates a different reason for the empty result:",
283
+ " - `closeMatches` / `recentRoutesSample` \u2014 your filter vocabulary doesn't match server-side names; the server returns the closest known names + a sample of routes actually present.",
284
+ ' - `windowActivity` \u2014 load-bearing four-way distinguisher. `totalTracesInWindow === 0` AND `totalTracesInTenantEver > 0` means "your time window missed the activity"; `totalTracesInTenantEver === 0` means "this tenant has never produced traces" (SDK not registered, or never hit); `captureConfigBlocksRequest === true` means "the SDK\'s capture config dropped this route"; otherwise the empty result is a vocabulary miss \u2014 see `closeMatches`.',
285
+ " - `humanReadable` \u2014 prose guidance written for the agent.",
286
+ " - `recoveryActions` \u2014 concrete next-call shapes.",
287
+ " - `diagnosticValue` / `recommendedNextStep` \u2014 whether to keep searching or stop.",
288
+ " Empty results carry `notAbsenceProof: true` \u2014 they are never proof the bug did not occur.",
209
289
  "",
210
290
  "### Tools",
211
291
  "- `find_trace_candidates` \u2014 discovery, vocabulary-tolerant filter",
@@ -247,110 +327,235 @@ function generateInfoSection(agent, endpoint, sdkVersion) {
247
327
  }
248
328
  const content = buildAgentInstructionBody();
249
329
  switch (agent.name) {
250
- case "claude": {
251
- const m = htmlMarkers(sdkVersion);
252
- return `${m.start}
253
- ${content}${m.end}
254
- `;
255
- }
256
- case "codex": {
330
+ case "claude":
331
+ case "codex":
332
+ case "gemini":
333
+ case "windsurf":
334
+ case "generic": {
257
335
  const m = htmlMarkers(sdkVersion);
258
336
  return `${m.start}
259
337
  ${content}${m.end}
260
338
  `;
261
339
  }
262
340
  case "cursor": {
263
- const m = hashMarkers(sdkVersion);
341
+ const m = htmlMarkers(sdkVersion);
264
342
  return `${m.start}
265
343
  ${content}${m.end}
266
344
  `;
267
345
  }
268
- case "gemini":
269
- case "windsurf":
270
- case "generic":
271
- return "";
272
346
  default: {
273
347
  const _exhaustive = agent.name;
274
348
  throw new Error(`Unknown agent: ${_exhaustive}`);
275
349
  }
276
350
  }
277
351
  }
278
-
279
- // src/agent-detection/inject.ts
280
- var import_promises2 = require("node:fs/promises");
281
- var import_node_path2 = require("node:path");
282
- var HTML_START_RE = /^<!--\s*glasstrace:mcp:start(?:\s+v=([^\s>]+))?\s*-->$/;
283
- var HTML_END = "<!-- glasstrace:mcp:end -->";
284
- var HASH_START_RE = /^#\s*glasstrace:mcp:start(?:\s+v=(\S+))?$/;
285
- var HASH_END = "# glasstrace:mcp:end";
286
- function parseStartMarkerLine(line) {
287
- const trimmed = line.trim();
288
- const html = HTML_START_RE.exec(trimmed);
289
- if (html !== null) {
290
- return { kind: "html", stamp: html[1] ?? null };
352
+ function generateInfoSectionForCursorrulesLegacy(endpoint, sdkVersion) {
353
+ if (!endpoint || endpoint.trim() === "") {
354
+ throw new Error("endpoint must not be empty");
291
355
  }
292
- const hash = HASH_START_RE.exec(trimmed);
293
- if (hash !== null) {
294
- return { kind: "hash", stamp: hash[1] ?? null };
356
+ if (!sdkVersion || sdkVersion.trim() === "") {
357
+ throw new Error("sdkVersion must not be empty");
295
358
  }
296
- return null;
297
- }
298
- function isEndMarker(line) {
299
- const trimmed = line.trim();
300
- return trimmed === HTML_END || trimmed === HASH_END;
359
+ if (!SDK_VERSION_STAMP_PATTERN.test(sdkVersion)) {
360
+ throw new Error(
361
+ "sdkVersion must match [A-Za-z0-9.+\\-]+ (semver-shaped, no whitespace, no angle brackets)"
362
+ );
363
+ }
364
+ const content = buildAgentInstructionBody();
365
+ const m = hashMarkers(sdkVersion);
366
+ return `${m.start}
367
+ ${content}${m.end}
368
+ `;
301
369
  }
302
- function isPermissionError(err) {
303
- const code = err.code;
304
- return code === "EACCES" || code === "EPERM" || code === "EROFS";
370
+ function generateInfoSectionForCursorMdc(endpoint, sdkVersion) {
371
+ if (!endpoint || endpoint.trim() === "") {
372
+ throw new Error("endpoint must not be empty");
373
+ }
374
+ if (!sdkVersion || sdkVersion.trim() === "") {
375
+ throw new Error("sdkVersion must not be empty");
376
+ }
377
+ if (!SDK_VERSION_STAMP_PATTERN.test(sdkVersion)) {
378
+ throw new Error(
379
+ "sdkVersion must match [A-Za-z0-9.+\\-]+ (semver-shaped, no whitespace, no angle brackets)"
380
+ );
381
+ }
382
+ const content = buildAgentInstructionBody();
383
+ const m = htmlMarkers(sdkVersion);
384
+ return [
385
+ "---",
386
+ "description: Glasstrace MCP runtime debugging tools \u2014 runtime evidence the agent reads when source alone cannot resolve a bug",
387
+ "alwaysApply: true",
388
+ "---",
389
+ "",
390
+ `${m.start}
391
+ ${content}${m.end}
392
+ `
393
+ ].join("\n");
305
394
  }
306
- function findMarkerBoundaries(lines) {
307
- let startIdx = -1;
308
- for (let i = 0; i < lines.length; i++) {
309
- if (parseStartMarkerLine(lines[i]) !== null) {
310
- startIdx = i;
311
- } else if (startIdx !== -1 && isEndMarker(lines[i])) {
312
- return { startIdx, endIdx: i };
395
+
396
+ // src/agent-detection/inject-all-targets.ts
397
+ async function injectAllTargets(agents, endpoint, sdkVersion, projectRoot) {
398
+ const writtenAgentsMd = /* @__PURE__ */ new Set();
399
+ for (const agent of agents) {
400
+ const targets = computeTargets(agent, projectRoot);
401
+ for (const target of targets) {
402
+ if (target.isAgentsMdCompanion) {
403
+ if (writtenAgentsMd.has(target.path)) {
404
+ continue;
405
+ }
406
+ writtenAgentsMd.add(target.path);
407
+ }
408
+ let createContent;
409
+ let managedSectionOnly;
410
+ if (target.kind === "cursor-mdc") {
411
+ createContent = generateInfoSectionForCursorMdc(endpoint, sdkVersion);
412
+ managedSectionOnly = generateInfoSection(agent, endpoint, sdkVersion);
413
+ } else if (target.kind === "cursorrules-legacy") {
414
+ createContent = generateInfoSectionForCursorrulesLegacy(
415
+ endpoint,
416
+ sdkVersion
417
+ );
418
+ managedSectionOnly = createContent;
419
+ } else {
420
+ createContent = generateInfoSection(agent, endpoint, sdkVersion);
421
+ managedSectionOnly = createContent;
422
+ }
423
+ if (managedSectionOnly === "") continue;
424
+ await writeManagedSectionToTarget(
425
+ target.path,
426
+ createContent,
427
+ managedSectionOnly
428
+ );
313
429
  }
314
430
  }
315
- return null;
316
431
  }
317
- async function injectInfoSection(agent, content, projectRoot) {
318
- if (agent.infoFilePath === null) {
319
- return;
432
+ function foundDirFromAgent(agent) {
433
+ if (agent.infoFilePath === null) return null;
434
+ switch (agent.name) {
435
+ case "claude":
436
+ case "codex":
437
+ case "gemini":
438
+ case "generic":
439
+ return (0, import_node_path2.dirname)(agent.infoFilePath);
440
+ case "cursor":
441
+ return (0, import_node_path2.dirname)((0, import_node_path2.dirname)((0, import_node_path2.dirname)(agent.infoFilePath)));
442
+ case "windsurf":
443
+ return (0, import_node_path2.dirname)((0, import_node_path2.dirname)((0, import_node_path2.dirname)(agent.infoFilePath)));
320
444
  }
321
- if (content === "") {
322
- return;
445
+ }
446
+ function computeTargets(agent, projectRoot) {
447
+ const targets = [];
448
+ const foundDir = foundDirFromAgent(agent) ?? projectRoot;
449
+ switch (agent.name) {
450
+ case "claude": {
451
+ if (agent.infoFilePath) {
452
+ targets.push({
453
+ path: agent.infoFilePath,
454
+ kind: "primary",
455
+ isAgentsMdCompanion: false
456
+ });
457
+ }
458
+ targets.push({
459
+ path: (0, import_node_path2.join)(foundDir, "AGENTS.md"),
460
+ kind: "agents-md-companion",
461
+ isAgentsMdCompanion: true
462
+ });
463
+ return targets;
464
+ }
465
+ case "codex": {
466
+ if (agent.infoFilePath) {
467
+ targets.push({
468
+ path: agent.infoFilePath,
469
+ kind: "primary",
470
+ isAgentsMdCompanion: true
471
+ });
472
+ }
473
+ return targets;
474
+ }
475
+ case "gemini": {
476
+ if (agent.infoFilePath) {
477
+ targets.push({
478
+ path: agent.infoFilePath,
479
+ kind: "primary",
480
+ isAgentsMdCompanion: false
481
+ });
482
+ }
483
+ targets.push({
484
+ path: (0, import_node_path2.join)(foundDir, "AGENTS.md"),
485
+ kind: "agents-md-companion",
486
+ isAgentsMdCompanion: true
487
+ });
488
+ return targets;
489
+ }
490
+ case "cursor": {
491
+ if (agent.infoFilePath) {
492
+ targets.push({
493
+ path: agent.infoFilePath,
494
+ kind: "cursor-mdc",
495
+ isAgentsMdCompanion: false
496
+ });
497
+ targets.push({
498
+ path: (0, import_node_path2.join)(foundDir, ".cursorrules"),
499
+ kind: "cursorrules-legacy",
500
+ isAgentsMdCompanion: false
501
+ });
502
+ }
503
+ targets.push({
504
+ path: (0, import_node_path2.join)(foundDir, "AGENTS.md"),
505
+ kind: "agents-md-companion",
506
+ isAgentsMdCompanion: true
507
+ });
508
+ return targets;
509
+ }
510
+ case "windsurf": {
511
+ if (agent.infoFilePath) {
512
+ targets.push({
513
+ path: agent.infoFilePath,
514
+ kind: "primary",
515
+ isAgentsMdCompanion: false
516
+ });
517
+ }
518
+ targets.push({
519
+ path: (0, import_node_path2.join)(foundDir, "AGENTS.md"),
520
+ kind: "agents-md-companion",
521
+ isAgentsMdCompanion: true
522
+ });
523
+ return targets;
524
+ }
525
+ case "generic": {
526
+ if (agent.infoFilePath) {
527
+ targets.push({
528
+ path: agent.infoFilePath,
529
+ kind: "primary",
530
+ isAgentsMdCompanion: true
531
+ });
532
+ }
533
+ return targets;
534
+ }
535
+ default: {
536
+ const _exhaustive = agent.name;
537
+ throw new Error(`Unknown agent: ${_exhaustive}`);
538
+ }
323
539
  }
324
- const filePath = agent.infoFilePath;
540
+ }
541
+ async function writeManagedSectionToTarget(filePath, createContent, managedSectionOnly) {
325
542
  let existingContent = null;
326
543
  try {
327
- existingContent = await (0, import_promises2.readFile)(filePath, "utf-8");
544
+ existingContent = await (0, import_promises3.readFile)(filePath, "utf-8");
328
545
  } catch (err) {
329
546
  const code = err.code;
330
547
  if (code !== "ENOENT") {
331
- if (isPermissionError(err)) {
332
- process.stderr.write(
333
- `Warning: cannot read info file ${filePath}: permission denied
334
- `
335
- );
336
- return;
337
- }
338
- throw err;
548
+ emitTargetWarning(filePath, "read", err);
549
+ return;
339
550
  }
340
551
  }
341
552
  if (existingContent === null) {
342
553
  try {
343
- await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(filePath), { recursive: true });
344
- await (0, import_promises2.writeFile)(filePath, content, "utf-8");
554
+ await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(filePath), { recursive: true });
555
+ await (0, import_promises3.writeFile)(filePath, createContent, "utf-8");
345
556
  } catch (err) {
346
- if (isPermissionError(err)) {
347
- process.stderr.write(
348
- `Warning: cannot write info file ${filePath}: permission denied
349
- `
350
- );
351
- return;
352
- }
353
- throw err;
557
+ emitTargetWarning(filePath, "write", err);
558
+ return;
354
559
  }
355
560
  return;
356
561
  }
@@ -360,38 +565,89 @@ async function injectInfoSection(agent, content, projectRoot) {
360
565
  if (boundaries !== null) {
361
566
  const before = lines.slice(0, boundaries.startIdx);
362
567
  const after = lines.slice(boundaries.endIdx + 1);
363
- const contentWithoutTrailingNewline = content.endsWith("\n") ? content.slice(0, -1) : content;
364
- newContent = [...before, contentWithoutTrailingNewline, ...after].join("\n");
568
+ const contentWithoutTrailingNewline = managedSectionOnly.endsWith("\n") ? managedSectionOnly.slice(0, -1) : managedSectionOnly;
569
+ newContent = [...before, contentWithoutTrailingNewline, ...after].join(
570
+ "\n"
571
+ );
365
572
  } else {
366
573
  const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
367
- newContent = existingContent + separator + content;
574
+ newContent = existingContent + separator + managedSectionOnly;
368
575
  }
369
576
  try {
370
- await (0, import_promises2.writeFile)(filePath, newContent, "utf-8");
577
+ await (0, import_promises3.writeFile)(filePath, newContent, "utf-8");
371
578
  } catch (err) {
372
- if (isPermissionError(err)) {
373
- process.stderr.write(
374
- `Warning: cannot write info file ${filePath}: permission denied
375
- `
376
- );
377
- return;
378
- }
379
- throw err;
579
+ emitTargetWarning(filePath, "write", err);
380
580
  }
381
581
  }
382
- async function hasManagedSection(filePath) {
383
- let content;
582
+ function emitTargetWarning(filePath, op, err) {
583
+ const code = err.code;
584
+ let qualifier;
585
+ switch (code) {
586
+ case "EACCES":
587
+ case "EPERM":
588
+ qualifier = "permission denied";
589
+ break;
590
+ case "EROFS":
591
+ qualifier = "filesystem read-only";
592
+ break;
593
+ case "ENOSPC":
594
+ qualifier = "disk full";
595
+ break;
596
+ case "ENAMETOOLONG":
597
+ qualifier = "path too long";
598
+ break;
599
+ case "ENOTDIR":
600
+ qualifier = "not a directory";
601
+ break;
602
+ case "EISDIR":
603
+ qualifier = "is a directory";
604
+ break;
605
+ default:
606
+ qualifier = "I/O error";
607
+ break;
608
+ }
384
609
  try {
385
- content = await (0, import_promises2.readFile)(filePath, "utf-8");
386
- } catch (err) {
387
- const code = err.code;
388
- if (code === "ENOENT") return false;
389
- throw err;
610
+ process.stderr.write(
611
+ `Warning: cannot ${op} info file ${filePath}: ${qualifier}
612
+ `
613
+ );
614
+ } catch {
390
615
  }
391
- return findMarkerBoundaries(content.split("\n")) !== null;
392
616
  }
393
617
 
394
618
  // src/cli/upgrade-instructions.ts
619
+ function legacyDestinationsForAgent(agent) {
620
+ if (agent.infoFilePath === null) {
621
+ return [];
622
+ }
623
+ switch (agent.name) {
624
+ case "codex":
625
+ return [(0, import_node_path3.join)((0, import_node_path3.dirname)(agent.infoFilePath), "codex.md")];
626
+ case "cursor":
627
+ return [
628
+ (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(agent.infoFilePath))), ".cursorrules")
629
+ ];
630
+ case "windsurf":
631
+ return [
632
+ (0, import_node_path3.join)(
633
+ (0, import_node_path3.dirname)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(agent.infoFilePath))),
634
+ ".windsurfrules"
635
+ )
636
+ ];
637
+ case "claude":
638
+ case "gemini":
639
+ case "generic":
640
+ return [];
641
+ }
642
+ }
643
+ async function anyHasManagedSection(paths) {
644
+ for (const p of paths) {
645
+ if (await hasManagedSection(p)) {
646
+ return true;
647
+ }
648
+ }
649
+ return false;
650
+ }
395
651
  function formatPathForOutput(filePath, projectRoot) {
396
652
  const rel = (0, import_node_path3.relative)(projectRoot, filePath);
397
653
  if (rel === "" || rel.startsWith("..") || (0, import_node_path3.isAbsolute)(rel)) {
@@ -413,7 +669,8 @@ async function runUpgradeInstructions(options) {
413
669
  );
414
670
  return { exitCode: 1, refreshed, skipped, warnings, errors };
415
671
  }
416
- const sdkVersion = true ? "1.10.1" : "0.0.0-dev";
672
+ const sdkVersion = true ? "1.11.0" : "0.0.0-dev";
673
+ const optedInAgents = [];
417
674
  for (const agent of agents) {
418
675
  if (agent.infoFilePath === null) {
419
676
  continue;
@@ -422,34 +679,54 @@ async function runUpgradeInstructions(options) {
422
679
  agent.infoFilePath,
423
680
  options.projectRoot
424
681
  );
425
- let containsSection;
682
+ const legacyDestinations = legacyDestinationsForAgent(agent);
683
+ let optedIn;
426
684
  try {
427
- containsSection = await hasManagedSection(agent.infoFilePath);
685
+ optedIn = await anyHasManagedSection([
686
+ agent.infoFilePath,
687
+ ...legacyDestinations
688
+ ]);
428
689
  } catch (err) {
429
690
  warnings.push(
430
691
  `Could not inspect ${displayPath}: ${err instanceof Error ? err.message : String(err)}`
431
692
  );
432
693
  continue;
433
694
  }
434
- if (!containsSection) {
695
+ if (!optedIn) {
435
696
  skipped.push(displayPath);
436
697
  continue;
437
698
  }
438
- const content = generateInfoSection(agent, MCP_ENDPOINT, sdkVersion);
439
- if (content === "") {
440
- continue;
441
- }
699
+ optedInAgents.push(agent);
700
+ }
701
+ if (optedInAgents.length > 0) {
442
702
  try {
443
- await injectInfoSection(agent, content, options.projectRoot);
444
- refreshed.push(displayPath);
703
+ await injectAllTargets(
704
+ optedInAgents,
705
+ MCP_ENDPOINT,
706
+ sdkVersion,
707
+ options.projectRoot
708
+ );
709
+ for (const a of optedInAgents) {
710
+ if (a.infoFilePath !== null) {
711
+ refreshed.push(formatPathForOutput(a.infoFilePath, options.projectRoot));
712
+ }
713
+ }
445
714
  } catch (err) {
446
715
  errors.push(
447
- `Failed to refresh ${displayPath}: ${err instanceof Error ? err.message : String(err)}`
716
+ `Failed to refresh agent-instruction files: ${err instanceof Error ? err.message : String(err)}`
448
717
  );
449
718
  }
450
719
  }
720
+ const refreshedSet = new Set(refreshed);
721
+ const dedupedSkipped = skipped.filter((p) => !refreshedSet.has(p));
451
722
  const exitCode = errors.length === 0 ? 0 : 1;
452
- return { exitCode, refreshed, skipped, warnings, errors };
723
+ return {
724
+ exitCode,
725
+ refreshed,
726
+ skipped: dedupedSkipped,
727
+ warnings,
728
+ errors
729
+ };
453
730
  }
454
731
  // Annotate the CommonJS export names for ESM import in node:
455
732
  0 && (module.exports = {