agentwheel 0.9.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
@@ -7,7 +7,7 @@
7
7
  <p align="center"><strong>One source. Every agent.</strong></p>
8
8
 
9
9
  <p align="center">
10
- Weave your skills, rules, and instructions across every AI agent you use,
10
+ Weave skills, rules, instructions, commands, subagents, MCP, hooks, settings, and plugins across every AI agent you use,
11
11
  from any source, reconciled into each runtime with your private tweaks intact.
12
12
  </p>
13
13
 
@@ -15,8 +15,9 @@
15
15
 
16
16
  ## Why agentwheel?
17
17
 
18
- Your AI agents are multiplying. Claude here, Codex there, custom runtimes elsewhere, and each one
19
- wants skills, rules, instructions, commands, MCP, hooks, and settings in its own native shape.
18
+ Your AI agents are multiplying. OpenClaw, Claude Code, Codex CLI, Copilot, Hermes and the next
19
+ harness your team adopts. Each one wants its own skills, rules, instructions, commands, subagents,
20
+ MCP servers, hooks, and settings, in its own layout.
20
21
 
21
22
  **agentwheel makes that one declared state.** Add packages to `.agentwheel/config.json`, preview the
22
23
  runtime changes, then install the declared state into each target. Updates move tracking sources
@@ -33,9 +34,23 @@ agentwheel install
33
34
  No lock-in. No central gatekeeper. Packages live in plain git repos or local folders, customizations
34
35
  live in your workspace, and runtimes stay generated output.
35
36
 
36
- > **Status: early (v0.9).** v0.9 switches the public CLI vocabulary to package-manager verbs:
37
- > `add`, `install`, `update`, and `uninstall`. A hidden `sync` shim remains for one release only
38
- > 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
+
41
+ ## Supported runtimes & resources
42
+
43
+ agentwheel installs OpenPack resources into five built-in runtimes and into custom harnesses:
44
+
45
+ - **OpenClaw** — `.openclaw/`
46
+ - **Claude Code** — `.claude/`
47
+ - **Codex CLI** — `.codex/`
48
+ - **GitHub Copilot** — `.github/`
49
+ - **Hermes** — `.hermes/`
50
+ - **Bring your own** — JSONC config adapters with `--adapter-config`, or programmatic adapters with `--adapter-module`
51
+
52
+ Supported resource types include instructions, rules, skills, commands, subagents, MCP, hooks,
53
+ settings, plugins, and fragments; see the full per-runtime target table below.
39
54
 
40
55
  ## Core Commands
41
56
 
@@ -133,6 +148,7 @@ For a control-plane setup, define named agents in config. Global config lives at
133
148
  agentwheel install --agent lab-openclaw
134
149
  agentwheel install --all
135
150
  agentwheel install --profile daily
151
+ agentwheel install --all-detected
136
152
  ```
137
153
 
138
154
  SSH targets use the same manifest and drift model as local targets. Planning reads the remote
@@ -146,7 +162,9 @@ agentwheel init --fleet-example
146
162
  ```
147
163
 
148
164
  Target resolution order is exact: `--target-root` wins, then `--agent`, then auto-detect from the
149
- current directory, then fallback to the current directory.
165
+ current directory, then fallback to the current directory. `--all-detected` is an explicit escape hatch
166
+ for applying to every runtime marker found in the current directory or `--target-root`; `--all` remains
167
+ reserved for configured agents.
150
168
 
151
169
  ## Core Ideas
152
170
 
@@ -254,6 +272,58 @@ still owned by another configured package.
254
272
  }
255
273
  ```
256
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
+
257
327
  Migrating an existing legacy package takes one command:
258
328
 
259
329
  ```bash
@@ -267,7 +337,9 @@ Drift detection blocks accidental edits to generated runtime files. Intentional
267
337
 
268
338
  - **Layer** local instructions with `agentwheel remember`.
269
339
  - **Add** separate local artifacts under `.agentwheel/additions`.
270
- - **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.
271
343
  - **Eject** an item into `.agentwheel/ejected` when you want local ownership.
272
344
 
273
345
  ## Custom And Private Runtimes
@@ -290,15 +362,21 @@ published:
290
362
  agentwheel install ./my-pack --adapter-config ./myco-internal.jsonc
291
363
  ```
292
364
 
365
+ For adapter behavior that needs code, load a programmatic adapter module:
366
+
367
+ ```bash
368
+ agentwheel install ./my-pack --adapter-module ./myco-adapter.js
369
+ ```
370
+
293
371
  Built-in runtime targets:
294
372
 
295
373
  | Runtime | Main targets |
296
374
  |---|---|
297
- | **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, MCP/hooks/settings, semantic plugin planning |
375
+ | **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, `.openclaw/agents`, MCP/hooks/settings, semantic plugin planning |
298
376
  | **Claude Code** | `.claude/CLAUDE.md`, `.claude/skills`, `.claude/commands`, `.claude/agents`, `.claude/rules`, `.claude/.mcp.json`, `.claude/settings.json` |
299
377
  | **Codex CLI** | `.codex/AGENTS.md`, `.codex/skills`, `.codex/commands`, `.codex/agents`, `.codex/rules`, `.codex/config.toml`, `.codex/hooks.json` |
300
- | **Hermes** | `.hermes/AGENTS.md`, `.hermes/skills`, `.hermes/rules`, `.hermes/commands`, MCP/hooks/settings |
301
- | **GitHub Copilot** | `.github/copilot-instructions.md`, `.github/instructions`, `.github/prompts` |
378
+ | **Hermes** | `.hermes/AGENTS.md`, `.hermes/skills`, `.hermes/rules`, `.hermes/commands`, `.hermes/agents`, MCP/hooks/settings |
379
+ | **GitHub Copilot** | `.github/copilot-instructions.md`, `.github/instructions`, `.github/prompts`, `.github/skills`, `.github/agents`, `.vscode/mcp.json` |
302
380
 
303
381
  ## Docs
304
382
 
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/cli/index.ts
11
11
  import { mkdir as mkdir14, rm as rm9, writeFile as writeFile12 } from "fs/promises";
12
- import { dirname as dirname21, join as join28, resolve as resolve17 } from "path";
12
+ import { dirname as dirname21, join as join28, resolve as resolve18 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
14
14
  import { Command } from "commander";
15
15
 
@@ -131,9 +131,9 @@ var copilotAdapter = {
131
131
  instructions: { enabled: true, dest: ".github/copilot-instructions.md" },
132
132
  rules: { enabled: true, dest: ".github/instructions" },
133
133
  commands: { enabled: true, dest: ".github/prompts" },
134
- // GitHub Copilot has no native SKILL.md runtime surface; keep skills disabled
135
- // until a conversion target is introduced.
136
- skills: { enabled: false, dest: ".github/skills" }
134
+ skills: { enabled: true, dest: ".github/skills" },
135
+ subagents: { enabled: true, dest: ".github/agents" },
136
+ mcp: { enabled: true, dest: ".vscode/mcp.json", merge: "json-deep" }
137
137
  }
138
138
  };
139
139
 
@@ -162,6 +162,7 @@ var hermesAdapter = {
162
162
  rules: { enabled: true, dest: ".hermes/rules" },
163
163
  skills: { enabled: true, dest: ".hermes/skills" },
164
164
  commands: { enabled: true, dest: ".hermes/commands" },
165
+ subagents: { enabled: true, dest: ".hermes/agents" },
165
166
  mcp: { enabled: true, dest: ".hermes/mcp", merge: "json-deep" },
166
167
  hooks: { enabled: true, dest: ".hermes/hooks", merge: "json-deep" },
167
168
  settings: { enabled: true, dest: ".hermes/settings.json", merge: "json-deep" }
@@ -177,6 +178,7 @@ var openClawAdapter = {
177
178
  rules: { enabled: true, dest: ".openclaw/rules" },
178
179
  skills: { enabled: true, dest: ".openclaw/skills" },
179
180
  commands: { enabled: true, dest: ".openclaw/commands" },
181
+ subagents: { enabled: true, dest: ".openclaw/agents" },
180
182
  mcp: { enabled: true, dest: ".openclaw/mcp", merge: "json-deep" },
181
183
  hooks: { enabled: true, dest: ".openclaw/hooks", merge: "json-deep" },
182
184
  settings: { enabled: true, dest: ".openclaw/settings.json", merge: "json-deep" },
@@ -295,7 +297,8 @@ var graphLockRootSchema = z3.object({
295
297
  graphNodeId: z3.string().min(1),
296
298
  mode: z3.enum(["pinned", "tracking"]),
297
299
  selected: z3.array(z3.string().min(1)),
298
- 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()
299
302
  });
300
303
  var graphLockEdgeSchema = z3.object({
301
304
  from: z3.string().min(1),
@@ -344,6 +347,15 @@ var graphLockNamespacingSchema = z3.object({
344
347
  installName: z3.string().min(1),
345
348
  reason: z3.enum(["alias", "transitive-collision"])
346
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
+ });
347
359
  var graphLockCanonicalSchema = z3.object({
348
360
  targetFingerprint: z3.string().min(1).optional(),
349
361
  roots: z3.array(graphLockRootSchema),
@@ -352,6 +364,7 @@ var graphLockCanonicalSchema = z3.object({
352
364
  includeEdges: z3.array(graphLockIncludeEdgeSchema).default([]),
353
365
  artifacts: z3.array(graphLockArtifactSchema).default([]),
354
366
  namespacing: z3.array(graphLockNamespacingSchema).default([]),
367
+ overrides: z3.array(graphLockOverrideSchema).default([]),
355
368
  plainNameIncumbents: z3.array(graphLockPlainNameIncumbentSchema).default([])
356
369
  });
357
370
  var graphLockSchema = z3.object({
@@ -382,7 +395,11 @@ function canonicalizeGraphLock(lock) {
382
395
  version: 1,
383
396
  canonical: {
384
397
  targetFingerprint: parsed.canonical.targetFingerprint,
385
- 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}`)),
386
403
  nodes: [...parsed.canonical.nodes].map((node) => ({
387
404
  ...node,
388
405
  requiredBy: sortedUnique(node.requiredBy),
@@ -393,6 +410,7 @@ function canonicalizeGraphLock(lock) {
393
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}`)),
394
411
  artifacts: [...parsed.canonical.artifacts].map((artifact) => ({ ...artifact, owners: sortedUnique(artifact.owners) })).sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector)),
395
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}`)),
396
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}`))
397
415
  }
398
416
  };
@@ -542,13 +560,13 @@ async function spawnWithInput(command, args, input) {
542
560
  if (result.stderr) throw new Error(result.stderr);
543
561
  }
544
562
  function waitForProcess(child, label) {
545
- return new Promise((resolve18, reject) => {
563
+ return new Promise((resolve19, reject) => {
546
564
  const stderr = [];
547
565
  child.stderr?.on("data", (chunk) => stderr.push(chunk));
548
566
  child.on("error", reject);
549
567
  child.on("close", (code) => {
550
568
  const message = Buffer.concat(stderr).toString("utf8");
551
- if (code === 0) resolve18({ stderr: "" });
569
+ if (code === 0) resolve19({ stderr: "" });
552
570
  else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
553
571
  });
554
572
  });
@@ -654,6 +672,7 @@ import { resolve as resolve3 } from "path";
654
672
  // src/model/manifest.ts
655
673
  import { z as z4 } from "zod";
656
674
  var dependencyRoleSchema = z4.enum(["root", "direct", "transitive", "fragment"]);
675
+ var legacyUnownedWorkspaceOwner = "legacy:unowned";
657
676
  var manifestEntryV1Schema = z4.object({
658
677
  path: z4.string().min(1),
659
678
  artifactType: artifactTypeSchema,
@@ -676,6 +695,7 @@ var manifestEntrySchema = manifestEntryV1Schema.extend({
676
695
  dependencyRole: dependencyRoleSchema.default("root"),
677
696
  owners: z4.array(z4.string().min(1)).min(1),
678
697
  refCount: z4.number().int().positive(),
698
+ workspaceOwner: z4.string().min(1).default(legacyUnownedWorkspaceOwner),
679
699
  graphLockDigest: z4.string().min(1).optional()
680
700
  }).transform((entry) => {
681
701
  const owners = [...new Set(entry.owners)].sort();
@@ -1388,6 +1408,7 @@ function manifestEntryForOperation(operation, values) {
1388
1408
  dependencyRole: operation.dependencyRole ?? "root",
1389
1409
  owners,
1390
1410
  refCount: owners.length,
1411
+ workspaceOwner: operation.workspaceOwner ?? "workspace:unknown",
1391
1412
  kind: operation.kind,
1392
1413
  hash: values.hash,
1393
1414
  sourceHash: values.sourceHash,
@@ -1476,6 +1497,10 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
1476
1497
  return createPlanFromOperations(desired, adapter, targetRoot, manifest, transport, options);
1477
1498
  }
1478
1499
  async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
1500
+ const workspaceOwner = options.workspaceOwner;
1501
+ if (workspaceOwner) {
1502
+ for (const op of desiredOps) op.workspaceOwner = workspaceOwner;
1503
+ }
1479
1504
  for (const op of desiredOps) assertOperationContained(op, targetRoot);
1480
1505
  const migration = await migrateManifestForPlan(manifest, desiredOps, targetRoot, transport);
1481
1506
  const effectiveEntries = migration.entries;
@@ -1518,6 +1543,12 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1518
1543
  continue;
1519
1544
  }
1520
1545
  const existing = manifestByPath.get(op.relativeDestPath);
1546
+ if (existing && workspaceOwner && !entryOwnedByWorkspace(existing, workspaceOwner)) {
1547
+ if (!await canAdoptLegacyUnownedEntry(existing, op, transport)) {
1548
+ operations.push(keepForeignManifestEntryOperation(existing, targetRoot, workspaceOwner, op));
1549
+ continue;
1550
+ }
1551
+ }
1521
1552
  const exists = await transport.pathExists(op.destPath);
1522
1553
  if (!exists) {
1523
1554
  operations.push({ ...op, action: "create", reason: "destination missing" });
@@ -1559,6 +1590,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1559
1590
  const destPath = join5(targetRoot, entry.path);
1560
1591
  if (!await transport.pathExists(destPath)) continue;
1561
1592
  const currentHash = await transport.hashPath(destPath);
1593
+ if (workspaceOwner && !entryOwnedByWorkspace(entry, workspaceOwner)) {
1594
+ operations.push(keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, void 0, currentHash));
1595
+ continue;
1596
+ }
1562
1597
  if (currentHash !== entry.hash) {
1563
1598
  operations.push({
1564
1599
  action: "drift",
@@ -1714,6 +1749,16 @@ function operationMatchesManifestEntry(op, entry) {
1714
1749
  if ("graphNodeId" in entry && entry.graphNodeId && op.graphNodeId && entry.graphNodeId === op.graphNodeId) return true;
1715
1750
  return op.packageName !== void 0 && entry.packageName === op.packageName;
1716
1751
  }
1752
+ function entryOwnedByWorkspace(entry, workspaceOwner) {
1753
+ return "workspaceOwner" in entry && entry.workspaceOwner === workspaceOwner;
1754
+ }
1755
+ async function canAdoptLegacyUnownedEntry(entry, op, transport) {
1756
+ if (!("workspaceOwner" in entry) || entry.workspaceOwner !== legacyUnownedWorkspaceOwner) return false;
1757
+ if (entry.artifactType !== op.artifactType || entry.kind !== op.kind) return false;
1758
+ if (!op.desiredHash || entry.sourceHash !== op.desiredHash) return false;
1759
+ if (!await transport.pathExists(op.destPath)) return false;
1760
+ return await transport.hashPath(op.destPath) === entry.hash;
1761
+ }
1717
1762
  function collisionOperation(op, group, incumbent) {
1718
1763
  const owners = group.map(describeOperationOwner).sort();
1719
1764
  const incumbentText = incumbent ? `; incumbent ${describeOperationOwner(incumbent)} keeps the plain name` : "";
@@ -1737,6 +1782,7 @@ function adoptLegacyEntry(entry, op) {
1737
1782
  dependencyRole: op.dependencyRole ?? "root",
1738
1783
  owners,
1739
1784
  refCount: owners.length,
1785
+ workspaceOwner: op.workspaceOwner ?? legacyUnownedWorkspaceOwner,
1740
1786
  graphLockDigest: op.graphLockDigest,
1741
1787
  composedFrom: op.composedFrom ?? entry.composedFrom
1742
1788
  };
@@ -1759,6 +1805,7 @@ function operationMetadataFromEntry(entry) {
1759
1805
  graphNodeId: entry.graphNodeId,
1760
1806
  dependencyRole: entry.dependencyRole,
1761
1807
  owners: entry.owners,
1808
+ workspaceOwner: entry.workspaceOwner,
1762
1809
  graphLockDigest: entry.graphLockDigest
1763
1810
  };
1764
1811
  }
@@ -1766,7 +1813,31 @@ function operationMetadataFromEntry(entry) {
1766
1813
  installName: entry.artifactName,
1767
1814
  logicalSelector: `${entry.artifactType}/${entry.artifactName}`,
1768
1815
  dependencyRole: "root",
1769
- owners: [entry.packageName ?? "legacy"]
1816
+ owners: [entry.packageName ?? "legacy"],
1817
+ workspaceOwner: legacyUnownedWorkspaceOwner
1818
+ };
1819
+ }
1820
+ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, operation, currentHash) {
1821
+ const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
1822
+ return {
1823
+ action: "keep",
1824
+ artifactType: entry.artifactType,
1825
+ artifactName: entry.artifactName,
1826
+ kind: entry.kind,
1827
+ destPath: operation?.destPath ?? join5(targetRoot, entry.path),
1828
+ relativeDestPath: entry.path,
1829
+ desiredHash: entry.sourceHash,
1830
+ currentHash: currentHash ?? operation?.currentHash ?? entry.hash,
1831
+ manifestHash: entry.hash,
1832
+ reason: `foreign artifact owned by ${owner}; kept outside workspace ${workspaceOwner}`,
1833
+ channel: entry.channel,
1834
+ packageName: entry.packageName,
1835
+ semanticCommand: entry.semanticCommand,
1836
+ execute: entry.executed,
1837
+ mergeStrategy: entry.mergeStrategy,
1838
+ composedFrom: entry.composedFrom,
1839
+ preserveInManifest: true,
1840
+ ...operationMetadataFromEntry(entry)
1770
1841
  };
1771
1842
  }
1772
1843
  function normalizeOperationOwners(op) {
@@ -2083,6 +2154,9 @@ function formatGraphPlan(result) {
2083
2154
  for (const decision of result.bundle.graphLock.canonical.namespacing) {
2084
2155
  lines.push(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
2085
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
+ }
2086
2160
  if (result.graphDiff.length > 0) {
2087
2161
  lines.push("Graph diff:");
2088
2162
  lines.push(...result.graphDiff);
@@ -2120,6 +2194,9 @@ function formatLockDependencyTree(lock) {
2120
2194
  for (const decision of lock.canonical.namespacing) {
2121
2195
  lines.push(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
2122
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
+ }
2123
2200
  return lines.join("\n");
2124
2201
  }
2125
2202
  function formatDepsWhy(lock, manifest, query) {
@@ -2142,6 +2219,9 @@ function formatDepsWhy(lock, manifest, query) {
2142
2219
  }
2143
2220
  const namespace = lock.canonical.namespacing.find((decision) => decision.graphNodeId === match.graphNodeId && decision.type === match.type && decision.name === match.name);
2144
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
+ }
2145
2225
  return lines.join("\n");
2146
2226
  }
2147
2227
  function formatSelected(selected, reasons) {
@@ -2714,7 +2794,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn) {
2714
2794
  if (Date.now() - started > timeoutMs) {
2715
2795
  throw new Error(`Timed out waiting for git cache lock at ${lockPath}`);
2716
2796
  }
2717
- await new Promise((resolve18) => setTimeout(resolve18, 50));
2797
+ await new Promise((resolve19) => setTimeout(resolve19, 50));
2718
2798
  }
2719
2799
  }
2720
2800
  try {
@@ -3721,7 +3801,8 @@ var workspacePackageSchema = z6.object({
3721
3801
  requestedRef: z6.string().min(1).optional(),
3722
3802
  select: z6.array(z6.string().min(1)).optional(),
3723
3803
  skills: z6.array(z6.string().min(1)).optional(),
3724
- 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()
3725
3806
  });
3726
3807
  var workspaceProfileRuntimeSchema = z6.object({
3727
3808
  agent: z6.string().min(1).optional(),
@@ -4260,6 +4341,7 @@ async function resolveDependencyGraph(roots, options) {
4260
4341
  requiredBy: `workspace:${rootId}`,
4261
4342
  rootId,
4262
4343
  aliases: root.aliases,
4344
+ overrides: root.overrides,
4263
4345
  useLock: root.useLock ?? options.lockedResolution,
4264
4346
  depth: 0,
4265
4347
  optional: false,
@@ -4291,7 +4373,7 @@ async function resolveDependencyGraph(roots, options) {
4291
4373
  generatedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
4292
4374
  };
4293
4375
  }
4294
- function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges = [], namespacing = []) {
4376
+ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges = [], namespacing = [], overrides = []) {
4295
4377
  const roots = graph.roots.map((root) => ({
4296
4378
  rootId: root.rootId,
4297
4379
  source: root.source,
@@ -4299,7 +4381,8 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
4299
4381
  graphNodeId: root.graphNodeId,
4300
4382
  mode: root.mode,
4301
4383
  selected: root.selected,
4302
- aliases: root.aliases
4384
+ aliases: root.aliases,
4385
+ overrides: root.overrides
4303
4386
  }));
4304
4387
  return {
4305
4388
  version: 1,
@@ -4311,6 +4394,7 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
4311
4394
  includeEdges,
4312
4395
  artifacts,
4313
4396
  namespacing,
4397
+ overrides,
4314
4398
  plainNameIncumbents: []
4315
4399
  }
4316
4400
  };
@@ -4318,13 +4402,33 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
4318
4402
  async function processRequirement(requirement, options, fetchCache, nodesByKey, rootResults, edgeMap) {
4319
4403
  try {
4320
4404
  const lockLabel = options.offline ? "Offline" : options.frozenLock ? "Frozen lock" : "Locked install";
4321
- const lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
4322
- const normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
4405
+ let lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
4406
+ let normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
4323
4407
  declaringPackageRoot: requirement.declaringPackageRoot,
4324
4408
  workspaceRoot: options.workspaceRoot,
4325
4409
  ref: requirement.ref,
4326
4410
  registryClient: options.registryClient
4327
4411
  });
4412
+ if (lockedByReference && shouldCheckLockedRootSource(requirement)) {
4413
+ const declared = await normalizeDependencySource(requirement.source, {
4414
+ declaringPackageRoot: requirement.declaringPackageRoot,
4415
+ workspaceRoot: options.workspaceRoot,
4416
+ ref: requirement.ref,
4417
+ registryClient: options.registryClient
4418
+ });
4419
+ if (lockedRootSourceDrifted(declared, lockedByReference.node)) {
4420
+ if (options.frozenLock || options.offline) {
4421
+ throw new Error(
4422
+ `${lockLabel} root '${requirement.rootId}' source differs from declared source:
4423
+ - declared: ${declared.normalizedSource}
4424
+ - locked: ${lockedByReference.node.normalizedSource}
4425
+ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
4426
+ );
4427
+ }
4428
+ lockedByReference = void 0;
4429
+ normalized = declared;
4430
+ }
4431
+ }
4328
4432
  const frozen = lockedByReference ?? lockedNodeForRequirement(normalized.normalizedSource, requirement, options, lockLabel);
4329
4433
  let fetched;
4330
4434
  try {
@@ -4387,7 +4491,8 @@ async function processRequirement(requirement, options, fetchCache, nodesByKey,
4387
4491
  graphNodeId: state.node.id,
4388
4492
  mode: state.node.mode,
4389
4493
  selected: state.node.selected,
4390
- aliases: requirement.aliases
4494
+ aliases: requirement.aliases,
4495
+ overrides: requirement.overrides
4391
4496
  });
4392
4497
  }
4393
4498
  if (requirement.parentId && requirement.alias) {
@@ -4747,6 +4852,18 @@ function lockedNodeForRequirementReference(requirement, options, label) {
4747
4852
  requestedRef: node.driver === "git" ? node.resolvedCommit ?? node.requestedRef : node.requestedRef
4748
4853
  };
4749
4854
  }
4855
+ function shouldCheckLockedRootSource(requirement) {
4856
+ if (!requirement.useLock) return false;
4857
+ if (requirement.depth !== 0 || !requirement.rootId) return false;
4858
+ return isExplicitNonRegistrySource(requirement.source);
4859
+ }
4860
+ function isExplicitNonRegistrySource(source) {
4861
+ const trimmed = source.trim();
4862
+ return trimmed === "~" || trimmed.startsWith("~/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/") || trimmed.startsWith("local:") || trimmed.startsWith("github:") || trimmed.startsWith("git:") || trimmed.startsWith("skillkit:") || trimmed.startsWith("vercel:");
4863
+ }
4864
+ function lockedRootSourceDrifted(declared, locked) {
4865
+ return declared.normalizedSource !== locked.normalizedSource || declared.requestedRef !== locked.requestedRef;
4866
+ }
4750
4867
  function normalizedSourceFromLockedNode(node) {
4751
4868
  return {
4752
4869
  source: node.source,
@@ -4765,8 +4882,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
4765
4882
  async function withCachePathLock(path, fn) {
4766
4883
  const previous = cacheLocks.get(path) ?? Promise.resolve();
4767
4884
  let release = () => void 0;
4768
- const current = previous.then(() => new Promise((resolve18) => {
4769
- release = resolve18;
4885
+ const current = previous.then(() => new Promise((resolve19) => {
4886
+ release = resolve19;
4770
4887
  }));
4771
4888
  cacheLocks.set(path, current);
4772
4889
  await previous;
@@ -4974,7 +5091,7 @@ import { rm as rm8 } from "fs/promises";
4974
5091
  // src/lifecycle/source-plan.ts
4975
5092
  import { createHash as createHash6 } from "crypto";
4976
5093
  import { mkdir as mkdir12 } from "fs/promises";
4977
- import { dirname as dirname17, join as join22 } from "path";
5094
+ import { dirname as dirname17, join as join22, resolve as resolve14 } from "path";
4978
5095
 
4979
5096
  // src/resolve/graph-diff.ts
4980
5097
  function diffGraphLocks(previous, next) {
@@ -4982,7 +5099,8 @@ function diffGraphLocks(previous, next) {
4982
5099
  return [
4983
5100
  ...diffNodes(previous.canonical.nodes, next.canonical.nodes),
4984
5101
  ...diffIncludeEdges(previous.canonical.includeEdges, next.canonical.includeEdges),
4985
- ...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing)
5102
+ ...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing),
5103
+ ...diffOverrides(previous.canonical.overrides, next.canonical.overrides)
4986
5104
  ];
4987
5105
  }
4988
5106
  function diffNodes(previous, next) {
@@ -5044,6 +5162,23 @@ function diffNamespacing(previous, next) {
5044
5162
  }
5045
5163
  return lines.sort((a, b) => a.localeCompare(b));
5046
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
+ }
5047
5182
  function stableNodeKey(node) {
5048
5183
  return `${node.normalizedSource}\0${node.name}`;
5049
5184
  }
@@ -5063,9 +5198,15 @@ function formatIncludeEdge(edge) {
5063
5198
  function namespaceKey(decision) {
5064
5199
  return `${decision.graphNodeId}\0${decision.type}\0${decision.name}`;
5065
5200
  }
5201
+ function overrideKey(decision) {
5202
+ return `${decision.rootId}\0${decision.selector}\0${decision.overriddenGraphNodeId}\0${decision.type}\0${decision.name}`;
5203
+ }
5066
5204
  function formatNamespace(decision) {
5067
5205
  return `${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`;
5068
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
+ }
5069
5210
  function short(hash) {
5070
5211
  return hash.slice(0, 12);
5071
5212
  }
@@ -5167,13 +5308,13 @@ async function renderGraphForTarget(graph, targetContext = {}) {
5167
5308
  owners: [...rawNode.node.requiredBy].sort((a, b) => a.localeCompare(b))
5168
5309
  })));
5169
5310
  }
5170
- const { artifacts: namedArtifacts, namespacing } = assignInstallNames(graph, artifacts);
5311
+ const { artifacts: namedArtifacts, namespacing, overrides } = assignInstallNames(graph, artifacts);
5171
5312
  const sortedArtifacts = namedArtifacts.sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector));
5172
5313
  return {
5173
5314
  root,
5174
5315
  nodes: graph.nodes,
5175
5316
  artifacts: sortedArtifacts,
5176
- graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing)
5317
+ graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing, overrides)
5177
5318
  };
5178
5319
  }
5179
5320
  function aliasEdgeMap(graph) {
@@ -5217,7 +5358,8 @@ function assignInstallNames(graph, artifacts) {
5217
5358
  decisions.set(decisionKey(updated), namespaceDecision(updated, "alias"));
5218
5359
  return updated;
5219
5360
  });
5220
- 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);
5221
5363
  const toRename = /* @__PURE__ */ new Set();
5222
5364
  for (const group of collisionGroups) {
5223
5365
  if (group.some((artifact) => decisions.has(decisionKey(artifact)))) throw installNameCollisionError(group);
@@ -5228,11 +5370,11 @@ function assignInstallNames(graph, artifacts) {
5228
5370
  }
5229
5371
  }
5230
5372
  const used = /* @__PURE__ */ new Map();
5231
- for (const artifact of withAliases) {
5373
+ for (const artifact of withOverrides) {
5232
5374
  if (toRename.has(artifact)) continue;
5233
5375
  used.set(`${artifact.type}\0${artifact.installName}`, artifact);
5234
5376
  }
5235
- const out = withAliases.filter((artifact) => !toRename.has(artifact));
5377
+ const out = withOverrides.filter((artifact) => !toRename.has(artifact));
5236
5378
  for (const group of groupBy([...toRename], (artifact) => `${artifact.type}\0${artifact.name}`).values()) {
5237
5379
  const renamed = namespaceTransitiveGroup(group, used);
5238
5380
  for (const artifact of renamed) {
@@ -5241,7 +5383,52 @@ function assignInstallNames(graph, artifacts) {
5241
5383
  out.push(artifact);
5242
5384
  }
5243
5385
  }
5244
- 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 };
5245
5432
  }
5246
5433
  function namespaceTransitiveGroup(group, used) {
5247
5434
  const sorted = [...group].sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector));
@@ -5279,6 +5466,16 @@ function workspaceAliases(graph) {
5279
5466
  }
5280
5467
  return aliases;
5281
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
+ }
5282
5479
  function validateAliasScopes(graph, artifacts, aliases) {
5283
5480
  for (const alias of aliases) {
5284
5481
  const matching = artifacts.filter((artifact) => {
@@ -5295,6 +5492,35 @@ function aliasMatchesArtifact(selector, artifact, node) {
5295
5492
  const artifactSelector = `${artifact.type}/${artifact.name}`;
5296
5493
  return selector === `${artifact.graphNodeId}:${artifactSelector}` || node !== void 0 && selector === `${node.name}@${node.version}:${artifactSelector}` || node !== void 0 && selector === `${node.name}:${artifactSelector}`;
5297
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
+ }
5298
5524
  function reachableNodeIds(graph, rootNodeId) {
5299
5525
  const reachable = /* @__PURE__ */ new Set();
5300
5526
  const queue = [rootNodeId];
@@ -5512,7 +5738,8 @@ async function createGraphSourcePlan(options) {
5512
5738
  const manifest = await readInstallManifest(options.targetRoot, options.adapter.name, transport);
5513
5739
  const plan = await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
5514
5740
  baseRevision: manifest?.revision ?? null,
5515
- graphLockDigest
5741
+ graphLockDigest,
5742
+ workspaceOwner: workspaceOwnerId(workspaceRoot)
5516
5743
  });
5517
5744
  return {
5518
5745
  plan,
@@ -5570,6 +5797,9 @@ function sanitizePathSegment(value) {
5570
5797
  function digestGraphLock(lock) {
5571
5798
  return createHash6("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
5572
5799
  }
5800
+ function workspaceOwnerId(workspaceRoot) {
5801
+ return `workspace-root:${resolve14(workspaceRoot)}`;
5802
+ }
5573
5803
  function assertFrozenGraph(previousLock, graph, frozen, label) {
5574
5804
  if (!frozen) return;
5575
5805
  if (!previousLock) {
@@ -5652,7 +5882,8 @@ async function syncProfile(options) {
5652
5882
  mode: options.mode ?? pkg.mode,
5653
5883
  ref: pkg.requestedRef,
5654
5884
  select: selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
5655
- aliases: pkg.aliases
5885
+ aliases: pkg.aliases,
5886
+ overrides: pkg.overrides
5656
5887
  })),
5657
5888
  targetRoot: target.targetRoot,
5658
5889
  workspaceRoot: options.workspaceRoot,
@@ -5743,7 +5974,7 @@ async function packageFromSource(source, options) {
5743
5974
  }
5744
5975
 
5745
5976
  // src/runtime/target.ts
5746
- import { basename as basename12, dirname as dirname18, join as join23, resolve as resolve14 } from "path";
5977
+ import { basename as basename12, dirname as dirname18, join as join23, resolve as resolve15 } from "path";
5747
5978
  var runtimeMarkers = [
5748
5979
  { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
5749
5980
  { adapter: "claude", dirs: [".claude"] },
@@ -5752,9 +5983,9 @@ var runtimeMarkers = [
5752
5983
  { adapter: "copilot", dirs: [".github"] }
5753
5984
  ];
5754
5985
  async function resolveRuntimeTarget(request = {}) {
5755
- const cwd = resolve14(request.cwd ?? process.cwd());
5986
+ const cwd = resolve15(request.cwd ?? process.cwd());
5756
5987
  if (request.targetRoot) {
5757
- const targetRoot = resolve14(request.targetRoot);
5988
+ const targetRoot = resolve15(request.targetRoot);
5758
5989
  return {
5759
5990
  adapter: request.adapter ?? "openclaw",
5760
5991
  targetRoot,
@@ -5783,7 +6014,7 @@ async function resolveRuntimeTarget(request = {}) {
5783
6014
  async function resolveAllRuntimeTargets(request = {}) {
5784
6015
  if (request.targetRoot) return [await resolveRuntimeTarget(request)];
5785
6016
  if (request.agent) return [await resolveRuntimeTarget(request)];
5786
- const cwd = resolve14(request.cwd ?? process.cwd());
6017
+ const cwd = resolve15(request.cwd ?? process.cwd());
5787
6018
  const workspaceRoot = await findWorkspaceRoot(cwd);
5788
6019
  const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
5789
6020
  const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
@@ -5792,8 +6023,29 @@ async function resolveAllRuntimeTargets(request = {}) {
5792
6023
  }
5793
6024
  return targets;
5794
6025
  }
6026
+ async function resolveAllDetectedRuntimeTargets(request = {}) {
6027
+ if (request.agent) return [await resolveRuntimeTarget(request)];
6028
+ const scanRoot = runtimeScanRoot(request);
6029
+ const matches = await detectRuntimeTargets(scanRoot, request.adapter);
6030
+ if (matches.length === 0) {
6031
+ throw new Error(`No runtime directories detected at ${scanRoot}. Pass --target-root or --agent.`);
6032
+ }
6033
+ return Promise.all(matches.map(async (match) => ({
6034
+ ...match,
6035
+ workspaceRoot: await findWorkspaceRoot(match.targetRoot),
6036
+ transport: "local",
6037
+ source: "auto-detect"
6038
+ })));
6039
+ }
5795
6040
  async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
5796
- const root = resolve14(cwd);
6041
+ const unique = await detectRuntimeTargets(cwd, adapterFilter);
6042
+ if (unique.length > 1) {
6043
+ throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter, --agent, or --all-detected.`);
6044
+ }
6045
+ return unique[0];
6046
+ }
6047
+ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
6048
+ const root = resolve15(cwd);
5797
6049
  const matches = [];
5798
6050
  for (const marker of runtimeMarkers) {
5799
6051
  if (adapterFilter && marker.adapter !== adapterFilter) continue;
@@ -5805,11 +6057,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
5805
6057
  }
5806
6058
  }
5807
6059
  }
5808
- const unique = dedupeTargets(matches);
5809
- if (unique.length > 1) {
5810
- throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter or --agent.`);
5811
- }
5812
- return unique[0];
6060
+ return dedupeTargets(matches);
5813
6061
  }
5814
6062
  function targetFromAgent(name, config, workspaceRoot) {
5815
6063
  const agent = config.agents[name];
@@ -5838,6 +6086,11 @@ function dedupeTargets(matches) {
5838
6086
  }
5839
6087
  return [...byKey.values()];
5840
6088
  }
6089
+ function runtimeScanRoot(request) {
6090
+ const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
6091
+ if (request.targetRoot) return root;
6092
+ return runtimeMarkers.some((marker) => marker.dirs.includes(basename12(root))) ? dirname18(root) : root;
6093
+ }
5841
6094
 
5842
6095
  // src/cli/update-check.ts
5843
6096
  import { mkdir as mkdir13, readFile as readFile16, writeFile as writeFile10 } from "fs/promises";
@@ -5919,9 +6172,9 @@ function normalizeVersion(version) {
5919
6172
 
5920
6173
  // src/model/package-validate.ts
5921
6174
  import { stat as stat11 } from "fs/promises";
5922
- import { resolve as resolve15 } from "path";
6175
+ import { resolve as resolve16 } from "path";
5923
6176
  async function validatePackage(root) {
5924
- const packageRoot = resolve15(root);
6177
+ const packageRoot = resolve16(root);
5925
6178
  const findings = [];
5926
6179
  const manifestPath = await findPackageManifestPath(packageRoot);
5927
6180
  if (!manifestPath) {
@@ -5989,7 +6242,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
5989
6242
  try {
5990
6243
  validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
5991
6244
  if (isCrossPackageSelector(selector)) return;
5992
- const full = resolve15(packageRoot, selector);
6245
+ const full = resolve16(packageRoot, selector);
5993
6246
  if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
5994
6247
  findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
5995
6248
  return;
@@ -6043,10 +6296,10 @@ function isCrossPackageSelector(value) {
6043
6296
 
6044
6297
  // src/model/package-migrate.ts
6045
6298
  import { readFile as readFile17, rename as rename4, writeFile as writeFile11 } from "fs/promises";
6046
- import { join as join26, resolve as resolve16 } from "path";
6299
+ import { join as join26, resolve as resolve17 } from "path";
6047
6300
  import { applyEdits, modify, parse as parse3 } from "jsonc-parser";
6048
6301
  async function migratePackageManifest(root) {
6049
- const packageRoot = resolve16(root);
6302
+ const packageRoot = resolve17(root);
6050
6303
  for (const name of openPackManifestNames) {
6051
6304
  const path = join26(packageRoot, name);
6052
6305
  if (await pathExists(path)) {
@@ -6133,7 +6386,7 @@ program.command("init").description("initialize an agentwheel workspace or packa
6133
6386
  if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
6134
6387
  console.log(nextInstallNudge());
6135
6388
  });
6136
- 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) => {
6137
6390
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6138
6391
  const entry = await packageEntryFromSource(source, targetRoot, options);
6139
6392
  await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
@@ -6165,13 +6418,13 @@ program.command("scan").description("scan a package source for validation findin
6165
6418
  }
6166
6419
  if (!result.ok) process.exitCode = 1;
6167
6420
  });
6168
- 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("--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) => {
6169
6422
  await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
6170
6423
  });
6171
- 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("--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) => {
6172
6425
  await runInstallCommand(source, options, { apply: !options.dryRun });
6173
6426
  });
6174
- 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("--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) => {
6175
6428
  console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
6176
6429
  await runInstallCommand(source, options, { apply: !options.dryRun });
6177
6430
  });
@@ -6191,6 +6444,9 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
6191
6444
  for (const decision of result.bundle.graphLock.canonical.namespacing) {
6192
6445
  console.log(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
6193
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
+ }
6194
6450
  await rm9(result.bundle.root, { recursive: true, force: true });
6195
6451
  }
6196
6452
  continue;
@@ -6345,7 +6601,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
6345
6601
  let extraPackage;
6346
6602
  if (nameOrSource && !configured) {
6347
6603
  try {
6348
- const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, options);
6604
+ const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, { ...options, adapter: options.adapter ?? target.adapter });
6349
6605
  scope = entry.name;
6350
6606
  if (behavior.apply) {
6351
6607
  await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), entry));
@@ -6407,7 +6663,8 @@ async function packageEntryFromSource(source, targetRoot, options) {
6407
6663
  adapterCodeHash: adapter.programmatic?.hash,
6408
6664
  mode: options.mode ?? "pinned",
6409
6665
  requestedRef: bundle.source.requestedRef,
6410
- select: selectedArtifacts
6666
+ select: selectedArtifacts,
6667
+ overrides: overrideArtifactsFromOptions(options)
6411
6668
  };
6412
6669
  } finally {
6413
6670
  await rm9(bundle.root, { recursive: true, force: true });
@@ -6433,9 +6690,15 @@ function nextInstallNudge() {
6433
6690
  return "Preview: agentwheel plan - Apply: agentwheel install";
6434
6691
  }
6435
6692
  async function resolveCliTargets(options) {
6693
+ if (options.all && options.allDetected) {
6694
+ throw new Error("Choose either --all for configured agents or --all-detected for detected runtime directories.");
6695
+ }
6436
6696
  if (options.all) {
6437
6697
  return resolveAllRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, all: options.all });
6438
6698
  }
6699
+ if (options.allDetected) {
6700
+ return resolveAllDetectedRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, allDetected: options.allDetected });
6701
+ }
6439
6702
  return [await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent })];
6440
6703
  }
6441
6704
  async function resolveAdapterForTarget(target, options) {
@@ -6523,6 +6786,7 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
6523
6786
  ref: pkg.requestedRef,
6524
6787
  select: selectedArtifacts ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
6525
6788
  aliases: pkg.aliases,
6789
+ overrides: pkg.overrides,
6526
6790
  useLock: behavior.mode === "install" ? true : !updateThisPackage
6527
6791
  };
6528
6792
  }),
@@ -6659,6 +6923,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
6659
6923
  graphNodeId: fresh?.graphNodeId ?? ("graphNodeId" in entry ? entry.graphNodeId : void 0),
6660
6924
  dependencyRole: fresh?.dependencyRole ?? ("dependencyRole" in entry ? entry.dependencyRole : "root"),
6661
6925
  owners: fresh?.owners ?? owners,
6926
+ workspaceOwner: fresh?.workspaceOwner ?? ("workspaceOwner" in entry ? entry.workspaceOwner : "legacy:unowned"),
6662
6927
  graphLockDigest: fresh ? void 0 : "graphLockDigest" in entry ? entry.graphLockDigest : void 0
6663
6928
  };
6664
6929
  }
@@ -6702,7 +6967,8 @@ async function uninstallConfiguredPackage(target, packageName, options) {
6702
6967
  mode: pkg2.mode,
6703
6968
  ref: pkg2.requestedRef,
6704
6969
  select: normalizeArtifactSelectors(pkg2.select, pkg2.skills),
6705
- aliases: pkg2.aliases
6970
+ aliases: pkg2.aliases,
6971
+ overrides: pkg2.overrides
6706
6972
  })),
6707
6973
  targetRoot: remainingGroup.target.targetRoot,
6708
6974
  workspaceRoot: remainingGroup.target.workspaceRoot,
@@ -6865,9 +7131,16 @@ function collectSkillOption(value, previous) {
6865
7131
  function collectTrustOption(value, previous) {
6866
7132
  return [...previous, value];
6867
7133
  }
7134
+ function collectOverrideOption(value, previous) {
7135
+ return [...previous, ...splitSelectorList(value)];
7136
+ }
6868
7137
  function selectedArtifactsFromOptions(options) {
6869
7138
  return normalizeArtifactSelectors(options.select, options.skills ?? options.skill);
6870
7139
  }
7140
+ function overrideArtifactsFromOptions(options) {
7141
+ const values = options.overrides ?? options.override;
7142
+ return values && values.length > 0 ? values : void 0;
7143
+ }
6871
7144
  function filterUninstallPlanBySelection(plan, selected) {
6872
7145
  if (!selected?.length) return plan;
6873
7146
  const requested = normalizeArtifactSelectors(selected) ?? [];
@@ -6955,7 +7228,7 @@ function withFleetExample(config) {
6955
7228
  };
6956
7229
  }
6957
7230
  async function findAgentwheelPackageRoot(start) {
6958
- let current = resolve17(start);
7231
+ let current = resolve18(start);
6959
7232
  while (true) {
6960
7233
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
6961
7234
  const parent = dirname21(current);
@@ -6988,7 +7261,9 @@ async function main() {
6988
7261
  });
6989
7262
  await program.parseAsync();
6990
7263
  }
6991
- main().catch((error) => {
7264
+ try {
7265
+ await main();
7266
+ } catch (error) {
6992
7267
  console.error(error instanceof Error ? error.message : String(error));
6993
7268
  process.exitCode = 1;
6994
- });
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.9.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: