agentwheel 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,9 +34,9 @@ agentwheel install
34
34
  No lock-in. No central gatekeeper. Packages live in plain git repos or local folders, customizations
35
35
  live in your workspace, and runtimes stay generated output.
36
36
 
37
- > **Status: early (v0.9).** v0.9 switches the public CLI vocabulary to package-manager verbs:
38
- > `add`, `install`, `update`, and `uninstall`. A hidden `sync` shim remains for one release only
39
- > so old bootstrapped skills can self-update; use `install` in all new docs and scripts.
37
+ > **Status: early (v0.11).** The public CLI vocabulary is package-manager style:
38
+ > `add`, `install`, `update`, and `uninstall`. A hidden `sync` shim remains for old bootstrapped
39
+ > skills; use `install` in all new docs and scripts.
40
40
 
41
41
  ## Supported runtimes & resources
42
42
 
@@ -272,6 +272,58 @@ still owned by another configured package.
272
272
  }
273
273
  ```
274
274
 
275
+ ### Source Overrides
276
+
277
+ Use package-level `overrides` when a workspace intentionally wants one selected source to replace
278
+ an artifact that arrives from another package, such as a forked skill replacing the same skill
279
+ pulled in by a meta-package. Overrides are explicit; package array order never decides precedence.
280
+
281
+ ```jsonc
282
+ {
283
+ "schemaVersion": 1,
284
+ "packages": [
285
+ {
286
+ "name": "nestdev-must-have-core",
287
+ "source": "github:NestDevLab/agent-must-have#core",
288
+ "driver": "git",
289
+ "adapter": "codex",
290
+ "mode": "tracking"
291
+ },
292
+ {
293
+ "name": "agent-toolkit-nestdev",
294
+ "source": "github:Yehonal/agent-toolkit#main",
295
+ "driver": "git",
296
+ "adapter": "codex",
297
+ "mode": "tracking",
298
+ "select": [
299
+ "rules/self-improve-on-correction.md",
300
+ "skills/self-improve"
301
+ ],
302
+ "overrides": [
303
+ "github:FrancescoBorzi/agent-toolkit::rules/self-improve-on-correction.md",
304
+ "github:FrancescoBorzi/agent-toolkit::skills/self-improve"
305
+ ]
306
+ }
307
+ ]
308
+ }
309
+ ```
310
+
311
+ The `source::type/name` selector identifies the artifact to replace. `github:owner/repo` matches
312
+ that repository at any ref; add `#main` or another ref to narrow it. The replacing package must
313
+ select exactly one artifact with the same `type/name`, and the override must match exactly one
314
+ losing artifact. Otherwise planning fails instead of hiding a collision.
315
+
316
+ The same declaration can be created from the CLI:
317
+
318
+ ```bash
319
+ agentwheel add github:Yehonal/agent-toolkit#main \
320
+ --skill self-improve \
321
+ --override 'github:FrancescoBorzi/agent-toolkit::skills/self-improve'
322
+ ```
323
+
324
+ `agentwheel plan`, `agentwheel deps tree`, and `agentwheel deps why` print `OVERRIDE` lines for
325
+ these decisions, and graph locks store them for review.
326
+
275
327
  Migrating an existing legacy package takes one command:
276
328
 
