agents-united 1.2.0 → 1.3.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.
- package/README.md +1 -1
- package/dist/cli.js +184 -36
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -368,7 +368,7 @@ agents doctor --host cline
|
|
|
368
368
|
---
|
|
369
369
|
## 🌐 One Library, Every Assistant
|
|
370
370
|
|
|
371
|
-
The `.agents/` folder is the **main library** — the *one* folder you edit. **Antigravity reads it directly
|
|
371
|
+
The `.agents/` folder is the **main library** — the *one* folder you edit. **Antigravity reads it directly** for interactive sessions (CLI TUI and desktop); see [ADR 0009](./docs/adr/0009-host-conformance-targets.md). Every other assistant (Claude Code, Cursor, Cline, OpenCode, Codex) can't read it natively, so Agents United writes **translated copies** it keeps in sync for you.
|
|
372
372
|
|
|
373
373
|
- **Edit only `.agents/`.** That's your source of truth — the `agents-united.json` lockfile tracks it.
|
|
374
374
|
- **Other assistants get their own translated copies**, in their own folders (`.claude/agents/`, `.cline/agents/`, …). Don't edit those — they're machine-managed and rewritten on every `agents update`.
|
package/dist/cli.js
CHANGED
|
@@ -419,8 +419,10 @@ ${bodyStr}
|
|
|
419
419
|
}
|
|
420
420
|
/**
|
|
421
421
|
* Render Team Manifest YAML matching ClineTeamManifest schema.
|
|
422
|
+
* @param excludeAddons optional addon names to omit from `recommendedAddons`
|
|
423
|
+
* (used to stop advertising bundles already present in the workspace).
|
|
422
424
|
*/
|
|
423
|
-
static renderTeamManifest(bundle, scope) {
|
|
425
|
+
static renderTeamManifest(bundle, scope, excludeAddons = []) {
|
|
424
426
|
const coordinatorFile = bundle.orchestrator || `${bundle.name}.md`;
|
|
425
427
|
const coordinatorName = coordinatorFile.replace(/\.md$/, "");
|
|
426
428
|
const coordinatorCanonical = `agents/${coordinatorFile}`;
|
|
@@ -430,7 +432,7 @@ ${bodyStr}
|
|
|
430
432
|
}));
|
|
431
433
|
const sortedSkills = [...bundle.skills || []].sort();
|
|
432
434
|
const sortedWorkflows = [...bundle.workflows || []].sort();
|
|
433
|
-
const recommendedAddons = bundle.recommendedAddons || [];
|
|
435
|
+
const recommendedAddons = (bundle.recommendedAddons || []).filter((a) => !excludeAddons.includes(a));
|
|
434
436
|
const manifest = {
|
|
435
437
|
schemaVersion: 1,
|
|
436
438
|
bundle: bundle.name,
|
|
@@ -452,8 +454,10 @@ ${bodyStr}
|
|
|
452
454
|
}
|
|
453
455
|
/**
|
|
454
456
|
* Render the coordinator rule file (.cline/rules/agents-united-<bundle>.md).
|
|
457
|
+
* @param excludeAddons optional addon names to omit from the "Recommended Addon
|
|
458
|
+
* Policy" list (used to stop advertising bundles already installed).
|
|
455
459
|
*/
|
|
456
|
-
static renderCoordinatorRule(bundle, scope) {
|
|
460
|
+
static renderCoordinatorRule(bundle, scope, excludeAddons = []) {
|
|
457
461
|
const marker = `<!-- managed-by: agents-united | profile: cline | bundle: ${bundle.name} | do not edit -->`;
|
|
458
462
|
const manifestRelPath = scope === "global" ? `~/.cline/agents-united/teams/${bundle.name}.yaml` : `.cline/agents-united/teams/${bundle.name}.yaml`;
|
|
459
463
|
const coordinatorFile = bundle.orchestrator || `${bundle.name}.md`;
|
|
@@ -463,7 +467,7 @@ ${bodyStr}
|
|
|
463
467
|
const name = agentFile.replace(/\.md$/, "");
|
|
464
468
|
return `- **${name}**: \`.agents/agents/${agentFile}\``;
|
|
465
469
|
});
|
|
466
|
-
const addons = bundle.recommendedAddons || [];
|
|
470
|
+
const addons = (bundle.recommendedAddons || []).filter((a) => !excludeAddons.includes(a));
|
|
467
471
|
const addonSection = addons.length > 0 ? `
|
|
468
472
|
### Recommended Addon Policy
|
|
469
473
|
When user tasks require capabilities from: ${addons.join(", ")}, explain the capability and request user confirmation to install via \`agents add <addon> -t cline -y\` before running the installation.` : "";
|
|
@@ -487,8 +491,10 @@ ${addonSection}
|
|
|
487
491
|
}
|
|
488
492
|
/**
|
|
489
493
|
* Plan all compound artifacts for a bundle installation into Cline.
|
|
494
|
+
* @param excludeAddons optional addon names forwarded to the coordinator rule and
|
|
495
|
+
* team manifest renderers so already-installed bundles are not advertised.
|
|
490
496
|
*/
|
|
491
|
-
static async planCompoundProjection(bundle, scope, resolved, registryDir) {
|
|
497
|
+
static async planCompoundProjection(bundle, scope, resolved, registryDir, excludeAddons = []) {
|
|
492
498
|
const artifacts = [];
|
|
493
499
|
const baseDir = scope === "global" ? ".cline" : ".cline";
|
|
494
500
|
for (const agentFile of resolved.agents) {
|
|
@@ -539,14 +545,14 @@ ${addonSection}
|
|
|
539
545
|
}
|
|
540
546
|
}
|
|
541
547
|
}
|
|
542
|
-
const ruleContent = this.renderCoordinatorRule(bundle, scope);
|
|
548
|
+
const ruleContent = this.renderCoordinatorRule(bundle, scope, excludeAddons);
|
|
543
549
|
artifacts.push({
|
|
544
550
|
kind: "rule",
|
|
545
551
|
relPath: `${baseDir}/rules/agents-united-${bundle.name}.md`.replace(/\\/g, "/"),
|
|
546
552
|
content: ruleContent,
|
|
547
553
|
managedMarker: true
|
|
548
554
|
});
|
|
549
|
-
const manifestContent = this.renderTeamManifest(bundle, scope);
|
|
555
|
+
const manifestContent = this.renderTeamManifest(bundle, scope, excludeAddons);
|
|
550
556
|
artifacts.push({
|
|
551
557
|
kind: "team-manifest",
|
|
552
558
|
relPath: `${baseDir}/agents-united/teams/${bundle.name}.yaml`.replace(/\\/g, "/"),
|
|
@@ -948,6 +954,11 @@ var InstallEngine = class _InstallEngine {
|
|
|
948
954
|
if (host === "cline") {
|
|
949
955
|
const bundleDef = resolved.targetBundle ? await this.registry.getBundle(resolved.targetBundle) : void 0;
|
|
950
956
|
if (bundleDef) {
|
|
957
|
+
const declared = /* @__PURE__ */ new Set();
|
|
958
|
+
if (bundleDef.orchestrator) declared.add(`agents/${bundleDef.orchestrator}`);
|
|
959
|
+
(bundleDef.agents || []).forEach((a) => declared.add(`agents/${a}`));
|
|
960
|
+
(bundleDef.skills || []).forEach((s) => declared.add(`skills/${s}/SKILL.md`));
|
|
961
|
+
(bundleDef.workflows || []).forEach((w) => declared.add(`workflows/${w}`));
|
|
951
962
|
const artifacts = await ClineProjector.planCompoundProjection(bundleDef, scope, resolved, registryDir);
|
|
952
963
|
for (const artifact of artifacts) {
|
|
953
964
|
const dest = path5.join(root, artifact.relPath);
|
|
@@ -978,14 +989,16 @@ var InstallEngine = class _InstallEngine {
|
|
|
978
989
|
lockfile.projections = lockfile.projections || {};
|
|
979
990
|
const existingProj = lockfile.projections[artifact.relPath];
|
|
980
991
|
const bundleName = bundleDef.name;
|
|
981
|
-
const
|
|
992
|
+
const declares = artifact.kind === "rule" || artifact.kind === "team-manifest" || (artifact.canonical ? declared.has(artifact.canonical) : false);
|
|
993
|
+
const priorOwners = existingProj?.owners ?? [];
|
|
994
|
+
const owners = declares ? Array.from(/* @__PURE__ */ new Set([...priorOwners, bundleName])) : priorOwners;
|
|
982
995
|
lockfile.projections[artifact.relPath] = {
|
|
983
996
|
host: "cline",
|
|
984
997
|
kind: artifact.kind,
|
|
985
998
|
canonical: artifact.canonical,
|
|
986
999
|
owners,
|
|
987
1000
|
hash: deployedHash,
|
|
988
|
-
installedAt: now,
|
|
1001
|
+
installedAt: existingProj?.installedAt ?? now,
|
|
989
1002
|
managedMarker: artifact.managedMarker
|
|
990
1003
|
};
|
|
991
1004
|
projections.push({ host, path: artifact.relPath, kind: artifact.kind, warnings: [] });
|
|
@@ -993,6 +1006,38 @@ var InstallEngine = class _InstallEngine {
|
|
|
993
1006
|
this.recordProjectedTo(lockfile, artifact.canonical, artifact.relPath);
|
|
994
1007
|
}
|
|
995
1008
|
}
|
|
1009
|
+
if (bundleDef.parentBundle) {
|
|
1010
|
+
const parentDef = await this.registry.getBundle(bundleDef.parentBundle);
|
|
1011
|
+
if (parentDef) {
|
|
1012
|
+
const parentProjection = await ClineProjector.planCompoundProjection(
|
|
1013
|
+
parentDef,
|
|
1014
|
+
scope,
|
|
1015
|
+
resolved,
|
|
1016
|
+
registryDir,
|
|
1017
|
+
lockfile.installed.bundles
|
|
1018
|
+
);
|
|
1019
|
+
for (const artifact of parentProjection) {
|
|
1020
|
+
if (artifact.kind !== "rule" && artifact.kind !== "team-manifest") continue;
|
|
1021
|
+
if (artifact.content === void 0) continue;
|
|
1022
|
+
const dest = path5.join(root, artifact.relPath);
|
|
1023
|
+
if (!await fs3.pathExists(dest)) continue;
|
|
1024
|
+
await this.deployProjection(dest, artifact.content);
|
|
1025
|
+
const deployedHash = await this.calculateHash(dest);
|
|
1026
|
+
lockfile.projections = lockfile.projections || {};
|
|
1027
|
+
const existingProj = lockfile.projections[artifact.relPath];
|
|
1028
|
+
lockfile.projections[artifact.relPath] = {
|
|
1029
|
+
host: "cline",
|
|
1030
|
+
kind: artifact.kind,
|
|
1031
|
+
canonical: artifact.canonical,
|
|
1032
|
+
// Preserve existing ownership; the parent remains the sole owner.
|
|
1033
|
+
owners: existingProj?.owners ?? [],
|
|
1034
|
+
hash: deployedHash,
|
|
1035
|
+
installedAt: existingProj?.installedAt ?? now,
|
|
1036
|
+
managedMarker: artifact.managedMarker
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
996
1041
|
continue;
|
|
997
1042
|
}
|
|
998
1043
|
}
|
|
@@ -1048,6 +1093,16 @@ var InstallEngine = class _InstallEngine {
|
|
|
1048
1093
|
if (options.fanout !== void 0) {
|
|
1049
1094
|
lockfile.fanout = effectiveFanout;
|
|
1050
1095
|
}
|
|
1096
|
+
const declared = /* @__PURE__ */ new Set();
|
|
1097
|
+
if (resolved.targetBundle) {
|
|
1098
|
+
const bundleDef = await this.registry.getBundle(resolved.targetBundle);
|
|
1099
|
+
if (bundleDef) {
|
|
1100
|
+
if (bundleDef.orchestrator) declared.add(`agents/${bundleDef.orchestrator}`);
|
|
1101
|
+
(bundleDef.agents || []).forEach((a) => declared.add(`agents/${a}`));
|
|
1102
|
+
(bundleDef.skills || []).forEach((s) => declared.add(`skills/${s}/SKILL.md`));
|
|
1103
|
+
(bundleDef.workflows || []).forEach((w) => declared.add(`workflows/${w}`));
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1051
1106
|
for (const agentFile of resolved.agents) {
|
|
1052
1107
|
const src = path5.join(registryDir, "agents", agentFile);
|
|
1053
1108
|
const dest = path5.join(subPaths.agentsDir, agentFile);
|
|
@@ -1060,14 +1115,18 @@ var InstallEngine = class _InstallEngine {
|
|
|
1060
1115
|
}
|
|
1061
1116
|
const actualMethod = await this.deployFile(src, dest, method, options.force);
|
|
1062
1117
|
const hash = await this.calculateHash(src);
|
|
1063
|
-
const relPath = path5.relative(targetDir, dest);
|
|
1064
|
-
const
|
|
1118
|
+
const relPath = this.toPosix(path5.relative(targetDir, dest));
|
|
1119
|
+
const existing = lockfile.files[relPath];
|
|
1120
|
+
const existingOwners = existing?.owners ?? (existing?.bundle ? [existing.bundle] : []);
|
|
1121
|
+
const declaresAsset = declared.has(`agents/${agentFile}`);
|
|
1122
|
+
const owners = resolved.targetBundle && declaresAsset ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
|
|
1065
1123
|
lockfile.files[relPath] = {
|
|
1066
1124
|
hash,
|
|
1067
|
-
bundle: resolved.targetBundle,
|
|
1125
|
+
bundle: existing?.bundle ?? resolved.targetBundle,
|
|
1126
|
+
owners,
|
|
1068
1127
|
method: actualMethod,
|
|
1069
|
-
installedAt: now,
|
|
1070
|
-
...
|
|
1128
|
+
installedAt: existing?.installedAt ?? now,
|
|
1129
|
+
...existing?.projectedTo ? { projectedTo: existing.projectedTo } : {}
|
|
1071
1130
|
};
|
|
1072
1131
|
if (!lockfile.installed.agents.includes(agentFile)) {
|
|
1073
1132
|
lockfile.installed.agents.push(agentFile);
|
|
@@ -1080,14 +1139,18 @@ var InstallEngine = class _InstallEngine {
|
|
|
1080
1139
|
const skillFile = path5.join(src, "SKILL.md");
|
|
1081
1140
|
if (await fs3.pathExists(skillFile)) {
|
|
1082
1141
|
const hash = await this.calculateHash(skillFile);
|
|
1083
|
-
const relPath = path5.relative(targetDir, path5.join(subPaths.skillsDir, skillName, "SKILL.md"));
|
|
1084
|
-
const
|
|
1142
|
+
const relPath = this.toPosix(path5.relative(targetDir, path5.join(subPaths.skillsDir, skillName, "SKILL.md")));
|
|
1143
|
+
const existing = lockfile.files[relPath];
|
|
1144
|
+
const existingOwners = existing?.owners ?? (existing?.bundle ? [existing.bundle] : []);
|
|
1145
|
+
const declaresAsset = declared.has(`skills/${skillName}/SKILL.md`);
|
|
1146
|
+
const owners = resolved.targetBundle && declaresAsset ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
|
|
1085
1147
|
lockfile.files[relPath] = {
|
|
1086
1148
|
hash,
|
|
1087
|
-
bundle: resolved.targetBundle,
|
|
1149
|
+
bundle: existing?.bundle ?? resolved.targetBundle,
|
|
1150
|
+
owners,
|
|
1088
1151
|
method: actualMethod,
|
|
1089
|
-
installedAt: now,
|
|
1090
|
-
...
|
|
1152
|
+
installedAt: existing?.installedAt ?? now,
|
|
1153
|
+
...existing?.projectedTo ? { projectedTo: existing.projectedTo } : {}
|
|
1091
1154
|
};
|
|
1092
1155
|
}
|
|
1093
1156
|
if (!lockfile.installed.skills.includes(skillName)) {
|
|
@@ -1099,14 +1162,18 @@ var InstallEngine = class _InstallEngine {
|
|
|
1099
1162
|
const dest = path5.join(subPaths.workflowsDir, workflowFile);
|
|
1100
1163
|
const actualMethod = await this.deployFile(src, dest, method, options.force);
|
|
1101
1164
|
const hash = await this.calculateHash(src);
|
|
1102
|
-
const relPath = path5.relative(targetDir, dest);
|
|
1103
|
-
const
|
|
1165
|
+
const relPath = this.toPosix(path5.relative(targetDir, dest));
|
|
1166
|
+
const existing = lockfile.files[relPath];
|
|
1167
|
+
const existingOwners = existing?.owners ?? (existing?.bundle ? [existing.bundle] : []);
|
|
1168
|
+
const declaresAsset = declared.has(`workflows/${workflowFile}`);
|
|
1169
|
+
const owners = resolved.targetBundle && declaresAsset ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
|
|
1104
1170
|
lockfile.files[relPath] = {
|
|
1105
1171
|
hash,
|
|
1106
|
-
bundle: resolved.targetBundle,
|
|
1172
|
+
bundle: existing?.bundle ?? resolved.targetBundle,
|
|
1173
|
+
owners,
|
|
1107
1174
|
method: actualMethod,
|
|
1108
|
-
installedAt: now,
|
|
1109
|
-
...
|
|
1175
|
+
installedAt: existing?.installedAt ?? now,
|
|
1176
|
+
...existing?.projectedTo ? { projectedTo: existing.projectedTo } : {}
|
|
1110
1177
|
};
|
|
1111
1178
|
if (!lockfile.installed.workflows.includes(workflowFile)) {
|
|
1112
1179
|
lockfile.installed.workflows.push(workflowFile);
|
|
@@ -1118,12 +1185,18 @@ var InstallEngine = class _InstallEngine {
|
|
|
1118
1185
|
if (await fs3.pathExists(src)) {
|
|
1119
1186
|
const actualMethod = await this.deployFile(src, dest, method, options.force);
|
|
1120
1187
|
const hash = await this.calculateHash(src);
|
|
1121
|
-
const relPath = path5.relative(targetDir, dest);
|
|
1188
|
+
const relPath = this.toPosix(path5.relative(targetDir, dest));
|
|
1189
|
+
const existing = lockfile.files[relPath];
|
|
1190
|
+
const existingOwners = existing?.owners ?? (existing?.bundle ? [existing.bundle] : []);
|
|
1191
|
+
const declaresAsset = true;
|
|
1192
|
+
const owners = resolved.targetBundle && declaresAsset ? Array.from(/* @__PURE__ */ new Set([...existingOwners, resolved.targetBundle])) : existingOwners;
|
|
1122
1193
|
lockfile.files[relPath] = {
|
|
1123
1194
|
hash,
|
|
1124
|
-
bundle: resolved.targetBundle,
|
|
1195
|
+
bundle: existing?.bundle ?? resolved.targetBundle,
|
|
1196
|
+
owners,
|
|
1125
1197
|
method: actualMethod,
|
|
1126
|
-
installedAt: now
|
|
1198
|
+
installedAt: existing?.installedAt ?? now,
|
|
1199
|
+
...existing?.projectedTo ? { projectedTo: existing.projectedTo } : {}
|
|
1127
1200
|
};
|
|
1128
1201
|
}
|
|
1129
1202
|
}
|
|
@@ -1223,6 +1296,42 @@ var UninstallEngine = class {
|
|
|
1223
1296
|
}
|
|
1224
1297
|
await fs4.remove(absPath);
|
|
1225
1298
|
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Installed-addon freshness (plan 003): after a child bundle (one with a
|
|
1301
|
+
* `parentBundle`) is removed, re-render the parent's coordinator rule + team
|
|
1302
|
+
* manifest excluding every remaining installed bundle in the lockfile, so the
|
|
1303
|
+
* removed addon returns to the parent's recommendedAddons. Presentation-free:
|
|
1304
|
+
* the two projection files carry the managed marker trustees inside (rule) and
|
|
1305
|
+
* are overwritten directly via fs-extra. If a parent artifact does not exist in
|
|
1306
|
+
* the workspace (parent not installed as its own bundle), it is skipped.
|
|
1307
|
+
*/
|
|
1308
|
+
async refreshParentCoordination(bundleName, workspaceRoot, lockfile, scope) {
|
|
1309
|
+
const bundleDef = await this.registry.getBundle(bundleName);
|
|
1310
|
+
if (!bundleDef?.parentBundle) return;
|
|
1311
|
+
const parentDef = await this.registry.getBundle(bundleDef.parentBundle);
|
|
1312
|
+
if (!parentDef) return;
|
|
1313
|
+
const parentResolved = await this.registry.resolve(parentDef.name).catch(() => null);
|
|
1314
|
+
if (!parentResolved) return;
|
|
1315
|
+
const registryDir = this.registry.getRegistryDir();
|
|
1316
|
+
const projection = await ClineProjector.planCompoundProjection(
|
|
1317
|
+
parentDef,
|
|
1318
|
+
scope,
|
|
1319
|
+
parentResolved,
|
|
1320
|
+
registryDir,
|
|
1321
|
+
lockfile.installed.bundles
|
|
1322
|
+
);
|
|
1323
|
+
for (const artifact of projection) {
|
|
1324
|
+
if (artifact.kind !== "rule" && artifact.kind !== "team-manifest") continue;
|
|
1325
|
+
if (artifact.content === void 0) continue;
|
|
1326
|
+
const dest = path6.join(workspaceRoot, artifact.relPath);
|
|
1327
|
+
if (!await fs4.pathExists(dest)) continue;
|
|
1328
|
+
await fs4.writeFile(dest, artifact.content, "utf8");
|
|
1329
|
+
const hash = await this.calculateHash(dest);
|
|
1330
|
+
if (lockfile.projections?.[artifact.relPath]) {
|
|
1331
|
+
lockfile.projections[artifact.relPath].hash = hash;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1226
1335
|
parseHosts(options) {
|
|
1227
1336
|
if (options.hosts && options.hosts.length > 0) return options.hosts;
|
|
1228
1337
|
if (options.target) {
|
|
@@ -1281,6 +1390,11 @@ var UninstallEngine = class {
|
|
|
1281
1390
|
if (lockfile.installed.bundles.includes(bundleName)) {
|
|
1282
1391
|
if (!options.dryRun) {
|
|
1283
1392
|
const workspaceRoot = path6.resolve(path6.dirname(targetDir));
|
|
1393
|
+
const ownedFiles = Object.entries(lockfile.files).filter(([, m]) => (m.owners ?? (m.bundle ? [m.bundle] : [])).includes(bundleName));
|
|
1394
|
+
const ownedProjections = lockfile.projections ? Object.values(lockfile.projections).filter((p) => p.owners.includes(bundleName)).length : 0;
|
|
1395
|
+
if (ownedFiles.length === 0 && ownedProjections === 0) {
|
|
1396
|
+
throw new Error(`No installed assets found matching "${bundleName}".`);
|
|
1397
|
+
}
|
|
1284
1398
|
if (lockfile.projections) {
|
|
1285
1399
|
for (const [projRelPath, proj] of Object.entries(lockfile.projections)) {
|
|
1286
1400
|
if (proj.owners.includes(bundleName)) {
|
|
@@ -1308,7 +1422,10 @@ var UninstallEngine = class {
|
|
|
1308
1422
|
}
|
|
1309
1423
|
}
|
|
1310
1424
|
for (const [relPath, assetMeta] of Object.entries(lockfile.files)) {
|
|
1311
|
-
|
|
1425
|
+
const assetOwners = assetMeta.owners ?? (assetMeta.bundle ? [assetMeta.bundle] : []);
|
|
1426
|
+
if (!assetOwners.includes(bundleName)) continue;
|
|
1427
|
+
const newOwners = assetOwners.filter((o) => o !== bundleName);
|
|
1428
|
+
if (newOwners.length === 0) {
|
|
1312
1429
|
if (assetMeta.projectedTo && assetMeta.projectedTo.length > 0) {
|
|
1313
1430
|
await this.removeProjections(workspaceRoot, assetMeta.projectedTo, options.force);
|
|
1314
1431
|
}
|
|
@@ -1324,21 +1441,28 @@ var UninstallEngine = class {
|
|
|
1324
1441
|
removedFiles.push(relPath);
|
|
1325
1442
|
}
|
|
1326
1443
|
delete lockfile.files[relPath];
|
|
1444
|
+
} else {
|
|
1445
|
+
lockfile.files[relPath] = { ...assetMeta, owners: newOwners };
|
|
1327
1446
|
}
|
|
1328
1447
|
}
|
|
1329
1448
|
lockfile.installed.bundles = lockfile.installed.bundles.filter((b) => b !== bundleName);
|
|
1330
1449
|
if (lockfile.bundleVersions) {
|
|
1331
1450
|
delete lockfile.bundleVersions[bundleName];
|
|
1332
1451
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1452
|
+
const surviving = lockfile.installed.bundles.filter((b) => b !== bundleName);
|
|
1453
|
+
const survival = /* @__PURE__ */ new Set();
|
|
1454
|
+
for (const b of surviving) {
|
|
1455
|
+
const bdef = await this.registry.getBundle(b);
|
|
1456
|
+
if (!bdef) continue;
|
|
1457
|
+
if (bdef.orchestrator) survival.add(`agents/${bdef.orchestrator}`);
|
|
1458
|
+
(bdef.agents || []).forEach((a) => survival.add(`agents/${a}`));
|
|
1459
|
+
(bdef.skills || []).forEach((s) => survival.add(`skills/${s}/SKILL.md`));
|
|
1460
|
+
(bdef.workflows || []).forEach((w) => survival.add(`workflows/${w}`));
|
|
1341
1461
|
}
|
|
1462
|
+
lockfile.installed.agents = lockfile.installed.agents.filter((a) => survival.has(`agents/${a}`));
|
|
1463
|
+
lockfile.installed.skills = lockfile.installed.skills.filter((s) => survival.has(`skills/${s}/SKILL.md`));
|
|
1464
|
+
lockfile.installed.workflows = lockfile.installed.workflows.filter((w) => survival.has(`workflows/${w}`));
|
|
1465
|
+
await this.refreshParentCoordination(bundleName, workspaceRoot, lockfile, scope);
|
|
1342
1466
|
await fs4.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
|
|
1343
1467
|
} else {
|
|
1344
1468
|
removedFiles.push(...resolved.agents, ...resolved.skills, ...resolved.workflows);
|
|
@@ -2020,6 +2144,30 @@ var DoctorEngine = class {
|
|
|
2020
2144
|
}
|
|
2021
2145
|
}
|
|
2022
2146
|
}
|
|
2147
|
+
const installedBundles = manifest.installed?.bundles ?? [];
|
|
2148
|
+
const fileOwnersOf = (rec) => rec.owners ?? (rec.bundle ? [rec.bundle] : []);
|
|
2149
|
+
if (manifest.projections) {
|
|
2150
|
+
for (const [projRelPath, proj] of Object.entries(manifest.projections)) {
|
|
2151
|
+
for (const owner of proj.owners) {
|
|
2152
|
+
if (!installedBundles.includes(owner)) {
|
|
2153
|
+
warnings.push(`Projection ${projRelPath} is owned by bundle "${owner}" which is not in installed.bundles.`);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
for (const [relPath, rec] of Object.entries(manifest.files)) {
|
|
2159
|
+
for (const owner of fileOwnersOf(rec)) {
|
|
2160
|
+
if (!installedBundles.includes(owner)) {
|
|
2161
|
+
warnings.push(`File record ${relPath} is owned by bundle "${owner}" which is not in installed.bundles.`);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
for (const bundleName of installedBundles) {
|
|
2166
|
+
const fileCount = Object.values(manifest.files).filter((m) => fileOwnersOf(m).includes(bundleName)).length;
|
|
2167
|
+
if (fileCount === 0) {
|
|
2168
|
+
warnings.push(`Installed bundle "${bundleName}" owns zero file records.`);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2023
2171
|
}
|
|
2024
2172
|
if (host === "cline") {
|
|
2025
2173
|
const probe = new ClineCapabilityProbe();
|