agents-united 0.12.0 → 0.13.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 (76) hide show
  1. package/dist/cli.d.ts +26 -1
  2. package/dist/cli.js +2238 -467
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +3 -3
  5. package/registry/agents/orchestrator-business.md +31 -13
  6. package/registry/agents/orchestrator-design.md +31 -9
  7. package/registry/agents/orchestrator-digital-agency.md +24 -27
  8. package/registry/agents/orchestrator-engineering.md +36 -17
  9. package/registry/agents/orchestrator-marketing.md +31 -10
  10. package/registry/agents/orchestrator-research.md +31 -14
  11. package/registry/agents/orchestrator-security.md +31 -13
  12. package/registry/agents/orchestrator-system-architecture.md +31 -8
  13. package/registry/agents/orchestrator-universal.md +34 -0
  14. package/registry/agents/subagent-accessibility-lead.md +9 -0
  15. package/registry/agents/subagent-ai-model-architect.md +8 -0
  16. package/registry/agents/subagent-android-architect.md +9 -0
  17. package/registry/agents/subagent-appsec-penetration-tester.md +8 -0
  18. package/registry/agents/subagent-backend-architect.md +18 -0
  19. package/registry/agents/subagent-business-panel-experts.md +9 -0
  20. package/registry/agents/subagent-cloud-infrastructure-architect.md +8 -0
  21. package/registry/agents/subagent-cloud-security-architect.md +8 -0
  22. package/registry/agents/subagent-code-reviewer.md +24 -11
  23. package/registry/agents/subagent-compliance-grc-specialist.md +16 -0
  24. package/registry/agents/subagent-cross-platform-specialist.md +9 -0
  25. package/registry/agents/subagent-data-engineer.md +9 -0
  26. package/registry/agents/subagent-database-administrator.md +8 -0
  27. package/registry/agents/subagent-deep-research.md +8 -0
  28. package/registry/agents/subagent-design-ops-lead.md +9 -0
  29. package/registry/agents/subagent-design-researcher.md +9 -0
  30. package/registry/agents/subagent-design-systems-architect.md +9 -0
  31. package/registry/agents/subagent-designer-toolkit-expert.md +8 -0
  32. package/registry/agents/subagent-devops-engineer.md +8 -0
  33. package/registry/agents/subagent-distributed-systems-architect.md +9 -0
  34. package/registry/agents/subagent-e2e-tester.md +8 -0
  35. package/registry/agents/subagent-financial-analyst.md +9 -0
  36. package/registry/agents/subagent-finops-cost-engineer.md +8 -0
  37. package/registry/agents/subagent-frontend-architect.md +20 -7
  38. package/registry/agents/subagent-interaction-designer.md +9 -0
  39. package/registry/agents/subagent-ios-architect.md +9 -0
  40. package/registry/agents/subagent-legal-contract-analyst.md +9 -0
  41. package/registry/agents/subagent-lifecycle-email-specialist.md +9 -0
  42. package/registry/agents/subagent-literature-patent-analyst.md +9 -0
  43. package/registry/agents/subagent-market-intelligence-analyst.md +9 -0
  44. package/registry/agents/subagent-marketing-campaign-specialist.md +16 -0
  45. package/registry/agents/subagent-marketing-content-strategist.md +16 -0
  46. package/registry/agents/subagent-marketing-conversion-specialist.md +16 -0
  47. package/registry/agents/subagent-marketing-creative-designer.md +16 -0
  48. package/registry/agents/subagent-marketing-growth-strategist.md +16 -0
  49. package/registry/agents/subagent-ml-platform-engineer.md +8 -0
  50. package/registry/agents/subagent-operations-strategist.md +9 -0
  51. package/registry/agents/subagent-paid-acquisition-specialist.md +9 -0
  52. package/registry/agents/subagent-plg-strategist.md +9 -0
  53. package/registry/agents/subagent-prototype-tester.md +8 -0
  54. package/registry/agents/subagent-qa-automation-lead.md +15 -0
  55. package/registry/agents/subagent-repo-index.md +18 -1
  56. package/registry/agents/subagent-security-engineer.md +9 -0
  57. package/registry/agents/subagent-seo-specialist.md +15 -0
  58. package/registry/agents/subagent-socratic-mentor.md +9 -0
  59. package/registry/agents/subagent-statistical-analyst.md +8 -0
  60. package/registry/agents/subagent-sysops-sre-lead.md +8 -0
  61. package/registry/agents/subagent-system-architect.md +9 -0
  62. package/registry/agents/subagent-ui-designer.md +9 -0
  63. package/registry/agents/subagent-ux-strategist.md +9 -0
  64. package/registry/bundles.json +290 -32
  65. package/registry/core/orchestrator-engineering.core.md +33 -0
  66. package/registry/core/subagent-backend-architect.core.md +73 -0
  67. package/registry/core/subagent-code-reviewer.core.md +83 -0
  68. package/registry/core/subagent-frontend-architect.core.md +55 -0
  69. package/registry/core/subagent-repo-index.core.md +73 -0
  70. package/registry/profiles/claude@2.1.271.json +48 -0
  71. package/registry/realizations/claude/orchestrator-engineering.json +14 -0
  72. package/registry/realizations/claude/subagent-backend-architect.json +16 -0
  73. package/registry/realizations/claude/subagent-code-reviewer.json +15 -0
  74. package/registry/realizations/claude/subagent-frontend-architect.json +15 -0
  75. package/registry/realizations/claude/subagent-repo-index.json +15 -0
  76. package/registry/translation-ledger.json +26 -0
package/dist/cli.js CHANGED
@@ -3,9 +3,9 @@
3
3
  // src/cli.ts
4
4
  import { cac } from "cac";
5
5
  import pc from "picocolors";
6
- import { intro, outro, spinner, note, select, multiselect, confirm, text } from "@clack/prompts";
7
- import fs12 from "fs-extra";
8
- import path14 from "path";
6
+ import { intro, outro, spinner, note, log, select, multiselect, confirm, text } from "@clack/prompts";
7
+ import fs15 from "fs-extra";
8
+ import path17 from "path";
9
9
 
10
10
  // src/core/registry.ts
11
11
  import path from "path";
@@ -309,6 +309,19 @@ var RegistryResolver = class _RegistryResolver {
309
309
  `Registry validation error: bundle "${name}" has unknown planningLoop.mode "${mode}". Valid modes: ${validModes.join(", ")}`
310
310
  );
311
311
  }
312
+ if (pl.budget) {
313
+ if (bundle.tier !== "organization") {
314
+ throw new Error(
315
+ `Registry validation error: bundle "${name}" declares a Consultation Budget but is not an organization-tier bundle. Iteration caps (planningLoop.budget.maxIterations) are Tier-2-only.`
316
+ );
317
+ }
318
+ const cap = pl.budget.maxIterations;
319
+ if (!Number.isInteger(cap) || cap < 1) {
320
+ throw new Error(
321
+ `Registry validation error: bundle "${name}" has planningLoop.budget.maxIterations ${String(cap)}; expected a positive integer.`
322
+ );
323
+ }
324
+ }
312
325
  if (mode === "planner-orchestrator") {
313
326
  if (pl.budget) {
314
327
  throw new Error(
@@ -326,9 +339,9 @@ var RegistryResolver = class _RegistryResolver {
326
339
  };
327
340
 
328
341
  // src/core/installer.ts
329
- import path5 from "path";
342
+ import path6 from "path";
330
343
  import crypto from "crypto";
331
- import fs3 from "fs-extra";
344
+ import fs4 from "fs-extra";
332
345
 
333
346
  // src/core/adapter.ts
334
347
  import path3 from "path";
@@ -345,7 +358,8 @@ var HOST_REGISTRY = {
345
358
  agentsSubdir: "agents",
346
359
  detectionMarkers: [".agents"],
347
360
  profile: "antigravity",
348
- projectionCapable: false
361
+ projectionCapable: false,
362
+ status: "supported"
349
363
  },
350
364
  gemini: {
351
365
  id: "gemini",
@@ -355,7 +369,8 @@ var HOST_REGISTRY = {
355
369
  agentsSubdir: "agents",
356
370
  detectionMarkers: [".gemini"],
357
371
  profile: "antigravity",
358
- projectionCapable: false
372
+ projectionCapable: false,
373
+ status: "supported"
359
374
  },
360
375
  claude: {
361
376
  id: "claude",
@@ -365,7 +380,8 @@ var HOST_REGISTRY = {
365
380
  agentsSubdir: "agents",
366
381
  detectionMarkers: [".claude"],
367
382
  profile: "claude-code",
368
- projectionCapable: true
383
+ projectionCapable: true,
384
+ status: "supported"
369
385
  },
370
386
  cursor: {
371
387
  id: "cursor",
@@ -375,7 +391,8 @@ var HOST_REGISTRY = {
375
391
  agentsSubdir: "agents",
376
392
  detectionMarkers: [".cursor"],
377
393
  profile: "cursor",
378
- projectionCapable: true
394
+ projectionCapable: true,
395
+ status: "under-development"
379
396
  },
380
397
  cline: {
381
398
  id: "cline",
@@ -385,7 +402,8 @@ var HOST_REGISTRY = {
385
402
  agentsSubdir: "agents",
386
403
  detectionMarkers: [".cline", ".clinerules"],
387
404
  profile: "cline",
388
- projectionCapable: true
405
+ projectionCapable: true,
406
+ status: "supported"
389
407
  },
390
408
  opencode: {
391
409
  id: "opencode",
@@ -395,7 +413,8 @@ var HOST_REGISTRY = {
395
413
  agentsSubdir: "agent",
396
414
  detectionMarkers: [".opencode", "opencode.json"],
397
415
  profile: "opencode",
398
- projectionCapable: true
416
+ projectionCapable: true,
417
+ status: "under-development"
399
418
  },
400
419
  codex: {
401
420
  id: "codex",
@@ -405,13 +424,29 @@ var HOST_REGISTRY = {
405
424
  agentsSubdir: void 0,
406
425
  detectionMarkers: ["AGENTS.md", ".codex"],
407
426
  profile: "agentsmd",
408
- projectionCapable: true
427
+ projectionCapable: true,
428
+ status: "under-development"
409
429
  }
410
430
  };
411
431
  var KNOWN_HOST_IDS = Object.keys(HOST_REGISTRY);
412
432
  function isKnownHost(id) {
413
433
  return Object.prototype.hasOwnProperty.call(HOST_REGISTRY, id);
414
434
  }
435
+ var SUPPORTED_HOST_IDS = Object.values(HOST_REGISTRY).filter((h) => h.status === "supported").map((h) => h.id);
436
+ var UNDER_DEVELOPMENT_HOST_IDS = Object.values(HOST_REGISTRY).filter((h) => h.status === "under-development").map((h) => h.id);
437
+ var PLANNED_HOSTS = [
438
+ { id: "kimi", label: "Kimi / Moonshot" }
439
+ ];
440
+ function hostAvailabilityNotice() {
441
+ const supported = SUPPORTED_HOST_IDS.map((id) => HOST_REGISTRY[id].label).join(" \xB7 ");
442
+ const underDev = UNDER_DEVELOPMENT_HOST_IDS.map((id) => HOST_REGISTRY[id].label).join(" \xB7 ");
443
+ const planned = PLANNED_HOSTS.map((h) => h.label).join(" \xB7 ");
444
+ return [
445
+ `\u2705 Supported: ${supported}`,
446
+ `\u{1F6A7} Under Development (unavailable): ${underDev}`,
447
+ `\u{1F5D3} Planned: ${planned} (projector on the planning list)`
448
+ ].join("\n");
449
+ }
415
450
  function resolveHostProjectDir(host, cwd) {
416
451
  return path2.resolve(cwd, HOST_REGISTRY[host].projectDir);
417
452
  }
@@ -682,7 +717,8 @@ ${bodyStr}
682
717
  const coordinatorCanonical = `agents/${coordinatorFile}`;
683
718
  const specialistLines = (bundle.agents || []).map((agentFile) => {
684
719
  const name = agentFile.replace(/\.md$/, "");
685
- return `- **${name}**: \`.agents/agents/${agentFile}\``;
720
+ const projected = this.stripSubagentPrefix(name);
721
+ return `- **${name}**: \`.cline/agents/${projected}.yml\` | \`.agents/agents/${agentFile}\``;
686
722
  });
687
723
  const rawWorkflows = [
688
724
  ...(bundle.workflows || []).map((w) => w.replace(/\.md$/i, "")),
@@ -1065,21 +1101,779 @@ ${normalizedBody}`;
1065
1101
  }
1066
1102
  };
1067
1103
 
1104
+ // src/core/claude-projector.ts
1105
+ import yaml3 from "yaml";
1106
+
1107
+ // src/core/guard.ts
1108
+ var GUARD_SCRIPT = String.raw`let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{let i={};try{i=JSON.parse(s)}catch(e){}const t=i.tool_input||{},c=String(t.command||""),f=String(t.file_path||"").replace(/\\/g,"/");let r="";if(/\bgit\b[^;&|]*\bpush\b[^;&|]*(--force(?!-with-lease)\b|(^|\s)-f\b)/.test(c))r="git push --force";else if(/\bvercel\b[^;&|]*--prod\b/.test(c))r="vercel --prod";else if(/(^|\/)\.env(\.(?!example$)[^\/]+)?$/.test(f)||/>\s*(\S*\/)?\.env(\.(?!example\b)\S+)?(\s|$)/.test(c))r="a .env write";if(r){process.stderr.write("Blocked by agents-united guard: "+r+" requires explicit human approval outside the agent session.\n");process.exit(2)}})`;
1109
+ function guardHandler() {
1110
+ return { type: "command", command: "node", args: ["-e", GUARD_SCRIPT] };
1111
+ }
1112
+ function managedGuardHooks() {
1113
+ return {
1114
+ PreToolUse: [
1115
+ { matcher: "Bash", hooks: [guardHandler()] },
1116
+ { matcher: "Write|Edit|NotebookEdit", hooks: [guardHandler()] }
1117
+ ]
1118
+ };
1119
+ }
1120
+
1121
+ // src/core/claude-projector.ts
1122
+ import path5 from "path";
1123
+ import fs3 from "fs-extra";
1124
+
1125
+ // src/core/residue-patterns.ts
1126
+ var RESIDUE_PATTERNS_BY_HOST = {
1127
+ claude: [/Nested Subagent Delegation/, /Host Routing/, /language_server/, /Cline & CLI/]
1128
+ };
1129
+
1130
+ // src/core/overlays.ts
1131
+ var OVERLAY_FIELD_VOCAB = {
1132
+ permissionMode: ["default", "acceptEdits", "bypassPermissions", "plan", "delegate"],
1133
+ model: ["opus", "sonnet", "haiku", "inherit"],
1134
+ effort: ["low", "medium", "high", "inherit"]
1135
+ };
1136
+ var CLAUDE_FIELD_POLICY = {
1137
+ name: "keep",
1138
+ description: "keep",
1139
+ model: "map",
1140
+ tools: "map",
1141
+ permissionMode: "map",
1142
+ effort: "map",
1143
+ skills: "map",
1144
+ projections: "map",
1145
+ version: "drop",
1146
+ type: "drop",
1147
+ commandExecutionPolicy: "drop",
1148
+ mainAgent: "drop",
1149
+ subagent: "drop",
1150
+ hooks: "drop",
1151
+ mcpServers: "drop",
1152
+ rules: "drop",
1153
+ inheritCustomizations: "drop"
1154
+ };
1155
+ function validateProjectionOverlays(projections, spec) {
1156
+ if (projections === void 0 || projections === null) return;
1157
+ if (typeof projections !== "object" || Array.isArray(projections)) {
1158
+ throw new Error("Projection overlay validation failed: projections must be an object.");
1159
+ }
1160
+ for (const [host, overlay] of Object.entries(projections)) {
1161
+ if (host !== spec.id) {
1162
+ throw new Error(`Projection overlay validation failed: unknown overlay host "${host}".`);
1163
+ }
1164
+ if (overlay === null || typeof overlay !== "object" || Array.isArray(overlay)) {
1165
+ throw new Error(`Projection overlay validation failed: overlay for "${host}" must be an object.`);
1166
+ }
1167
+ for (const [field, value] of Object.entries(overlay)) {
1168
+ const policy = spec.fields[field];
1169
+ if (policy === void 0) {
1170
+ throw new Error(`Projection overlay validation failed: unknown overlay field "${field}".`);
1171
+ }
1172
+ if (policy === "drop") {
1173
+ throw new Error(
1174
+ `Projection overlay validation failed: "${field}" is unsupported on host "${host}" and must never be introduced by an overlay.`
1175
+ );
1176
+ }
1177
+ const vocab = OVERLAY_FIELD_VOCAB[field];
1178
+ if (vocab && (typeof value !== "string" || !vocab.includes(value))) {
1179
+ throw new Error(
1180
+ `Projection overlay validation failed: invalid value "${String(value)}" for "${field}" (allowed: ${vocab.join(", ")}).`
1181
+ );
1182
+ }
1183
+ }
1184
+ }
1185
+ }
1186
+
1187
+ // src/core/claude-projector.ts
1188
+ var INLINE_KEYS = /* @__PURE__ */ new Set([
1189
+ "name",
1190
+ "description",
1191
+ "model",
1192
+ "permissionMode",
1193
+ "effort",
1194
+ "tools",
1195
+ "maxIterations",
1196
+ "projections"
1197
+ ]);
1198
+ var FEATURE_LEDGER = {
1199
+ version: { disposition: "mapped", rationale: "Authoring metadata for the canonical store; no Claude field needed." },
1200
+ type: { disposition: "mapped", rationale: "Canonical role discriminator; Claude distinguishes roles by file location." },
1201
+ mainAgent: { disposition: "mapped", rationale: "Canonical masthead flag; Claude decides main-thread status at launch (--agent)." },
1202
+ subagent: { disposition: "mapped", rationale: "Canonical masthead flag; every file under .claude/agents/ is a subagent." },
1203
+ inheritCustomizations: { disposition: "mapped", rationale: "Claude loads CLAUDE.md and project memory unless omitClaudeMd is set (flag-gated)." },
1204
+ commandExecutionPolicy: { disposition: "unsupported", rationale: "Antigravity shell policy has no Claude frontmatter equivalent; Bash permissions cover it." },
1205
+ hooks: { disposition: "unsupported", rationale: "Antigravity hook schema (PreInvocation/PostInvocation matchers) differs from Claude hook events; canonical prose hooks are not translated and stay advisory. The one enforced hook is the managed PreToolUse guard every role carries (Plan 022 H5)." },
1206
+ rules: { disposition: "mapped", rationale: "Agent-referenced rules project into the lean .claude/rules/ lane." },
1207
+ effort: { disposition: "mapped", rationale: "Reasoning effort passes through to the Claude effort field." },
1208
+ invoke_subagent: { disposition: "mapped", rationale: "Delegation maps to the Claude Agent tool (allowlist on coordinators)." },
1209
+ send_message: { disposition: "approximated", rationale: "Maps to the Claude SendMessage tool; cross-session reach differs from Antigravity messaging." },
1210
+ manage_task: { disposition: "approximated", rationale: 'Maps to the Claude task tools (TaskCreate/TaskUpdate), but the live tools reference says they are "provided by default only on the models listed under Task tool availability, and on other models when you opt in" \u2014 so the grant can resolve to nothing depending on the model.' },
1211
+ schedule: { disposition: "approximated", rationale: "Maps to Cron tools, but Antigravity reactive liveness timers are event-driven rather than cron-scheduled." },
1212
+ define_subagent: { disposition: "approximated", rationale: "Claude agents are pre-defined: runtime subagent definition is unavailable, so the prompt delegates via the Agent tool." },
1213
+ manage_subagents: { disposition: "approximated", rationale: "Claude manages subagent lifecycle itself; the roster is static and the prompt delegates via the Agent tool." },
1214
+ ask_question: { disposition: "approximated", rationale: "Claude exposes AskUserQuestion only in the main conversation (excluded from subagents by default)." },
1215
+ generate_image: { disposition: "unsupported", rationale: "Claude Code exposes no image-generation tool; asset creation must happen outside the agent run." },
1216
+ skills: { disposition: "degraded", rationale: "Claude subagents discover project skills and can invoke them; the canonical preload list is not injected to avoid paying its token cost on every spawn." },
1217
+ mcpServers: { disposition: "degraded", rationale: "Claude supports per-subagent mcpServers, but the canonical descriptor dialect differs; MCP wiring stays host-configured via the MCP location registry (translation deferred)." }
1218
+ };
1219
+ var MARKER_PROFILE = "claude";
1220
+ var MARKER_PREFIX = "managed-by: agents-united";
1221
+ var HOST = "claude";
1222
+ var ENTRYPOINT_RULES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md", "GEMINI.md", "AGENTS.md", "CURSOR.md"]);
1223
+ var COORDINATOR_BASELINE_TOOLS = ["Read", "Write", "Edit", "Bash", "Grep", "Glob"];
1224
+ var READ_ONLY_EXCLUDED_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "NotebookEdit", "Bash"]);
1225
+ var FRONTMATTER_REGEX2 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
1226
+ var RUNTIME_NOTE = `## Claude runtime note
1227
+
1228
+ Delegation runs through the Agent tool: the coordinator spawns the specialists named in its own tools
1229
+ allowlist; specialists hold no Agent tool and never spawn peers (the coordinator relays and wakes them). Canonical tool names in this prompt were rewritten to their
1230
+ Claude equivalents; a fenced code block may still show the original spelling because code is preserved
1231
+ byte-for-byte. A subagent does not hand results to a peer: its final report is returned to the
1232
+ conversation that spawned it, and on Claude Code v2.1.271+ in auto mode the runtime delivers it through the
1233
+ SubagentHandback tool.
1234
+
1235
+ Enforced guard: a PreToolUse hook in this file's frontmatter blocks \`git push --force\`, \`.env\` writes and
1236
+ \`vercel --prod\` (exit 2, with the reason); ask the user to run those steps themselves.
1237
+ All other lifecycle hooks described in this prompt are advisory: this host does not fire them.`;
1238
+ var MANAGED_HOOKS = managedGuardHooks();
1239
+ var ClaudeProjector = class _ClaudeProjector {
1240
+ /** Pure data (Plan 017 lifts this object into a shared HostDialectSpec). */
1241
+ static CLAUDE_DIALECT = {
1242
+ id: "claude",
1243
+ nameRegex: /^[a-z0-9]+(-[a-z0-9]+)*$/,
1244
+ toolVocabulary: {
1245
+ view_file: "Read",
1246
+ read_file: "Read",
1247
+ replace_file_content: "Edit",
1248
+ apply_diff: "Edit",
1249
+ multi_replace_file_content: "Edit",
1250
+ write_to_file: "Write",
1251
+ run_command: "Bash",
1252
+ grep_search: "Grep",
1253
+ search_file_content: "Grep",
1254
+ list_dir: "Glob",
1255
+ find_by_name: "Glob",
1256
+ web_search: "WebSearch",
1257
+ search_web: "WebSearch",
1258
+ read_url_content: "WebFetch",
1259
+ invoke_subagent: "Agent",
1260
+ define_subagent: "Agent",
1261
+ manage_subagents: "Agent",
1262
+ send_message: "SendMessage",
1263
+ manage_task: "TaskCreate",
1264
+ schedule: "CronCreate",
1265
+ ask_question: "AskUserQuestion",
1266
+ // The runtime's own hand-off channel for subagents (Claude Code v2.1.271+, auto mode): it delivers
1267
+ // a subagent's final report to whichever conversation receives that subagent's result. It has no
1268
+ // Antigravity-dialect equivalent, so the lane grants it to specialists directly (see renderRole);
1269
+ // the token exists so a canonical agent could also name it explicitly.
1270
+ subagent_handback: "SubagentHandback"
1271
+ },
1272
+ bodyToolVocabulary: {
1273
+ view_file: "Read",
1274
+ read_file: "Read",
1275
+ write_to_file: "Write",
1276
+ replace_file_content: "Edit",
1277
+ apply_diff: "Edit",
1278
+ multi_replace_file_content: "Edit",
1279
+ run_command: "Bash",
1280
+ grep_search: "Grep",
1281
+ find_by_name: "Glob",
1282
+ list_dir: "Glob",
1283
+ search_web: "WebSearch",
1284
+ read_url_content: "WebFetch",
1285
+ invoke_subagent: "Agent",
1286
+ define_subagent: "the Agent tool",
1287
+ manage_subagents: "the Agent tool",
1288
+ // ADR 0018 decision 8 requires canonical tool names in prose to be rewritten, not just the
1289
+ // frontmatter vocabulary: a specialist reading "reach a peer with `send_message`" would otherwise
1290
+ // look for a tool that does not exist on this host. Whole-word and code-fence-aware like the rest.
1291
+ send_message: "SendMessage",
1292
+ manage_task: "TaskCreate",
1293
+ ask_question: "AskUserQuestion",
1294
+ generate_image: "image generation (unsupported on this host)"
1295
+ // `schedule` is deliberately absent: it is an ordinary English word, so rewriting it in prose
1296
+ // would corrupt sentences. Its loss is recorded once at frontmatter level instead.
1297
+ },
1298
+ commandVocabulary: {
1299
+ team_command: "Agent Teams (opt-in: agents start --host claude --teams)",
1300
+ deep_planning_command: "/workflow-grill",
1301
+ interview_command: "/grill-me"
1302
+ },
1303
+ permissionModeMap: {
1304
+ acceptEdits: "acceptEdits",
1305
+ readOnly: "plan",
1306
+ requestReview: "default",
1307
+ strict: "default"
1308
+ },
1309
+ modelMap: { pro: "sonnet", flash: "haiku" },
1310
+ // ADR 0018 decision 7, amended 2026-09-22 by the product owner. The canonical catalog declares
1311
+ // `model: inherit` on all 59 agents, which made `inherit` the de-facto value and left every
1312
+ // projection inheriting the *session* model instead of a chosen posture. The intended posture is
1313
+ // explicit — coordinators reason on Opus at high effort, specialists run on Sonnet at medium
1314
+ // effort — so `inherit` now resolves to a role default rather than being omitted. Both fields are
1315
+ // documented subagent frontmatter: `model` accepts sonnet|opus|haiku|fable|<full id>|inherit, and
1316
+ // `effort` (low|medium|high|xhigh|max) overrides the session effort level. Caveats recorded rather
1317
+ // than coded around: Opus is unavailable on some plans, so a workspace may need a different anchor;
1318
+ // and `xhigh`/`max` exist only on newer models. Both are one-constant changes here.
1319
+ roleModelDefaults: { coordinator: "opus", specialist: "sonnet" },
1320
+ roleEffortDefaults: { coordinator: "high", specialist: "medium" },
1321
+ bodySectionOverrides: [
1322
+ {
1323
+ // Plan 019 escape hatch (2026-09-25): the old anchor — "Subagent Delegation & Host
1324
+ // Routing" — was residue (it described OTHER runtimes: Antigravity's language_server.exe
1325
+ // limitation and Cline's subagent_* tools) and the G1 purge deleted it. Its Claude-side
1326
+ // rendering survives as this per-host overlay, keyed on the neutral "Delegation
1327
+ // Mechanics" stub in canonical; hosts without an overlay render the stub itself.
1328
+ heading: /^#{2,3}\s*.*Delegation Mechanics/m,
1329
+ replacement: [
1330
+ "### \u26A1 Subagent Delegation & Agent Routing (Claude Code)",
1331
+ "",
1332
+ "- **Delegation tool**: spawn specialists with the `Agent` tool. Your `tools` allowlist names exactly the specialist types you may call (the projected definitions in `.claude/agents/`, with the `subagent-` prefix stripped); calling any other type fails, so send work only to those names. There are no `TypeName`/`Role`/`Prompt` fields \u2014 `Agent` takes the specialist type and one self-contained prompt, so put the role, the scope boundaries and the acceptance criteria inside that prompt.",
1333
+ "- **Parallel work**: spawn independent specialists in one turn \u2014 each runs in its own context window and returns its result to you (delivered by `SubagentHandback` on Claude Code v2.1.271+ in auto mode). You are the single synthesis and relay point: name who reported what, and never let one specialist wait on another.",
1334
+ "- **Your allowlist is the whole domain team, not only what is installed.** A listed type whose definition is not in this workspace yet will fail to spawn: say so, recommend installing it (`agents add <addon>`), and handle that slice yourself if the user declines. Never delegate to a type outside your allowlist.",
1335
+ "- **Agent Teams (opt-in, `--teams`)**: the lead can spawn *teammates* instead of subagents and message them directly with `SendMessage`. The scaffold is experimental \u2014 one team per session, a fixed lead, no nested teams \u2014 so never make it load-bearing."
1336
+ ].join("\n")
1337
+ }
1338
+ ],
1339
+ budgets: { skillDescriptionChars: 1536, agentDescriptionTokens: 15e3 },
1340
+ maxRuleLines: 200
1341
+ };
1342
+ /** `subagent-backend-architect` -> `backend-architect` (Step 0 proved zero collisions). */
1343
+ static stripSubagentPrefix(name) {
1344
+ return name.trim().replace(/^subagent-/, "");
1345
+ }
1346
+ /** Uniform managed-projection marker; always the first line of the projected body. */
1347
+ static marker(canonicalRelPath) {
1348
+ const norm = canonicalRelPath.replace(/\\/g, "/");
1349
+ return `<!-- managed-by: agents-united | profile: ${MARKER_PROFILE} | canonical: ${norm} | do not edit -->`;
1350
+ }
1351
+ /** `generative_ui` -> `generative-ui`: every dialect requires lowercase-hyphen names. */
1352
+ static normalizeSkillName(name) {
1353
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1354
+ }
1355
+ static parse(canonicalContent, canonicalRelPath) {
1356
+ const match = canonicalContent.match(FRONTMATTER_REGEX2);
1357
+ if (!match) {
1358
+ throw new Error(`Cannot project ${canonicalRelPath}: no YAML frontmatter found.`);
1359
+ }
1360
+ let parsed;
1361
+ try {
1362
+ parsed = yaml3.parse(match[1]);
1363
+ } catch (cause) {
1364
+ const err = cause instanceof Error ? cause : new Error(String(cause));
1365
+ throw new Error(`Cannot project ${canonicalRelPath}: invalid YAML frontmatter (${err.message}).`);
1366
+ }
1367
+ const meta = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1368
+ return { meta, body: match[2].replace(/^\r?\n/, "") };
1369
+ }
1370
+ static ledgerEntry(feature) {
1371
+ const known = FEATURE_LEDGER[feature];
1372
+ if (!known) return void 0;
1373
+ return { feature, host: HOST, disposition: known.disposition, rationale: known.rationale };
1374
+ }
1375
+ /** Guard: an unmapped feature must never be dropped without a recorded disposition. */
1376
+ static requireDisposition(feature, canonicalRelPath) {
1377
+ const entry = _ClaudeProjector.ledgerEntry(feature);
1378
+ if (!entry) {
1379
+ throw new Error(
1380
+ `No translation-ledger disposition recorded for feature "${feature}" in ${canonicalRelPath}. Add it to FEATURE_LEDGER so translation loss is never silent (ADR 0018 decision 9).`
1381
+ );
1382
+ }
1383
+ return entry;
1384
+ }
1385
+ /**
1386
+ * Replace one canonical body section, matched by its heading, with a host-native rendering.
1387
+ *
1388
+ * Deterministic: the section runs from the matched heading to the next heading of the same or a higher
1389
+ * level (an `h3` stops at the next `h1`/`h2`), and trailing blank lines and the `---` separator are left
1390
+ * with the following section so the document structure is unchanged. A heading that is absent is a no-op.
1391
+ */
1392
+ static replaceSection(body, heading, replacement) {
1393
+ const lines = body.split("\n");
1394
+ const start = lines.findIndex((line) => heading.test(line));
1395
+ if (start < 0) return body;
1396
+ let end = lines.length;
1397
+ for (let i = start + 1; i < lines.length; i += 1) {
1398
+ if (/^#{1,2}\s/.test(lines[i])) {
1399
+ end = i;
1400
+ break;
1401
+ }
1402
+ }
1403
+ while (end > start + 1 && (lines[end - 1].trim() === "" || lines[end - 1].trim() === "---")) end -= 1;
1404
+ return [...lines.slice(0, start), ...replacement.split("\n"), ...lines.slice(end)].join("\n");
1405
+ }
1406
+ /**
1407
+ * Deterministically rewrite canonical tool names in prompt prose (whole-word, case-sensitive,
1408
+ * fenced code blocks byte-preserved) and report a disposition for every rewrite that loses fidelity.
1409
+ */
1410
+ /** Command tokens are ledger citizens (Plan 020 note 7) — rewritten and dispositioned. */
1411
+ static COMMAND_LEDGER = {
1412
+ team_command: { disposition: "mapped", rationale: "Rewritten to the host team entrypoint (Agent Teams opt-in)." },
1413
+ deep_planning_command: { disposition: "mapped", rationale: "Rewritten to the host deep-planning command." },
1414
+ interview_command: { disposition: "mapped", rationale: "Rewritten to the host interview command." }
1415
+ };
1416
+ /** Plan 019 safety net: residue-headed sections never survive into a projection body. */
1417
+ static dropResidueSections(body) {
1418
+ let out = body;
1419
+ for (const pattern of RESIDUE_PATTERNS_BY_HOST.claude) {
1420
+ out = out.replace(
1421
+ new RegExp(`(^|\\n)#{1,6}[^\\n]*${pattern.source}[^\\n]*\\n[\\s\\S]*?(?=\\n#{1,6} |$)`, "g"),
1422
+ "$1"
1423
+ );
1424
+ }
1425
+ return out;
1426
+ }
1427
+ /** Plan 019 safety net: residual residue phrases in prose are neutralized deterministically. */
1428
+ static scrubResiduePhrases(body) {
1429
+ return body.replace(/Cline & CLI/g, "other hosts").replace(/language_server(\.exe)?/g, "the runtime").replace(/Nested Subagent Delegation/g, "peer delegation").replace(/Host Routing/g, "host delegation");
1430
+ }
1431
+ /** Whole-word canonical tokens surviving OUTSIDE code fences after the rewrite pass. */
1432
+ static survivingBodyTokens(body) {
1433
+ const vocab = {
1434
+ ..._ClaudeProjector.CLAUDE_DIALECT.toolVocabulary,
1435
+ ..._ClaudeProjector.CLAUDE_DIALECT.commandVocabulary
1436
+ };
1437
+ const prose = body.split(/(```[\s\S]*?```)/g).filter((_, index) => index % 2 === 0).join("\n");
1438
+ return Object.keys(vocab).filter(
1439
+ (token) => new RegExp(`(^|[^A-Za-z0-9_])${token}(?=[^A-Za-z0-9_]|$)`).test(prose)
1440
+ );
1441
+ }
1442
+ static rewriteBody(body) {
1443
+ const ledger = /* @__PURE__ */ new Map();
1444
+ body = _ClaudeProjector.dropResidueSections(body);
1445
+ body = _ClaudeProjector.scrubResiduePhrases(body);
1446
+ for (const override of _ClaudeProjector.CLAUDE_DIALECT.bodySectionOverrides) {
1447
+ body = _ClaudeProjector.replaceSection(body, override.heading, override.replacement);
1448
+ }
1449
+ const vocab = {
1450
+ ..._ClaudeProjector.CLAUDE_DIALECT.bodyToolVocabulary,
1451
+ ..._ClaudeProjector.CLAUDE_DIALECT.commandVocabulary
1452
+ };
1453
+ const tokens = Object.keys(vocab).sort((a, b) => b.length - a.length);
1454
+ const parts = body.split(/(```[\s\S]*?```)/g);
1455
+ const rewritten = parts.map((part, index) => {
1456
+ if (index % 2 === 1) return part;
1457
+ let text2 = part;
1458
+ for (const token of tokens) {
1459
+ const re = new RegExp(`(^|[^A-Za-z0-9_])${token}(?=[^A-Za-z0-9_]|$)`, "g");
1460
+ if (!text2.match(re)) continue;
1461
+ const command = _ClaudeProjector.COMMAND_LEDGER[token];
1462
+ const entry = command ? { feature: token, host: HOST, disposition: command.disposition, rationale: command.rationale } : _ClaudeProjector.ledgerEntry(token);
1463
+ if (entry) ledger.set(token, entry);
1464
+ text2 = text2.replace(re, `$1${vocab[token]}`);
1465
+ }
1466
+ return text2;
1467
+ });
1468
+ return { body: rewritten.join(""), ledger: [...ledger.values()] };
1469
+ }
1470
+ /** Render `.claude/agents/<role>.md` from a canonical agent definition. */
1471
+ static renderRole(canonicalContent, canonicalRelPath, opts = {}) {
1472
+ const { meta, body } = _ClaudeProjector.parse(canonicalContent, canonicalRelPath);
1473
+ const ledger = /* @__PURE__ */ new Map();
1474
+ for (const key of Object.keys(meta)) {
1475
+ if (INLINE_KEYS.has(key)) continue;
1476
+ const entry = _ClaudeProjector.requireDisposition(key, canonicalRelPath);
1477
+ ledger.set(entry.feature, entry);
1478
+ }
1479
+ const rawName = typeof meta.name === "string" && meta.name.trim().length > 0 ? meta.name.trim() : path5.basename(canonicalRelPath).replace(/\.md$/i, "");
1480
+ const out = { name: _ClaudeProjector.stripSubagentPrefix(rawName) };
1481
+ if (typeof meta.description === "string" && meta.description.trim().length > 0) {
1482
+ out.description = meta.description.trim();
1483
+ }
1484
+ const isCoordinator = !!(opts.allowlist && opts.allowlist.length > 0) || meta.type === "orchestrator" || meta.mainAgent === true;
1485
+ const tools = [];
1486
+ if (isCoordinator) {
1487
+ tools.push(opts.allowlist && opts.allowlist.length > 0 ? `Agent(${opts.allowlist.join(", ")})` : "Agent");
1488
+ }
1489
+ for (const raw of Array.isArray(meta.tools) ? meta.tools : []) {
1490
+ if (typeof raw !== "string") continue;
1491
+ const entry = _ClaudeProjector.ledgerEntry(raw);
1492
+ if (entry) ledger.set(entry.feature, entry);
1493
+ const mapped = _ClaudeProjector.CLAUDE_DIALECT.toolVocabulary[raw];
1494
+ if (!mapped) {
1495
+ _ClaudeProjector.requireDisposition(raw, canonicalRelPath);
1496
+ continue;
1497
+ }
1498
+ if (!tools.includes(mapped)) tools.push(mapped);
1499
+ }
1500
+ if (opts.allowlist && opts.allowlist.length > 0) {
1501
+ const withoutBareAgent = tools.filter((t) => t !== "Agent");
1502
+ tools.length = 0;
1503
+ tools.push(...withoutBareAgent);
1504
+ }
1505
+ if (!isCoordinator) {
1506
+ const withoutAgent = tools.filter((t) => t !== "Agent" && !t.startsWith("Agent("));
1507
+ tools.length = 0;
1508
+ tools.push(...withoutAgent);
1509
+ }
1510
+ if (isCoordinator) {
1511
+ for (const base of COORDINATOR_BASELINE_TOOLS) {
1512
+ if (!tools.includes(base)) tools.push(base);
1513
+ }
1514
+ } else if (!tools.includes("SubagentHandback")) {
1515
+ tools.push("SubagentHandback");
1516
+ }
1517
+ const permission = typeof meta.permissionMode === "string" ? _ClaudeProjector.CLAUDE_DIALECT.permissionModeMap[meta.permissionMode] ?? "default" : void 0;
1518
+ if (permission === "plan") {
1519
+ const restricted = tools.filter((t) => !READ_ONLY_EXCLUDED_TOOLS.has(t));
1520
+ tools.length = 0;
1521
+ tools.push(...restricted);
1522
+ }
1523
+ out.tools = tools;
1524
+ if (permission) out.permissionMode = permission;
1525
+ const declaredModel = typeof meta.model === "string" ? meta.model : void 0;
1526
+ const mappedModel = declaredModel && declaredModel !== "inherit" ? _ClaudeProjector.CLAUDE_DIALECT.modelMap[declaredModel] ?? declaredModel : void 0;
1527
+ out.model = mappedModel ?? (isCoordinator ? _ClaudeProjector.CLAUDE_DIALECT.roleModelDefaults.coordinator : _ClaudeProjector.CLAUDE_DIALECT.roleModelDefaults.specialist);
1528
+ const declaredEffort = typeof meta.effort === "string" ? meta.effort : void 0;
1529
+ out.effort = declaredEffort ?? (isCoordinator ? _ClaudeProjector.CLAUDE_DIALECT.roleEffortDefaults.coordinator : _ClaudeProjector.CLAUDE_DIALECT.roleEffortDefaults.specialist);
1530
+ if (typeof opts.maxTurns === "number") out.maxTurns = opts.maxTurns;
1531
+ out.hooks = MANAGED_HOOKS;
1532
+ const rewritten = _ClaudeProjector.rewriteBody(body);
1533
+ for (const entry of rewritten.ledger) ledger.set(entry.feature, entry);
1534
+ for (const token of _ClaudeProjector.survivingBodyTokens(rewritten.body)) {
1535
+ _ClaudeProjector.requireDisposition(token, canonicalRelPath);
1536
+ }
1537
+ if (meta.projections !== void 0 && meta.projections !== null) {
1538
+ validateProjectionOverlays(meta.projections, { id: HOST, fields: CLAUDE_FIELD_POLICY });
1539
+ const overlay = meta.projections[HOST];
1540
+ if (overlay) Object.assign(out, overlay);
1541
+ }
1542
+ const yamlStr = yaml3.stringify(out).replace(/\r\n/g, "\n").trimEnd();
1543
+ const content = `---
1544
+ ${yamlStr}
1545
+ ---
1546
+ ${_ClaudeProjector.marker(canonicalRelPath)}
1547
+
1548
+ ${RUNTIME_NOTE}
1549
+
1550
+ ${rewritten.body.trim()}
1551
+ `;
1552
+ return { content, ledger: [...ledger.values()] };
1553
+ }
1554
+ /** Render `.claude/skills/<name>/SKILL.md` from a canonical skill. */
1555
+ static renderSkill(canonicalContent, canonicalRelPath) {
1556
+ const { meta, body } = _ClaudeProjector.parse(canonicalContent, canonicalRelPath);
1557
+ const ledger = /* @__PURE__ */ new Map();
1558
+ const rawName = typeof meta.name === "string" && meta.name.trim().length > 0 ? meta.name.trim() : path5.basename(path5.dirname(canonicalRelPath));
1559
+ const normalized = _ClaudeProjector.normalizeSkillName(rawName);
1560
+ if (normalized !== rawName) {
1561
+ ledger.set(`skill-name:${rawName}`, {
1562
+ feature: `skill-name:${rawName}`,
1563
+ host: HOST,
1564
+ disposition: "mapped",
1565
+ rationale: `Skill name normalized to the dialect rule (${rawName} -> ${normalized}); body references follow the artifact name.`
1566
+ });
1567
+ }
1568
+ const out = { name: normalized };
1569
+ if (typeof meta.description === "string" && meta.description.trim().length > 0) {
1570
+ out.description = meta.description.trim();
1571
+ }
1572
+ if (meta["disable-slash-command"] === true || meta.disableSlashCommand === true) {
1573
+ out["user-invocable"] = false;
1574
+ ledger.set("disable-slash-command", {
1575
+ feature: "disable-slash-command",
1576
+ host: HOST,
1577
+ disposition: "mapped",
1578
+ rationale: "Hidden from the / palette while staying model-invocable (Claude polarity: user-invocable, never disable-model-invocation)."
1579
+ });
1580
+ }
1581
+ const metadata = meta.metadata;
1582
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
1583
+ const allowed = ["author", "version", "icon", "source", "license"];
1584
+ const kept = {};
1585
+ for (const key of allowed) {
1586
+ const value = metadata[key];
1587
+ if (value !== void 0) kept[key] = value;
1588
+ }
1589
+ for (const key of Object.keys(metadata)) {
1590
+ if (allowed.includes(key)) continue;
1591
+ ledger.set(`metadata:${key}`, {
1592
+ feature: `metadata:${key}`,
1593
+ host: HOST,
1594
+ disposition: "mapped",
1595
+ rationale: "Non-standard skill metadata; stripped because the projection carries only the Agent Skills contract."
1596
+ });
1597
+ }
1598
+ if (Object.keys(kept).length > 0) out.metadata = kept;
1599
+ }
1600
+ if (typeof meta.license === "string") out.license = meta.license;
1601
+ const HANDLED_SKILL_KEYS = /* @__PURE__ */ new Set([
1602
+ "name",
1603
+ "description",
1604
+ "metadata",
1605
+ "license",
1606
+ "disable-slash-command",
1607
+ "disableSlashCommand"
1608
+ ]);
1609
+ for (const key of Object.keys(meta)) {
1610
+ if (HANDLED_SKILL_KEYS.has(key)) continue;
1611
+ const entry = _ClaudeProjector.requireDisposition(key, canonicalRelPath);
1612
+ ledger.set(entry.feature, entry);
1613
+ }
1614
+ let rewrittenBody = body;
1615
+ if (normalized !== rawName) {
1616
+ const re = new RegExp(`(^|[^A-Za-z0-9_-])${rawName}(?![A-Za-z0-9_-])`, "gi");
1617
+ rewrittenBody = rewrittenBody.replace(re, `$1${normalized}`);
1618
+ }
1619
+ const rewritten = _ClaudeProjector.rewriteBody(rewrittenBody);
1620
+ for (const entry of rewritten.ledger) ledger.set(entry.feature, entry);
1621
+ const yamlStr = yaml3.stringify(out).replace(/\r\n/g, "\n").trimEnd();
1622
+ const content = `---
1623
+ ${yamlStr}
1624
+ ---
1625
+ ${_ClaudeProjector.marker(canonicalRelPath)}
1626
+
1627
+ ${rewritten.body.trim()}
1628
+ `;
1629
+ return { content, ledger: [...ledger.values()] };
1630
+ }
1631
+ /**
1632
+ * Render `.claude/rules/<rule>.md`. Rules load unconditionally in every Claude session, so this
1633
+ * lane is deliberately lean: optional `paths:` scoping plus a hard adherence budget (ADR 0018).
1634
+ *
1635
+ * The `paths:` block is the ONLY frontmatter field this lane emits, so it is emitted only when a
1636
+ * rule is actually scoped. The live doc is explicit — "Rules without a `paths` field are loaded
1637
+ * unconditionally and apply to all files" — which makes frontmatter optional; an unscoped rule is
1638
+ * therefore rendered marker-first with no frontmatter block at all, matching the Cline lane's shape
1639
+ * instead of emitting a stray empty `--- {} ---`.
1640
+ */
1641
+ static renderRule(canonicalContent, canonicalRelPath, opts = {}) {
1642
+ const lineCount = canonicalContent.split(/\r?\n/).length;
1643
+ if (lineCount > _ClaudeProjector.CLAUDE_DIALECT.maxRuleLines) {
1644
+ throw new Error(
1645
+ `Rule ${canonicalRelPath} has ${lineCount} lines, exceeding the ${_ClaudeProjector.CLAUDE_DIALECT.maxRuleLines}-line adherence budget (ADR 0018 decision 5).`
1646
+ );
1647
+ }
1648
+ const frontmatter = {};
1649
+ if (opts.paths && opts.paths.length > 0) frontmatter.paths = opts.paths;
1650
+ const head = Object.keys(frontmatter).length > 0 ? `---
1651
+ ${yaml3.stringify(frontmatter).replace(/\r\n/g, "\n").trimEnd()}
1652
+ ---
1653
+ ` : "";
1654
+ const rewritten = _ClaudeProjector.rewriteBody(canonicalContent.trim());
1655
+ const content = `${head}${_ClaudeProjector.marker(canonicalRelPath)}
1656
+
1657
+ ${rewritten.body}
1658
+ `;
1659
+ return { content, ledger: rewritten.ledger };
1660
+ }
1661
+ /**
1662
+ * The specialist roster a coordinator may delegate to: the whole **domain** team, not only the slice
1663
+ * this bundle declares or what happens to be installed.
1664
+ *
1665
+ * `software-engineering` declares 4 specialists, but the `engineering` domain has 15 — so an essentials
1666
+ * install used to leave the domain lead able to call 4 of its 15 people, with the list growing only when
1667
+ * the operator happened to install more *and* re-project. `bundles.json` already groups every bundle under
1668
+ * a `domain`, so the roster is the union of that domain's declared agents minus the coordinator itself.
1669
+ * The allowlist is then stable (independent of install state) and complete (it never silently hides a
1670
+ * teammate). The cost is that a listed type may not be installed yet — the delegation section turns that
1671
+ * into an install recommendation rather than a dead end.
1672
+ *
1673
+ * Sorted for determinism: repeated projections must be byte-identical, because `agents doctor` diffs them.
1674
+ */
1675
+ static resolveDomainRoster(bundle, coordinatorFile, registryDir) {
1676
+ const names = /* @__PURE__ */ new Set();
1677
+ try {
1678
+ const raw = fs3.readFileSync(path5.join(registryDir, "bundles.json"), "utf8");
1679
+ const bundles = JSON.parse(raw).bundles ?? {};
1680
+ for (const candidate of Object.values(bundles)) {
1681
+ if (!bundle.domain || candidate.domain !== bundle.domain) continue;
1682
+ for (const agentFile of candidate.agents || []) {
1683
+ if (agentFile === coordinatorFile) continue;
1684
+ names.add(_ClaudeProjector.stripSubagentPrefix(agentFile.replace(/\.md$/i, "")));
1685
+ }
1686
+ }
1687
+ } catch {
1688
+ }
1689
+ return [...names].sort();
1690
+ }
1691
+ /**
1692
+ * Plan the full Claude compound projection for one bundle: roles (with a coordinator allowlist of
1693
+ * its whole domain team), skills (normalized names plus auxiliary files copied byte-for-byte), and
1694
+ * the lean deduplicated rule set. It emits **no** team-manifest duplicate and **no** workflows lane,
1695
+ * because Claude workflows are JavaScript orchestration scripts rather than markdown (ADR 0018 d14).
1696
+ */
1697
+ static async planCompoundProjection(bundle, scope, resolved, registryDir, _excludeAddons) {
1698
+ void scope;
1699
+ const artifacts = [];
1700
+ const coordinatorFile = bundle.orchestrator || `${bundle.name}.md`;
1701
+ const domainRoster = _ClaudeProjector.resolveDomainRoster(bundle, coordinatorFile, registryDir);
1702
+ const specialistNames = domainRoster.length > 0 ? domainRoster : (bundle.agents || []).map((f) => _ClaudeProjector.stripSubagentPrefix(f.replace(/\.md$/i, "")));
1703
+ const maxTurns = bundle.planningLoop?.budget?.maxIterations;
1704
+ for (const agentFile of resolved.agents || []) {
1705
+ const canonicalRel = `agents/${agentFile}`;
1706
+ const src = path5.join(registryDir, "agents", agentFile);
1707
+ if (!await fs3.pathExists(src)) continue;
1708
+ const content = await fs3.readFile(src, "utf8");
1709
+ const isCoordinator = coordinatorFile === agentFile;
1710
+ const roleName = _ClaudeProjector.stripSubagentPrefix(agentFile.replace(/\.md$/i, ""));
1711
+ const rendered = _ClaudeProjector.renderRole(content, canonicalRel, {
1712
+ allowlist: isCoordinator ? specialistNames : void 0,
1713
+ maxTurns: isCoordinator ? maxTurns : void 0
1714
+ });
1715
+ artifacts.push({
1716
+ kind: "role",
1717
+ canonical: canonicalRel,
1718
+ relPath: `.claude/agents/${roleName}.md`,
1719
+ content: rendered.content,
1720
+ managedMarker: true
1721
+ });
1722
+ }
1723
+ for (const skillName of resolved.skills || []) {
1724
+ const skillDir = path5.join(registryDir, "skills", skillName);
1725
+ const skillFile = path5.join(skillDir, "SKILL.md");
1726
+ if (!await fs3.pathExists(skillFile)) continue;
1727
+ const canonicalRel = `skills/${skillName}/SKILL.md`;
1728
+ const normalized = _ClaudeProjector.normalizeSkillName(skillName);
1729
+ artifacts.push({
1730
+ kind: "skill",
1731
+ canonical: canonicalRel,
1732
+ relPath: `.claude/skills/${normalized}/SKILL.md`,
1733
+ content: _ClaudeProjector.renderSkill(await fs3.readFile(skillFile, "utf8"), canonicalRel).content,
1734
+ managedMarker: true
1735
+ });
1736
+ const entries = (await fs3.readdir(skillDir, { withFileTypes: true })).filter((entry) => entry.name !== "SKILL.md").sort((a, b) => a.name.localeCompare(b.name));
1737
+ for (const entry of entries) {
1738
+ if (!entry.isFile()) continue;
1739
+ artifacts.push({
1740
+ kind: "skill",
1741
+ canonical: `skills/${skillName}/${entry.name}`,
1742
+ relPath: `.claude/skills/${normalized}/${entry.name}`,
1743
+ sourceFilePath: path5.join(skillDir, entry.name),
1744
+ managedMarker: false
1745
+ });
1746
+ }
1747
+ }
1748
+ for (const ruleFile of [...new Set(resolved.rules || [])].sort()) {
1749
+ if (ENTRYPOINT_RULES.has(ruleFile)) continue;
1750
+ const src = path5.join(registryDir, "rules", ruleFile);
1751
+ if (!await fs3.pathExists(src)) continue;
1752
+ const canonicalRel = `rules/${ruleFile}`;
1753
+ const content = await fs3.readFile(src, "utf8");
1754
+ if (content.split(/\r?\n/).length > _ClaudeProjector.CLAUDE_DIALECT.maxRuleLines) continue;
1755
+ artifacts.push({
1756
+ kind: "rule",
1757
+ canonical: canonicalRel,
1758
+ relPath: `.claude/rules/${ruleFile}`,
1759
+ content: _ClaudeProjector.renderRule(content, canonicalRel).content,
1760
+ managedMarker: true
1761
+ });
1762
+ }
1763
+ return artifacts;
1764
+ }
1765
+ /** Claude plugin package name rule; every bundle in `registry/bundles.json` satisfies it today. */
1766
+ static PLUGIN_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/;
1767
+ /**
1768
+ * Render the Claude plugin manifest (`.claude-plugin/plugin.json`).
1769
+ *
1770
+ * Deterministic: fixed field order, two-space indent, trailing newline, and no data read
1771
+ * from the environment (a `package.json` lookup would make the bytes differ between a
1772
+ * project and a global install and would trip doctor's recorded-hash drift check).
1773
+ */
1774
+ static renderPluginManifest(bundle, canonicalRelPath) {
1775
+ if (!_ClaudeProjector.PLUGIN_NAME_REGEX.test(bundle.name)) {
1776
+ throw new Error(
1777
+ `Cannot project ${canonicalRelPath}: bundle name "${bundle.name}" is not a valid Claude plugin name (must match ^[a-z0-9][a-z0-9-]*$).`
1778
+ );
1779
+ }
1780
+ const markerText = `${MARKER_PREFIX} | profile: ${MARKER_PROFILE} | do not edit`;
1781
+ const description = bundle.description ? `${bundle.description} [${markerText}]` : markerText;
1782
+ const manifest = {
1783
+ author: "",
1784
+ description,
1785
+ homepage: "",
1786
+ license: "",
1787
+ name: bundle.name,
1788
+ repository: "",
1789
+ version: bundle.version || "1.0.0"
1790
+ };
1791
+ return JSON.stringify(manifest, null, 2) + "\n";
1792
+ }
1793
+ /**
1794
+ * ADR 0018 decision 12 / Plan 016 decision 13 — the opt-in Claude **plugin lane**.
1795
+ *
1796
+ * Plans the `claude --plugin-dir` consumable package *inside* the organization package:
1797
+ * `.agents/plugins/<bundle>/.claude-plugin/plugin.json` plus an `agents/` subdirectory that
1798
+ * mirrors the `.claude/agents/` projection name-for-name and byte-for-byte (same `renderRole`
1799
+ * call, same coordinator allowlist, same `maxTurns`).
1800
+ *
1801
+ * **Distribution-only, never the behavioural source.** Claude has no project-local plugin
1802
+ * auto-discovery, plugin agents are namespaced (`plugin:agent`) and a plugin
1803
+ * `permissionMode` is ignored — so the `.claude/agents/` projection produced by
1804
+ * `planCompoundProjection` stays the single behavioural source. This package exists purely
1805
+ * so the same folder can be zipped and consumed elsewhere via `--plugin-dir`.
1806
+ *
1807
+ * Additive and caller-gated (`InstallOptions.pluginLane`): when the flag is off this method
1808
+ * is never called and the install plan is byte-identical to before.
1809
+ */
1810
+ static async planPluginLane(bundle, resolved, registryDir) {
1811
+ const artifacts = [];
1812
+ const baseDir = `.agents/plugins/${bundle.name}`;
1813
+ artifacts.push({
1814
+ kind: "plugin-manifest",
1815
+ relPath: `${baseDir}/.claude-plugin/plugin.json`,
1816
+ content: _ClaudeProjector.renderPluginManifest(bundle, `${baseDir}/.claude-plugin/plugin.json`),
1817
+ managedMarker: true,
1818
+ distributionOnly: true
1819
+ });
1820
+ const coordinatorFile = bundle.orchestrator || `${bundle.name}.md`;
1821
+ const domainRoster = _ClaudeProjector.resolveDomainRoster(bundle, coordinatorFile, registryDir);
1822
+ const specialistNames = domainRoster.length > 0 ? domainRoster : (bundle.agents || []).map((f) => _ClaudeProjector.stripSubagentPrefix(f.replace(/\.md$/i, "")));
1823
+ const maxTurns = bundle.planningLoop?.budget?.maxIterations;
1824
+ for (const agentFile of resolved.agents || []) {
1825
+ const canonicalRel = `agents/${agentFile}`;
1826
+ const src = path5.join(registryDir, "agents", agentFile);
1827
+ if (!await fs3.pathExists(src)) continue;
1828
+ const isCoordinator = coordinatorFile === agentFile;
1829
+ const roleName = _ClaudeProjector.stripSubagentPrefix(agentFile.replace(/\.md$/i, ""));
1830
+ const rendered = _ClaudeProjector.renderRole(await fs3.readFile(src, "utf8"), canonicalRel, {
1831
+ allowlist: isCoordinator ? specialistNames : void 0,
1832
+ maxTurns: isCoordinator ? maxTurns : void 0
1833
+ });
1834
+ artifacts.push({
1835
+ kind: "role",
1836
+ canonical: canonicalRel,
1837
+ relPath: `${baseDir}/agents/${roleName}.md`,
1838
+ content: rendered.content,
1839
+ managedMarker: true,
1840
+ distributionOnly: true
1841
+ });
1842
+ }
1843
+ return artifacts;
1844
+ }
1845
+ };
1846
+
1068
1847
  // src/core/installer.ts
1069
1848
  import YAML2 from "yaml";
1849
+
1850
+ // src/core/types.ts
1851
+ function assetOwners(rec) {
1852
+ if (!rec) return [];
1853
+ const declared = rec.owners ?? [];
1854
+ if (declared.length > 0) return declared;
1855
+ return rec.bundle ? [rec.bundle] : [];
1856
+ }
1857
+ function mergeAssetOwners(rec, owner) {
1858
+ const owners = assetOwners(rec);
1859
+ if (!owner) return owners;
1860
+ return owners.includes(owner) ? owners : [...owners, owner];
1861
+ }
1862
+
1863
+ // src/core/installer.ts
1070
1864
  var InstallEngine = class _InstallEngine {
1071
1865
  registry;
1072
1866
  constructor(registry2) {
1073
1867
  this.registry = registry2 || new RegistryResolver();
1074
1868
  }
1075
1869
  async calculateHash(filePath) {
1076
- const buffer = await fs3.readFile(filePath);
1870
+ const buffer = await fs4.readFile(filePath);
1077
1871
  return "sha256:" + crypto.createHash("sha256").update(buffer).digest("hex");
1078
1872
  }
1079
1873
  async readLockfile(lockfilePath) {
1080
- if (await fs3.pathExists(lockfilePath)) {
1874
+ if (await fs4.pathExists(lockfilePath)) {
1081
1875
  try {
1082
- return await fs3.readJson(lockfilePath);
1876
+ return await fs4.readJson(lockfilePath);
1083
1877
  } catch {
1084
1878
  }
1085
1879
  }
@@ -1122,24 +1916,24 @@ var InstallEngine = class _InstallEngine {
1122
1916
  return options.scope || "project";
1123
1917
  }
1124
1918
  async deployFile(src, dest, method, force) {
1125
- await fs3.ensureDir(path5.dirname(dest));
1126
- if (await fs3.pathExists(dest) || await fs3.pathExists(dest).catch(() => false)) {
1919
+ await fs4.ensureDir(path6.dirname(dest));
1920
+ if (await fs4.pathExists(dest) || await fs4.pathExists(dest).catch(() => false)) {
1127
1921
  if (force) {
1128
- await fs3.remove(dest);
1922
+ await fs4.remove(dest);
1129
1923
  }
1130
1924
  }
1131
1925
  if (method === "symlink") {
1132
1926
  try {
1133
- const stat = await fs3.stat(src);
1927
+ const stat = await fs4.stat(src);
1134
1928
  const type = stat.isDirectory() ? process.platform === "win32" ? "junction" : "dir" : "file";
1135
- await fs3.ensureSymlink(src, dest, type);
1929
+ await fs4.ensureSymlink(src, dest, type);
1136
1930
  return "symlink";
1137
1931
  } catch {
1138
- await fs3.copy(src, dest, { overwrite: true });
1932
+ await fs4.copy(src, dest, { overwrite: true });
1139
1933
  return "copy";
1140
1934
  }
1141
1935
  }
1142
- await fs3.copy(src, dest, { overwrite: true });
1936
+ await fs4.copy(src, dest, { overwrite: true });
1143
1937
  return "copy";
1144
1938
  }
1145
1939
  /**
@@ -1147,8 +1941,8 @@ var InstallEngine = class _InstallEngine {
1147
1941
  * copies of canonical assets, so their bytes differ from the source.
1148
1942
  */
1149
1943
  async deployProjection(dest, content) {
1150
- await fs3.ensureDir(path5.dirname(dest));
1151
- await fs3.writeFile(dest, content, "utf8");
1944
+ await fs4.ensureDir(path6.dirname(dest));
1945
+ await fs4.writeFile(dest, content, "utf8");
1152
1946
  }
1153
1947
  /**
1154
1948
  * Computes the canonical `.agents/` target dir and the workspace root that contains
@@ -1156,27 +1950,27 @@ var InstallEngine = class _InstallEngine {
1156
1950
  */
1157
1951
  static projectionRoot(scope, targetDir) {
1158
1952
  const agentsTarget = AgentHostAdapter.resolveHostDir(scope, "agents", targetDir);
1159
- return { agentsTarget, root: path5.resolve(path5.dirname(agentsTarget)) };
1953
+ return { agentsTarget, root: path6.resolve(path6.dirname(agentsTarget)) };
1160
1954
  }
1161
1955
  toPosix(p) {
1162
- return p.split(path5.sep).join("/");
1956
+ return p.split(path6.sep).join("/");
1163
1957
  }
1164
1958
  canonicalRelAgent(agentFile) {
1165
1959
  return `.agents/agents/${agentFile}`;
1166
1960
  }
1167
1961
  lockRel(subdir, file) {
1168
- return path5.join(subdir, file);
1962
+ return path6.join(subdir, file);
1169
1963
  }
1170
1964
  destForProjection(host, root, file) {
1171
1965
  const def = HOST_REGISTRY[host];
1172
1966
  const base = resolveHostProjectDir(host, root);
1173
- if (def.agentsSubdir) return path5.join(base, def.agentsSubdir, file);
1967
+ if (def.agentsSubdir) return path6.join(base, def.agentsSubdir, file);
1174
1968
  return base;
1175
1969
  }
1176
1970
  recordProjectedTo(lockfile, relKey, projPath) {
1177
1971
  const normProj = this.toPosix(projPath);
1178
1972
  const posixKey = relKey.replace(/\\/g, "/");
1179
- const sysKey = relKey.replace(/\//g, path5.sep);
1973
+ const sysKey = relKey.replace(/\//g, path6.sep);
1180
1974
  const targetKey = lockfile.files[posixKey] ? posixKey : lockfile.files[sysKey] ? sysKey : relKey;
1181
1975
  if (!lockfile.files[targetKey]) {
1182
1976
  lockfile.files[targetKey] = { hash: "", installedAt: (/* @__PURE__ */ new Date()).toISOString() };
@@ -1190,7 +1984,7 @@ var InstallEngine = class _InstallEngine {
1190
1984
  removeProjectedTo(lockfile, canonicalRelPath, projectedRelPath) {
1191
1985
  const normProj = this.toPosix(projectedRelPath);
1192
1986
  const posixKey = canonicalRelPath.replace(/\\/g, "/");
1193
- const sysKey = canonicalRelPath.replace(/\//g, path5.sep);
1987
+ const sysKey = canonicalRelPath.replace(/\//g, path6.sep);
1194
1988
  const targetKey = lockfile.files[posixKey] ? posixKey : lockfile.files[sysKey] ? sysKey : canonicalRelPath;
1195
1989
  const asset = lockfile.files[targetKey];
1196
1990
  if (asset && asset.projectedTo) {
@@ -1247,7 +2041,7 @@ var InstallEngine = class _InstallEngine {
1247
2041
  if (prefixes.length === 0 || producedByCanonical.size === 0) return;
1248
2042
  for (const [canonical, producedPaths] of producedByCanonical) {
1249
2043
  const posixKey = canonical.replace(/\\/g, "/");
1250
- const sysKey = canonical.replace(/\//g, path5.sep);
2044
+ const sysKey = canonical.replace(/\//g, path6.sep);
1251
2045
  const targetKey = lockfile.files[posixKey] ? posixKey : lockfile.files[sysKey] ? sysKey : null;
1252
2046
  if (!targetKey) continue;
1253
2047
  const asset = lockfile.files[targetKey];
@@ -1264,17 +2058,142 @@ var InstallEngine = class _InstallEngine {
1264
2058
  }
1265
2059
  }
1266
2060
  }
2061
+ /**
2062
+ * ADR 0018 — the shared compound-lane body used by every host that owns a
2063
+ * dedicated projector (Cline per ADR 0013, Claude per ADR 0018).
2064
+ *
2065
+ * Parameterising by `host` instead of forking the body is deliberate: the two
2066
+ * lanes must stay behaviourally identical in pruning, refcounting and
2067
+ * reconcile semantics, and the Cline path is provably unchanged because it
2068
+ * passes the exact same literal `'cline'` (and the same `declared` set, plan
2069
+ * artifacts and force flag) it previously used inline.
2070
+ *
2071
+ * The host-specific plan (the projector call) stays at the call site; this body
2072
+ * is purely mechanical: prune superseded projections, deploy artifacts, record
2073
+ * refcounted projection entries, reconcile `projectedTo`, surface `projections`.
2074
+ */
2075
+ async applyCompoundLane(host, bundleName, artifacts, context) {
2076
+ const { root, lockfile, declared, projections, now, force } = context;
2077
+ const newPaths = new Set(artifacts.map((a) => a.relPath));
2078
+ if (lockfile.projections) {
2079
+ for (const [projRelPath, proj] of Object.entries(lockfile.projections)) {
2080
+ if (proj.host === host && !newPaths.has(projRelPath) && proj.owners.includes(bundleName)) {
2081
+ proj.owners = proj.owners.filter((o) => o !== bundleName);
2082
+ if (proj.owners.length === 0) {
2083
+ const absProjection = path6.join(root, projRelPath);
2084
+ if (await fs4.pathExists(absProjection)) {
2085
+ await fs4.remove(absProjection);
2086
+ await this.removeEmptyProjectionDirs(root, projRelPath);
2087
+ }
2088
+ delete lockfile.projections[projRelPath];
2089
+ }
2090
+ if (proj.canonical) {
2091
+ this.removeProjectedTo(lockfile, proj.canonical, projRelPath);
2092
+ }
2093
+ }
2094
+ }
2095
+ }
2096
+ for (const artifact of artifacts) {
2097
+ const dest = path6.join(root, artifact.relPath);
2098
+ if (await fs4.pathExists(dest) && !force) {
2099
+ const existing = await fs4.readFile(dest, "utf8").catch(() => null);
2100
+ if (existing !== null) {
2101
+ if (artifact.managedMarker && !HostProjector.hasManagedMarker(existing)) {
2102
+ throw new Error(
2103
+ `Projection target ${artifact.relPath} already exists and is not managed by agents-united. Use --force to overwrite.`
2104
+ );
2105
+ } else if (!artifact.managedMarker) {
2106
+ const registeredProj = lockfile.projections?.[artifact.relPath];
2107
+ if (!registeredProj) {
2108
+ throw new Error(
2109
+ `Projection target ${artifact.relPath} already exists and is not managed by agents-united. Use --force to overwrite.`
2110
+ );
2111
+ }
2112
+ }
2113
+ }
2114
+ }
2115
+ if (artifact.content !== void 0) {
2116
+ await this.deployProjection(dest, artifact.content);
2117
+ } else if (artifact.sourceFilePath) {
2118
+ await fs4.ensureDir(path6.dirname(dest));
2119
+ await fs4.copy(artifact.sourceFilePath, dest, { overwrite: true });
2120
+ }
2121
+ const deployedHash = await this.calculateHash(dest);
2122
+ lockfile.projections = lockfile.projections || {};
2123
+ const existingProj = lockfile.projections[artifact.relPath];
2124
+ const declares = artifact.kind === "rule" || artifact.kind === "team-manifest" || artifact.kind === "plugin-manifest" || (artifact.canonical ? declared.has(artifact.canonical) : false);
2125
+ const bundleScoped = artifact.relPath.startsWith(".agents/plugins/");
2126
+ const canonicalOwners = !bundleScoped && artifact.canonical ? lockfile.files[artifact.canonical]?.owners ?? [] : [];
2127
+ const priorOwners = existingProj?.owners ?? [];
2128
+ const owners = Array.from(/* @__PURE__ */ new Set([
2129
+ ...priorOwners,
2130
+ ...canonicalOwners,
2131
+ ...declares ? [bundleName] : []
2132
+ ]));
2133
+ lockfile.projections[artifact.relPath] = {
2134
+ host,
2135
+ kind: artifact.kind,
2136
+ canonical: artifact.canonical,
2137
+ owners,
2138
+ hash: deployedHash,
2139
+ installedAt: existingProj?.installedAt ?? now,
2140
+ managedMarker: artifact.managedMarker
2141
+ };
2142
+ projections.push({ host, path: artifact.relPath, kind: artifact.kind, warnings: [] });
2143
+ if (artifact.canonical && !artifact.distributionOnly) {
2144
+ this.recordProjectedTo(lockfile, artifact.canonical, artifact.relPath);
2145
+ }
2146
+ }
2147
+ const producedByCanonical = /* @__PURE__ */ new Map();
2148
+ for (const artifact of artifacts) {
2149
+ if (!artifact.canonical || artifact.distributionOnly) continue;
2150
+ const producedPaths = producedByCanonical.get(artifact.canonical);
2151
+ if (producedPaths) {
2152
+ producedPaths.push(artifact.relPath);
2153
+ } else {
2154
+ producedByCanonical.set(artifact.canonical, [artifact.relPath]);
2155
+ }
2156
+ }
2157
+ this.reconcileProjectedTo(lockfile, host, bundleName, producedByCanonical);
2158
+ }
2159
+ /**
2160
+ * Plan 016 / ADR 0018 decision 12 — resolve the effective plugin-lane opt-in.
2161
+ *
2162
+ * An explicit `InstallOptions.pluginLane` always wins (the CLI passes `true` for `--plugin` and
2163
+ * `false` for `--no-plugin`); an unset flag inherits the recorded choice so that `agents update`
2164
+ * — which re-runs the installer without the flag — cannot silently prune an opted-in package.
2165
+ */
2166
+ static effectivePluginLane(options, lockfile) {
2167
+ if (typeof options.pluginLane === "boolean") return options.pluginLane;
2168
+ return lockfile?.pluginLane === true;
2169
+ }
2170
+ /**
2171
+ * ADR 0018 — the host-specific projection plan, the single input that differs per host once
2172
+ * `applyCompoundLane` shared the mechanics. `pluginLane` (Plan 016 decision 13) is honoured for
2173
+ * the **Claude lane only**: the Cline package must stay byte-identical with the flag on and off,
2174
+ * so the flag is inert for `cline`.
2175
+ */
2176
+ static async planCompoundArtifacts(host, bundleDef, scope, resolved, registryDir, pluginLane) {
2177
+ if (host === "cline") {
2178
+ return ClineProjector.planCompoundProjection(bundleDef, scope, resolved, registryDir);
2179
+ }
2180
+ const artifacts = await ClaudeProjector.planCompoundProjection(bundleDef, scope, resolved, registryDir);
2181
+ if (pluginLane) {
2182
+ artifacts.push(...await ClaudeProjector.planPluginLane(bundleDef, resolved, registryDir));
2183
+ }
2184
+ return artifacts;
2185
+ }
1267
2186
  async removeEmptyProjectionDirs(workspaceRoot, projPath) {
1268
- let dir = path5.dirname(path5.join(workspaceRoot, projPath));
2187
+ let dir = path6.dirname(path6.join(workspaceRoot, projPath));
1269
2188
  while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {
1270
2189
  try {
1271
- const entries = await fs3.readdir(dir);
2190
+ const entries = await fs4.readdir(dir);
1272
2191
  if (entries.length > 0) break;
1273
- await fs3.rmdir(dir);
2192
+ await fs4.rmdir(dir);
1274
2193
  } catch {
1275
2194
  break;
1276
2195
  }
1277
- dir = path5.dirname(dir);
2196
+ dir = path6.dirname(dir);
1278
2197
  }
1279
2198
  }
1280
2199
  // ADR 0016: Silent auto-migration of legacy .agents/workflows/*.md into
@@ -1284,27 +2203,27 @@ var InstallEngine = class _InstallEngine {
1284
2203
  let migratedCount = 0;
1285
2204
  let targetLockfile = lockfile;
1286
2205
  let lockfileLoadedFromDisk = false;
1287
- if (!targetLockfile && await fs3.pathExists(subPaths.lockfile)) {
2206
+ if (!targetLockfile && await fs4.pathExists(subPaths.lockfile)) {
1288
2207
  try {
1289
- targetLockfile = await fs3.readJson(subPaths.lockfile);
2208
+ targetLockfile = await fs4.readJson(subPaths.lockfile);
1290
2209
  lockfileLoadedFromDisk = true;
1291
2210
  } catch {
1292
2211
  }
1293
2212
  }
1294
- if (await fs3.pathExists(subPaths.workflowsDir)) {
1295
- const entries = await fs3.readdir(subPaths.workflowsDir).catch(() => []);
2213
+ if (await fs4.pathExists(subPaths.workflowsDir)) {
2214
+ const entries = await fs4.readdir(subPaths.workflowsDir).catch(() => []);
1296
2215
  for (const entry of entries) {
1297
2216
  if (entry.endsWith(".md")) {
1298
- const legacyPath = path5.join(subPaths.workflowsDir, entry);
2217
+ const legacyPath = path6.join(subPaths.workflowsDir, entry);
1299
2218
  let skillName = entry.replace(/\.md$/, "").replace(/--/g, "-");
1300
2219
  if (!skillName.startsWith("workflow-")) {
1301
2220
  skillName = `workflow-${skillName}`;
1302
2221
  }
1303
- const targetSkillDir = path5.join(subPaths.skillsDir, skillName);
1304
- const targetSkillFile = path5.join(targetSkillDir, "SKILL.md");
1305
- if (!await fs3.pathExists(targetSkillFile)) {
1306
- await fs3.ensureDir(targetSkillDir);
1307
- const legacyContent = await fs3.readFile(legacyPath, "utf8");
2222
+ const targetSkillDir = path6.join(subPaths.skillsDir, skillName);
2223
+ const targetSkillFile = path6.join(targetSkillDir, "SKILL.md");
2224
+ if (!await fs4.pathExists(targetSkillFile)) {
2225
+ await fs4.ensureDir(targetSkillDir);
2226
+ const legacyContent = await fs4.readFile(legacyPath, "utf8");
1308
2227
  let skillContent = legacyContent;
1309
2228
  const fmMatch = legacyContent.match(/^---\r?\n([\s\S]+?)\r?\n---/);
1310
2229
  if (fmMatch) {
@@ -1325,14 +2244,14 @@ ${YAML2.stringify(meta).trim()}
1325
2244
  } catch {
1326
2245
  }
1327
2246
  }
1328
- await fs3.writeFile(targetSkillFile, skillContent, "utf8");
2247
+ await fs4.writeFile(targetSkillFile, skillContent, "utf8");
1329
2248
  }
1330
- await fs3.remove(legacyPath).catch(() => {
2249
+ await fs4.remove(legacyPath).catch(() => {
1331
2250
  });
1332
2251
  migratedCount++;
1333
2252
  if (targetLockfile) {
1334
- const legacyRel = this.toPosix(path5.relative(targetDir, legacyPath));
1335
- const newRel = this.toPosix(path5.relative(targetDir, targetSkillFile));
2253
+ const legacyRel = this.toPosix(path6.relative(targetDir, legacyPath));
2254
+ const newRel = this.toPosix(path6.relative(targetDir, targetSkillFile));
1336
2255
  if (targetLockfile.files && targetLockfile.files[legacyRel]) {
1337
2256
  targetLockfile.files[newRel] = targetLockfile.files[legacyRel];
1338
2257
  delete targetLockfile.files[legacyRel];
@@ -1345,9 +2264,9 @@ ${YAML2.stringify(meta).trim()}
1345
2264
  }
1346
2265
  }
1347
2266
  }
1348
- const remaining = await fs3.readdir(subPaths.workflowsDir).catch(() => []);
2267
+ const remaining = await fs4.readdir(subPaths.workflowsDir).catch(() => []);
1349
2268
  if (remaining.length === 0) {
1350
- await fs3.remove(subPaths.workflowsDir).catch(() => {
2269
+ await fs4.remove(subPaths.workflowsDir).catch(() => {
1351
2270
  });
1352
2271
  }
1353
2272
  }
@@ -1363,15 +2282,15 @@ ${YAML2.stringify(meta).trim()}
1363
2282
  }
1364
2283
  targetLockfile.installed.workflows = [];
1365
2284
  }
1366
- if (targetLockfile && (lockfileLoadedFromDisk || await fs3.pathExists(subPaths.lockfile))) {
1367
- await fs3.writeJson(subPaths.lockfile, targetLockfile, { spaces: 2 });
2285
+ if (targetLockfile && (lockfileLoadedFromDisk || await fs4.pathExists(subPaths.lockfile))) {
2286
+ await fs4.writeJson(subPaths.lockfile, targetLockfile, { spaces: 2 });
1368
2287
  }
1369
2288
  return migratedCount;
1370
2289
  }
1371
2290
  /**
1372
2291
  * Dry-run: compute the projection plan (paths + warnings) without writing anything.
1373
2292
  */
1374
- async buildProjections(fanoutHosts, resolved, registryDir, scope, targetDir) {
2293
+ async buildProjections(fanoutHosts, resolved, registryDir, scope, targetDir, pluginLane = false) {
1375
2294
  const infos = [];
1376
2295
  if (fanoutHosts.length === 0) return infos;
1377
2296
  const { root } = _InstallEngine.projectionRoot(scope, targetDir);
@@ -1380,10 +2299,17 @@ ${YAML2.stringify(meta).trim()}
1380
2299
  infos.push({ host, path: "AGENTS.md", kind: "bridge", warnings: [] });
1381
2300
  continue;
1382
2301
  }
1383
- if (host === "cline") {
2302
+ if (host === "cline" || host === "claude") {
1384
2303
  const bundleDef = resolved.targetBundle ? await this.registry.getBundle(resolved.targetBundle) : void 0;
1385
2304
  if (bundleDef) {
1386
- const artifacts = await ClineProjector.planCompoundProjection(bundleDef, scope, resolved, registryDir);
2305
+ const artifacts = await _InstallEngine.planCompoundArtifacts(
2306
+ host,
2307
+ bundleDef,
2308
+ scope,
2309
+ resolved,
2310
+ registryDir,
2311
+ pluginLane
2312
+ );
1387
2313
  for (const artifact of artifacts) {
1388
2314
  infos.push({ host, path: artifact.relPath, kind: artifact.kind, warnings: [] });
1389
2315
  }
@@ -1393,10 +2319,10 @@ ${YAML2.stringify(meta).trim()}
1393
2319
  const base = resolveHostProjectDir(host, root);
1394
2320
  const subdir = HOST_REGISTRY[host].agentsSubdir ?? "agents";
1395
2321
  for (const agentFile of resolved.agents) {
1396
- const content = await fs3.readFile(path5.join(registryDir, "agents", agentFile), "utf8");
2322
+ const content = await fs4.readFile(path6.join(registryDir, "agents", agentFile), "utf8");
1397
2323
  const res = HostProjector.projectAgent(content, HOST_REGISTRY[host].profile, this.canonicalRelAgent(agentFile));
1398
- const dest = path5.join(base, subdir, agentFile);
1399
- infos.push({ host, path: this.toPosix(path5.relative(root, dest)), kind: "role", warnings: res.warnings });
2324
+ const dest = path6.join(base, subdir, agentFile);
2325
+ infos.push({ host, path: this.toPosix(path6.relative(root, dest)), kind: "role", warnings: res.warnings });
1400
2326
  }
1401
2327
  }
1402
2328
  return infos;
@@ -1410,6 +2336,14 @@ ${YAML2.stringify(meta).trim()}
1410
2336
  if (fanoutHosts.length === 0) return;
1411
2337
  const { root } = _InstallEngine.projectionRoot(scope, options.targetDir);
1412
2338
  const now = (/* @__PURE__ */ new Date()).toISOString();
2339
+ const pluginLane = _InstallEngine.effectivePluginLane(options, lockfile);
2340
+ if (fanoutHosts.includes("claude")) {
2341
+ if (pluginLane) {
2342
+ lockfile.pluginLane = true;
2343
+ } else if (options.pluginLane === false) {
2344
+ delete lockfile.pluginLane;
2345
+ }
2346
+ }
1413
2347
  for (const host of fanoutHosts) {
1414
2348
  if (host === "codex") {
1415
2349
  const assets = [];
@@ -1417,9 +2351,9 @@ ${YAML2.stringify(meta).trim()}
1417
2351
  resolved.workflows.forEach((f) => assets.push({ name: f.replace(/\.md$/, ""), type: "workflow", relPath: `.agents/workflows/${f}` }));
1418
2352
  resolved.skills.forEach((s) => assets.push({ name: s, type: "skill", relPath: `.agents/skills/${s}/SKILL.md` }));
1419
2353
  const bridge = HostProjector.buildAgentsMdIndex(assets);
1420
- const bridgeDest = path5.join(root, "AGENTS.md");
1421
- if (await fs3.pathExists(bridgeDest) && !options.force) {
1422
- const existing = await fs3.readFile(bridgeDest, "utf8");
2354
+ const bridgeDest = path6.join(root, "AGENTS.md");
2355
+ if (await fs4.pathExists(bridgeDest) && !options.force) {
2356
+ const existing = await fs4.readFile(bridgeDest, "utf8");
1423
2357
  if (!HostProjector.hasManagedMarker(existing)) {
1424
2358
  throw new Error(
1425
2359
  "File AGENTS.md already exists at the workspace root and is not managed by agents-united. Use --force to overwrite."
@@ -1430,10 +2364,10 @@ ${YAML2.stringify(meta).trim()}
1430
2364
  projections.push({ host, path: "AGENTS.md", kind: "bridge", warnings: [] });
1431
2365
  resolved.agents.forEach((f) => this.recordProjectedTo(lockfile, this.lockRel("agents", f), "AGENTS.md"));
1432
2366
  resolved.workflows.forEach((f) => this.recordProjectedTo(lockfile, this.lockRel("workflows", f), "AGENTS.md"));
1433
- resolved.skills.forEach((s) => this.recordProjectedTo(lockfile, this.lockRel("skills", path5.join(s, "SKILL.md")), "AGENTS.md"));
2367
+ resolved.skills.forEach((s) => this.recordProjectedTo(lockfile, this.lockRel("skills", path6.join(s, "SKILL.md")), "AGENTS.md"));
1434
2368
  continue;
1435
2369
  }
1436
- if (host === "cline") {
2370
+ if (host === "cline" || host === "claude") {
1437
2371
  const bundleDef = resolved.targetBundle ? await this.registry.getBundle(resolved.targetBundle) : void 0;
1438
2372
  if (bundleDef) {
1439
2373
  const declared = /* @__PURE__ */ new Set();
@@ -1441,84 +2375,23 @@ ${YAML2.stringify(meta).trim()}
1441
2375
  (bundleDef.agents || []).forEach((a) => declared.add(`agents/${a}`));
1442
2376
  (bundleDef.skills || []).forEach((s) => declared.add(`skills/${s}/SKILL.md`));
1443
2377
  (bundleDef.workflows || []).forEach((w) => declared.add(`workflows/${w}`));
1444
- const artifacts = await ClineProjector.planCompoundProjection(bundleDef, scope, resolved, registryDir);
1445
- const newPaths = new Set(artifacts.map((a) => a.relPath));
1446
- if (lockfile.projections) {
1447
- for (const [projRelPath, proj] of Object.entries(lockfile.projections)) {
1448
- if (proj.host === "cline" && !newPaths.has(projRelPath) && proj.owners.includes(bundleDef.name)) {
1449
- proj.owners = proj.owners.filter((o) => o !== bundleDef.name);
1450
- if (proj.owners.length === 0) {
1451
- const absProjection = path5.join(root, projRelPath);
1452
- if (await fs3.pathExists(absProjection)) {
1453
- await fs3.remove(absProjection);
1454
- await this.removeEmptyProjectionDirs(root, projRelPath);
1455
- }
1456
- delete lockfile.projections[projRelPath];
1457
- }
1458
- if (proj.canonical) {
1459
- this.removeProjectedTo(lockfile, proj.canonical, projRelPath);
1460
- }
1461
- }
1462
- }
1463
- }
1464
- for (const artifact of artifacts) {
1465
- const dest = path5.join(root, artifact.relPath);
1466
- if (await fs3.pathExists(dest) && !options.force) {
1467
- const existing = await fs3.readFile(dest, "utf8").catch(() => null);
1468
- if (existing !== null) {
1469
- if (artifact.managedMarker && !HostProjector.hasManagedMarker(existing)) {
1470
- throw new Error(
1471
- `Projection target ${artifact.relPath} already exists and is not managed by agents-united. Use --force to overwrite.`
1472
- );
1473
- } else if (!artifact.managedMarker) {
1474
- const registeredProj = lockfile.projections?.[artifact.relPath];
1475
- if (!registeredProj) {
1476
- throw new Error(
1477
- `Projection target ${artifact.relPath} already exists and is not managed by agents-united. Use --force to overwrite.`
1478
- );
1479
- }
1480
- }
1481
- }
1482
- }
1483
- if (artifact.content !== void 0) {
1484
- await this.deployProjection(dest, artifact.content);
1485
- } else if (artifact.sourceFilePath) {
1486
- await fs3.ensureDir(path5.dirname(dest));
1487
- await fs3.copy(artifact.sourceFilePath, dest, { overwrite: true });
1488
- }
1489
- const deployedHash = await this.calculateHash(dest);
1490
- lockfile.projections = lockfile.projections || {};
1491
- const existingProj = lockfile.projections[artifact.relPath];
1492
- const bundleName = bundleDef.name;
1493
- const declares = artifact.kind === "rule" || artifact.kind === "team-manifest" || artifact.kind === "plugin-manifest" || (artifact.canonical ? declared.has(artifact.canonical) : false);
1494
- const priorOwners = existingProj?.owners ?? [];
1495
- const owners = declares ? Array.from(/* @__PURE__ */ new Set([...priorOwners, bundleName])) : priorOwners;
1496
- lockfile.projections[artifact.relPath] = {
1497
- host: "cline",
1498
- kind: artifact.kind,
1499
- canonical: artifact.canonical,
1500
- owners,
1501
- hash: deployedHash,
1502
- installedAt: existingProj?.installedAt ?? now,
1503
- managedMarker: artifact.managedMarker
1504
- };
1505
- projections.push({ host, path: artifact.relPath, kind: artifact.kind, warnings: [] });
1506
- if (artifact.canonical) {
1507
- this.recordProjectedTo(lockfile, artifact.canonical, artifact.relPath);
1508
- }
1509
- }
1510
- const producedByCanonical = /* @__PURE__ */ new Map();
1511
- for (const artifact of artifacts) {
1512
- if (!artifact.canonical) continue;
1513
- const producedPaths = producedByCanonical.get(artifact.canonical);
1514
- if (producedPaths) {
1515
- producedPaths.push(artifact.relPath);
1516
- } else {
1517
- producedByCanonical.set(artifact.canonical, [artifact.relPath]);
1518
- }
1519
- }
1520
- this.reconcileProjectedTo(lockfile, "cline", bundleDef.name, producedByCanonical);
1521
- if (bundleDef.parentBundle) {
2378
+ const artifacts = await _InstallEngine.planCompoundArtifacts(
2379
+ host,
2380
+ bundleDef,
2381
+ scope,
2382
+ resolved,
2383
+ registryDir,
2384
+ pluginLane
2385
+ );
2386
+ await this.applyCompoundLane(host, bundleDef.name, artifacts, {
2387
+ root,
2388
+ lockfile,
2389
+ declared,
2390
+ projections,
2391
+ now,
2392
+ force: options.force
2393
+ });
2394
+ if (host === "cline" && bundleDef.parentBundle) {
1522
2395
  const parentDef = await this.registry.getBundle(bundleDef.parentBundle);
1523
2396
  if (parentDef) {
1524
2397
  const parentProjection = await ClineProjector.planCompoundProjection(
@@ -1531,8 +2404,8 @@ ${YAML2.stringify(meta).trim()}
1531
2404
  for (const artifact of parentProjection) {
1532
2405
  if (artifact.kind !== "rule" && artifact.kind !== "team-manifest") continue;
1533
2406
  if (artifact.content === void 0) continue;
1534
- const dest = path5.join(root, artifact.relPath);
1535
- if (!await fs3.pathExists(dest)) continue;
2407
+ const dest = path6.join(root, artifact.relPath);
2408
+ if (!await fs4.pathExists(dest)) continue;
1536
2409
  await this.deployProjection(dest, artifact.content);
1537
2410
  const deployedHash = await this.calculateHash(dest);
1538
2411
  lockfile.projections = lockfile.projections || {};
@@ -1556,35 +2429,51 @@ ${YAML2.stringify(meta).trim()}
1556
2429
  const base = resolveHostProjectDir(host, root);
1557
2430
  const subdir = HOST_REGISTRY[host].agentsSubdir ?? "agents";
1558
2431
  for (const agentFile of resolved.agents) {
1559
- const content = await fs3.readFile(path5.join(registryDir, "agents", agentFile), "utf8");
2432
+ const content = await fs4.readFile(path6.join(registryDir, "agents", agentFile), "utf8");
1560
2433
  const canonicalRel = this.canonicalRelAgent(agentFile);
1561
- const res = HostProjector.projectAgent(content, HOST_REGISTRY[host].profile, canonicalRel);
1562
- const projName = host === "cline" ? `${agentFile.replace(/\.md$/i, "").replace(/^subagent-/, "")}.yml` : agentFile;
1563
- const dest = path5.join(base, subdir, projName);
1564
- const projPath = this.toPosix(path5.relative(root, dest));
1565
- if (host === "cline" && lockfile.projections?.[projPath]?.kind === "role") {
1566
- continue;
2434
+ const isCoordinatorRole = /^\s*type:\s*orchestrator\s*$/m.test(content) || /^\s*mainAgent:\s*true\s*$/m.test(content);
2435
+ const peerRoles = resolved.agents.filter((f) => f !== agentFile).map((f) => ClaudeProjector.stripSubagentPrefix(f.replace(/\.md$/i, "")));
2436
+ const res = host === "claude" ? ClaudeProjector.renderRole(content, canonicalRel, {
2437
+ allowlist: isCoordinatorRole ? peerRoles : void 0
2438
+ }) : HostProjector.projectAgent(content, HOST_REGISTRY[host].profile, canonicalRel);
2439
+ const strippedRoleName = agentFile.replace(/\.md$/i, "").replace(/^subagent-/, "");
2440
+ const projName = host === "cline" ? `${strippedRoleName}.yml` : host === "claude" ? `${strippedRoleName}.md` : agentFile;
2441
+ const dest = path6.join(base, subdir, projName);
2442
+ const projPath = this.toPosix(path6.relative(root, dest));
2443
+ const existingRoleProj = lockfile.projections?.[projPath];
2444
+ if (existingRoleProj?.kind === "role") {
2445
+ const compoundOwned = !(existingRoleProj.canonical ?? "").startsWith(".agents/");
2446
+ if (host === "cline" || host === "claude" && compoundOwned) {
2447
+ continue;
2448
+ }
1567
2449
  }
1568
- if (await fs3.pathExists(dest) && !options.force) {
1569
- const existing = await fs3.readFile(dest, "utf8");
2450
+ if (await fs4.pathExists(dest) && !options.force) {
2451
+ const existing = await fs4.readFile(dest, "utf8");
1570
2452
  if (!HostProjector.hasManagedMarker(existing)) {
1571
2453
  throw new Error(
1572
- `Projection target ${this.toPosix(path5.relative(root, dest))} already exists and is not managed by agents-united. Use --force to overwrite.`
2454
+ `Projection target ${this.toPosix(path6.relative(root, dest))} already exists and is not managed by agents-united. Use --force to overwrite.`
1573
2455
  );
1574
2456
  }
1575
2457
  }
1576
2458
  await this.deployProjection(dest, res.content);
1577
- projections.push({ host, path: projPath, kind: "role", warnings: res.warnings });
2459
+ projections.push({
2460
+ host,
2461
+ path: projPath,
2462
+ kind: "role",
2463
+ warnings: "warnings" in res ? res.warnings : []
2464
+ });
1578
2465
  this.recordProjectedTo(lockfile, this.lockRel("agents", agentFile), projPath);
1579
- if (host === "cline") {
2466
+ if (host === "cline" || host === "claude") {
1580
2467
  lockfile.projections = lockfile.projections || {};
2468
+ const existingProj = lockfile.projections[projPath];
2469
+ const canonicalRecord = lockfile.files[canonicalRel] ?? lockfile.files[canonicalRel.replace(/^\.agents\//, "")];
1581
2470
  lockfile.projections[projPath] = {
1582
2471
  host,
1583
2472
  kind: "role",
1584
2473
  canonical: canonicalRel,
1585
- owners: resolved.targetBundle ? [resolved.targetBundle] : [],
2474
+ owners: mergeAssetOwners(existingProj, resolved.targetBundle ?? void 0).concat(canonicalRecord?.owners ?? []).filter((o, i, arr) => arr.indexOf(o) === i),
1586
2475
  hash: await this.calculateHash(dest),
1587
- installedAt: lockfile.projections[projPath]?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2476
+ installedAt: existingProj?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1588
2477
  managedMarker: true
1589
2478
  };
1590
2479
  }
@@ -1602,17 +2491,19 @@ ${YAML2.stringify(meta).trim()}
1602
2491
  const fanoutHosts = (options.fanout || []).map((h) => typeof h === "string" ? h.trim().toLowerCase() : h).filter((h) => isKnownHost(h) && HOST_REGISTRY[h]?.projectionCapable);
1603
2492
  if (options.dryRun) {
1604
2493
  let effectiveDryFanout = fanoutHosts;
1605
- if (options.fanout === void 0 && hasCanonicalAgents) {
1606
- const agentsTarget2 = AgentHostAdapter.resolveHostDir(scope, "agents", options.targetDir);
1607
- const lockfilePath = path5.join(agentsTarget2, "agents-united.json");
1608
- if (await fs3.pathExists(lockfilePath)) {
1609
- const lockfile = await fs3.readJson(lockfilePath).catch(() => null);
1610
- if (lockfile?.fanout) {
1611
- effectiveDryFanout = lockfile.fanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h]?.projectionCapable);
1612
- }
2494
+ let effectiveDryPluginLane = options.pluginLane === true;
2495
+ const agentsTarget2 = AgentHostAdapter.resolveHostDir(scope, "agents", options.targetDir);
2496
+ const lockfilePath = path6.join(agentsTarget2, "agents-united.json");
2497
+ if (await fs4.pathExists(lockfilePath)) {
2498
+ const lockfile = await fs4.readJson(lockfilePath).catch(() => null);
2499
+ if (options.fanout === void 0 && hasCanonicalAgents && lockfile?.fanout) {
2500
+ effectiveDryFanout = lockfile.fanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h]?.projectionCapable);
2501
+ }
2502
+ if (options.pluginLane === void 0 && lockfile?.pluginLane === true) {
2503
+ effectiveDryPluginLane = true;
1613
2504
  }
1614
2505
  }
1615
- const projections2 = hasCanonicalAgents ? await this.buildProjections(effectiveDryFanout, resolved, registryDir, scope, options.targetDir) : [];
2506
+ const projections2 = hasCanonicalAgents ? await this.buildProjections(effectiveDryFanout, resolved, registryDir, scope, options.targetDir, effectiveDryPluginLane) : [];
1616
2507
  return { installed: resolved, targetDirs, dryRun: true, method, projections: projections2 };
1617
2508
  }
1618
2509
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1620,12 +2511,12 @@ ${YAML2.stringify(meta).trim()}
1620
2511
  const agentsTarget = AgentHostAdapter.resolveHostDir(scope, "agents", options.targetDir);
1621
2512
  for (const targetDir of targetDirs) {
1622
2513
  const subPaths = AgentHostAdapter.getSubPaths(targetDir);
1623
- await fs3.ensureDir(subPaths.agentsDir);
1624
- await fs3.ensureDir(subPaths.skillsDir);
2514
+ await fs4.ensureDir(subPaths.agentsDir);
2515
+ await fs4.ensureDir(subPaths.skillsDir);
1625
2516
  if (resolved.workflows && resolved.workflows.length > 0) {
1626
- await fs3.ensureDir(subPaths.workflowsDir);
2517
+ await fs4.ensureDir(subPaths.workflowsDir);
1627
2518
  }
1628
- await fs3.ensureDir(subPaths.rulesDir);
2519
+ await fs4.ensureDir(subPaths.rulesDir);
1629
2520
  const lockfile = await this.readLockfile(subPaths.lockfile);
1630
2521
  await this.migrateLegacyWorkspaceWorkflows(targetDir, lockfile);
1631
2522
  lockfile.scope = scope;
@@ -1636,32 +2527,34 @@ ${YAML2.stringify(meta).trim()}
1636
2527
  lockfile.fanout = effectiveFanout;
1637
2528
  }
1638
2529
  const declared = /* @__PURE__ */ new Set();
2530
+ let hasDeclaredRoster = false;
1639
2531
  if (resolved.targetBundle) {
1640
2532
  const bundleDef = await this.registry.getBundle(resolved.targetBundle);
1641
2533
  if (bundleDef) {
2534
+ hasDeclaredRoster = true;
1642
2535
  if (bundleDef.orchestrator) declared.add(`agents/${bundleDef.orchestrator}`);
1643
2536
  (bundleDef.agents || []).forEach((a) => declared.add(`agents/${a}`));
1644
2537
  (bundleDef.skills || []).forEach((s) => declared.add(`skills/${s}/SKILL.md`));
1645
2538
  (bundleDef.workflows || []).forEach((w) => declared.add(`workflows/${w}`));
1646
2539
  }
1647
2540
  }
2541
+ const declaresAsset = (assetKey) => !hasDeclaredRoster || declared.has(assetKey);
1648
2542
  for (const agentFile of resolved.agents) {
1649
- const src = path5.join(registryDir, "agents", agentFile);
1650
- const dest = path5.join(subPaths.agentsDir, agentFile);
1651
- if (await fs3.pathExists(dest) && !options.force && method !== "symlink") {
2543
+ const src = path6.join(registryDir, "agents", agentFile);
2544
+ const dest = path6.join(subPaths.agentsDir, agentFile);
2545
+ if (await fs4.pathExists(dest) && !options.force && method !== "symlink") {
1652
2546
  const existingHash = await this.calculateHash(dest).catch(() => null);
1653
- const relPath2 = path5.relative(targetDir, dest);
2547
+ const relPath2 = path6.relative(targetDir, dest);
1654
2548
  if (existingHash && lockfile.files[relPath2] && lockfile.files[relPath2].hash !== existingHash) {
1655
2549
  throw new Error(`File ${relPath2} has user modifications. Use --force to overwrite.`);
1656
2550
  }
1657
2551
  }
1658
2552
  const actualMethod = await this.deployFile(src, dest, method, options.force);
1659
2553
  const hash = await this.calculateHash(src);
1660
- const relPath = this.toPosix(path5.relative(targetDir, dest));
2554
+ const relPath = this.toPosix(path6.relative(targetDir, dest));
1661
2555
  const existing = lockfile.files[relPath];
1662
- const existingOwners = existing?.owners ?? (existing?.bundle ? [existing.bundle] : []);
1663
- const declaresAsset = declared.has(`agents/${agentFile}`);
1664
- const owners = resolved.targetBundle && declaresAsset ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
2556
+ const existingOwners = existing?.owners ?? [];
2557
+ const owners = resolved.targetBundle && declaresAsset(`agents/${agentFile}`) ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
1665
2558
  lockfile.files[relPath] = {
1666
2559
  hash,
1667
2560
  bundle: existing?.bundle ?? resolved.targetBundle,
@@ -1675,17 +2568,16 @@ ${YAML2.stringify(meta).trim()}
1675
2568
  }
1676
2569
  }
1677
2570
  for (const skillName of resolved.skills) {
1678
- const src = path5.join(registryDir, "skills", skillName);
1679
- const dest = path5.join(subPaths.skillsDir, skillName);
2571
+ const src = path6.join(registryDir, "skills", skillName);
2572
+ const dest = path6.join(subPaths.skillsDir, skillName);
1680
2573
  const actualMethod = await this.deployFile(src, dest, method, options.force);
1681
- const skillFile = path5.join(src, "SKILL.md");
1682
- if (await fs3.pathExists(skillFile)) {
2574
+ const skillFile = path6.join(src, "SKILL.md");
2575
+ if (await fs4.pathExists(skillFile)) {
1683
2576
  const hash = await this.calculateHash(skillFile);
1684
- const relPath = this.toPosix(path5.relative(targetDir, path5.join(subPaths.skillsDir, skillName, "SKILL.md")));
2577
+ const relPath = this.toPosix(path6.relative(targetDir, path6.join(subPaths.skillsDir, skillName, "SKILL.md")));
1685
2578
  const existing = lockfile.files[relPath];
1686
- const existingOwners = existing?.owners ?? (existing?.bundle ? [existing.bundle] : []);
1687
- const declaresAsset = declared.has(`skills/${skillName}/SKILL.md`);
1688
- const owners = resolved.targetBundle && declaresAsset ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
2579
+ const existingOwners = existing?.owners ?? [];
2580
+ const owners = resolved.targetBundle && declaresAsset(`skills/${skillName}/SKILL.md`) ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
1689
2581
  lockfile.files[relPath] = {
1690
2582
  hash,
1691
2583
  bundle: existing?.bundle ?? resolved.targetBundle,
@@ -1695,20 +2587,43 @@ ${YAML2.stringify(meta).trim()}
1695
2587
  ...existing?.projectedTo ? { projectedTo: existing.projectedTo } : {}
1696
2588
  };
1697
2589
  }
2590
+ const sidecarStack = [dest];
2591
+ while (sidecarStack.length > 0) {
2592
+ const dir = sidecarStack.pop();
2593
+ for (const entry of await fs4.readdir(dir, { withFileTypes: true })) {
2594
+ const abs = path6.join(dir, entry.name);
2595
+ if (entry.isDirectory()) {
2596
+ sidecarStack.push(abs);
2597
+ continue;
2598
+ }
2599
+ const sideRel = this.toPosix(path6.relative(targetDir, abs));
2600
+ if (abs === path6.join(dest, "SKILL.md")) continue;
2601
+ const existingSide = lockfile.files[sideRel];
2602
+ const existingSideOwners = existingSide?.owners ?? [];
2603
+ const sideOwners = resolved.targetBundle && declaresAsset(`skills/${skillName}/SKILL.md`) ? Array.from(/* @__PURE__ */ new Set([...existingSideOwners, resolved.targetBundle])) : existingSideOwners;
2604
+ lockfile.files[sideRel] = {
2605
+ hash: await this.calculateHash(abs),
2606
+ bundle: existingSide?.bundle ?? resolved.targetBundle,
2607
+ owners: sideOwners,
2608
+ method: actualMethod,
2609
+ installedAt: existingSide?.installedAt ?? now,
2610
+ ...existingSide?.projectedTo ? { projectedTo: existingSide.projectedTo } : {}
2611
+ };
2612
+ }
2613
+ }
1698
2614
  if (!lockfile.installed.skills.includes(skillName)) {
1699
2615
  lockfile.installed.skills.push(skillName);
1700
2616
  }
1701
2617
  }
1702
2618
  for (const workflowFile of resolved.workflows) {
1703
- const src = path5.join(registryDir, "workflows", workflowFile);
1704
- const dest = path5.join(subPaths.workflowsDir, workflowFile);
2619
+ const src = path6.join(registryDir, "workflows", workflowFile);
2620
+ const dest = path6.join(subPaths.workflowsDir, workflowFile);
1705
2621
  const actualMethod = await this.deployFile(src, dest, method, options.force);
1706
2622
  const hash = await this.calculateHash(src);
1707
- const relPath = this.toPosix(path5.relative(targetDir, dest));
2623
+ const relPath = this.toPosix(path6.relative(targetDir, dest));
1708
2624
  const existing = lockfile.files[relPath];
1709
- const existingOwners = existing?.owners ?? (existing?.bundle ? [existing.bundle] : []);
1710
- const declaresAsset = declared.has(`workflows/${workflowFile}`);
1711
- const owners = resolved.targetBundle && declaresAsset ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
2625
+ const existingOwners = existing?.owners ?? [];
2626
+ const owners = resolved.targetBundle && declaresAsset(`workflows/${workflowFile}`) ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
1712
2627
  lockfile.files[relPath] = {
1713
2628
  hash,
1714
2629
  bundle: existing?.bundle ?? resolved.targetBundle,
@@ -1723,16 +2638,15 @@ ${YAML2.stringify(meta).trim()}
1723
2638
  }
1724
2639
  }
1725
2640
  for (const ruleFile of resolved.rules) {
1726
- const src = path5.join(registryDir, "rules", ruleFile);
1727
- const dest = path5.join(subPaths.rulesDir, ruleFile);
1728
- if (await fs3.pathExists(src)) {
2641
+ const src = path6.join(registryDir, "rules", ruleFile);
2642
+ const dest = path6.join(subPaths.rulesDir, ruleFile);
2643
+ if (await fs4.pathExists(src)) {
1729
2644
  const actualMethod = await this.deployFile(src, dest, method, options.force);
1730
2645
  const hash = await this.calculateHash(src);
1731
- const relPath = this.toPosix(path5.relative(targetDir, dest));
2646
+ const relPath = this.toPosix(path6.relative(targetDir, dest));
1732
2647
  const existing = lockfile.files[relPath];
1733
- const existingOwners = existing?.owners ?? (existing?.bundle ? [existing.bundle] : []);
1734
- const declaresAsset = true;
1735
- const owners = resolved.targetBundle && declaresAsset ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
2648
+ const existingOwners = existing?.owners ?? [];
2649
+ const owners = resolved.targetBundle ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
1736
2650
  lockfile.files[relPath] = {
1737
2651
  hash,
1738
2652
  bundle: existing?.bundle ?? resolved.targetBundle,
@@ -1755,26 +2669,26 @@ ${YAML2.stringify(meta).trim()}
1755
2669
  lockfile.bundleModes[resolved.targetBundle] = options.mode;
1756
2670
  }
1757
2671
  }
1758
- if (hasCanonicalAgents && effectiveFanout.length > 0 && path5.resolve(targetDir) === path5.resolve(agentsTarget)) {
2672
+ if (hasCanonicalAgents && effectiveFanout.length > 0 && path6.resolve(targetDir) === path6.resolve(agentsTarget)) {
1759
2673
  await this.applyFanout(effectiveFanout, resolved, registryDir, scope, targetDir, lockfile, options, projections);
1760
2674
  }
1761
- await fs3.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
2675
+ await fs4.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
1762
2676
  }
1763
2677
  return { installed: resolved, targetDirs, dryRun: false, method, projections };
1764
2678
  }
1765
2679
  };
1766
2680
 
1767
2681
  // src/core/uninstaller.ts
1768
- import path6 from "path";
2682
+ import path7 from "path";
1769
2683
  import crypto2 from "crypto";
1770
- import fs4 from "fs-extra";
2684
+ import fs5 from "fs-extra";
1771
2685
  var UninstallEngine = class {
1772
2686
  registry;
1773
2687
  constructor(registry2) {
1774
2688
  this.registry = registry2 || new RegistryResolver();
1775
2689
  }
1776
2690
  async calculateHash(filePath) {
1777
- const buffer = await fs4.readFile(filePath);
2691
+ const buffer = await fs5.readFile(filePath);
1778
2692
  return "sha256:" + crypto2.createHash("sha256").update(buffer).digest("hex");
1779
2693
  }
1780
2694
  /**
@@ -1783,42 +2697,47 @@ var UninstallEngine = class {
1783
2697
  * AGENTS.md bridge (agentsmd profile) is a plain markdown index with no
1784
2698
  * frontmatter, so the marker is checked against the whole content.
1785
2699
  */
1786
- async isManagedProjection(absPath, isAgentsMd) {
1787
- const content = await fs4.readFile(absPath, "utf8");
2700
+ async isManagedProjection(absPath, isAgentsMd, recordedHash) {
2701
+ const content = await fs5.readFile(absPath, "utf8");
1788
2702
  if (isAgentsMd) {
1789
2703
  return content.includes("managed-by: agents-united");
1790
2704
  }
1791
- return HostProjector.hasManagedMarker(content);
2705
+ if (HostProjector.hasManagedMarker(content)) return true;
2706
+ if (recordedHash) {
2707
+ const bytes = await fs5.readFile(absPath);
2708
+ return `sha256:${crypto2.createHash("sha256").update(bytes).digest("hex")}` === recordedHash;
2709
+ }
2710
+ return false;
1792
2711
  }
1793
2712
  /**
1794
2713
  * Removes empty projection dirs left behind after deleting a projected file
1795
2714
  * (e.g. `.claude/agents/` and then `.claude/`). Only ever removes empty dirs.
1796
2715
  */
1797
2716
  async removeEmptyProjectionDirs(workspaceRoot, projPath) {
1798
- let dir = path6.dirname(path6.join(workspaceRoot, projPath));
2717
+ let dir = path7.dirname(path7.join(workspaceRoot, projPath));
1799
2718
  while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {
1800
2719
  try {
1801
- const entries = await fs4.readdir(dir);
2720
+ const entries = await fs5.readdir(dir);
1802
2721
  if (entries.length > 0) break;
1803
- await fs4.rmdir(dir);
2722
+ await fs5.rmdir(dir);
1804
2723
  } catch {
1805
2724
  break;
1806
2725
  }
1807
- dir = path6.dirname(dir);
2726
+ dir = path7.dirname(dir);
1808
2727
  }
1809
2728
  }
1810
2729
  /**
1811
2730
  * Removes every recorded projection for a canonical asset, guarding against
1812
2731
  * clobbering user-modified files. Mirrors the hash-conflict error pattern.
1813
2732
  */
1814
- async removeProjections(workspaceRoot, projectedTo, force) {
2733
+ async removeProjections(workspaceRoot, projectedTo, force, projectionHashes) {
1815
2734
  for (const projPath of projectedTo) {
1816
- const absProjection = path6.join(workspaceRoot, projPath);
1817
- if (!await fs4.pathExists(absProjection)) {
2735
+ const absProjection = path7.join(workspaceRoot, projPath);
2736
+ if (!await fs5.pathExists(absProjection)) {
1818
2737
  continue;
1819
2738
  }
1820
2739
  const isAgentsMd = projPath === "AGENTS.md";
1821
- const managed = await this.isManagedProjection(absProjection, isAgentsMd);
2740
+ const managed = await this.isManagedProjection(absProjection, isAgentsMd, projectionHashes?.[projPath]?.hash);
1822
2741
  if (!managed && !force) {
1823
2742
  throw new Error(
1824
2743
  `Projection ${projPath} has user modifications. Use --force to remove.`
@@ -1830,14 +2749,14 @@ var UninstallEngine = class {
1830
2749
  }
1831
2750
  async safeRemoveProjection(absPath) {
1832
2751
  try {
1833
- const lstat = await fs4.lstat(absPath);
2752
+ const lstat = await fs5.lstat(absPath);
1834
2753
  if (lstat.isSymbolicLink()) {
1835
- await fs4.unlink(absPath);
2754
+ await fs5.unlink(absPath);
1836
2755
  return;
1837
2756
  }
1838
2757
  } catch {
1839
2758
  }
1840
- await fs4.remove(absPath);
2759
+ await fs5.remove(absPath);
1841
2760
  }
1842
2761
  /**
1843
2762
  * Installed-addon freshness (plan 003): after a child bundle (one with a
@@ -1866,9 +2785,9 @@ var UninstallEngine = class {
1866
2785
  for (const artifact of projection) {
1867
2786
  if (artifact.kind !== "rule" && artifact.kind !== "team-manifest") continue;
1868
2787
  if (artifact.content === void 0) continue;
1869
- const dest = path6.join(workspaceRoot, artifact.relPath);
1870
- if (!await fs4.pathExists(dest)) continue;
1871
- await fs4.writeFile(dest, artifact.content, "utf8");
2788
+ const dest = path7.join(workspaceRoot, artifact.relPath);
2789
+ if (!await fs5.pathExists(dest)) continue;
2790
+ await fs5.writeFile(dest, artifact.content, "utf8");
1872
2791
  const hash = await this.calculateHash(dest);
1873
2792
  if (lockfile.projections?.[artifact.relPath]) {
1874
2793
  lockfile.projections[artifact.relPath].hash = hash;
@@ -1890,15 +2809,15 @@ var UninstallEngine = class {
1890
2809
  return options.scope || "project";
1891
2810
  }
1892
2811
  async safeRemove(targetDir, relPath) {
1893
- const fullPath = path6.join(targetDir, relPath);
1894
- if (relPath.startsWith("skills" + path6.sep) || relPath.startsWith("skills/")) {
2812
+ const fullPath = path7.join(targetDir, relPath);
2813
+ if (relPath.startsWith("skills" + path7.sep) || relPath.startsWith("skills/")) {
1895
2814
  const parts = relPath.split(/[/\\]/);
1896
2815
  if (parts.length >= 2) {
1897
- const skillDir = path6.join(targetDir, parts[0], parts[1]);
2816
+ const skillDir = path7.join(targetDir, parts[0], parts[1]);
1898
2817
  try {
1899
- const lstat = await fs4.lstat(skillDir);
2818
+ const lstat = await fs5.lstat(skillDir);
1900
2819
  if (lstat.isSymbolicLink()) {
1901
- await fs4.unlink(skillDir);
2820
+ await fs5.unlink(skillDir);
1902
2821
  return;
1903
2822
  }
1904
2823
  } catch {
@@ -1906,14 +2825,14 @@ var UninstallEngine = class {
1906
2825
  }
1907
2826
  }
1908
2827
  try {
1909
- const lstat = await fs4.lstat(fullPath);
2828
+ const lstat = await fs5.lstat(fullPath);
1910
2829
  if (lstat.isSymbolicLink()) {
1911
- await fs4.unlink(fullPath);
2830
+ await fs5.unlink(fullPath);
1912
2831
  return;
1913
2832
  }
1914
2833
  } catch {
1915
2834
  }
1916
- await fs4.remove(fullPath);
2835
+ await fs5.remove(fullPath);
1917
2836
  }
1918
2837
  async uninstall(identifier, options = {}) {
1919
2838
  const scope = this.parseScope(options);
@@ -1921,30 +2840,92 @@ var UninstallEngine = class {
1921
2840
  const targetDirs = hosts.map((h) => AgentHostAdapter.resolveHostDir(scope, h, options.targetDir));
1922
2841
  const resolved = await this.registry.resolve(identifier).catch(() => null);
1923
2842
  const totalRemoved = [];
2843
+ const totalKept = [];
2844
+ const totalStale = [];
2845
+ const retainedOwners = /* @__PURE__ */ new Set();
1924
2846
  for (const targetDir of targetDirs) {
1925
2847
  const subPaths = AgentHostAdapter.getSubPaths(targetDir);
1926
- if (!await fs4.pathExists(subPaths.lockfile)) {
2848
+ if (!await fs5.pathExists(subPaths.lockfile)) {
1927
2849
  continue;
1928
2850
  }
1929
- const lockfile = await fs4.readJson(subPaths.lockfile);
2851
+ const lockfile = await fs5.readJson(subPaths.lockfile);
1930
2852
  const removedFiles = [];
2853
+ const keptFiles = [];
2854
+ const staleRecords = [];
2855
+ const projectionOwners = (projRelPath, proj) => {
2856
+ const own = proj.owners ?? [];
2857
+ if (projRelPath.startsWith(".agents/plugins/")) return own;
2858
+ const canonical = proj.canonical ?? "";
2859
+ const rec = lockfile.files[canonical] ?? lockfile.files[canonical.replace(/^\.agents\//, "")];
2860
+ return Array.from(/* @__PURE__ */ new Set([...own, ...assetOwners(rec)]));
2861
+ };
2862
+ const survivingCoverage = async (source, removedId) => {
2863
+ const survivors = lockfile.installed.bundles.filter((b) => b !== removedId);
2864
+ const covering = [];
2865
+ for (const id of survivors) {
2866
+ try {
2867
+ const def = await this.registry.getBundle(id);
2868
+ const have = /* @__PURE__ */ new Set([]);
2869
+ if (def) {
2870
+ if (def.orchestrator) have.add(`agents/${def.orchestrator}`);
2871
+ (def.agents || []).forEach((a) => have.add(`agents/${a}`));
2872
+ (def.skills || []).forEach((s) => have.add(`skills/${s}/SKILL.md`));
2873
+ (def.workflows || []).forEach((w) => have.add(`workflows/${w}`));
2874
+ (def.rules || []).forEach((rl) => have.add(`rules/${rl}`));
2875
+ } else {
2876
+ const r = await this.registry.resolve(id);
2877
+ r.agents.forEach((f) => have.add(`agents/${f}`));
2878
+ r.skills.forEach((s) => have.add(`skills/${s}/SKILL.md`));
2879
+ (r.workflows || []).forEach((w) => have.add(`workflows/${w}`));
2880
+ (r.rules || []).forEach((rl) => have.add(`rules/${rl}`));
2881
+ }
2882
+ if (source.asset) {
2883
+ if (have.has(source.asset)) covering.push(id);
2884
+ } else if (source.declaringBundle) {
2885
+ const def2 = await this.registry.getBundle(source.declaringBundle);
2886
+ if (!def2) continue;
2887
+ const want = /* @__PURE__ */ new Set([]);
2888
+ if (def2.orchestrator) want.add(`agents/${def2.orchestrator}`);
2889
+ (def2.agents || []).forEach((a) => want.add(`agents/${a}`));
2890
+ (def2.skills || []).forEach((s) => want.add(`skills/${s}/SKILL.md`));
2891
+ (def2.workflows || []).forEach((w) => want.add(`workflows/${w}`));
2892
+ (def2.rules || []).forEach((rl) => want.add(`rules/${rl}`));
2893
+ if (want.size > 0 && [...want].every((k) => have.has(k))) covering.push(id);
2894
+ }
2895
+ } catch {
2896
+ }
2897
+ }
2898
+ return covering;
2899
+ };
1931
2900
  if (resolved && resolved.targetBundle) {
1932
2901
  const bundleName = resolved.targetBundle;
1933
- if (lockfile.installed.bundles.includes(bundleName)) {
2902
+ const ownsSomething = Object.values(lockfile.files).some((m) => assetOwners(m).includes(bundleName)) || Object.values(lockfile.projections || {}).some((p) => (p.owners ?? []).includes(bundleName));
2903
+ if (lockfile.installed.bundles.includes(bundleName) || ownsSomething) {
1934
2904
  if (!options.dryRun) {
1935
- const workspaceRoot = path6.resolve(path6.dirname(targetDir));
1936
- const ownedFiles = Object.entries(lockfile.files).filter(([, m]) => (m.owners ?? (m.bundle ? [m.bundle] : [])).includes(bundleName));
1937
- const ownedProjections = lockfile.projections ? Object.values(lockfile.projections).filter((p) => p.owners.includes(bundleName)).length : 0;
2905
+ const workspaceRoot = path7.resolve(path7.dirname(targetDir));
2906
+ const ownedFiles = Object.entries(lockfile.files).filter(([, m]) => assetOwners(m).includes(bundleName));
2907
+ const ownedProjections = lockfile.projections ? Object.entries(lockfile.projections).filter(([p, v]) => projectionOwners(p, v).includes(bundleName)).length : 0;
1938
2908
  if (ownedFiles.length === 0 && ownedProjections === 0) {
1939
2909
  throw new Error(`No installed assets found matching "${bundleName}".`);
1940
2910
  }
1941
2911
  if (lockfile.projections) {
1942
2912
  for (const [projRelPath, proj] of Object.entries(lockfile.projections)) {
1943
- if (proj.owners.includes(bundleName)) {
1944
- proj.owners = proj.owners.filter((o) => o !== bundleName);
2913
+ const owners = projectionOwners(projRelPath, proj);
2914
+ if (owners.includes(bundleName)) {
2915
+ proj.owners = owners.filter((o) => o !== bundleName);
1945
2916
  if (proj.owners.length === 0) {
1946
- const absProjection = path6.join(workspaceRoot, projRelPath);
1947
- if (await fs4.pathExists(absProjection)) {
2917
+ const declaringMatch = /^\.agents\/plugins\/([^/]+)\//.exec(projRelPath);
2918
+ const derivedCoordination = Boolean(declaringMatch) || proj.kind === "rule" || proj.kind === "team-manifest" || proj.kind === "plugin-manifest";
2919
+ const covering = derivedCoordination ? await survivingCoverage({ declaringBundle: declaringMatch ? declaringMatch[1] : bundleName }, bundleName) : [];
2920
+ if (covering.length > 0) {
2921
+ proj.owners = covering;
2922
+ keptFiles.push(projRelPath);
2923
+ covering.forEach((o) => retainedOwners.add(o));
2924
+ continue;
2925
+ }
2926
+ const absProjection = path7.join(workspaceRoot, projRelPath);
2927
+ const existedOnDisk = await fs5.pathExists(absProjection);
2928
+ if (existedOnDisk) {
1948
2929
  if (proj.managedMarker) {
1949
2930
  const managed = await this.isManagedProjection(absProjection, projRelPath === "AGENTS.md");
1950
2931
  if (!managed && !options.force) {
@@ -1960,20 +2941,36 @@ var UninstallEngine = class {
1960
2941
  await this.removeEmptyProjectionDirs(workspaceRoot, projRelPath);
1961
2942
  }
1962
2943
  delete lockfile.projections[projRelPath];
2944
+ if (existedOnDisk) {
2945
+ removedFiles.push(projRelPath);
2946
+ } else {
2947
+ staleRecords.push(projRelPath);
2948
+ }
2949
+ const canonicalKey = (proj.canonical ?? "").replace(/^\.agents\//, "");
2950
+ for (const key of /* @__PURE__ */ new Set([proj.canonical ?? "", canonicalKey])) {
2951
+ const rec = key ? lockfile.files[key] : void 0;
2952
+ if (rec?.projectedTo) {
2953
+ rec.projectedTo = rec.projectedTo.filter((p) => p !== projRelPath);
2954
+ if (rec.projectedTo.length === 0) delete rec.projectedTo;
2955
+ }
2956
+ }
2957
+ } else {
2958
+ keptFiles.push(projRelPath);
2959
+ proj.owners.forEach((o) => retainedOwners.add(o));
1963
2960
  }
1964
2961
  }
1965
2962
  }
1966
2963
  }
1967
2964
  for (const [relPath, assetMeta] of Object.entries(lockfile.files)) {
1968
- const assetOwners = assetMeta.owners ?? (assetMeta.bundle ? [assetMeta.bundle] : []);
1969
- if (!assetOwners.includes(bundleName)) continue;
1970
- const newOwners = assetOwners.filter((o) => o !== bundleName);
2965
+ const recordOwners = assetOwners(assetMeta);
2966
+ if (!recordOwners.includes(bundleName)) continue;
2967
+ const newOwners = recordOwners.filter((o) => o !== bundleName);
1971
2968
  if (newOwners.length === 0) {
1972
2969
  if (assetMeta.projectedTo && assetMeta.projectedTo.length > 0) {
1973
- await this.removeProjections(workspaceRoot, assetMeta.projectedTo, options.force);
2970
+ await this.removeProjections(workspaceRoot, assetMeta.projectedTo, options.force, lockfile.projections);
1974
2971
  }
1975
- const fullPath = path6.join(targetDir, relPath);
1976
- if (await fs4.pathExists(fullPath) || await fs4.pathExists(fullPath).catch(() => false)) {
2972
+ const fullPath = path7.join(targetDir, relPath);
2973
+ if (await fs5.pathExists(fullPath) || await fs5.pathExists(fullPath).catch(() => false)) {
1977
2974
  if (!options.force && assetMeta.method !== "symlink") {
1978
2975
  const currentHash = await this.calculateHash(fullPath).catch(() => null);
1979
2976
  if (currentHash && currentHash !== assetMeta.hash) {
@@ -1986,6 +2983,16 @@ var UninstallEngine = class {
1986
2983
  delete lockfile.files[relPath];
1987
2984
  } else {
1988
2985
  lockfile.files[relPath] = { ...assetMeta, owners: newOwners };
2986
+ keptFiles.push(relPath);
2987
+ newOwners.forEach((o) => retainedOwners.add(o));
2988
+ }
2989
+ }
2990
+ for (const dir of [subPaths.agentsDir, subPaths.skillsDir, subPaths.workflowsDir, subPaths.rulesDir]) {
2991
+ if (await fs5.pathExists(dir)) {
2992
+ const entries = await fs5.readdir(dir).catch(() => []);
2993
+ if (entries.length === 0) {
2994
+ await fs5.remove(dir);
2995
+ }
1989
2996
  }
1990
2997
  }
1991
2998
  lockfile.installed.bundles = lockfile.installed.bundles.filter((b) => b !== bundleName);
@@ -1996,7 +3003,12 @@ var UninstallEngine = class {
1996
3003
  const survival = /* @__PURE__ */ new Set();
1997
3004
  for (const b of surviving) {
1998
3005
  const bdef = await this.registry.getBundle(b);
1999
- if (!bdef) continue;
3006
+ if (!bdef) {
3007
+ for (const [relPath, meta] of Object.entries(lockfile.files)) {
3008
+ if (assetOwners(meta).includes(b)) survival.add(relPath);
3009
+ }
3010
+ continue;
3011
+ }
2000
3012
  if (bdef.orchestrator) survival.add(`agents/${bdef.orchestrator}`);
2001
3013
  (bdef.agents || []).forEach((a) => survival.add(`agents/${a}`));
2002
3014
  (bdef.skills || []).forEach((s) => survival.add(`skills/${s}/SKILL.md`));
@@ -2008,17 +3020,30 @@ var UninstallEngine = class {
2008
3020
  lockfile.installed.workflows = lockfile.installed.workflows.filter((w) => survival.has(`workflows/${w}`));
2009
3021
  }
2010
3022
  await this.refreshParentCoordination(bundleName, workspaceRoot, lockfile, scope);
2011
- await fs4.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
3023
+ await fs5.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
2012
3024
  } else {
2013
- removedFiles.push(...resolved.agents, ...resolved.skills, ...resolved.workflows);
3025
+ for (const [relPath, m] of Object.entries(lockfile.files)) {
3026
+ const owners = assetOwners(m);
3027
+ if (!owners.includes(bundleName)) continue;
3028
+ const remaining = owners.filter((o) => o !== bundleName);
3029
+ (remaining.length === 0 ? removedFiles : keptFiles).push(relPath);
3030
+ remaining.forEach((o) => retainedOwners.add(o));
3031
+ }
3032
+ for (const [projRelPath, proj] of Object.entries(lockfile.projections || {})) {
3033
+ const owners = projectionOwners(projRelPath, proj);
3034
+ if (!owners.includes(bundleName)) continue;
3035
+ const remaining = owners.filter((o) => o !== bundleName);
3036
+ (remaining.length === 0 ? removedFiles : keptFiles).push(projRelPath);
3037
+ remaining.forEach((o) => retainedOwners.add(o));
3038
+ }
2014
3039
  }
2015
3040
  }
2016
3041
  } else {
2017
3042
  const itemTargetRelPaths = Object.keys(lockfile.files).filter((p) => p.includes(identifier));
2018
3043
  if (!options.dryRun) {
2019
3044
  for (const relPath of itemTargetRelPaths) {
2020
- const fullPath = path6.join(targetDir, relPath);
2021
- if (await fs4.pathExists(fullPath) || await fs4.pathExists(fullPath).catch(() => false)) {
3045
+ const fullPath = path7.join(targetDir, relPath);
3046
+ if (await fs5.pathExists(fullPath) || await fs5.pathExists(fullPath).catch(() => false)) {
2022
3047
  if (!options.force && lockfile.files[relPath]?.method !== "symlink") {
2023
3048
  const currentHash = await this.calculateHash(fullPath).catch(() => null);
2024
3049
  const assetMeta = lockfile.files[relPath];
@@ -2031,24 +3056,33 @@ var UninstallEngine = class {
2031
3056
  }
2032
3057
  delete lockfile.files[relPath];
2033
3058
  }
2034
- await fs4.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
3059
+ await fs5.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
2035
3060
  } else {
2036
3061
  removedFiles.push(...itemTargetRelPaths);
2037
3062
  }
2038
3063
  }
2039
3064
  totalRemoved.push(...removedFiles);
3065
+ totalKept.push(...keptFiles);
3066
+ totalStale.push(...staleRecords);
2040
3067
  }
2041
- if (totalRemoved.length === 0 && !options.dryRun) {
3068
+ if (totalRemoved.length === 0 && totalKept.length === 0 && totalStale.length === 0 && !options.dryRun) {
2042
3069
  throw new Error(`No installed assets found matching "${identifier}".`);
2043
3070
  }
2044
- return { removed: totalRemoved, targetDirs, dryRun: options.dryRun || false };
3071
+ return {
3072
+ removed: totalRemoved,
3073
+ kept: totalKept,
3074
+ staleRecords: totalStale,
3075
+ retainedOwners: [...retainedOwners].sort(),
3076
+ targetDirs,
3077
+ dryRun: options.dryRun || false
3078
+ };
2045
3079
  }
2046
3080
  };
2047
3081
 
2048
3082
  // src/core/inventory.ts
2049
- import path7 from "path";
3083
+ import path8 from "path";
2050
3084
  import os2 from "os";
2051
- import fs5 from "fs-extra";
3085
+ import fs6 from "fs-extra";
2052
3086
  var InventoryScanner = class _InventoryScanner {
2053
3087
  registry;
2054
3088
  constructor(registry2) {
@@ -2059,10 +3093,10 @@ var InventoryScanner = class _InventoryScanner {
2059
3093
  const cwd = process.cwd();
2060
3094
  let prettyPath = targetDir;
2061
3095
  if (targetDir.startsWith(cwd)) {
2062
- const rel = path7.relative(cwd, targetDir);
3096
+ const rel = path8.relative(cwd, targetDir);
2063
3097
  prettyPath = rel ? `./${rel.replace(/\\/g, "/")}` : ".";
2064
3098
  } else if (targetDir.startsWith(home)) {
2065
- const rel = path7.relative(home, targetDir);
3099
+ const rel = path8.relative(home, targetDir);
2066
3100
  prettyPath = `~/${rel.replace(/\\/g, "/")}`;
2067
3101
  }
2068
3102
  const fanoutSuffix = fanout && fanout.length > 0 ? ` + ${fanout.join(", ")}` : "";
@@ -2092,7 +3126,7 @@ var InventoryScanner = class _InventoryScanner {
2092
3126
  const candidateDirs = [];
2093
3127
  if (options.targetDir) {
2094
3128
  candidateDirs.push({
2095
- dir: path7.resolve(options.targetDir),
3129
+ dir: path8.resolve(options.targetDir),
2096
3130
  scope: options.scope || "project",
2097
3131
  host: hosts[0] || "agents"
2098
3132
  });
@@ -2114,10 +3148,10 @@ var InventoryScanner = class _InventoryScanner {
2114
3148
  const canonicalDir = AgentHostAdapter.resolveHostDir(scope, "agents", options.targetDir);
2115
3149
  let displayLoc = canonicalDir;
2116
3150
  if (canonicalDir.startsWith(cwd)) {
2117
- const rel = path7.relative(cwd, canonicalDir);
3151
+ const rel = path8.relative(cwd, canonicalDir);
2118
3152
  displayLoc = rel ? `./${rel.replace(/\\/g, "/")}` : ".";
2119
3153
  } else if (canonicalDir.startsWith(home)) {
2120
- const rel = path7.relative(home, canonicalDir);
3154
+ const rel = path8.relative(home, canonicalDir);
2121
3155
  displayLoc = `~/${rel.replace(/\\/g, "/")}`;
2122
3156
  }
2123
3157
  scannedLocationsMap.set(`${scope}:${canonicalDir}`, {
@@ -2129,14 +3163,14 @@ var InventoryScanner = class _InventoryScanner {
2129
3163
  });
2130
3164
  }
2131
3165
  for (const item of candidateDirs) {
2132
- const lockfilePath = path7.join(item.dir, "agents-united.json");
2133
- if (!await fs5.pathExists(lockfilePath)) {
3166
+ const lockfilePath = path8.join(item.dir, "agents-united.json");
3167
+ if (!await fs6.pathExists(lockfilePath)) {
2134
3168
  continue;
2135
3169
  }
2136
3170
  scannedTargetDirs.add(item.dir);
2137
3171
  let lockfile;
2138
3172
  try {
2139
- lockfile = await fs5.readJson(lockfilePath);
3173
+ lockfile = await fs6.readJson(lockfilePath);
2140
3174
  } catch {
2141
3175
  continue;
2142
3176
  }
@@ -2285,10 +3319,10 @@ var InventoryScanner = class _InventoryScanner {
2285
3319
  };
2286
3320
 
2287
3321
  // src/core/updater.ts
2288
- import path8 from "path";
3322
+ import path9 from "path";
2289
3323
  import crypto3 from "crypto";
2290
- import fs6 from "fs-extra";
2291
- var UpdateEngine = class {
3324
+ import fs7 from "fs-extra";
3325
+ var UpdateEngine = class _UpdateEngine {
2292
3326
  registry;
2293
3327
  scanner;
2294
3328
  installer;
@@ -2310,7 +3344,7 @@ var UpdateEngine = class {
2310
3344
  return deduplicated;
2311
3345
  }
2312
3346
  async calculateHash(filePath) {
2313
- const buffer = await fs6.readFile(filePath);
3347
+ const buffer = await fs7.readFile(filePath);
2314
3348
  return "sha256:" + crypto3.createHash("sha256").update(buffer).digest("hex");
2315
3349
  }
2316
3350
  /**
@@ -2318,26 +3352,31 @@ var UpdateEngine = class {
2318
3352
  * M5). Agent projections place the marker as the first body line after frontmatter;
2319
3353
  * the AGENTS.md bridge has no frontmatter so it is checked over the whole content.
2320
3354
  */
2321
- async isManagedProjection(absPath, isAgentsMd) {
2322
- const content = await fs6.readFile(absPath, "utf8");
3355
+ async isManagedProjection(absPath, isAgentsMd, recordedHash) {
3356
+ const content = await fs7.readFile(absPath, "utf8");
2323
3357
  if (isAgentsMd) {
2324
3358
  return content.includes("managed-by: agents-united");
2325
3359
  }
2326
- return HostProjector.hasManagedMarker(content);
3360
+ if (HostProjector.hasManagedMarker(content)) return true;
3361
+ if (recordedHash) {
3362
+ const bytes = await fs7.readFile(absPath);
3363
+ return `sha256:${crypto3.createHash("sha256").update(bytes).digest("hex")}` === recordedHash;
3364
+ }
3365
+ return false;
2327
3366
  }
2328
3367
  /**
2329
3368
  * Returns the first user-modified projection path belonging to a record, or '' when
2330
3369
  * every recorded projection still carries the managed marker.
2331
3370
  */
2332
3371
  async staleProjection(record, lockfile) {
2333
- let workspaceRoot = path8.dirname(record.targetDir);
3372
+ let workspaceRoot = path9.dirname(record.targetDir);
2334
3373
  for (const assetMeta of Object.values(lockfile.files)) {
2335
3374
  if (assetMeta.bundle !== record.name) continue;
2336
3375
  if (!assetMeta.projectedTo || assetMeta.projectedTo.length === 0) continue;
2337
3376
  for (const projPath of assetMeta.projectedTo) {
2338
- const absProjection = path8.join(workspaceRoot, projPath);
2339
- if (await fs6.pathExists(absProjection)) {
2340
- const managed = await this.isManagedProjection(absProjection, projPath === "AGENTS.md");
3377
+ const absProjection = path9.join(workspaceRoot, projPath);
3378
+ if (await fs7.pathExists(absProjection)) {
3379
+ const managed = await this.isManagedProjection(absProjection, projPath === "AGENTS.md", lockfile.projections?.[projPath]?.hash);
2341
3380
  if (!managed) {
2342
3381
  return projPath;
2343
3382
  }
@@ -2346,6 +3385,79 @@ var UpdateEngine = class {
2346
3385
  }
2347
3386
  return "";
2348
3387
  }
3388
+ /** Plan 016 Step 4 — the canonical asset whose dialect name changed (ADR 0016 pattern). */
3389
+ static LEGACY_GENERATIVE_UI_CANONICAL = "skills/generative_ui/SKILL.md";
3390
+ static LEGACY_GENERATIVE_UI_PATH = /(^|\/)generative_ui\/SKILL\.md$/;
3391
+ /** Removes empty projection dirs left behind by the migration (never a non-empty one). */
3392
+ async removeEmptyProjectionDirs(workspaceRoot, projPath) {
3393
+ let dir = path9.dirname(path9.join(workspaceRoot, projPath));
3394
+ while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {
3395
+ try {
3396
+ const entries = await fs7.readdir(dir);
3397
+ if (entries.length > 0) break;
3398
+ await fs7.rmdir(dir);
3399
+ } catch {
3400
+ break;
3401
+ }
3402
+ dir = path9.dirname(dir);
3403
+ }
3404
+ }
3405
+ /** Drops one projected path from every canonical file record (host-agnostic). */
3406
+ dropProjectedTo(lockfile, projectedRelPath) {
3407
+ const normProj = projectedRelPath.replace(/\\/g, "/");
3408
+ for (const asset of Object.values(lockfile.files)) {
3409
+ if (!asset.projectedTo || asset.projectedTo.length === 0) continue;
3410
+ const kept = asset.projectedTo.filter((p) => p !== normProj);
3411
+ if (kept.length === asset.projectedTo.length) continue;
3412
+ if (kept.length === 0) {
3413
+ delete asset.projectedTo;
3414
+ } else {
3415
+ asset.projectedTo = kept;
3416
+ }
3417
+ }
3418
+ }
3419
+ /**
3420
+ * Plan 016 Step 4 — silent, bounded, idempotent migration of the `generative_ui`
3421
+ * projection rename (ADR 0016 pattern).
3422
+ *
3423
+ * `registry/skills/generative_ui/` is deliberately NOT renamed: the underscore is
3424
+ * invalid in every host dialect, so the projectors normalize the name at projection
3425
+ * time (`generative_ui` -> `generative-ui`; ADR 0018 decision 2 — normalization is
3426
+ * projection-level). Projections recorded before that change therefore still point at
3427
+ * `.claude/skills/generative_ui/SKILL.md` and/or the Cline plugin copy
3428
+ * `.agents/plugins/<bundle>/skills/generative_ui/SKILL.md`, which the current renderers
3429
+ * no longer produce.
3430
+ *
3431
+ * Runs before each record's re-install so the freshly rendered normalized path is
3432
+ * registered by the normal install path. Guarantees: (a) a file that lacks the managed
3433
+ * marker is never deleted (its lockfile entries are left untouched too, so the
3434
+ * migration never lies about a file it did not remove); (b) a second run is a no-op
3435
+ * because the entries are gone; (c) it is host-agnostic, so it covers `.claude/**` and
3436
+ * the Cline plugin lane alike, and it never touches the canonical registry store.
3437
+ */
3438
+ async migrateLegacyGenerativeUiProjections(workspaceRoot, lockfile) {
3439
+ const projections = lockfile.projections;
3440
+ if (!projections) return [];
3441
+ const normalized = ClaudeProjector.normalizeSkillName("generative_ui");
3442
+ const removed = [];
3443
+ for (const [projRelPath, proj] of Object.entries(projections)) {
3444
+ const posixPath = projRelPath.replace(/\\/g, "/");
3445
+ const isLegacy = proj.canonical === _UpdateEngine.LEGACY_GENERATIVE_UI_CANONICAL || _UpdateEngine.LEGACY_GENERATIVE_UI_PATH.test(posixPath);
3446
+ if (!isLegacy) continue;
3447
+ if (posixPath.includes(`/${normalized}/`)) continue;
3448
+ const absProjection = path9.join(workspaceRoot, projRelPath);
3449
+ if (await fs7.pathExists(absProjection)) {
3450
+ const managed = await this.isManagedProjection(absProjection, posixPath === "AGENTS.md");
3451
+ if (!managed) continue;
3452
+ await fs7.remove(absProjection);
3453
+ await this.removeEmptyProjectionDirs(workspaceRoot, projRelPath);
3454
+ }
3455
+ delete projections[projRelPath];
3456
+ this.dropProjectedTo(lockfile, projRelPath);
3457
+ removed.push(projRelPath);
3458
+ }
3459
+ return removed;
3460
+ }
2349
3461
  async checkUpdates(options = {}) {
2350
3462
  const inventory = await this.scanner.scan(options);
2351
3463
  const items = [];
@@ -2397,8 +3509,8 @@ var UpdateEngine = class {
2397
3509
  if (options.dryRun) {
2398
3510
  const dryProjections = [];
2399
3511
  for (const record of recordsToUpdate) {
2400
- const lockfilePath = path8.join(record.targetDir, "agents-united.json");
2401
- if (await fs6.pathExists(lockfilePath)) {
3512
+ const lockfilePath = path9.join(record.targetDir, "agents-united.json");
3513
+ if (await fs7.pathExists(lockfilePath)) {
2402
3514
  const dryResult = await this.installer.install(record.name, {
2403
3515
  scope: record.scope,
2404
3516
  method: record.method,
@@ -2424,18 +3536,18 @@ var UpdateEngine = class {
2424
3536
  const skipped = [];
2425
3537
  const collectedProjections = [];
2426
3538
  for (const record of recordsToUpdate) {
2427
- const lockfilePath = path8.join(record.targetDir, "agents-united.json");
2428
- if (!await fs6.pathExists(lockfilePath)) {
3539
+ const lockfilePath = path9.join(record.targetDir, "agents-united.json");
3540
+ if (!await fs7.pathExists(lockfilePath)) {
2429
3541
  continue;
2430
3542
  }
2431
- const lockfile = await fs6.readJson(lockfilePath);
3543
+ const lockfile = await fs7.readJson(lockfilePath);
2432
3544
  if (record.method === "copy" && !options.force && lockfile.files) {
2433
3545
  let hasConflict = false;
2434
3546
  let conflictRelPath = "";
2435
3547
  for (const [relPath, assetMeta] of Object.entries(lockfile.files)) {
2436
3548
  if (assetMeta.bundle === record.name || relPath.includes(record.name)) {
2437
- const fullPath = path8.join(record.targetDir, relPath);
2438
- if (await fs6.pathExists(fullPath)) {
3549
+ const fullPath = path9.join(record.targetDir, relPath);
3550
+ if (await fs7.pathExists(fullPath)) {
2439
3551
  const currentHash = await this.calculateHash(fullPath).catch(() => null);
2440
3552
  if (currentHash && currentHash !== assetMeta.hash) {
2441
3553
  hasConflict = true;
@@ -2463,6 +3575,13 @@ var UpdateEngine = class {
2463
3575
  continue;
2464
3576
  }
2465
3577
  }
3578
+ const migratedGenerativeUi = await this.migrateLegacyGenerativeUiProjections(
3579
+ path9.dirname(record.targetDir),
3580
+ lockfile
3581
+ );
3582
+ if (migratedGenerativeUi.length > 0) {
3583
+ await fs7.writeJson(lockfilePath, lockfile, { spaces: 2 });
3584
+ }
2466
3585
  const installResult = await this.installer.install(record.name, {
2467
3586
  scope: record.scope,
2468
3587
  method: record.method,
@@ -2475,11 +3594,11 @@ var UpdateEngine = class {
2475
3594
  if (installResult.projections) {
2476
3595
  collectedProjections.push(...installResult.projections);
2477
3596
  }
2478
- if (await fs6.pathExists(lockfilePath)) {
2479
- const updatedLockfile = await fs6.readJson(lockfilePath);
3597
+ if (await fs7.pathExists(lockfilePath)) {
3598
+ const updatedLockfile = await fs7.readJson(lockfilePath);
2480
3599
  updatedLockfile.bundleVersions = updatedLockfile.bundleVersions || {};
2481
3600
  updatedLockfile.bundleVersions[record.name] = record.upstreamVersion;
2482
- await fs6.writeJson(lockfilePath, updatedLockfile, { spaces: 2 });
3601
+ await fs7.writeJson(lockfilePath, updatedLockfile, { spaces: 2 });
2483
3602
  if (updatedLockfile.fanout) {
2484
3603
  record.fanout = updatedLockfile.fanout;
2485
3604
  record.projections = updatedLockfile.fanout;
@@ -2506,14 +3625,18 @@ var UpdateEngine = class {
2506
3625
  };
2507
3626
 
2508
3627
  // src/core/doctor.ts
2509
- import path10 from "path";
2510
- import fs8 from "fs-extra";
3628
+ import path12 from "path";
3629
+ import fs10 from "fs-extra";
2511
3630
  import crypto4 from "crypto";
2512
3631
  import YAML3 from "yaml";
2513
3632
 
3633
+ // src/core/claude-capabilities.ts
3634
+ import path11 from "path";
3635
+ import fs9 from "fs-extra";
3636
+
2514
3637
  // src/core/cline-capabilities.ts
2515
- import path9 from "path";
2516
- import fs7 from "fs-extra";
3638
+ import path10 from "path";
3639
+ import fs8 from "fs-extra";
2517
3640
  import { execFile } from "child_process";
2518
3641
  var defaultProcessRunner = (executable, args, options) => {
2519
3642
  return new Promise((resolve) => {
@@ -2555,7 +3678,165 @@ var defaultProcessRunner = (executable, args, options) => {
2555
3678
  }
2556
3679
  });
2557
3680
  };
2558
- var ClineCapabilityProbe = class {
3681
+ var ClineCapabilityProbe = class {
3682
+ runner;
3683
+ customResolver;
3684
+ constructor(runner, options) {
3685
+ this.runner = runner || defaultProcessRunner;
3686
+ this.customResolver = options?.resolveExecutable;
3687
+ }
3688
+ /**
3689
+ * Safe executable resolution across POSIX and Windows layouts.
3690
+ */
3691
+ resolveExecutable() {
3692
+ if (this.customResolver) {
3693
+ return this.customResolver();
3694
+ }
3695
+ const envBin = process.env.CLINE_BIN_PATH;
3696
+ if (envBin && path10.isAbsolute(envBin) && fs8.existsSync(envBin)) {
3697
+ return {
3698
+ executable: envBin,
3699
+ prefixArgs: [],
3700
+ source: "env-binary"
3701
+ };
3702
+ }
3703
+ const pathEnv = process.env.PATH || "";
3704
+ const pathSeparator = process.platform === "win32" ? ";" : ":";
3705
+ const dirs = pathEnv.split(pathSeparator).filter(Boolean);
3706
+ if (process.platform === "win32") {
3707
+ const exeNames = ["cline.cmd", "cline.bat", "cline.exe", "cline"];
3708
+ for (const dir of dirs) {
3709
+ for (const exeName of exeNames) {
3710
+ const fullPath = path10.join(dir, exeName);
3711
+ if (fs8.existsSync(fullPath)) {
3712
+ const adjacentWrapper1 = path10.join(dir, "node_modules", "cline", "bin", "cline");
3713
+ const adjacentWrapper2 = path10.join(dir, "node_modules", "@cline", "cline", "bin", "cline");
3714
+ if (fs8.existsSync(adjacentWrapper1)) {
3715
+ return {
3716
+ executable: process.execPath,
3717
+ prefixArgs: [adjacentWrapper1],
3718
+ source: "node-wrapper"
3719
+ };
3720
+ }
3721
+ if (fs8.existsSync(adjacentWrapper2)) {
3722
+ return {
3723
+ executable: process.execPath,
3724
+ prefixArgs: [adjacentWrapper2],
3725
+ source: "node-wrapper"
3726
+ };
3727
+ }
3728
+ const ext = path10.extname(fullPath).toLowerCase();
3729
+ if (ext === ".cmd" || ext === ".bat") {
3730
+ return {
3731
+ executable: "cmd.exe",
3732
+ prefixArgs: ["/c", fullPath],
3733
+ source: "path-executable"
3734
+ };
3735
+ }
3736
+ return {
3737
+ executable: fullPath,
3738
+ prefixArgs: [],
3739
+ source: "path-executable"
3740
+ };
3741
+ }
3742
+ }
3743
+ }
3744
+ } else {
3745
+ for (const dir of dirs) {
3746
+ const fullPath = path10.join(dir, "cline");
3747
+ if (fs8.existsSync(fullPath)) {
3748
+ return {
3749
+ executable: fullPath,
3750
+ prefixArgs: [],
3751
+ source: "path-executable"
3752
+ };
3753
+ }
3754
+ }
3755
+ }
3756
+ return null;
3757
+ }
3758
+ /**
3759
+ * Run read-only capability probe against local Cline.
3760
+ */
3761
+ async probe() {
3762
+ const cmd = this.resolveExecutable();
3763
+ const diagnostics = [];
3764
+ if (!cmd) {
3765
+ diagnostics.push("Cline executable not found on PATH or via CLINE_BIN_PATH.");
3766
+ return {
3767
+ installed: false,
3768
+ namedTeams: false,
3769
+ rolePresetConsumer: "unknown",
3770
+ diagnostics
3771
+ };
3772
+ }
3773
+ let version;
3774
+ try {
3775
+ const verRes = await this.runner(cmd.executable, [...cmd.prefixArgs, "version"], {
3776
+ timeoutMs: 5e3
3777
+ });
3778
+ if (verRes.exitCode === 0) {
3779
+ version = verRes.stdout.trim().split("\n")[0]?.trim();
3780
+ } else {
3781
+ diagnostics.push(`Cline version check failed (exit code ${verRes.exitCode}): ${verRes.stderr.trim()}`);
3782
+ return {
3783
+ installed: false,
3784
+ command: cmd,
3785
+ namedTeams: false,
3786
+ rolePresetConsumer: "unknown",
3787
+ diagnostics
3788
+ };
3789
+ }
3790
+ } catch (err) {
3791
+ const msg = err instanceof Error ? err.message : String(err);
3792
+ diagnostics.push(`Error executing Cline version check: ${msg}`);
3793
+ return {
3794
+ installed: false,
3795
+ command: cmd,
3796
+ namedTeams: false,
3797
+ rolePresetConsumer: "unknown",
3798
+ diagnostics
3799
+ };
3800
+ }
3801
+ let namedTeams = false;
3802
+ try {
3803
+ const probeRes = await this.runner(
3804
+ cmd.executable,
3805
+ [...cmd.prefixArgs, "--team-name", "agents-united-capability-probe", "version"],
3806
+ { timeoutMs: 5e3 }
3807
+ );
3808
+ if (probeRes.exitCode === 0) {
3809
+ namedTeams = true;
3810
+ } else {
3811
+ diagnostics.push(`Named teams probe returned non-zero (exit code ${probeRes.exitCode}). Falling back to adaptive session.`);
3812
+ }
3813
+ } catch (err) {
3814
+ const msg = err instanceof Error ? err.message : String(err);
3815
+ diagnostics.push(`Named teams probe failed: ${msg}`);
3816
+ }
3817
+ return {
3818
+ installed: true,
3819
+ version,
3820
+ command: cmd,
3821
+ namedTeams,
3822
+ rolePresetConsumer: "unknown",
3823
+ diagnostics
3824
+ };
3825
+ }
3826
+ };
3827
+
3828
+ // src/core/claude-capabilities.ts
3829
+ var SUBAGENT_HANDBACK_MIN = { major: 2, minor: 1, patch: 271 };
3830
+ function supportsSubagentHandback(version) {
3831
+ if (!version) return false;
3832
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
3833
+ if (!match) return false;
3834
+ const [major, minor, patch] = [Number(match[1]), Number(match[2]), Number(match[3])];
3835
+ if (major !== SUBAGENT_HANDBACK_MIN.major) return major > SUBAGENT_HANDBACK_MIN.major;
3836
+ if (minor !== SUBAGENT_HANDBACK_MIN.minor) return minor > SUBAGENT_HANDBACK_MIN.minor;
3837
+ return patch >= SUBAGENT_HANDBACK_MIN.patch;
3838
+ }
3839
+ var ClaudeCapabilityProbe = class {
2559
3840
  runner;
2560
3841
  customResolver;
2561
3842
  constructor(runner, options) {
@@ -2569,8 +3850,8 @@ var ClineCapabilityProbe = class {
2569
3850
  if (this.customResolver) {
2570
3851
  return this.customResolver();
2571
3852
  }
2572
- const envBin = process.env.CLINE_BIN_PATH;
2573
- if (envBin && path9.isAbsolute(envBin) && fs7.existsSync(envBin)) {
3853
+ const envBin = process.env.CLAUDE_BIN_PATH;
3854
+ if (envBin && path11.isAbsolute(envBin) && fs9.existsSync(envBin)) {
2574
3855
  return {
2575
3856
  executable: envBin,
2576
3857
  prefixArgs: [],
@@ -2581,28 +3862,28 @@ var ClineCapabilityProbe = class {
2581
3862
  const pathSeparator = process.platform === "win32" ? ";" : ":";
2582
3863
  const dirs = pathEnv.split(pathSeparator).filter(Boolean);
2583
3864
  if (process.platform === "win32") {
2584
- const exeNames = ["cline.cmd", "cline.bat", "cline.exe", "cline"];
3865
+ const exeNames = ["claude.cmd", "claude.bat", "claude.exe", "claude"];
2585
3866
  for (const dir of dirs) {
2586
3867
  for (const exeName of exeNames) {
2587
- const fullPath = path9.join(dir, exeName);
2588
- if (fs7.existsSync(fullPath)) {
2589
- const adjacentWrapper1 = path9.join(dir, "node_modules", "cline", "bin", "cline");
2590
- const adjacentWrapper2 = path9.join(dir, "node_modules", "@cline", "cline", "bin", "cline");
2591
- if (fs7.existsSync(adjacentWrapper1)) {
3868
+ const fullPath = path11.join(dir, exeName);
3869
+ if (fs9.existsSync(fullPath)) {
3870
+ const adjacentWrapper1 = path11.join(dir, "node_modules", "@anthropic-ai", "claude-code", "cli.js");
3871
+ const adjacentWrapper2 = path11.join(dir, "node_modules", "claude", "bin", "claude");
3872
+ if (fs9.existsSync(adjacentWrapper1)) {
2592
3873
  return {
2593
3874
  executable: process.execPath,
2594
3875
  prefixArgs: [adjacentWrapper1],
2595
3876
  source: "node-wrapper"
2596
3877
  };
2597
3878
  }
2598
- if (fs7.existsSync(adjacentWrapper2)) {
3879
+ if (fs9.existsSync(adjacentWrapper2)) {
2599
3880
  return {
2600
3881
  executable: process.execPath,
2601
3882
  prefixArgs: [adjacentWrapper2],
2602
3883
  source: "node-wrapper"
2603
3884
  };
2604
3885
  }
2605
- const ext = path9.extname(fullPath).toLowerCase();
3886
+ const ext = path11.extname(fullPath).toLowerCase();
2606
3887
  if (ext === ".cmd" || ext === ".bat") {
2607
3888
  return {
2608
3889
  executable: "cmd.exe",
@@ -2620,8 +3901,8 @@ var ClineCapabilityProbe = class {
2620
3901
  }
2621
3902
  } else {
2622
3903
  for (const dir of dirs) {
2623
- const fullPath = path9.join(dir, "cline");
2624
- if (fs7.existsSync(fullPath)) {
3904
+ const fullPath = path11.join(dir, "claude");
3905
+ if (fs9.existsSync(fullPath)) {
2625
3906
  return {
2626
3907
  executable: fullPath,
2627
3908
  prefixArgs: [],
@@ -2633,90 +3914,134 @@ var ClineCapabilityProbe = class {
2633
3914
  return null;
2634
3915
  }
2635
3916
  /**
2636
- * Run read-only capability probe against local Cline.
3917
+ * Run the read-only capability probe against local Claude Code. Never throws: an absent
3918
+ * executable, a non-zero exit and a runner error are all reported as data.
2637
3919
  */
2638
3920
  async probe() {
2639
3921
  const cmd = this.resolveExecutable();
2640
3922
  const diagnostics = [];
2641
3923
  if (!cmd) {
2642
- diagnostics.push("Cline executable not found on PATH or via CLINE_BIN_PATH.");
3924
+ diagnostics.push("Claude Code executable not found on PATH or via CLAUDE_BIN_PATH.");
2643
3925
  return {
2644
3926
  installed: false,
2645
- namedTeams: false,
2646
- rolePresetConsumer: "unknown",
3927
+ pluginSupport: false,
3928
+ agentTeamsExperimental: false,
3929
+ subagentHandback: false,
2647
3930
  diagnostics
2648
3931
  };
2649
3932
  }
2650
3933
  let version;
3934
+ let subagentHandback = false;
2651
3935
  try {
2652
- const verRes = await this.runner(cmd.executable, [...cmd.prefixArgs, "version"], {
3936
+ const verRes = await this.runner(cmd.executable, [...cmd.prefixArgs, "--version"], {
2653
3937
  timeoutMs: 5e3
2654
3938
  });
2655
3939
  if (verRes.exitCode === 0) {
2656
3940
  version = verRes.stdout.trim().split("\n")[0]?.trim();
3941
+ subagentHandback = supportsSubagentHandback(version);
2657
3942
  } else {
2658
- diagnostics.push(`Cline version check failed (exit code ${verRes.exitCode}): ${verRes.stderr.trim()}`);
3943
+ diagnostics.push(`Claude Code version check failed (exit code ${verRes.exitCode}): ${verRes.stderr.trim()}`);
2659
3944
  return {
2660
3945
  installed: false,
2661
3946
  command: cmd,
2662
- namedTeams: false,
2663
- rolePresetConsumer: "unknown",
3947
+ pluginSupport: false,
3948
+ agentTeamsExperimental: false,
3949
+ subagentHandback: false,
2664
3950
  diagnostics
2665
3951
  };
2666
3952
  }
2667
3953
  } catch (err) {
2668
3954
  const msg = err instanceof Error ? err.message : String(err);
2669
- diagnostics.push(`Error executing Cline version check: ${msg}`);
3955
+ diagnostics.push(`Error executing Claude Code version check: ${msg}`);
2670
3956
  return {
2671
3957
  installed: false,
2672
3958
  command: cmd,
2673
- namedTeams: false,
2674
- rolePresetConsumer: "unknown",
3959
+ pluginSupport: false,
3960
+ agentTeamsExperimental: false,
3961
+ subagentHandback: false,
2675
3962
  diagnostics
2676
3963
  };
2677
3964
  }
2678
- let namedTeams = false;
3965
+ let pluginSupport = false;
3966
+ let agentTeamsExperimental = false;
2679
3967
  try {
2680
- const probeRes = await this.runner(
2681
- cmd.executable,
2682
- [...cmd.prefixArgs, "--team-name", "agents-united-capability-probe", "version"],
2683
- { timeoutMs: 5e3 }
2684
- );
2685
- if (probeRes.exitCode === 0) {
2686
- namedTeams = true;
3968
+ const helpRes = await this.runner(cmd.executable, [...cmd.prefixArgs, "--help"], {
3969
+ timeoutMs: 5e3
3970
+ });
3971
+ if (helpRes.exitCode === 0) {
3972
+ const help = helpRes.stdout;
3973
+ pluginSupport = help.includes("--plugin-dir");
3974
+ agentTeamsExperimental = /agent teams/i.test(help) || help.includes("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS");
2687
3975
  } else {
2688
- diagnostics.push(`Named teams probe returned non-zero (exit code ${probeRes.exitCode}). Falling back to adaptive session.`);
3976
+ diagnostics.push(
3977
+ `Claude Code help probe returned non-zero (exit code ${helpRes.exitCode}); plugin and agent-team support could not be verified.`
3978
+ );
2689
3979
  }
2690
3980
  } catch (err) {
2691
3981
  const msg = err instanceof Error ? err.message : String(err);
2692
- diagnostics.push(`Named teams probe failed: ${msg}`);
3982
+ diagnostics.push(`Claude Code help probe failed: ${msg}`);
2693
3983
  }
2694
3984
  return {
2695
3985
  installed: true,
2696
3986
  version,
2697
3987
  command: cmd,
2698
- namedTeams,
2699
- rolePresetConsumer: "unknown",
3988
+ pluginSupport,
3989
+ agentTeamsExperimental,
3990
+ subagentHandback,
2700
3991
  diagnostics
2701
3992
  };
2702
3993
  }
2703
3994
  };
2704
3995
 
2705
3996
  // src/core/doctor.ts
2706
- var DoctorEngine = class {
2707
- static async isManagedProjection(absPath, isAgentsMd) {
2708
- const content = await fs8.readFile(absPath, "utf8");
3997
+ var DoctorEngine = class _DoctorEngine {
3998
+ static async isManagedProjection(absPath, isAgentsMd, recordedHash) {
3999
+ const content = await fs10.readFile(absPath, "utf8");
2709
4000
  if (isAgentsMd) {
2710
4001
  return content.includes("managed-by: agents-united");
2711
4002
  }
2712
- return HostProjector.hasManagedMarker(content);
4003
+ if (HostProjector.hasManagedMarker(content)) return true;
4004
+ if (recordedHash) {
4005
+ const bytes = await fs10.readFile(absPath);
4006
+ return `sha256:${crypto4.createHash("sha256").update(bytes).digest("hex")}` === recordedHash;
4007
+ }
4008
+ return false;
4009
+ }
4010
+ static isCompoundLaneHost(host) {
4011
+ return host === "cline" || host === "claude";
4012
+ }
4013
+ /**
4014
+ * Plan 015 §5.9 item 3 / ADR 0018 decision 2 — find the projection that now serves
4015
+ * `canonical` at a *different* path and actually exists on disk. A hit turns an absent
4016
+ * recorded path into self-healing staleness (`Stale projection … (superseded by …)`)
4017
+ * instead of a user-deletion report.
4018
+ *
4019
+ * Covers every rename the lockfile can still carry: the ADR 0016 workflow slug rename
4020
+ * (`.cline/workflows/implement-feature-or-fix.md` → `…/workflow-implement.md`) and the
4021
+ * ADR 0018 prefix strip (`.claude/agents/subagent-<role>.md` → `.claude/agents/<role>.md`).
4022
+ */
4023
+ static async findSupersedingProjection(projPath, canonical, manifest, workspaceRoot) {
4024
+ if (!canonical) return null;
4025
+ for (const [projRelPath, proj] of Object.entries(manifest.projections ?? {})) {
4026
+ if (projRelPath === projPath) continue;
4027
+ if (proj.canonical !== canonical) continue;
4028
+ if (!await fs10.pathExists(path12.join(workspaceRoot, projRelPath))) continue;
4029
+ return { relPath: projRelPath, host: proj.host, owners: proj.owners ?? [] };
4030
+ }
4031
+ return null;
2713
4032
  }
2714
4033
  /**
2715
4034
  * ADR 0017 — re-render the compound projections owned by the installed bundles so
2716
4035
  * doctor can compare what is on disk with what the current renderer produces.
2717
4036
  *
2718
- * Returns projection path -> acceptable content variants, or `null` when the registry
2719
- * is unavailable (the freshness check is then skipped rather than guessed).
4037
+ * Returns projection path -> acceptable content variants **for `host` only**, or `null`
4038
+ * when the registry is unavailable (the freshness check is then skipped rather than
4039
+ * guessed).
4040
+ *
4041
+ * ADR 0018 decision 11 — the renderer is dispatched by the *recorded projection's* host.
4042
+ * Cline entries are diffed against `ClineProjector` output and Claude entries against
4043
+ * `ClaudeProjector` output; nothing else is comparable, and the Cline path is unchanged
4044
+ * because it renders exactly what it rendered before.
2720
4045
  *
2721
4046
  * Two variants are produced per path on purpose: the coordinator rule and team manifest
2722
4047
  * legitimately render either with or without the installed-bundle addon exclusions
@@ -2724,7 +4049,7 @@ var DoctorEngine = class {
2724
4049
  * projection is fresh, which keeps the check false-positive free while still catching
2725
4050
  * content that matches neither.
2726
4051
  */
2727
- static async renderProjectionVariants(owners, installedBundles) {
4052
+ static async renderProjectionVariants(owners, installedBundles, host) {
2728
4053
  if (owners.size === 0) return null;
2729
4054
  let resolver;
2730
4055
  try {
@@ -2741,7 +4066,13 @@ var DoctorEngine = class {
2741
4066
  if (!bundleDef) continue;
2742
4067
  const resolved = await resolver.resolve(bundleDef.name);
2743
4068
  for (const excludeAddons of [void 0, installedBundles]) {
2744
- const artifacts = await ClineProjector.planCompoundProjection(
4069
+ const artifacts = host === "cline" ? await ClineProjector.planCompoundProjection(
4070
+ bundleDef,
4071
+ "project",
4072
+ resolved,
4073
+ registryDir,
4074
+ excludeAddons
4075
+ ) : await ClaudeProjector.planCompoundProjection(
2745
4076
  bundleDef,
2746
4077
  "project",
2747
4078
  resolved,
@@ -2771,12 +4102,13 @@ var DoctorEngine = class {
2771
4102
  let workflowsCount = 0;
2772
4103
  let manifest;
2773
4104
  let clineCapability;
4105
+ let claudeCapability;
2774
4106
  let isInitialized = false;
2775
- const lockfileExists = await fs8.pathExists(subPaths.lockfile);
2776
- const agentsDirExists = await fs8.pathExists(subPaths.agentsDir);
4107
+ const lockfileExists = await fs10.pathExists(subPaths.lockfile);
4108
+ const agentsDirExists = await fs10.pathExists(subPaths.agentsDir);
2777
4109
  if (lockfileExists) {
2778
4110
  try {
2779
- const parsed = await fs8.readJson(subPaths.lockfile);
4111
+ const parsed = await fs10.readJson(subPaths.lockfile);
2780
4112
  manifest = parsed;
2781
4113
  agentsCount = parsed.installed?.agents?.length || 0;
2782
4114
  skillsCount = parsed.installed?.skills?.length || 0;
@@ -2790,11 +4122,11 @@ var DoctorEngine = class {
2790
4122
  } else if (agentsDirExists) {
2791
4123
  warnings.push(`No lockfile found at ${subPaths.lockfile}, but agent files were detected in ${subPaths.agentsDir}. Run 'agents update' to sync.`);
2792
4124
  }
2793
- if (await fs8.pathExists(subPaths.agentsDir)) {
2794
- const agentFiles = await fs8.readdir(subPaths.agentsDir);
4125
+ if (await fs10.pathExists(subPaths.agentsDir)) {
4126
+ const agentFiles = await fs10.readdir(subPaths.agentsDir);
2795
4127
  for (const file of agentFiles) {
2796
4128
  if (file.endsWith(".md")) {
2797
- const content = await fs8.readFile(path10.join(subPaths.agentsDir, file), "utf8");
4129
+ const content = await fs10.readFile(path12.join(subPaths.agentsDir, file), "utf8");
2798
4130
  const frontmatterMatch = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
2799
4131
  if (!frontmatterMatch) {
2800
4132
  warnings.push(`Agent ${file} is missing YAML frontmatter.`);
@@ -2811,79 +4143,104 @@ var DoctorEngine = class {
2811
4143
  }
2812
4144
  }
2813
4145
  }
2814
- if (await fs8.pathExists(subPaths.workflowsDir)) {
2815
- const remainingWorkflowFiles = (await fs8.readdir(subPaths.workflowsDir)).filter((f) => f.endsWith(".md"));
4146
+ if (await fs10.pathExists(subPaths.workflowsDir)) {
4147
+ const remainingWorkflowFiles = (await fs10.readdir(subPaths.workflowsDir)).filter((f) => f.endsWith(".md"));
2816
4148
  if (remainingWorkflowFiles.length > 0) {
2817
4149
  warnings.push(`Detected ${remainingWorkflowFiles.length} legacy workflows in ${subPaths.workflowsDir}. Run 'agents update' to automatically migrate them to modern skills.`);
2818
4150
  }
2819
4151
  }
2820
4152
  if (manifest) {
2821
- const workspaceRoot = path10.resolve(path10.dirname(root));
4153
+ const workspaceRoot = path12.resolve(path12.dirname(root));
4154
+ const warnedProjectionPaths = /* @__PURE__ */ new Set();
4155
+ const pushProjectionWarning = (projPath, message) => {
4156
+ if (warnedProjectionPaths.has(projPath)) return;
4157
+ warnedProjectionPaths.add(projPath);
4158
+ warnings.push(message);
4159
+ };
2822
4160
  for (const [relPath, assetMeta] of Object.entries(manifest.files)) {
2823
4161
  const projectedTo = assetMeta.projectedTo;
2824
4162
  if (!projectedTo || projectedTo.length === 0) continue;
2825
4163
  for (const projPath of projectedTo) {
2826
- const absProjection = path10.join(workspaceRoot, projPath);
2827
- if (!await fs8.pathExists(absProjection)) {
2828
- let superseding = null;
2829
- for (const [projRelPath, proj] of Object.entries(manifest.projections ?? {})) {
2830
- if (projRelPath === projPath) continue;
2831
- if (proj.canonical !== relPath) continue;
2832
- if (!await fs8.pathExists(path10.join(workspaceRoot, projRelPath))) continue;
2833
- superseding = { relPath: projRelPath, host: proj.host, owners: proj.owners ?? [] };
2834
- break;
2835
- }
4164
+ const absProjection = path12.join(workspaceRoot, projPath);
4165
+ if (!await fs10.pathExists(absProjection)) {
4166
+ const superseding = await this.findSupersedingProjection(projPath, relPath, manifest, workspaceRoot);
2836
4167
  if (superseding) {
2837
4168
  const owner = superseding.owners[0];
2838
- warnings.push(
4169
+ pushProjectionWarning(
4170
+ projPath,
2839
4171
  `Stale projection ${projPath} for canonical ${relPath} (superseded by ${superseding.relPath}).` + (owner ? ` Run: agents update ${owner} --fanout ${superseding.host} to reconcile.` : "")
2840
4172
  );
2841
4173
  } else {
2842
- warnings.push(
4174
+ pushProjectionWarning(
4175
+ projPath,
2843
4176
  `Missing projection ${projPath} for canonical ${relPath}. Re-run: agents add ... --fanout`
2844
4177
  );
2845
4178
  }
2846
4179
  continue;
2847
4180
  }
2848
- const managed = await this.isManagedProjection(absProjection, projPath === "AGENTS.md");
4181
+ const managed = await this.isManagedProjection(absProjection, projPath === "AGENTS.md", manifest.projections?.[projPath]?.hash);
2849
4182
  if (!managed) {
2850
- warnings.push(`user-modified projection ${projPath}`);
4183
+ pushProjectionWarning(projPath, `user-modified projection ${projPath}`);
2851
4184
  }
2852
4185
  }
2853
4186
  }
2854
4187
  if (manifest.projections) {
2855
4188
  for (const [projRelPath, proj] of Object.entries(manifest.projections)) {
2856
- const absPath = path10.join(workspaceRoot, projRelPath);
2857
- if (!await fs8.pathExists(absPath)) {
2858
- warnings.push(`Missing Cline projection ${projRelPath} (owners: ${proj.owners.join(", ")}).`);
4189
+ const absPath = path12.join(workspaceRoot, projRelPath);
4190
+ if (!await fs10.pathExists(absPath)) {
4191
+ const superseding = await this.findSupersedingProjection(projRelPath, proj.canonical, manifest, workspaceRoot);
4192
+ if (superseding) {
4193
+ const owner = superseding.owners[0];
4194
+ pushProjectionWarning(
4195
+ projRelPath,
4196
+ `Stale projection ${projRelPath} for canonical ${proj.canonical} (superseded by ${superseding.relPath}).` + (owner ? ` Run: agents update ${owner} --fanout ${superseding.host} to reconcile.` : "")
4197
+ );
4198
+ } else {
4199
+ pushProjectionWarning(
4200
+ projRelPath,
4201
+ `Missing ${proj.host} projection ${projRelPath} (owners: ${proj.owners.join(", ")}).`
4202
+ );
4203
+ }
2859
4204
  continue;
2860
4205
  }
2861
4206
  if (proj.managedMarker) {
2862
- const managed = await this.isManagedProjection(absPath, projRelPath === "AGENTS.md");
4207
+ const managed = await this.isManagedProjection(absPath, projRelPath === "AGENTS.md", proj.hash);
2863
4208
  if (!managed) {
2864
- warnings.push(`user-modified projection ${projRelPath}`);
4209
+ pushProjectionWarning(projRelPath, `user-modified projection ${projRelPath}`);
2865
4210
  }
2866
4211
  }
2867
4212
  }
2868
4213
  }
2869
4214
  if (manifest.projections && manifest.installed) {
2870
- const ownerNames = /* @__PURE__ */ new Set();
4215
+ const ownersByHost = /* @__PURE__ */ new Map();
2871
4216
  for (const proj of Object.values(manifest.projections)) {
2872
- for (const owner of proj.owners) ownerNames.add(owner);
4217
+ if (!_DoctorEngine.isCompoundLaneHost(proj.host)) continue;
4218
+ const owners = ownersByHost.get(proj.host) ?? /* @__PURE__ */ new Set();
4219
+ for (const owner of proj.owners) owners.add(owner);
4220
+ ownersByHost.set(proj.host, owners);
2873
4221
  }
2874
- const expectedVariants = await this.renderProjectionVariants(ownerNames, manifest.installed.bundles ?? []);
2875
- if (expectedVariants) {
4222
+ const variantsByHost = /* @__PURE__ */ new Map();
4223
+ for (const [projHost, owners] of ownersByHost) {
4224
+ const variants = await this.renderProjectionVariants(owners, manifest.installed.bundles ?? [], projHost);
4225
+ if (variants) variantsByHost.set(projHost, variants);
4226
+ }
4227
+ if (variantsByHost.size > 0) {
2876
4228
  for (const [projRelPath, proj] of Object.entries(manifest.projections)) {
2877
- const absPath = path10.join(workspaceRoot, projRelPath);
2878
- if (!await fs8.pathExists(absPath)) continue;
4229
+ const projHost = proj.host;
4230
+ if (!_DoctorEngine.isCompoundLaneHost(projHost)) continue;
4231
+ const expectedVariants = variantsByHost.get(projHost);
4232
+ if (!expectedVariants) continue;
4233
+ const absPath = path12.join(workspaceRoot, projRelPath);
4234
+ if (!await fs10.pathExists(absPath)) continue;
2879
4235
  if (!proj.managedMarker) continue;
2880
- const content = await fs8.readFile(absPath, "utf8");
4236
+ const content = await fs10.readFile(absPath, "utf8");
2881
4237
  if (!HostProjector.hasManagedMarker(content)) continue;
2882
4238
  if (proj.hash) {
2883
- const diskHash = `sha256:${crypto4.createHash("sha256").update(await fs8.readFile(absPath)).digest("hex")}`;
4239
+ const diskHash = `sha256:${crypto4.createHash("sha256").update(await fs10.readFile(absPath)).digest("hex")}`;
2884
4240
  if (diskHash !== proj.hash) {
2885
4241
  const owner = proj.owners[0];
2886
- warnings.push(
4242
+ pushProjectionWarning(
4243
+ projRelPath,
2887
4244
  `Content drift ${projRelPath} \u2014 edited after installation (recorded ${proj.hash.slice(0, 16)}\u2026, on disk ${diskHash.slice(0, 16)}\u2026).` + (owner ? ` Run: agents update ${owner} --fanout ${proj.host} to restore it.` : "")
2888
4245
  );
2889
4246
  continue;
@@ -2894,7 +4251,8 @@ var DoctorEngine = class {
2894
4251
  const onDisk = content.replace(/\r\n/g, "\n");
2895
4252
  if (!variants.some((variant) => variant.replace(/\r\n/g, "\n") === onDisk)) {
2896
4253
  const owner = proj.owners[0];
2897
- warnings.push(
4254
+ pushProjectionWarning(
4255
+ projRelPath,
2898
4256
  `Outdated projection ${projRelPath} (content differs from the current render).` + (owner ? ` Run: agents update ${owner} --fanout ${proj.host} to refresh it.` : "")
2899
4257
  );
2900
4258
  }
@@ -2902,7 +4260,7 @@ var DoctorEngine = class {
2902
4260
  }
2903
4261
  }
2904
4262
  const installedBundles = manifest.installed?.bundles ?? [];
2905
- const fileOwnersOf = (rec) => rec.owners ?? (rec.bundle ? [rec.bundle] : []);
4263
+ const fileOwnersOf = (rec) => assetOwners(rec);
2906
4264
  if (manifest.projections) {
2907
4265
  for (const [projRelPath, proj] of Object.entries(manifest.projections)) {
2908
4266
  for (const owner of proj.owners) {
@@ -2932,6 +4290,12 @@ var DoctorEngine = class {
2932
4290
  if (!clineCapability.installed) {
2933
4291
  warnings.push("Cline executable was not detected on PATH or via CLINE_BIN_PATH.");
2934
4292
  }
4293
+ } else if (host === "claude") {
4294
+ const probe = new ClaudeCapabilityProbe();
4295
+ claudeCapability = await probe.probe();
4296
+ if (!claudeCapability.installed) {
4297
+ warnings.push("Claude Code executable was not detected on PATH or via CLAUDE_BIN_PATH.");
4298
+ }
2935
4299
  }
2936
4300
  return {
2937
4301
  valid: issues.length === 0,
@@ -2942,16 +4306,17 @@ var DoctorEngine = class {
2942
4306
  agentsCount,
2943
4307
  skillsCount,
2944
4308
  workflowsCount,
2945
- clineCapability
4309
+ clineCapability,
4310
+ claudeCapability
2946
4311
  };
2947
4312
  }
2948
4313
  };
2949
4314
 
2950
4315
  // src/core/cline-launcher.ts
2951
- import path11 from "path";
4316
+ import path13 from "path";
2952
4317
  import os3 from "os";
2953
4318
  import crypto5 from "crypto";
2954
- import fs9 from "fs-extra";
4319
+ import fs11 from "fs-extra";
2955
4320
  var ClineLauncher = class _ClineLauncher {
2956
4321
  probe;
2957
4322
  runner;
@@ -2964,7 +4329,7 @@ var ClineLauncher = class _ClineLauncher {
2964
4329
  */
2965
4330
  static generateTeamName(bundleName, workspace) {
2966
4331
  const sanitizedBundle = bundleName.replace(/[^A-Za-z0-9_-]/g, "-");
2967
- const hash = crypto5.createHash("sha256").update(path11.resolve(workspace)).digest("hex").slice(0, 8);
4332
+ const hash = crypto5.createHash("sha256").update(path13.resolve(workspace)).digest("hex").slice(0, 8);
2968
4333
  const prefix = `au-${sanitizedBundle}`;
2969
4334
  const maxPrefixLen = 64 - 9;
2970
4335
  const truncatedPrefix = prefix.slice(0, maxPrefixLen);
@@ -2991,16 +4356,16 @@ var ClineLauncher = class _ClineLauncher {
2991
4356
  workspace = os3.homedir();
2992
4357
  } else {
2993
4358
  workspace = cwd;
2994
- targetDir = path11.join(cwd, ".agents");
4359
+ targetDir = path13.join(cwd, ".agents");
2995
4360
  scope = "project";
2996
4361
  }
2997
- const lockfilePath = path11.join(targetDir, "agents-united.json");
2998
- if (!await fs9.pathExists(lockfilePath)) {
4362
+ const lockfilePath = path13.join(targetDir, "agents-united.json");
4363
+ if (!await fs11.pathExists(lockfilePath)) {
2999
4364
  if (!isGlobal) {
3000
4365
  const globalTarget = AgentHostAdapter.resolveHostDir("global", "agents");
3001
- const globalLockfile = path11.join(globalTarget, "agents-united.json");
3002
- if (await fs9.pathExists(globalLockfile)) {
3003
- const gLock = await fs9.readJson(globalLockfile);
4366
+ const globalLockfile = path13.join(globalTarget, "agents-united.json");
4367
+ if (await fs11.pathExists(globalLockfile)) {
4368
+ const gLock = await fs11.readJson(globalLockfile);
3004
4369
  if (gLock.installed?.bundles?.includes(bundleName)) {
3005
4370
  return this.resolveInstallation(bundleName, { ...options, global: true });
3006
4371
  }
@@ -3008,14 +4373,14 @@ var ClineLauncher = class _ClineLauncher {
3008
4373
  }
3009
4374
  throw new Error(`Bundle '${bundleName}' is not installed in ${scope} scope.`);
3010
4375
  }
3011
- const lockfile = await fs9.readJson(lockfilePath);
4376
+ const lockfile = await fs11.readJson(lockfilePath);
3012
4377
  if (!lockfile.installed?.bundles?.includes(bundleName)) {
3013
4378
  throw new Error(`Bundle '${bundleName}' is not installed in ${scope} scope.`);
3014
4379
  }
3015
4380
  const hasClineFanout = (lockfile.fanout || []).includes("cline");
3016
4381
  const manifestRel = `.agents/plugins/${bundleName}/agents-united/teams/${bundleName}.yaml`;
3017
- const manifestPath = path11.join(workspace, manifestRel);
3018
- if (!hasClineFanout || !await fs9.pathExists(manifestPath)) {
4382
+ const manifestPath = path13.join(workspace, manifestRel);
4383
+ if (!hasClineFanout || !await fs11.pathExists(manifestPath)) {
3019
4384
  throw new Error(
3020
4385
  `Bundle '${bundleName}' is not projected to Cline. Run 'agents update ${bundleName} --fanout cline' first.`
3021
4386
  );
@@ -3114,15 +4479,190 @@ var ClineLauncher = class _ClineLauncher {
3114
4479
  }
3115
4480
  };
3116
4481
 
4482
+ // src/core/claude-launcher.ts
4483
+ import path14 from "path";
4484
+ import os4 from "os";
4485
+ import fs12 from "fs-extra";
4486
+ var DEFAULT_COORDINATOR = "orchestrator-engineering.md";
4487
+ var ClaudeLauncher = class _ClaudeLauncher {
4488
+ probe;
4489
+ runner;
4490
+ constructor(probe, runner) {
4491
+ this.probe = probe || new ClaudeCapabilityProbe();
4492
+ this.runner = runner || defaultProcessRunner;
4493
+ }
4494
+ /**
4495
+ * The `--agent` value must equal the projected coordinator definition's basename without `.md`.
4496
+ *
4497
+ * A projected coordinator keeps its full `orchestrator-<x>` name in `.claude/agents/`: coordinators are
4498
+ * never `subagent-<x>` files, so the prefix stripping that applies to every specialist role is a no-op
4499
+ * here. We still route through the projector's own helper so the launcher and the renderer can never
4500
+ * diverge if a bundle ever declares a differently-prefixed orchestrator.
4501
+ */
4502
+ static resolveCoordinatorName(orchestrator) {
4503
+ const file = (orchestrator && orchestrator.trim().length > 0 ? orchestrator.trim() : DEFAULT_COORDINATOR).replace(/\.md$/i, "");
4504
+ return ClaudeProjector.stripSubagentPrefix(path14.basename(file));
4505
+ }
4506
+ /**
4507
+ * Resolve an installed bundle for Claude Code activation. Mirrors `ClineLauncher.resolveInstallation`:
4508
+ * same global→project fallback, same `.agents/agents-united.json` lockfile path, same error text.
4509
+ */
4510
+ async resolveInstallation(bundleName, options = {}) {
4511
+ const cwd = options.cwd || process.cwd();
4512
+ const isGlobal = !!options.global;
4513
+ let targetDir;
4514
+ let scope;
4515
+ let workspace;
4516
+ if (isGlobal) {
4517
+ targetDir = AgentHostAdapter.resolveHostDir("global", "agents");
4518
+ scope = "global";
4519
+ workspace = os4.homedir();
4520
+ } else {
4521
+ workspace = cwd;
4522
+ targetDir = path14.join(cwd, ".agents");
4523
+ scope = "project";
4524
+ }
4525
+ const lockfilePath = path14.join(targetDir, "agents-united.json");
4526
+ if (!await fs12.pathExists(lockfilePath)) {
4527
+ if (!isGlobal) {
4528
+ const globalTarget = AgentHostAdapter.resolveHostDir("global", "agents");
4529
+ const globalLockfile = path14.join(globalTarget, "agents-united.json");
4530
+ if (await fs12.pathExists(globalLockfile)) {
4531
+ const gLock = await fs12.readJson(globalLockfile);
4532
+ if (gLock.installed?.bundles?.includes(bundleName)) {
4533
+ return this.resolveInstallation(bundleName, { ...options, global: true });
4534
+ }
4535
+ }
4536
+ }
4537
+ throw new Error(`Bundle '${bundleName}' is not installed in ${scope} scope.`);
4538
+ }
4539
+ const lockfile = await fs12.readJson(lockfilePath);
4540
+ if (!lockfile.installed?.bundles?.includes(bundleName)) {
4541
+ throw new Error(`Bundle '${bundleName}' is not installed in ${scope} scope.`);
4542
+ }
4543
+ const hasClaudeFanout = (lockfile.fanout || []).includes("claude");
4544
+ const manifestRel = `.agents/plugins/${bundleName}/agents-united/teams/${bundleName}.yaml`;
4545
+ const manifestPath = path14.join(workspace, manifestRel);
4546
+ if (!hasClaudeFanout && !await fs12.pathExists(manifestPath)) {
4547
+ throw new Error(
4548
+ `Bundle '${bundleName}' is not projected to Claude. Run 'agents update ${bundleName} --fanout claude' first.`
4549
+ );
4550
+ }
4551
+ return {
4552
+ scope,
4553
+ workspace,
4554
+ lockfile,
4555
+ manifestPath
4556
+ };
4557
+ }
4558
+ /**
4559
+ * Plan the argv, bootstrap prompt and ephemeral environment for a Claude Code session. Pure: no process
4560
+ * is spawned and nothing is written to disk, so `--dry-run` can call it safely.
4561
+ */
4562
+ planActivation(options) {
4563
+ const {
4564
+ bundleName,
4565
+ workspace,
4566
+ scope,
4567
+ report,
4568
+ prompt,
4569
+ orchestrator,
4570
+ allowAddons,
4571
+ background,
4572
+ teams,
4573
+ pluginDir,
4574
+ manifestAvailable
4575
+ } = options;
4576
+ const command = report.command || {
4577
+ executable: "claude",
4578
+ prefixArgs: [],
4579
+ source: "path-executable"
4580
+ };
4581
+ const coordinatorName = _ClaudeLauncher.resolveCoordinatorName(orchestrator);
4582
+ const homePrefix = scope === "global" ? "~/" : "";
4583
+ const manifestRel = `${homePrefix}.agents/plugins/${bundleName}/agents-united/teams/${bundleName}.yaml`;
4584
+ const coordinatorRel = `${homePrefix}.claude/agents/${coordinatorName}.md`;
4585
+ const addonPolicyText = allowAddons ? "Addon auto-installation is pre-authorized for this session." : `Before installing any recommended addon, explain the requirement to the user and request explicit confirmation to run: agents add <addon> -t claude ${scope === "global" ? "-g " : ""}-y.`;
4586
+ const deploymentNoteText = `Deployment note: this bundle is already projected natively for this workspace - roles in "${homePrefix}.claude/agents/", skills in "${homePrefix}.claude/skills/" and rules in "${homePrefix}.claude/rules/". The single source of truth stays the canonical store under "${homePrefix}.agents/" (ADR 0018): read the projections, edit the canonical files, never the copies.`;
4587
+ const delegationNoteText = `Delegation on this host works by description: in an ordinary Claude Code session you name a specialist and Claude picks the matching definition from "${homePrefix}.claude/agents/". This session was started with \`claude --agent ${coordinatorName}\`, so your own projected coordinator definition runs on the main thread and the real \`Agent(...)\` allowlist in its frontmatter is what bounds the specialists you may delegate to - send work only to those exact agent type names.`;
4588
+ const taskText = prompt && prompt.trim().length > 0 ? `User task: ${prompt.trim()}` : "Please introduce your coordinator role to the user and ask for their first task.";
4589
+ const manifestInstruction = manifestAvailable === false ? `Read your coordinator role definition at "${coordinatorRel}" before acting. Specialist roles are discoverable by name and description from the projected definitions in "${homePrefix}.claude/agents/" (the host-neutral team manifest is not present in this workspace yet - it is written by a \`--fanout cline\` install).` : `Read the Team Manifest at "${manifestRel}" and your coordinator role definition at "${coordinatorRel}" before acting.`;
4590
+ const bootstrapLines = [
4591
+ `You are coordinating the "${bundleName}" team in Agents United.`,
4592
+ manifestInstruction,
4593
+ delegationNoteText,
4594
+ "Use specialist roles only when necessary.",
4595
+ addonPolicyText,
4596
+ deploymentNoteText,
4597
+ taskText
4598
+ ];
4599
+ if (teams === true) {
4600
+ bootstrapLines.push(
4601
+ `Agent teams are enabled for this session as an EXPERIMENTAL scaffold: spawn teammates from the projected agent types by name - the names you see in "${homePrefix}.claude/agents/" (specialists are projected with their "subagent-" prefix stripped, e.g. \`backend-architect\`). The scaffold is deliberately minimal: exactly one team per session, you are the fixed lead, there is no nested teams and no session resumption, and nothing about it is persisted to disk. Never make it load-bearing - if a teammate cannot be spawned, continue with ordinary delegated subagents using the Agent tool.`
4602
+ );
4603
+ }
4604
+ const bootstrapPrompt = bootstrapLines.join("\n\n");
4605
+ const env = {};
4606
+ if (teams === true) {
4607
+ env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
4608
+ }
4609
+ const argv = [...command.prefixArgs];
4610
+ argv.push("--agent");
4611
+ argv.push(coordinatorName);
4612
+ argv.push("--add-dir");
4613
+ argv.push(workspace);
4614
+ if (background === true) {
4615
+ argv.push("--bg");
4616
+ }
4617
+ if (pluginDir) {
4618
+ argv.push("--plugin-dir");
4619
+ argv.push(pluginDir);
4620
+ }
4621
+ argv.push(bootstrapPrompt);
4622
+ return {
4623
+ bundleName,
4624
+ scope,
4625
+ workspace,
4626
+ executable: command.executable,
4627
+ argv,
4628
+ bootstrapPrompt,
4629
+ env
4630
+ };
4631
+ }
4632
+ /**
4633
+ * Launch the Claude Code session from the argv array. Mirrors `ClineLauncher.launch`: `shell: false`
4634
+ * (never `shell: true`), `stdio: 'inherit'`, and the plan's ephemeral env merged over `process.env`.
4635
+ */
4636
+ async launch(plan) {
4637
+ const { spawn } = await import("child_process");
4638
+ const child = spawn(plan.executable, plan.argv, {
4639
+ cwd: plan.workspace,
4640
+ stdio: "inherit",
4641
+ shell: false,
4642
+ env: { ...process.env, ...plan.env }
4643
+ });
4644
+ await new Promise((resolve, reject) => {
4645
+ child.on("error", reject);
4646
+ child.on("exit", (code) => {
4647
+ if (code === 0 || code === null) {
4648
+ resolve();
4649
+ } else {
4650
+ resolve();
4651
+ }
4652
+ });
4653
+ });
4654
+ }
4655
+ };
4656
+
3117
4657
  // src/core/prerequisites.ts
3118
- import path13 from "path";
3119
- import os5 from "os";
3120
- import fs11 from "fs-extra";
4658
+ import path16 from "path";
4659
+ import os6 from "os";
4660
+ import fs14 from "fs-extra";
3121
4661
 
3122
4662
  // src/core/mcp-locations.ts
3123
- import path12 from "path";
3124
- import os4 from "os";
3125
- import fs10 from "fs-extra";
4663
+ import path15 from "path";
4664
+ import os5 from "os";
4665
+ import fs13 from "fs-extra";
3126
4666
  var McpLocationRegistry = class _McpLocationRegistry {
3127
4667
  /**
3128
4668
  * Declarative catalog of known MCP configuration files across AI tools, CLIs, extensions, and workspaces.
@@ -3134,7 +4674,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3134
4674
  host: "gemini",
3135
4675
  category: "cli",
3136
4676
  label: "Google Antigravity & Gemini Global Config",
3137
- resolvePath: (_, home) => path12.join(home, ".gemini", "config", "mcp_config.json"),
4677
+ resolvePath: (_, home) => path15.join(home, ".gemini", "config", "mcp_config.json"),
3138
4678
  isPrimary: true
3139
4679
  },
3140
4680
  {
@@ -3142,21 +4682,21 @@ var McpLocationRegistry = class _McpLocationRegistry {
3142
4682
  host: "gemini",
3143
4683
  category: "desktop",
3144
4684
  label: "Google Antigravity System Config",
3145
- resolvePath: (_, home) => path12.join(home, ".gemini", "antigravity", "mcp_config.json")
4685
+ resolvePath: (_, home) => path15.join(home, ".gemini", "antigravity", "mcp_config.json")
3146
4686
  },
3147
4687
  {
3148
4688
  id: "antigravity-workspace",
3149
4689
  host: "gemini",
3150
4690
  category: "workspace",
3151
4691
  label: "Google Antigravity Local Workspace Config",
3152
- resolvePath: (cwd) => path12.join(cwd, ".gemini", "config", "mcp_config.json")
4692
+ resolvePath: (cwd) => path15.join(cwd, ".gemini", "config", "mcp_config.json")
3153
4693
  },
3154
4694
  {
3155
4695
  id: "gemini-appdata",
3156
4696
  host: "gemini",
3157
4697
  category: "cli",
3158
4698
  label: "Google Gemini AppData Config",
3159
- resolvePath: (_, __, appData) => path12.join(appData, "gemini", "config", "mcp_config.json")
4699
+ resolvePath: (_, __, appData) => path15.join(appData, "gemini", "config", "mcp_config.json")
3160
4700
  },
3161
4701
  // --- Cline CLI & VS Code Extension ---
3162
4702
  {
@@ -3164,7 +4704,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3164
4704
  host: "cline",
3165
4705
  category: "cli",
3166
4706
  label: "Cline CLI MCP Settings",
3167
- resolvePath: (_, home) => path12.join(home, ".cline", "data", "settings", "cline_mcp_settings.json"),
4707
+ resolvePath: (_, home) => path15.join(home, ".cline", "data", "settings", "cline_mcp_settings.json"),
3168
4708
  isPrimary: true
3169
4709
  },
3170
4710
  {
@@ -3172,42 +4712,42 @@ var McpLocationRegistry = class _McpLocationRegistry {
3172
4712
  host: "cline",
3173
4713
  category: "extension",
3174
4714
  label: "Cline VS Code Extension Global Settings",
3175
- resolvePath: (_, __, appData) => path12.join(appData, "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
4715
+ resolvePath: (_, __, appData) => path15.join(appData, "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
3176
4716
  },
3177
4717
  {
3178
4718
  id: "cline-insiders-extension-settings",
3179
4719
  host: "cline",
3180
4720
  category: "extension",
3181
4721
  label: "Cline VS Code Insiders Extension Settings",
3182
- resolvePath: (_, __, appData) => path12.join(appData, "Code - Insiders", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
4722
+ resolvePath: (_, __, appData) => path15.join(appData, "Code - Insiders", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
3183
4723
  },
3184
4724
  {
3185
4725
  id: "cline-legacy-settings",
3186
4726
  host: "cline",
3187
4727
  category: "cli",
3188
4728
  label: "Cline Home Legacy Settings",
3189
- resolvePath: (_, home) => path12.join(home, ".cline", "settings", "cline_mcp_settings.json")
4729
+ resolvePath: (_, home) => path15.join(home, ".cline", "settings", "cline_mcp_settings.json")
3190
4730
  },
3191
4731
  {
3192
4732
  id: "cline-home-flat",
3193
4733
  host: "cline",
3194
4734
  category: "cli",
3195
4735
  label: "Cline Home Flat Settings",
3196
- resolvePath: (_, home) => path12.join(home, "cline_mcp_settings.json")
4736
+ resolvePath: (_, home) => path15.join(home, "cline_mcp_settings.json")
3197
4737
  },
3198
4738
  {
3199
4739
  id: "cline-workspace-flat",
3200
4740
  host: "cline",
3201
4741
  category: "workspace",
3202
4742
  label: "Cline Workspace Settings",
3203
- resolvePath: (cwd) => path12.join(cwd, "cline_mcp_settings.json")
4743
+ resolvePath: (cwd) => path15.join(cwd, "cline_mcp_settings.json")
3204
4744
  },
3205
4745
  {
3206
4746
  id: "cline-workspace-dot",
3207
4747
  host: "cline",
3208
4748
  category: "workspace",
3209
4749
  label: "Cline Workspace .cline/mcp.json",
3210
- resolvePath: (cwd) => path12.join(cwd, ".cline", "mcp.json")
4750
+ resolvePath: (cwd) => path15.join(cwd, ".cline", "mcp.json")
3211
4751
  },
3212
4752
  // --- Roo Code Extension ---
3213
4753
  {
@@ -3215,7 +4755,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3215
4755
  host: "cline",
3216
4756
  category: "extension",
3217
4757
  label: "Roo Code VS Code Extension Settings",
3218
- resolvePath: (_, __, appData) => path12.join(appData, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline", "settings", "cline_mcp_settings.json")
4758
+ resolvePath: (_, __, appData) => path15.join(appData, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline", "settings", "cline_mcp_settings.json")
3219
4759
  },
3220
4760
  // --- Cursor IDE / CLI ---
3221
4761
  {
@@ -3223,7 +4763,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3223
4763
  host: "cursor",
3224
4764
  category: "workspace",
3225
4765
  label: "Cursor Workspace Config",
3226
- resolvePath: (cwd) => path12.join(cwd, ".cursor", "mcp.json"),
4766
+ resolvePath: (cwd) => path15.join(cwd, ".cursor", "mcp.json"),
3227
4767
  isPrimary: true
3228
4768
  },
3229
4769
  {
@@ -3231,7 +4771,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3231
4771
  host: "cursor",
3232
4772
  category: "desktop",
3233
4773
  label: "Cursor Global User Config",
3234
- resolvePath: (_, home) => path12.join(home, ".cursor", "mcp.json")
4774
+ resolvePath: (_, home) => path15.join(home, ".cursor", "mcp.json")
3235
4775
  },
3236
4776
  // --- Claude Code CLI & Claude Desktop ---
3237
4777
  {
@@ -3239,7 +4779,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3239
4779
  host: "claude",
3240
4780
  category: "workspace",
3241
4781
  label: "Claude Code Workspace Config",
3242
- resolvePath: (cwd) => path12.join(cwd, ".claude", "mcp.json"),
4782
+ resolvePath: (cwd) => path15.join(cwd, ".claude", "mcp.json"),
3243
4783
  isPrimary: true
3244
4784
  },
3245
4785
  {
@@ -3247,28 +4787,28 @@ var McpLocationRegistry = class _McpLocationRegistry {
3247
4787
  host: "claude",
3248
4788
  category: "workspace",
3249
4789
  label: "Claude Workspace claude.json",
3250
- resolvePath: (cwd) => path12.join(cwd, "claude.json")
4790
+ resolvePath: (cwd) => path15.join(cwd, "claude.json")
3251
4791
  },
3252
4792
  {
3253
4793
  id: "claude-global-dot",
3254
4794
  host: "claude",
3255
4795
  category: "cli",
3256
4796
  label: "Claude Code Global Config",
3257
- resolvePath: (_, home) => path12.join(home, ".claude", "mcp.json")
4797
+ resolvePath: (_, home) => path15.join(home, ".claude", "mcp.json")
3258
4798
  },
3259
4799
  {
3260
4800
  id: "claude-global-flat",
3261
4801
  host: "claude",
3262
4802
  category: "cli",
3263
4803
  label: "Claude Code Global claude.json",
3264
- resolvePath: (_, home) => path12.join(home, "claude.json")
4804
+ resolvePath: (_, home) => path15.join(home, "claude.json")
3265
4805
  },
3266
4806
  {
3267
4807
  id: "claude-desktop-global",
3268
4808
  host: "claude",
3269
4809
  category: "desktop",
3270
4810
  label: "Claude Desktop Global Config",
3271
- resolvePath: (_, __, appData) => path12.join(appData, "Claude", "claude_desktop_config.json")
4811
+ resolvePath: (_, __, appData) => path15.join(appData, "Claude", "claude_desktop_config.json")
3272
4812
  },
3273
4813
  // --- Windsurf IDE ---
3274
4814
  {
@@ -3276,7 +4816,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3276
4816
  host: "windsurf",
3277
4817
  category: "desktop",
3278
4818
  label: "Windsurf Global Config",
3279
- resolvePath: (_, home) => path12.join(home, ".codeium", "windsurf", "mcp_config.json"),
4819
+ resolvePath: (_, home) => path15.join(home, ".codeium", "windsurf", "mcp_config.json"),
3280
4820
  isPrimary: true
3281
4821
  },
3282
4822
  {
@@ -3284,7 +4824,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3284
4824
  host: "windsurf",
3285
4825
  category: "desktop",
3286
4826
  label: "Windsurf AppData Config",
3287
- resolvePath: (_, __, appData) => path12.join(appData, "Windsurf", "mcp_config.json")
4827
+ resolvePath: (_, __, appData) => path15.join(appData, "Windsurf", "mcp_config.json")
3288
4828
  },
3289
4829
  // --- OpenCode & Codex ---
3290
4830
  {
@@ -3292,7 +4832,7 @@ var McpLocationRegistry = class _McpLocationRegistry {
3292
4832
  host: "opencode",
3293
4833
  category: "workspace",
3294
4834
  label: "OpenCode Workspace Config",
3295
- resolvePath: (cwd) => path12.join(cwd, ".opencode", "mcp.json"),
4835
+ resolvePath: (cwd) => path15.join(cwd, ".opencode", "mcp.json"),
3296
4836
  isPrimary: true
3297
4837
  },
3298
4838
  {
@@ -3300,28 +4840,28 @@ var McpLocationRegistry = class _McpLocationRegistry {
3300
4840
  host: "opencode",
3301
4841
  category: "cli",
3302
4842
  label: "OpenCode Global Config",
3303
- resolvePath: (_, home) => path12.join(home, ".config", "opencode", "mcp.json")
4843
+ resolvePath: (_, home) => path15.join(home, ".config", "opencode", "mcp.json")
3304
4844
  }
3305
4845
  ];
3306
4846
  /**
3307
4847
  * Discovers all MCP configuration files across the filesystem, inspecting existence and parsing server definitions.
3308
4848
  */
3309
4849
  static async discoverAll(cwd = process.cwd()) {
3310
- const homeDir = os4.homedir();
3311
- const appData = process.env.APPDATA || (process.platform === "darwin" ? path12.join(homeDir, "Library", "Application Support") : path12.join(homeDir, ".config"));
4850
+ const homeDir = os5.homedir();
4851
+ const appData = process.env.APPDATA || (process.platform === "darwin" ? path15.join(homeDir, "Library", "Application Support") : path15.join(homeDir, ".config"));
3312
4852
  const results = [];
3313
4853
  const seenPaths = /* @__PURE__ */ new Set();
3314
4854
  for (const desc of _McpLocationRegistry.LOCATIONS) {
3315
- const resolved = path12.normalize(desc.resolvePath(cwd, homeDir, appData));
4855
+ const resolved = path15.normalize(desc.resolvePath(cwd, homeDir, appData));
3316
4856
  const key = resolved.toLowerCase();
3317
4857
  if (seenPaths.has(key)) continue;
3318
4858
  seenPaths.add(key);
3319
- const exists = await fs10.pathExists(resolved);
4859
+ const exists = await fs13.pathExists(resolved);
3320
4860
  let serverCount = 0;
3321
4861
  let servers;
3322
4862
  if (exists) {
3323
4863
  try {
3324
- const config = await fs10.readJson(resolved);
4864
+ const config = await fs13.readJson(resolved);
3325
4865
  const rawServers = config?.mcpServers || config?.servers || config;
3326
4866
  if (rawServers && typeof rawServers === "object") {
3327
4867
  servers = rawServers;
@@ -3356,14 +4896,14 @@ var McpLocationRegistry = class _McpLocationRegistry {
3356
4896
  * Resolves the primary target write path for a given host runtime.
3357
4897
  */
3358
4898
  static getPrimaryWritePath(host, cwd = process.cwd()) {
3359
- const homeDir = os4.homedir();
3360
- const appData = process.env.APPDATA || (process.platform === "darwin" ? path12.join(homeDir, "Library", "Application Support") : path12.join(homeDir, ".config"));
4899
+ const homeDir = os5.homedir();
4900
+ const appData = process.env.APPDATA || (process.platform === "darwin" ? path15.join(homeDir, "Library", "Application Support") : path15.join(homeDir, ".config"));
3361
4901
  const targetHost = host === "agents" ? "gemini" : host;
3362
4902
  const desc = _McpLocationRegistry.LOCATIONS.find((l) => l.host === targetHost && l.isPrimary);
3363
4903
  if (desc) {
3364
- return path12.normalize(desc.resolvePath(cwd, homeDir, appData));
4904
+ return path15.normalize(desc.resolvePath(cwd, homeDir, appData));
3365
4905
  }
3366
- return path12.normalize(path12.join(cwd, ".cursor", "mcp.json"));
4906
+ return path15.normalize(path15.join(cwd, ".cursor", "mcp.json"));
3367
4907
  }
3368
4908
  };
3369
4909
 
@@ -3478,21 +5018,21 @@ var PrerequisiteChecker = class _PrerequisiteChecker {
3478
5018
  });
3479
5019
  }
3480
5020
  }
3481
- const homeDir = os5.homedir();
5021
+ const homeDir = os6.homedir();
3482
5022
  const dirCandidates = [
3483
- path13.join(cwd, ".gemini", "antigravity", "mcp"),
3484
- path13.join(homeDir, ".gemini", "antigravity", "mcp")
5023
+ path16.join(cwd, ".gemini", "antigravity", "mcp"),
5024
+ path16.join(homeDir, ".gemini", "antigravity", "mcp")
3485
5025
  ];
3486
5026
  for (const dir of dirCandidates) {
3487
- if (await fs11.pathExists(dir)) {
3488
- const entries = await fs11.readdir(dir).catch(() => []);
5027
+ if (await fs14.pathExists(dir)) {
5028
+ const entries = await fs14.readdir(dir).catch(() => []);
3489
5029
  for (const entry of entries) {
3490
5030
  if (_PrerequisiteChecker.matchesMcpServer(mcpName, entry, {})) {
3491
5031
  foundInConfigs.push({
3492
5032
  id: "antigravity-dir",
3493
5033
  host: "gemini",
3494
5034
  label: "Google Antigravity MCP Directory",
3495
- path: path13.join(dir, entry),
5035
+ path: path16.join(dir, entry),
3496
5036
  disabled: false,
3497
5037
  serverKey: entry
3498
5038
  });
@@ -3517,14 +5057,14 @@ var PrerequisiteChecker = class _PrerequisiteChecker {
3517
5057
  * Checks if an npm package is installed in node_modules or declared in package.json
3518
5058
  */
3519
5059
  static async isPackageInstalled(pkgName, cwd = process.cwd()) {
3520
- const modulePath = path13.join(cwd, "node_modules", ...pkgName.split("/"));
3521
- if (await fs11.pathExists(modulePath)) {
5060
+ const modulePath = path16.join(cwd, "node_modules", ...pkgName.split("/"));
5061
+ if (await fs14.pathExists(modulePath)) {
3522
5062
  return { installed: true, location: modulePath };
3523
5063
  }
3524
- const pkgJsonPath = path13.join(cwd, "package.json");
3525
- if (await fs11.pathExists(pkgJsonPath)) {
5064
+ const pkgJsonPath = path16.join(cwd, "package.json");
5065
+ if (await fs14.pathExists(pkgJsonPath)) {
3526
5066
  try {
3527
- const pkg = await fs11.readJson(pkgJsonPath);
5067
+ const pkg = await fs14.readJson(pkgJsonPath);
3528
5068
  const deps = {
3529
5069
  ...pkg.dependencies,
3530
5070
  ...pkg.devDependencies,
@@ -3547,20 +5087,20 @@ var PrerequisiteChecker = class _PrerequisiteChecker {
3547
5087
  return { set: true, source: "process.env" };
3548
5088
  }
3549
5089
  const envFiles = [
3550
- path13.join(cwd, ".env.local"),
3551
- path13.join(cwd, ".env")
5090
+ path16.join(cwd, ".env.local"),
5091
+ path16.join(cwd, ".env")
3552
5092
  ];
3553
5093
  for (const envFile of envFiles) {
3554
- if (await fs11.pathExists(envFile)) {
5094
+ if (await fs14.pathExists(envFile)) {
3555
5095
  try {
3556
- const content = await fs11.readFile(envFile, "utf8");
5096
+ const content = await fs14.readFile(envFile, "utf8");
3557
5097
  const lines = content.split("\n");
3558
5098
  for (const line of lines) {
3559
5099
  const trimmed = line.trim();
3560
5100
  if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
3561
5101
  const [k, ...v] = trimmed.split("=");
3562
5102
  if (k.trim() === varName && v.join("=").trim() !== "") {
3563
- return { set: true, source: path13.basename(envFile) };
5103
+ return { set: true, source: path16.basename(envFile) };
3564
5104
  }
3565
5105
  }
3566
5106
  } catch {
@@ -3766,11 +5306,11 @@ var PrerequisiteChecker = class _PrerequisiteChecker {
3766
5306
  * activating disabled servers and appending missing definitions.
3767
5307
  */
3768
5308
  static async autoConfigureConfigFile(targetFilePath, mcpNames, mode = "operational", envVars = {}) {
3769
- await fs11.ensureDir(path13.dirname(targetFilePath));
5309
+ await fs14.ensureDir(path16.dirname(targetFilePath));
3770
5310
  let existingConfig = {};
3771
- if (await fs11.pathExists(targetFilePath)) {
5311
+ if (await fs14.pathExists(targetFilePath)) {
3772
5312
  try {
3773
- existingConfig = await fs11.readJson(targetFilePath);
5313
+ existingConfig = await fs14.readJson(targetFilePath);
3774
5314
  } catch {
3775
5315
  existingConfig = {};
3776
5316
  }
@@ -3802,7 +5342,7 @@ var PrerequisiteChecker = class _PrerequisiteChecker {
3802
5342
  } else {
3803
5343
  existingConfig.mcpServers = existingServers;
3804
5344
  }
3805
- await fs11.writeJson(targetFilePath, existingConfig, { spaces: 2 });
5345
+ await fs14.writeJson(targetFilePath, existingConfig, { spaces: 2 });
3806
5346
  return {
3807
5347
  success: true,
3808
5348
  targetFile: targetFilePath,
@@ -3847,7 +5387,7 @@ var updater = new UpdateEngine(registry, scanner, installer);
3847
5387
  function detectWorkspaceHosts(cwd = process.cwd()) {
3848
5388
  const detected = [];
3849
5389
  for (const host of Object.values(HOST_REGISTRY)) {
3850
- const found = host.detectionMarkers.some((marker) => fs12.pathExistsSync(path14.join(cwd, marker)));
5390
+ const found = host.detectionMarkers.some((marker) => fs15.pathExistsSync(path17.join(cwd, marker)));
3851
5391
  if (found) detected.push(host.id);
3852
5392
  }
3853
5393
  return detected;
@@ -4078,7 +5618,7 @@ var BUNDLE_DISPLAY_NAMES = {
4078
5618
  summary: "Complete suite with all 7 team leads, 38 agents, and all 160 modular skills & workflows"
4079
5619
  }
4080
5620
  };
4081
- cli.command("add [identifier]", "Add a bundle, agent, skill, or workflow to project or global configuration").option("-g, --global", "Install globally into home directory (~/.agents/)").option("-s, --symlink", "Create symbolic links to central registry cache (default / recommended)").option("--copy", "Create independent standalone copies of asset files").option("-t, --target <hosts>", "Which assistants to set up (agents = main library; claude, cursor, cline, opencode, codex get translated copies)", { default: "agents" }).option("--fanout <hosts>", "Also make translated copies for these assistants: claude, cursor, cline, opencode, codex").option("--mode <mode>", "Execution mode for organization bundles (operational | brainstorming)", { default: "operational" }).option("--allow-missing-prereqs", "Proceed with installation even if some prerequisites are missing").option("--allow-under-construction", "Allow installation of bundles marked as under construction").option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Force overwrite user modified files").option("--start", "Start the installed team in Cline immediately after setup").option("--dry-run", "Simulate installation without writing files").action(async (targetIdentifier, options = {}) => {
5621
+ cli.command("add [identifier]", "Add a bundle, agent, skill, or workflow to project or global configuration").option("-g, --global", "Install globally into home directory (~/.agents/)").option("-s, --symlink", "Create symbolic links to central registry cache (default / recommended)").option("--copy", "Create independent standalone copies of asset files").option("-t, --target <hosts>", "Which assistants to set up (agents = main library; claude, cursor, cline, opencode, codex get translated copies)", { default: "agents" }).option("--fanout <hosts>", "Also make translated copies for these assistants: claude, cline. Under Development hosts (cursor, opencode, codex) are refused").option("--plugin", "Claude lane only: also emit the distribution-only plugin package (.agents/plugins/<bundle>/.claude-plugin/plugin.json + agents/) for `claude --plugin-dir`. Adds nothing when --fanout claude is absent; never the behavioural source. Sticky: the opt-in is recorded in the lockfile, so `agents update` keeps it. Use --no-plugin to turn it back off.").option("--mode <mode>", "Execution mode for organization bundles (operational | brainstorming)", { default: "operational" }).option("--allow-missing-prereqs", "Proceed with installation even if some prerequisites are missing").option("--allow-under-construction", "Allow installation of bundles marked as under construction").option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Force overwrite user modified files").option("--start", "Start the installed team in Cline immediately after setup").option("--dry-run", "Simulate installation without writing files").action(async (targetIdentifier, options = {}) => {
4082
5622
  intro(pc.cyan("Agents United \u2014 AI Agent Ecosystem"));
4083
5623
  let identifier = targetIdentifier;
4084
5624
  let scope = options.global ? "global" : "project";
@@ -4112,6 +5652,14 @@ cli.command("add [identifier]", "Add a bundle, agent, skill, or workflow to proj
4112
5652
  );
4113
5653
  }
4114
5654
  fanout = Array.from(/* @__PURE__ */ new Set([...fanout, ...parsedFanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h].projectionCapable)]));
5655
+ const underDevFanout = Array.from(new Set(parsedFanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h].status === "under-development")));
5656
+ if (underDevFanout.length > 0) {
5657
+ note(
5658
+ `Refusing --fanout host(s): ${underDevFanout.join(", ")} \u2014 they are listed as Under Development and unavailable. Supported --fanout ids: ${SUPPORTED_HOST_IDS.filter((h) => HOST_REGISTRY[h].projectionCapable).join(", ")}. Drop ${underDevFanout.join(", ")} from --fanout and run again.`,
5659
+ "Host unavailable"
5660
+ );
5661
+ process.exit(1);
5662
+ }
4115
5663
  }
4116
5664
  const isInteractive = process.stdout.isTTY && !options.yes;
4117
5665
  if (isInteractive && !options.global && !options.copy && !options.symlink && options.target === "agents") {
@@ -4122,6 +5670,7 @@ cli.command("add [identifier]", "Add a bundle, agent, skill, or workflow to proj
4122
5670
  "Workspace Environment Discovery"
4123
5671
  );
4124
5672
  }
5673
+ note(hostAvailabilityNotice(), "Host availability");
4125
5674
  const hostSelection = await multiselect({
4126
5675
  message: "1. Which AI tools should we configure agent teams & personas for?",
4127
5676
  options: [
@@ -4135,33 +5684,18 @@ cli.command("add [identifier]", "Add a bundle, agent, skill, or workflow to proj
4135
5684
  label: HOST_REGISTRY.claude.label,
4136
5685
  hint: detectedHosts.includes("claude") ? "found in this project" : "orchestrator & subagent personas for Claude Code"
4137
5686
  },
4138
- {
4139
- value: "cursor",
4140
- label: HOST_REGISTRY.cursor.label,
4141
- hint: detectedHosts.includes("cursor") ? "found in this project" : "orchestrator & subagent personas for Cursor IDE"
4142
- },
4143
5687
  {
4144
5688
  value: "cline",
4145
5689
  label: HOST_REGISTRY.cline.label,
4146
5690
  hint: detectedHosts.includes("cline") ? "found in this project" : "configured agents, skills, rules, workflows & team manifests for Cline"
4147
5691
  },
4148
- {
4149
- value: "opencode",
4150
- label: HOST_REGISTRY.opencode.label,
4151
- hint: detectedHosts.includes("opencode") ? "found in this project" : "orchestrator & subagent personas for OpenCode"
4152
- },
4153
- {
4154
- value: "codex",
4155
- label: HOST_REGISTRY.codex.label,
4156
- hint: detectedHosts.includes("codex") ? "found in this project" : "root index linking orchestrators, subagents & skills for Codex, Copilot, Aider & Zed"
4157
- },
4158
5692
  {
4159
5693
  value: "gemini",
4160
5694
  label: HOST_REGISTRY.gemini.label,
4161
5695
  hint: detectedHosts.includes("gemini") ? "found in this project" : "older Antigravity 1.0 / Gemini folder"
4162
5696
  }
4163
5697
  ],
4164
- initialValues: detectedHosts.length > 0 ? detectedHosts : ["agents"],
5698
+ initialValues: detectedHosts.length > 0 ? detectedHosts.filter((h) => HOST_REGISTRY[h]?.status === "supported") : ["agents"],
4165
5699
  required: true
4166
5700
  });
4167
5701
  if (Array.isArray(hostSelection) && hostSelection.length > 0) {
@@ -4466,6 +6000,10 @@ To bypass this gate and install draft assets anyway, pass: --allow-under-constru
4466
6000
  method,
4467
6001
  hosts,
4468
6002
  fanout,
6003
+ // Plan 016 decision 13 — opt-in Claude plugin lane (distribution-only; ignored unless the
6004
+ // claude lane runs). `--plugin` opts in, `--no-plugin` opts out explicitly, and an omitted
6005
+ // flag inherits the recorded choice so `agents update` cannot prune the package.
6006
+ pluginLane: typeof options.plugin === "boolean" ? options.plugin : void 0,
4469
6007
  mode: executionMode,
4470
6008
  allowMissingPrereqs: options.allowMissingPrereqs,
4471
6009
  yes: options.yes,
@@ -4589,6 +6127,14 @@ rules & workflows run: agents update ${identifier} --fanout cline
4589
6127
  "Adaptive Tooling"
4590
6128
  );
4591
6129
  }
6130
+ if (!options.dryRun) {
6131
+ note(
6132
+ pc.yellow(
6133
+ "New skills are discovered at session start. To pick them up now:\n Claude Code ..... user types /reload-skills (#58733)\n Antigravity CLI . user types /skills reload\n Antigravity App . restart the app, or use @<skill_name>\n Cline ........... agents read .cline/skills/ directly\nSlash commands only fire when YOU type them; agents can read the file."
6134
+ ),
6135
+ "Pick up new skills"
6136
+ );
6137
+ }
4592
6138
  outro(pc.green(`\u2714 Installed "${identifier}" successfully!`));
4593
6139
  } catch (err) {
4594
6140
  s.stop(pc.red("Failed resolution"));
@@ -4656,10 +6202,25 @@ cli.command("remove [identifier]", "Remove an installed bundle, agent, skill, or
4656
6202
  });
4657
6203
  s.stop(`Uninstall processed`);
4658
6204
  if (options.dryRun) {
4659
- outro(pc.yellow(`[DRY RUN] Would remove ${result.removed.length} assets from ${result.targetDirs.join(", ")}`));
6205
+ outro(pc.yellow(
6206
+ `[DRY RUN] Would delete ${result.removed.length} and keep ${result.kept.length}` + (result.kept.length > 0 ? ` (still owned by: ${result.retainedOwners.join(", ")})` : "") + ` \u2014 ${result.targetDirs.join(", ")}`
6207
+ ));
4660
6208
  return;
4661
6209
  }
4662
- outro(pc.green(`\u2714 Successfully removed ${result.removed.length} files matching "${identifier}"`));
6210
+ if (result.kept.length > 0) {
6211
+ log.info(`${result.removed.length} deleted \xB7 ${result.kept.length} kept \u2014 still owned by: ${result.retainedOwners.join(", ")}`);
6212
+ }
6213
+ if (result.removed.length > 0) {
6214
+ const sample = result.removed.slice(0, 3).join(", ");
6215
+ const more = result.removed.length > 3 ? `, \u2026 +${result.removed.length - 3} more` : "";
6216
+ log.info(`deleted: ${sample}${more}`);
6217
+ }
6218
+ if (result.staleRecords.length > 0) {
6219
+ log.info(`${result.staleRecords.length} stale record(s) cleaned \u2014 bookkeeping only, those files were already gone`);
6220
+ }
6221
+ outro(pc.green(
6222
+ result.removed.length === 0 && result.kept.length > 0 ? `\u2714 Removed "${identifier}" from this workspace \u2014 nothing was deleted, ${result.kept.length} co-owned assets kept` : `\u2714 Removed "${identifier}" from this workspace \u2014 ${result.removed.length} deleted, ${result.kept.length} kept`
6223
+ ));
4663
6224
  } catch (err) {
4664
6225
  s.stop(pc.red("Uninstall failed"));
4665
6226
  outro(pc.red(`Error: ${err.message}`));
@@ -4681,6 +6242,16 @@ cli.command("update [identifier]", "Update installed packages to upstream regist
4681
6242
  "Invalid fanout"
4682
6243
  );
4683
6244
  }
6245
+ const updateUnderDev = Array.from(new Set(parsedFanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h].status === "under-development")));
6246
+ if (updateUnderDev.length > 0) {
6247
+ note(
6248
+ pc.yellow(
6249
+ `Refusing --fanout host(s): ${updateUnderDev.join(", ")} \u2014 they are listed as Under Development and unavailable. Supported --fanout ids: ${SUPPORTED_HOST_IDS.filter((h) => HOST_REGISTRY[h].projectionCapable).join(", ")}. Drop ${updateUnderDev.join(", ")} from --fanout and run again.`
6250
+ ),
6251
+ "Host unavailable"
6252
+ );
6253
+ process.exit(1);
6254
+ }
4684
6255
  updateFanout = parsedFanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h].projectionCapable);
4685
6256
  }
4686
6257
  const s = spinner();
@@ -4803,7 +6374,7 @@ Updates Available: ${report.outdatedCount > 0 ? pc.yellow(pc.bold(`${report.outd
4803
6374
  const unprojected = [];
4804
6375
  for (const rec of result.updated) {
4805
6376
  try {
4806
- const lf = await fs12.readJson(path14.join(rec.targetDir, "agents-united.json"));
6377
+ const lf = await fs15.readJson(path17.join(rec.targetDir, "agents-united.json"));
4807
6378
  if (!lf.fanout || lf.fanout.length === 0) unprojected.push(rec.name);
4808
6379
  } catch {
4809
6380
  }
@@ -5242,6 +6813,12 @@ Planned Capabilities:
5242
6813
  method: "symlink"
5243
6814
  });
5244
6815
  installSpinner.stop(pc.green(`\u2714 Successfully installed ${bundle.name}!`));
6816
+ note(
6817
+ pc.yellow(
6818
+ "New skills are discovered at session start. To pick them up now:\n Claude Code ..... user types /reload-skills (#58733)\n Antigravity CLI . user types /skills reload\n Antigravity App . restart the app, or use @<skill_name>\n Cline ........... agents read .cline/skills/ directly\nSlash commands only fire when YOU type them; agents can read the file."
6819
+ ),
6820
+ "Pick up new skills"
6821
+ );
5245
6822
  note(
5246
6823
  formatInstallationSummary({
5247
6824
  bundleName: result.installed.targetBundle || bundle.name,
@@ -5537,11 +7114,19 @@ cli.command("init", "Initialize project workspace directory and install recommen
5537
7114
  process.exit(1);
5538
7115
  }
5539
7116
  });
5540
- cli.command("start <bundle> [prompt]", "Start an installed bundle team in its host runtime (e.g. Cline)").option("--host <host>", "Host runtime (default: auto-detect from lockfile fanout)").option("-g, --global", "Select global installation").option("--team <name>", "Override generated team name").option("--allow-addons", "Pre-authorize recommended addon installations for this session").option("--headless", "Run non-interactively without interactive TUI").option("--dry-run", "Print activation resolution and argv summary without launching").action(async (bundle, prompt, options = {}) => {
7117
+ cli.command("start <bundle> [prompt]", "Start an installed bundle team in its host runtime (e.g. Cline, Claude Code)").option("--host <host>", "Host runtime (default: auto-detect from lockfile fanout)").option("-g, --global", "Select global installation").option("--team <name>", "Override generated team name").option("--allow-addons", "Pre-authorize recommended addon installations for this session").option("--headless", "Run non-interactively without interactive TUI").option("--bg", "Run the Claude Code session in the background (claude host only)").option("--teams", "Force the experimental agent-teams scaffold on (claude host only). Default: ON for organization-tier (Tier 2) bundles, OFF for domain-tier (Tier 1) bundles \u2014 pass --no-teams to opt out. Ephemeral env only, nothing persisted").option("--plugin", "Pass --plugin-dir with the bundle plugin root (claude host only)").option("--dry-run", "Print activation resolution and argv summary without launching").action(async (bundle, prompt, options = {}) => {
5541
7118
  intro(pc.cyan("Agents United \u2014 Runtime Activation"));
5542
7119
  const launcher = new ClineLauncher();
5543
7120
  const probe = new ClineCapabilityProbe();
5544
7121
  try {
7122
+ const effectiveHost = await resolveStartHost(bundle, options);
7123
+ if (effectiveHost === "claude") {
7124
+ await runClaudeStart(bundle, prompt, options);
7125
+ return;
7126
+ }
7127
+ if (effectiveHost === "unsupported") {
7128
+ note(pc.yellow(`Host '${options.host}' has no activation launcher yet; falling back to the Cline lane.`), "Host Runtime");
7129
+ }
5545
7130
  const resolution = await launcher.resolveInstallation(bundle, {
5546
7131
  global: options.global,
5547
7132
  cwd: process.cwd()
@@ -5552,6 +7137,7 @@ cli.command("start <bundle> [prompt]", "Start an installed bundle team in its ho
5552
7137
  process.exit(1);
5553
7138
  }
5554
7139
  const bundleDef = await registry.getBundle(bundle);
7140
+ const organizationTier = bundleDef?.tier === "organization";
5555
7141
  const plan = launcher.planActivation({
5556
7142
  bundleName: bundle,
5557
7143
  workspace: resolution.workspace,
@@ -5566,6 +7152,8 @@ cli.command("start <bundle> [prompt]", "Start an installed bundle team in its ho
5566
7152
  if (options.dryRun) {
5567
7153
  note(
5568
7154
  `Bundle: ${plan.bundleName}
7155
+ Tier: ${bundleDef?.tier ?? "not declared (Tier-1 domain default)"}
7156
+ Agent teams: ${organizationTier ? "not available on this host (claude-only runtime feature)" : "no"}
5569
7157
  Scope: ${plan.scope}
5570
7158
  Workspace: ${plan.workspace}
5571
7159
  Team Name: ${plan.teamName}
@@ -5577,6 +7165,16 @@ Argv: ${plan.argv.map((a) => a.includes(" ") || a.includes("\n") ? `"${a.replace
5577
7165
  outro(pc.yellow("Dry run complete. No processes launched."));
5578
7166
  return;
5579
7167
  }
7168
+ if (organizationTier) {
7169
+ note(
7170
+ pc.yellow(
7171
+ `"${bundle}" is an organization-tier (Tier 2) bundle. Agent Teams is a Claude Code runtime
7172
+ feature and is not available on this host, so '--teams' is claude-only. This session runs
7173
+ Cline's ${plan.strategy} strategy with the projected specialist tools under .cline/agents/.`
7174
+ ),
7175
+ "Tier 2 (organization)"
7176
+ );
7177
+ }
5580
7178
  note(
5581
7179
  `Team: ${pc.bold(plan.teamName)}
5582
7180
  Strategy: ${pc.cyan(plan.strategy)}
@@ -5590,7 +7188,7 @@ Workspace: ${plan.workspace}`,
5590
7188
  process.exit(1);
5591
7189
  }
5592
7190
  });
5593
- cli.command("doctor", "Verify health of installed agents, frontmatter schemas, and hooks").option("--host <host>", "Audit specific host runtime (e.g. cline)").action(async (options = {}) => {
7191
+ cli.command("doctor", "Verify health of installed agents, frontmatter schemas, and hooks").option("--host <host>", "Audit specific host runtime (e.g. cline, claude)").action(async (options = {}) => {
5594
7192
  intro(pc.cyan("\u{1FA7A} Agents United \u2014 Health Doctor"));
5595
7193
  const report = await DoctorEngine.runDoctor(void 0, options.host);
5596
7194
  if (!report.isInitialized || report.agentsCount === 0 && report.skillsCount === 0 && report.workflowsCount === 0 && report.issues.length === 0) {
@@ -5612,6 +7210,13 @@ cli.command("doctor", "Verify health of installed agents, frontmatter schemas, a
5612
7210
  console.log(` Named Teams: ${report.clineCapability.namedTeams ? pc.green("\u2714 Supported") : pc.yellow("\u2716 Unsupported (Adaptive fallback)")}
5613
7211
  `);
5614
7212
  }
7213
+ if (report.claudeCapability) {
7214
+ renderClaudeCapabilityBlock(report.claudeCapability);
7215
+ for (const diagnostic of report.claudeCapability.diagnostics) {
7216
+ console.log(` ${pc.dim(diagnostic)}`);
7217
+ }
7218
+ console.log();
7219
+ }
5615
7220
  if (report.warnings.length > 0) {
5616
7221
  console.log(pc.yellow(pc.bold("\u26A0\uFE0F Warnings:")));
5617
7222
  report.warnings.forEach((w) => console.log(` \u26A0 ${w}`));
@@ -5632,6 +7237,11 @@ cli.command("doctor", "Verify health of installed agents, frontmatter schemas, a
5632
7237
  }
5633
7238
  console.log(` Named Teams: ${report.clineCapability.namedTeams ? pc.green("\u2714 Supported") : pc.yellow("\u2716 Unsupported (Adaptive fallback)")}`);
5634
7239
  console.log(` Configured Agents: ${report.agentsCount} active natively in any Cline session ("agents start" = optional team-session launcher)
7240
+ `);
7241
+ }
7242
+ if (report.claudeCapability) {
7243
+ renderClaudeCapabilityBlock(report.claudeCapability);
7244
+ console.log(` Configured Agents: ${report.agentsCount} projected into .claude/agents/ ("agents start --host claude" = optional launcher)
5635
7245
  `);
5636
7246
  }
5637
7247
  if (report.issues.length > 0) {
@@ -5651,15 +7261,176 @@ cli.command("doctor", "Verify health of installed agents, frontmatter schemas, a
5651
7261
  process.exit(1);
5652
7262
  }
5653
7263
  });
7264
+ async function resolveStartHost(bundle, options) {
7265
+ const explicitHost = typeof options.host === "string" ? options.host.trim().toLowerCase() : "";
7266
+ if (explicitHost === "claude") {
7267
+ return "claude";
7268
+ }
7269
+ if (explicitHost === "cline") {
7270
+ return "cline";
7271
+ }
7272
+ if (explicitHost.length > 0) {
7273
+ return "unsupported";
7274
+ }
7275
+ let recordedFanout = [];
7276
+ try {
7277
+ const detection = await new ClaudeLauncher().resolveInstallation(bundle, {
7278
+ global: options.global,
7279
+ cwd: process.cwd()
7280
+ });
7281
+ recordedFanout = detection.lockfile.fanout || [];
7282
+ } catch {
7283
+ recordedFanout = [];
7284
+ }
7285
+ return recordedFanout.includes("claude") ? "claude" : "cline";
7286
+ }
7287
+ function resolveClaudeTeamsPosture(teamsOption, tier) {
7288
+ if (teamsOption === true) return { active: true, reason: "explicit", tier };
7289
+ if (teamsOption === false) return { active: false, reason: "opt-out", tier };
7290
+ if (tier === "organization") return { active: true, reason: "tier-default", tier };
7291
+ if (tier === "domain") return { active: false, reason: "domain-tier", tier };
7292
+ return { active: false, reason: "unresolved-tier" };
7293
+ }
7294
+ function describeClaudeTeamsPosture(posture) {
7295
+ switch (posture.reason) {
7296
+ case "explicit":
7297
+ return "explicit --teams";
7298
+ case "tier-default":
7299
+ return "default for tier 'organization'";
7300
+ case "opt-out":
7301
+ return "--no-teams opt-out";
7302
+ case "domain-tier":
7303
+ return "tier 'domain' \u2014 pass --teams to enable";
7304
+ case "unresolved-tier":
7305
+ return "no declared tier (Tier-1 domain default) \u2014 pass --teams to enable";
7306
+ }
7307
+ }
7308
+ async function runClaudeStart(bundle, prompt, options) {
7309
+ const launcher = new ClaudeLauncher();
7310
+ const probe = new ClaudeCapabilityProbe();
7311
+ const resolution = await launcher.resolveInstallation(bundle, {
7312
+ global: options.global,
7313
+ cwd: process.cwd()
7314
+ });
7315
+ const probeReport = await probe.probe();
7316
+ if (!probeReport.installed && !options.dryRun) {
7317
+ outro(pc.red(`Claude Code executable was not found on PATH or via CLAUDE_BIN_PATH. Please install Claude Code or ensure it is accessible.`));
7318
+ process.exit(1);
7319
+ }
7320
+ const bundleDef = await registry.getBundle(bundle);
7321
+ const teamsPosture = resolveClaudeTeamsPosture(options.teams, bundleDef?.tier);
7322
+ const teamsActive = teamsPosture.active;
7323
+ const manifestAvailable = await fs15.pathExists(resolution.manifestPath);
7324
+ const pluginDir = options.plugin ? path17.join(".agents", "plugins", bundle).split(path17.sep).join("/") : void 0;
7325
+ const plan = launcher.planActivation({
7326
+ bundleName: bundle,
7327
+ workspace: resolution.workspace,
7328
+ scope: resolution.scope,
7329
+ report: probeReport,
7330
+ prompt,
7331
+ orchestrator: bundleDef?.orchestrator,
7332
+ allowAddons: options.allowAddons,
7333
+ background: options.bg,
7334
+ teams: teamsActive,
7335
+ pluginDir,
7336
+ manifestAvailable
7337
+ });
7338
+ const recordedFanout = resolution.lockfile.fanout || [];
7339
+ if (options.dryRun) {
7340
+ note(renderClaudeActivationPlan(plan, probeReport, recordedFanout, teamsPosture), "Claude Activation Plan (dry run)");
7341
+ outro(pc.yellow("Dry run complete. No processes launched."));
7342
+ return;
7343
+ }
7344
+ if (!recordedFanout.includes("claude")) {
7345
+ note(
7346
+ pc.yellow(
7347
+ `This workspace records fanout [${recordedFanout.join(", ") || "none"}]. Run
7348
+ 'agents update ${bundle} --fanout claude' to project .claude/agents/ before the session needs it.`
7349
+ ),
7350
+ "Claude Projection"
7351
+ );
7352
+ }
7353
+ if (teamsActive && teamsPosture.reason === "tier-default") {
7354
+ note(
7355
+ pc.yellow(
7356
+ `Agent Teams is ON by default for this organization-tier bundle (tier 'organization').
7357
+ --no-teams falls back to ordinary parallel subagents.`
7358
+ ),
7359
+ "Agent Teams"
7360
+ );
7361
+ } else if (teamsActive) {
7362
+ note(pc.cyan("Agent Teams explicitly requested with --teams; the scaffold stays experimental."), "Agent Teams");
7363
+ }
7364
+ if (teamsActive && !probeReport.agentTeamsExperimental) {
7365
+ note(pc.yellow("The capability probe could not confirm agent-team support from --help; the scaffold stays experimental and unverified."), "Agent Teams");
7366
+ }
7367
+ if (pluginDir && !probeReport.pluginSupport) {
7368
+ note(pc.yellow("The capability probe could not confirm --plugin-dir support from --help."), "Plugin Dir");
7369
+ }
7370
+ note(
7371
+ `Host: ${pc.cyan("claude")}
7372
+ Executable: ${plan.executable}
7373
+ Workspace: ${plan.workspace}
7374
+ Background: ${options.bg ? "yes (--bg)" : "no"}
7375
+ Teams scaffold: ${teamsActive ? `yes (${describeClaudeTeamsPosture(teamsPosture)}; experimental, ephemeral env only)` : `no (${describeClaudeTeamsPosture(teamsPosture)})`}
7376
+ Plugin dir: ${pluginDir || "(none)"}`,
7377
+ "Starting Claude Code Session"
7378
+ );
7379
+ const coordinatorRel = `.claude/agents/${ClaudeLauncher.resolveCoordinatorName(bundleDef?.orchestrator)}.md`;
7380
+ if (!await fs15.pathExists(path17.join(resolution.workspace, coordinatorRel))) {
7381
+ outro(
7382
+ pc.red(
7383
+ `Claude projection "${coordinatorRel}" was not found in ${resolution.workspace}.
7384
+ Run 'agents update ${bundle} --fanout claude' before starting an --agent session.`
7385
+ )
7386
+ );
7387
+ process.exit(1);
7388
+ }
7389
+ await launcher.launch(plan);
7390
+ outro(pc.green(`\u2714 Claude Code session finished.`));
7391
+ }
7392
+ function renderClaudeCapabilityBlock(capability) {
7393
+ console.log(pc.bold(pc.cyan("Claude Code Runtime & Native Discovery Audit:")));
7394
+ console.log(` Installed: ${capability.installed ? pc.green("\u2714 Detected") : pc.yellow("\u2716 Not Found")}`);
7395
+ if (capability.version) {
7396
+ console.log(` Version: ${capability.version}`);
7397
+ }
7398
+ console.log(` Plugin Support (--plugin-dir): ${capability.pluginSupport ? pc.green("\u2714 Supported") : pc.yellow("\u2716 Unsupported")}`);
7399
+ console.log(` Agent Teams (experimental): ${capability.agentTeamsExperimental ? pc.green("\u2714 Supported") : pc.yellow("\u2716 Unsupported")}`);
7400
+ console.log(` Subagent hand-off (SubagentHandback): ${capability.subagentHandback ? pc.green("\u2714 Supported") : pc.yellow("\u2716 Needs v2.1.271+ (auto mode)")}`);
7401
+ }
7402
+ function renderClaudeActivationPlan(plan, report, recordedFanout, posture) {
7403
+ const envEntries = Object.keys(plan.env);
7404
+ const pluginFlagIndex = plan.argv.indexOf("--plugin-dir");
7405
+ const teamsActive = envEntries.includes("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS");
7406
+ return [
7407
+ `Bundle: ${plan.bundleName}`,
7408
+ `Host: claude`,
7409
+ `Bundle tier: ${posture.tier ?? "not declared (Tier-1 'domain' default)"}`,
7410
+ `Scope: ${plan.scope}`,
7411
+ `Workspace: ${plan.workspace}`,
7412
+ `Executable: ${plan.executable}`,
7413
+ `Background (--bg): ${plan.argv.includes("--bg") ? "yes" : "no"}`,
7414
+ teamsActive ? `Agent teams (--teams): yes (${describeClaudeTeamsPosture(posture)} \u2014 experimental; ephemeral env only, nothing persisted)` : `Agent teams (--teams): no (${describeClaudeTeamsPosture(posture)})`,
7415
+ `Plugin (--plugin-dir): ${pluginFlagIndex >= 0 ? plan.argv[pluginFlagIndex + 1] : "(none)"}`,
7416
+ `Env injected (merged over process.env): ${envEntries.length > 0 ? envEntries.map((k) => `${k}=${plan.env[k]}`).join(", ") : "(none)"}`,
7417
+ `Capability probe: installed=${report.installed ? `yes${report.version ? ` (${report.version})` : ""}` : "no"}, --plugin-dir=${report.pluginSupport ? "yes" : "no"}, agent teams=${report.agentTeamsExperimental ? "yes" : "no"}, subagent handback=${report.subagentHandback ? "yes" : "no"}`,
7418
+ `Recorded fanout: [${recordedFanout.join(", ")}]`,
7419
+ `Argv (${plan.argv.length} elements, one element per line, spawned with shell: false):`,
7420
+ ...plan.argv.map((arg, index) => ` [${index}] ${arg.replace(/\r?\n/g, "\\n")}`)
7421
+ ].join("\n");
7422
+ }
5654
7423
  cli.help();
5655
7424
  cli.version("1.0.0");
5656
7425
  cli.parse();
5657
7426
  export {
7427
+ describeClaudeTeamsPosture,
5658
7428
  detectWorkspaceHosts,
5659
7429
  formatInstallationSummary,
5660
7430
  formatWrappedList,
5661
7431
  getTerminalContentWidth,
5662
7432
  renderProjections,
7433
+ resolveClaudeTeamsPosture,
5663
7434
  wrapText
5664
7435
  };
5665
7436
  //# sourceMappingURL=cli.js.map