277
329
  ```bash
@@ -285,7 +337,9 @@ Drift detection blocks accidental edits to generated runtime files. Intentional
285
337
 
286
338
  - **Layer** local instructions with `agentwheel remember`.
287
339
  - **Add** separate local artifacts under `.agentwheel/additions`.
288
- - **Override** an upstream item under `.agentwheel/overrides`.
340
+ - **Override content** for an upstream item under `.agentwheel/overrides`.
341
+ - **Override source precedence** with package `overrides` when a forked source should replace a
342
+ colliding artifact from another package.
289
343
  - **Eject** an item into `.agentwheel/ejected` when you want local ownership.
290
344
 
291
345
  ## Custom And Private Runtimes
package/dist/index.js CHANGED
@@ -297,7 +297,8 @@ var graphLockRootSchema = z3.object({
297
297
  graphNodeId: z3.string().min(1),
298
298
  mode: z3.enum(["pinned", "tracking"]),
299
299
  selected: z3.array(z3.string().min(1)),
300
- aliases: z3.record(z3.string(), z3.string().min(1)).optional()
300
+ aliases: z3.record(z3.string(), z3.string().min(1)).optional(),
301
+ overrides: z3.array(z3.string().min(1)).optional()
301
302
  });
302
303
  var graphLockEdgeSchema = z3.object({
303
304
  from: z3.string().min(1),
@@ -346,6 +347,15 @@ var graphLockNamespacingSchema = z3.object({
346
347
  installName: z3.string().min(1),
347
348
  reason: z3.enum(["alias", "transitive-collision"])
348
349
  });
350
+ var graphLockOverrideSchema = z3.object({
351
+ rootId: z3.string().min(1),
352
+ selector: z3.string().min(1),
353
+ graphNodeId: z3.string().min(1),
354
+ overriddenGraphNodeId: z3.string().min(1),
355
+ type: artifactTypeSchema,
356
+ name: z3.string().min(1),
357
+ installName: z3.string().min(1)
358
+ });
349
359
  var graphLockCanonicalSchema = z3.object({
350
360
  targetFingerprint: z3.string().min(1).optional(),
351
361
  roots: z3.array(graphLockRootSchema),
@@ -354,6 +364,7 @@ var graphLockCanonicalSchema = z3.object({
354
364
  includeEdges: z3.array(graphLockIncludeEdgeSchema).default([]),
355
365
  artifacts: z3.array(graphLockArtifactSchema).default([]),
356
366
  namespacing: z3.array(graphLockNamespacingSchema).default([]),
367
+ overrides: z3.array(graphLockOverrideSchema).default([]),
357
368
  plainNameIncumbents: z3.array(graphLockPlainNameIncumbentSchema).default([])
358
369
  });
359
370
  var graphLockSchema = z3.object({
@@ -384,7 +395,11 @@ function canonicalizeGraphLock(lock) {
384
395
  version: 1,
385
396
  canonical: {
386
397
  targetFingerprint: parsed.canonical.targetFingerprint,
387
- roots: [...parsed.canonical.roots].map((root) => ({ ...root, selected: sortedUnique(root.selected) })).sort((a, b) => `${a.rootId}:${a.graphNodeId}`.localeCompare(`${b.rootId}:${b.graphNodeId}`)),
398
+ roots: [...parsed.canonical.roots].map((root) => ({
399
+ ...root,
400
+ selected: sortedUnique(root.selected),
401
+ overrides: root.overrides ? sortedUnique(root.overrides) : void 0
402
+ })).sort((a, b) => `${a.rootId}:${a.graphNodeId}`.localeCompare(`${b.rootId}:${b.graphNodeId}`)),
388
403
  nodes: [...parsed.canonical.nodes].map((node) => ({
389
404
  ...node,
390
405
  requiredBy: sortedUnique(node.requiredBy),
@@ -395,6 +410,7 @@ function canonicalizeGraphLock(lock) {
395
410
  includeEdges: [...parsed.canonical.includeEdges].sort((a, b) => `${a.fromNodeId}:${a.alias}:${a.toNodeId}:${a.selector}`.localeCompare(`${b.fromNodeId}:${b.alias}:${b.toNodeId}:${b.selector}`)),
396
411
  artifacts: [...parsed.canonical.artifacts].map((artifact) => ({ ...artifact, owners: sortedUnique(artifact.owners) })).sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector)),
397
412
  namespacing: [...parsed.canonical.namespacing].sort((a, b) => `${a.type}:${a.installName}:${a.graphNodeId}:${a.name}`.localeCompare(`${b.type}:${b.installName}:${b.graphNodeId}:${b.name}`)),
413
+ overrides: [...parsed.canonical.overrides].sort((a, b) => `${a.type}:${a.installName}:${a.graphNodeId}:${a.overriddenGraphNodeId}`.localeCompare(`${b.type}:${b.installName}:${b.graphNodeId}:${b.overriddenGraphNodeId}`)),
398
414
  plainNameIncumbents: [...parsed.canonical.plainNameIncumbents].sort((a, b) => `${a.adapter}:${a.targetFingerprint}:${a.type}:${a.name}`.localeCompare(`${b.adapter}:${b.targetFingerprint}:${b.type}:${b.name}`))
399
415
  }
400
416
  };
@@ -2138,6 +2154,9 @@ function formatGraphPlan(result) {
2138
2154
  for (const decision of result.bundle.graphLock.canonical.namespacing) {
2139
2155
  lines.push(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
2140
2156
  }
2157
+ for (const decision of result.bundle.graphLock.canonical.overrides) {
2158
+ lines.push(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
2159
+ }
2141
2160
  if (result.graphDiff.length > 0) {
2142
2161
  lines.push("Graph diff:");
2143
2162
  lines.push(...result.graphDiff);
@@ -2175,6 +2194,9 @@ function formatLockDependencyTree(lock) {
2175
2194
  for (const decision of lock.canonical.namespacing) {
2176
2195
  lines.push(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
2177
2196
  }
2197
+ for (const decision of lock.canonical.overrides) {
2198
+ lines.push(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
2199
+ }
2178
2200
  return lines.join("\n");
2179
2201
  }
2180
2202
  function formatDepsWhy(lock, manifest, query) {
@@ -2197,6 +2219,9 @@ function formatDepsWhy(lock, manifest, query) {
2197
2219
  }
2198
2220
  const namespace = lock.canonical.namespacing.find((decision) => decision.graphNodeId === match.graphNodeId && decision.type === match.type && decision.name === match.name);
2199
2221
  lines.push(namespace ? `NAME ${namespace.reason}: ${namespace.type}/${namespace.name} -> ${namespace.type}/${namespace.installName}` : `NAME plain: ${match.type}/${match.name}`);
2222
+ for (const override of lock.canonical.overrides.filter((decision) => decision.graphNodeId === match.graphNodeId && decision.type === match.type && decision.name === match.name)) {
2223
+ lines.push(`OVERRIDE replaces ${override.overriddenGraphNodeId}:${override.type}/${override.name} via ${override.rootId}`);
2224
+ }
2200
2225
  return lines.join("\n");
2201
2226
  }
2202
2227
  function formatSelected(selected, reasons) {
@@ -3776,7 +3801,8 @@ var workspacePackageSchema = z6.object({
3776
3801
  requestedRef: z6.string().min(1).optional(),
3777
3802
  select: z6.array(z6.string().min(1)).optional(),
3778
3803
  skills: z6.array(z6.string().min(1)).optional(),
3779
- aliases: z6.record(z6.string(), z6.string().min(1)).optional()
3804
+ aliases: z6.record(z6.string(), z6.string().min(1)).optional(),
3805
+ overrides: z6.array(z6.string().min(1)).optional()
3780
3806
  });
3781
3807
  var workspaceProfileRuntimeSchema = z6.object({
3782
3808
  agent: z6.string().min(1).optional(),
@@ -4315,6 +4341,7 @@ async function resolveDependencyGraph(roots, options) {
4315
4341
  requiredBy: `workspace:${rootId}`,
4316
4342
  rootId,
4317
4343
  aliases: root.aliases,
4344
+ overrides: root.overrides,
4318
4345
  useLock: root.useLock ?? options.lockedResolution,
4319
4346
  depth: 0,
4320
4347
  optional: false,
@@ -4346,7 +4373,7 @@ async function resolveDependencyGraph(roots, options) {
4346
4373
  generatedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
4347
4374
  };
4348
4375
  }
4349
- function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges = [], namespacing = []) {
4376
+ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges = [], namespacing = [], overrides = []) {
4350
4377
  const roots = graph.roots.map((root) => ({
4351
4378
  rootId: root.rootId,
4352
4379
  source: root.source,
@@ -4354,7 +4381,8 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
4354
4381
  graphNodeId: root.graphNodeId,
4355
4382
  mode: root.mode,
4356
4383
  selected: root.selected,
4357
- aliases: root.aliases
4384
+ aliases: root.aliases,
4385
+ overrides: root.overrides
4358
4386
  }));
4359
4387
  return {
4360
4388
  version: 1,
@@ -4366,6 +4394,7 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
4366
4394
  includeEdges,
4367
4395
  artifacts,
4368
4396
  namespacing,
4397
+ overrides,
4369
4398
  plainNameIncumbents: []
4370
4399
  }
4371
4400
  };
@@ -4462,7 +4491,8 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
4462
4491
  graphNodeId: state.node.id,
4463
4492
  mode: state.node.mode,
4464
4493
  selected: state.node.selected,
4465
- aliases: requirement.aliases
4494
+ aliases: requirement.aliases,
4495
+ overrides: requirement.overrides
4466
4496
  });
4467
4497
  }
4468
4498
  if (requirement.parentId && requirement.alias) {
@@ -5069,7 +5099,8 @@ function diffGraphLocks(previous, next) {
5069
5099
  return [
5070
5100
  ...diffNodes(previous.canonical.nodes, next.canonical.nodes),
5071
5101
  ...diffIncludeEdges(previous.canonical.includeEdges, next.canonical.includeEdges),
5072
- ...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing)
5102
+ ...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing),
5103
+ ...diffOverrides(previous.canonical.overrides, next.canonical.overrides)
5073
5104
  ];
5074
5105
  }
5075
5106
  function diffNodes(previous, next) {
@@ -5131,6 +5162,23 @@ function diffNamespacing(previous, next) {
5131
5162
  }
5132
5163
  return lines.sort((a, b) => a.localeCompare(b));
5133
5164
  }
5165
+ function diffOverrides(previous, next) {
5166
+ const previousByKey = new Map(previous.map((decision) => [overrideKey(decision), decision]));
5167
+ const nextByKey = new Map(next.map((decision) => [overrideKey(decision), decision]));
5168
+ const lines = [];
5169
+ for (const [key, decision] of nextByKey) {
5170
+ const old = previousByKey.get(key);
5171
+ if (!old) {
5172
+ lines.push(`ADDED override ${formatOverride(decision)}`);
5173
+ } else if (old.graphNodeId !== decision.graphNodeId || old.installName !== decision.installName) {
5174
+ lines.push(`CHANGED override ${decision.selector} ${old.graphNodeId}:${old.type}/${old.name} -> ${decision.graphNodeId}:${decision.type}/${decision.name}`);
5175
+ }
5176
+ }
5177
+ for (const [key, decision] of previousByKey) {
5178
+ if (!nextByKey.has(key)) lines.push(`REMOVED override ${formatOverride(decision)}`);
5179
+ }
5180
+ return lines.sort((a, b) => a.localeCompare(b));
5181
+ }
5134
5182
  function stableNodeKey(node) {
5135
5183
  return `${node.normalizedSource}\0${node.name}`;
5136
5184
  }
@@ -5150,9 +5198,15 @@ function formatIncludeEdge(edge) {
5150
5198
  function namespaceKey(decision) {
5151
5199
  return `${decision.graphNodeId}\0${decision.type}\0${decision.name}`;
5152
5200
  }
5201
+ function overrideKey(decision) {
5202
+ return `${decision.rootId}\0${decision.selector}\0${decision.overriddenGraphNodeId}\0${decision.type}\0${decision.name}`;
5203
+ }
5153
5204
  function formatNamespace(decision) {
5154
5205
  return `${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`;
5155
5206
  }
5207
+ function formatOverride(decision) {
5208
+ return `${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`;
5209
+ }
5156
5210
  function short(hash) {
5157
5211
  return hash.slice(0, 12);
5158
5212
  }
@@ -5254,13 +5308,13 @@ async function renderGraphForTarget(graph, targetContext = {}) {
5254
5308
  owners: [...rawNode.node.requiredBy].sort((a, b) => a.localeCompare(b))
5255
5309
  })));
5256
5310
  }
5257
- const { artifacts: namedArtifacts, namespacing } = assignInstallNames(graph, artifacts);
5311
+ const { artifacts: namedArtifacts, namespacing, overrides } = assignInstallNames(graph, artifacts);
5258
5312
  const sortedArtifacts = namedArtifacts.sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector));
5259
5313
  return {
5260
5314
  root,
5261
5315
  nodes: graph.nodes,
5262
5316
  artifacts: sortedArtifacts,
5263
- graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing)
5317
+ graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing, overrides)
5264
5318
  };
5265
5319
  }
5266
5320
  function aliasEdgeMap(graph) {
@@ -5304,7 +5358,8 @@ function assignInstallNames(graph, artifacts) {
5304
5358
  decisions.set(decisionKey(updated), namespaceDecision(updated, "alias"));
5305
5359
  return updated;
5306
5360
  });
5307
- const collisionGroups = [...groupBy(withAliases, (artifact) => `${artifact.type}\0${artifact.installName}`).values()].filter((group) => group.length > 1);
5361
+ const { artifacts: withOverrides, overrides } = applyWorkspaceOverrides(graph, withAliases);
5362
+ const collisionGroups = [...groupBy(withOverrides, (artifact) => `${artifact.type}\0${artifact.installName}`).values()].filter((group) => group.length > 1);
5308
5363
  const toRename = /* @__PURE__ */ new Set();
5309
5364
  for (const group of collisionGroups) {
5310
5365
  if (group.some((artifact) => decisions.has(decisionKey(artifact)))) throw installNameCollisionError(group);
@@ -5315,11 +5370,11 @@ function assignInstallNames(graph, artifacts) {
5315
5370
  }
5316
5371
  }
5317
5372
  const used = /* @__PURE__ */ new Map();
5318
- for (const artifact of withAliases) {
5373
+ for (const artifact of withOverrides) {
5319
5374
  if (toRename.has(artifact)) continue;
5320
5375
  used.set(`${artifact.type}\0${artifact.installName}`, artifact);
5321
5376
  }
5322
- const out = withAliases.filter((artifact) => !toRename.has(artifact));
5377
+ const out = withOverrides.filter((artifact) => !toRename.has(artifact));
5323
5378
  for (const group of groupBy([...toRename], (artifact) => `${artifact.type}\0${artifact.name}`).values()) {
5324
5379
  const renamed = namespaceTransitiveGroup(group, used);
5325
5380
  for (const artifact of renamed) {
@@ -5328,7 +5383,52 @@ function assignInstallNames(graph, artifacts) {
5328
5383
  out.push(artifact);
5329
5384
  }
5330
5385
  }
5331
- return { artifacts: out, namespacing: [...decisions.values()] };
5386
+ const finalInstallNames = new Map(out.map((artifact) => [decisionKey(artifact), artifact.installName]));
5387
+ const finalOverrides = overrides.map((override) => ({
5388
+ ...override,
5389
+ installName: finalInstallNames.get(`${override.graphNodeId}\0${override.type}\0${override.name}`) ?? override.installName
5390
+ }));
5391
+ return { artifacts: out, namespacing: [...decisions.values()], overrides: finalOverrides };
5392
+ }
5393
+ function applyWorkspaceOverrides(graph, artifacts) {
5394
+ const directives = workspaceOverrides(graph);
5395
+ if (directives.length === 0) return { artifacts, overrides: [] };
5396
+ const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
5397
+ let remaining = [...artifacts];
5398
+ const decisions = [];
5399
+ for (const directive of directives) {
5400
+ const matched = remaining.filter((artifact) => overrideMatchesArtifact(directive.selector, artifact, nodeById.get(artifact.graphNodeId)));
5401
+ const losers = matched.filter((artifact) => !directive.reachable.has(artifact.graphNodeId));
5402
+ if (matched.length === 0) {
5403
+ throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} did not match any rendered artifact.`);
5404
+ }
5405
+ if (losers.length === 0) {
5406
+ throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} matched only artifacts inside the replacing root.`);
5407
+ }
5408
+ if (losers.length > 1) {
5409
+ throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} matched multiple artifacts: ${losers.map((artifact) => artifact.logicalSelector).sort().join(", ")}`);
5410
+ }
5411
+ const loser = losers[0];
5412
+ const winners = remaining.filter((artifact) => artifact !== loser && directive.reachable.has(artifact.graphNodeId) && artifact.type === loser.type && artifact.name === loser.name);
5413
+ if (winners.length === 0) {
5414
+ throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} has no selected replacement for ${loser.type}/${loser.name}.`);
5415
+ }
5416
+ if (winners.length > 1) {
5417
+ throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} has multiple selected replacements for ${loser.type}/${loser.name}: ${winners.map((artifact) => artifact.logicalSelector).sort().join(", ")}`);
5418
+ }
5419
+ const winner = winners[0];
5420
+ remaining = remaining.filter((artifact) => artifact !== loser);
5421
+ decisions.push({
5422
+ rootId: directive.rootId,
5423
+ selector: directive.selector,
5424
+ graphNodeId: winner.graphNodeId,
5425
+ overriddenGraphNodeId: loser.graphNodeId,
5426
+ type: winner.type,
5427
+ name: winner.name,
5428
+ installName: winner.installName
5429
+ });
5430
+ }
5431
+ return { artifacts: remaining, overrides: decisions };
5332
5432
  }
5333
5433
  function namespaceTransitiveGroup(group, used) {
5334
5434
  const sorted = [...group].sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector));
@@ -5366,6 +5466,16 @@ function workspaceAliases(graph) {
5366
5466
  }
5367
5467
  return aliases;
5368
5468
  }
5469
+ function workspaceOverrides(graph) {
5470
+ const overrides = [];
5471
+ for (const root of graph.roots) {
5472
+ const reachable = reachableNodeIds(graph, root.graphNodeId);
5473
+ for (const selector of root.overrides ?? []) {
5474
+ overrides.push({ rootId: root.rootId, selector, reachable });
5475
+ }
5476
+ }
5477
+ return overrides;
5478
+ }
5369
5479
  function validateAliasScopes(graph, artifacts, aliases) {
5370
5480
  for (const alias of aliases) {
5371
5481
  const matching = artifacts.filter((artifact) => {
@@ -5382,6 +5492,35 @@ function aliasMatchesArtifact(selector, artifact, node) {
5382
5492
  const artifactSelector = `${artifact.type}/${artifact.name}`;
5383
5493
  return selector === `${artifact.graphNodeId}:${artifactSelector}` || node !== void 0 && selector === `${node.name}@${node.version}:${artifactSelector}` || node !== void 0 && selector === `${node.name}:${artifactSelector}`;
5384
5494
  }
5495
+ function overrideMatchesArtifact(selector, artifact, node) {
5496
+ const sourceSeparator = selector.lastIndexOf("::");
5497
+ if (sourceSeparator >= 0) {
5498
+ const sourceSelector = selector.slice(0, sourceSeparator).trim();
5499
+ const artifactSelector = selector.slice(sourceSeparator + 2).trim();
5500
+ return artifactSelector === `${artifact.type}/${artifact.name}` && sourceMatchesArtifact(sourceSelector, node);
5501
+ }
5502
+ return aliasMatchesArtifact(selector, artifact, node);
5503
+ }
5504
+ function sourceMatchesArtifact(selector, node) {
5505
+ if (!node || selector.length === 0) return false;
5506
+ if (selector === node.source || selector === node.normalizedSource || selector === node.name || selector === node.id) return true;
5507
+ const normalized = node.normalizedSource.toLowerCase();
5508
+ const source = node.source.toLowerCase();
5509
+ const value = selector.toLowerCase();
5510
+ if (value === source || value === normalized || value === node.name.toLowerCase() || value === node.id.toLowerCase()) return true;
5511
+ const github = /^github:([^#]+?)(?:#(.+))?$/.exec(value);
5512
+ if (github) {
5513
+ const repo = github[1].replace(/\.git$/i, "");
5514
+ const ref = github[2];
5515
+ const prefix = `git:https://github.com/${repo}.git#`;
5516
+ return ref ? normalized.includes(`${prefix}${ref}`) : normalized.includes(prefix);
5517
+ }
5518
+ if (!value.includes(":") && value.includes("/")) {
5519
+ const repo = value.replace(/\.git$/i, "");
5520
+ return normalized.includes(`github.com/${repo}.git#`) || source.includes(`github.com/${repo}.git`);
5521
+ }
5522
+ return false;
5523
+ }
5385
5524
  function reachableNodeIds(graph, rootNodeId) {
5386
5525
  const reachable = /* @__PURE__ */ new Set();
5387
5526
  const queue = [rootNodeId];
@@ -5743,7 +5882,8 @@ async function syncProfile(options) {
5743
5882
  mode: options.mode ?? pkg.mode,
5744
5883
  ref: pkg.requestedRef,
5745
5884
  select: selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
5746
- aliases: pkg.aliases
5885
+ aliases: pkg.aliases,
5886
+ overrides: pkg.overrides
5747
5887
  })),
5748
5888
  targetRoot: target.targetRoot,
5749
5889
  workspaceRoot: options.workspaceRoot,
@@ -6246,7 +6386,7 @@ program.command("init").description("initialize an agentwheel workspace or packa
6246
6386
  if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
6247
6387
  console.log(nextInstallNudge());
6248
6388
  });
6249
- program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).action(async (source, options) => {
6389
+ program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
6250
6390
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6251
6391
  const entry = await packageEntryFromSource(source, targetRoot, options);
6252
6392
  await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
@@ -6278,13 +6418,13 @@ program.command("scan").description("scan a package source for validation findin
6278
6418
  }
6279
6419
  if (!result.ok) process.exitCode = 1;
6280
6420
  });
6281
- program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6421
+ program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6282
6422
  await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
6283
6423
  });
6284
- program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
6424
+ program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
6285
6425
  await runInstallCommand(source, options, { apply: !options.dryRun });
6286
6426
  });
6287
- program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6427
+ program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6288
6428
  console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
6289
6429
  await runInstallCommand(source, options, { apply: !options.dryRun });
6290
6430
  });
@@ -6304,6 +6444,9 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
6304
6444
  for (const decision of result.bundle.graphLock.canonical.namespacing) {
6305
6445
  console.log(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
6306
6446
  }
6447
+ for (const decision of result.bundle.graphLock.canonical.overrides) {
6448
+ console.log(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
6449
+ }
6307
6450
  await rm9(result.bundle.root, { recursive: true, force: true });
6308
6451
  }
6309
6452
  continue;
@@ -6520,7 +6663,8 @@ async function packageEntryFromSource(source, targetRoot, options) {
6520
6663
  adapterCodeHash: adapter.programmatic?.hash,
6521
6664
  mode: options.mode ?? "pinned",
6522
6665
  requestedRef: bundle.source.requestedRef,
6523
- select: selectedArtifacts
6666
+ select: selectedArtifacts,
6667
+ overrides: overrideArtifactsFromOptions(options)
6524
6668
  };
6525
6669
  } finally {
6526
6670
  await rm9(bundle.root, { recursive: true, force: true });
@@ -6642,6 +6786,7 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
6642
6786
  ref: pkg.requestedRef,
6643
6787
  select: selectedArtifacts ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
6644
6788
  aliases: pkg.aliases,
6789
+ overrides: pkg.overrides,
6645
6790
  useLock: behavior.mode === "install" ? true : !updateThisPackage
6646
6791
  };
6647
6792
  }),
@@ -6822,7 +6967,8 @@ async function uninstallConfiguredPackage(target, packageName, options) {
6822
6967
  mode: pkg2.mode,
6823
6968
  ref: pkg2.requestedRef,
6824
6969
  select: normalizeArtifactSelectors(pkg2.select, pkg2.skills),
6825
- aliases: pkg2.aliases
6970
+ aliases: pkg2.aliases,
6971
+ overrides: pkg2.overrides
6826
6972
  })),
6827
6973
  targetRoot: remainingGroup.target.targetRoot,
6828
6974
  workspaceRoot: remainingGroup.target.workspaceRoot,
@@ -6985,9 +7131,16 @@ function collectSkillOption(value, previous) {
6985
7131
  function collectTrustOption(value, previous) {
6986
7132
  return [...previous, value];
6987
7133
  }
7134
+ function collectOverrideOption(value, previous) {
7135
+ return [...previous, ...splitSelectorList(value)];
7136
+ }
6988
7137
  function selectedArtifactsFromOptions(options) {
6989
7138
  return normalizeArtifactSelectors(options.select, options.skills ?? options.skill);
6990
7139
  }
7140
+ function overrideArtifactsFromOptions(options) {
7141
+ const values = options.overrides ?? options.override;
7142
+ return values && values.length > 0 ? values : void 0;
7143
+ }
6991
7144
  function filterUninstallPlanBySelection(plan, selected) {
6992
7145
  if (!selected?.length) return plan;
6993
7146
  const requested = normalizeArtifactSelectors(selected) ?? [];
@@ -7108,7 +7261,9 @@ async function main() {
7108
7261
  });
7109
7262
  await program.parseAsync();
7110
7263
  }
7111
- main().catch((error) => {
7264
+ try {
7265
+ await main();
7266
+ } catch (error) {
7112
7267
  console.error(error instanceof Error ? error.message : String(error));
7113
7268
  process.exitCode = 1;
7114
- });
7269
+ }
package/openpack.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.9.0",
4
+ "version": "0.11.0",
5
5
  "provides": [
6
6
  { "type": "skills", "path": "skills", "required": true }
7
7
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -141,6 +141,34 @@ agentwheel add github:owner/repo --select skills/code-review --select rules/core
141
141
  agentwheel add github:owner/repo --select skills/code-review,rules/core.md
142
142
  ```
143
143
 
144
+ Use a source override when a selected package should replace the same artifact coming from another
145
+ source, such as a forked skill overriding a meta-pack dependency. The declaration is explicit and
146
+ planning fails if it does not match exactly one losing artifact and one selected replacement:
147
+
148
+ ```bash
149
+ agentwheel add github:Yehonal/agent-toolkit#main \
150
+ --skill self-improve \
151
+ --override 'github:FrancescoBorzi/agent-toolkit::skills/self-improve'
152
+ ```
153
+
154
+ Equivalent config:
155
+
156
+ ```json
157
+ {
158
+ "name": "agent-toolkit-nestdev",
159
+ "source": "github:Yehonal/agent-toolkit#main",
160
+ "driver": "git",
161
+ "adapter": "codex",
162
+ "mode": "tracking",
163
+ "select": ["skills/self-improve"],
164
+ "overrides": ["github:FrancescoBorzi/agent-toolkit::skills/self-improve"]
165
+ }
166
+ ```
167
+
168
+ `source::type/name` identifies the artifact being replaced. `github:owner/repo` matches any ref
169
+ for that repo; include `#main` or another ref to narrow it. Review `OVERRIDE` lines in
170
+ `agentwheel plan`, `agentwheel deps tree`, or `agentwheel deps why` before applying fleet-wide.
171
+
144
172
  Use `--name` for a stable local alias:
145
173
 
146
174
  ```bash
@@ -289,7 +317,9 @@ Drift means a managed runtime output changed outside agentwheel. Fix drift by ch
289
317
 
290
318
  - Layer local instructions with `agentwheel remember`.
291
319
  - Add separate local artifacts under `.agentwheel/additions`.
292
- - Override an upstream item under `.agentwheel/overrides`.
320
+ - Override upstream content under `.agentwheel/overrides`.
321
+ - Override source precedence with package `overrides` when a forked source should replace a
322
+ colliding upstream artifact.
293
323
  - Eject an item into `.agentwheel/ejected` when the user wants local ownership.
294
324
 
295
325
  Append durable local instruction text: