@alessandroraffa/tangyr 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +45 -36
  2. package/dist/index.js +544 -142
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -21,23 +21,16 @@ tangyr <command>
21
21
 
22
22
  ### Kit management
23
23
 
24
- | Command | Description |
25
- | ----------------------------- | ------------------------------------------------------------------- |
26
- | `tangyr install --kit <name>` | Install a kit; runs assess, resolves conflicts, writes artifacts |
27
- | `tangyr uninstall` | Remove Tangyr from the target scope |
28
- | `tangyr sync` | Synchronize installed kit to the latest version |
29
- | `tangyr verify` | Check installation integrity against the manifest |
30
- | `tangyr cleanup` | Remove managed artifacts; delegates to uninstall if manifest exists |
31
- | `tangyr assess` | Pre-installation conflict analysis |
32
- | `tangyr list` | Display available and installed kits |
33
-
34
- ### Authentication
35
-
36
- | Command | Description |
37
- | -------------------- | ------------------------------------- |
38
- | `tangyr auth login` | Store a license key for the Full tier |
39
- | `tangyr auth status` | Show current authentication state |
40
- | `tangyr auth logout` | Remove stored credentials |
24
+ | Command | Description |
25
+ | ---------------------------------- | ---------------------------------------------------------------------------------------------------- |
26
+ | `tangyr install --kit <name>` | Install a kit resolved from configured search paths (`kitSearchPaths`/`kit` in `tangyr.config.yaml`) |
27
+ | `tangyr install --kit-path <path>` | Install a kit from an explicit kit root path |
28
+ | `tangyr uninstall` | Remove Tangyr from the target scope |
29
+ | `tangyr sync` | Synchronize installed kit to the latest version |
30
+ | `tangyr verify` | Check installation integrity against the manifest |
31
+ | `tangyr cleanup` | Remove managed artifacts; delegates to uninstall if manifest exists |
32
+ | `tangyr assess` | Pre-installation conflict analysis |
33
+ | `tangyr list` | Display kits discovered in configured search paths and any installed manifests |
41
34
 
42
35
  ### Environment
43
36
 
@@ -58,15 +51,16 @@ tangyr <command>
58
51
 
59
52
  ## Global flags
60
53
 
61
- | Flag | Description |
62
- | ------------------- | -------------------------------------- |
63
- | `--config <path>` | Use an explicit configuration file |
64
- | `--kit-path <path>` | Use an explicit operating kit root |
65
- | `--dry-run` | Report actions without modifying files |
66
- | `--yes` | Skip confirmation prompts |
67
- | `--verbose` | Detailed output |
68
- | `--quiet` | Errors only |
69
- | `--no-color` | Disable colors |
54
+ | Flag | Description |
55
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------- |
56
+ | `--config <path>` | Use an explicit configuration file |
57
+ | `--kit-path <path>` | Use an explicit operating kit root |
58
+ | `--dry-run` | Report actions without modifying files |
59
+ | `--yes` | Skip confirmation prompts |
60
+ | `--verbose` | Detailed output |
61
+ | `--quiet` | Errors only |
62
+ | `--no-color` | Disable colors |
63
+ | `--claude-config-dir <path>` | Claude Code global root directory (repeatable). Overrides `claudeCodeGlobalPaths` in the config file. |
70
64
 
71
65
  ## Supported tools
72
66
 
@@ -83,14 +77,12 @@ tangyr <command>
83
77
  # Initialize a configuration in your project
84
78
  npx @alessandroraffa/tangyr init
85
79
 
86
- # Install a kit (dry-run first to preview)
87
- npx @alessandroraffa/tangyr install --kit lite --dry-run
88
- npx @alessandroraffa/tangyr install --kit lite
80
+ # Install a kit by path (preview with --dry-run first)
81
+ npx @alessandroraffa/tangyr install --kit-path ./path/to/operating-kit --dry-run
82
+ npx @alessandroraffa/tangyr install --kit-path ./path/to/operating-kit
89
83
 
90
- # Check installation status
84
+ # Check installation status, then sync after a kit update
91
85
  npx @alessandroraffa/tangyr status
92
-
93
- # Synchronize after a kit update
94
86
  npx @alessandroraffa/tangyr sync
95
87
  ```
96
88
 
@@ -103,9 +95,26 @@ Every write command accepts `--scope global` or `--scope project`. The CLI flag
103
95
  | `global` | `~/.tangyr/manifest.json` | `~/.claude/` |
104
96
  | `project` | `.tangyr/manifest.json` | repo root (`AGENTS.md`, `.claude/`) |
105
97
 
106
- ## Distribution model
98
+ ## Multiple Claude Code global roots
99
+
100
+ If you maintain more than one Claude Code account (e.g. `~/.claude` and `~/.claude-work`), you can keep them identical with a single sync operation.
101
+
102
+ **Via config** (`tangyr.config.yaml`):
103
+
104
+ ```yaml
105
+ claudeCodeGlobalPaths:
106
+ - ~/.claude
107
+ - ~/.claude-work
108
+ ```
109
+
110
+ **Via CLI flag** (repeatable, overrides the config field):
111
+
112
+ ```sh
113
+ tangyr sync --claude-config-dir ~/.claude --claude-config-dir ~/.claude-work
114
+ ```
115
+
116
+ Precedence: `--claude-config-dir` flag > `claudeCodeGlobalPaths` in config > default `~/.claude`.
107
117
 
108
- - **Lite** publicly available, core agents and essential skills
109
- - **Full** — requires license key, complete agent roster and all skills
118
+ Per-root artifacts (instructions, agents, skills, commands, rules, output-styles, hooks settings) are written under each root. Shared artifacts (`~/.claude.json` for MCP, `~/.agents/hooks` for hooks) are resolved from the platform default and written once regardless of how many roots are configured.
110
119
 
111
- Run `tangyr auth login` to authenticate with a Full-tier license key.
120
+ The manifest records the full set of roots written so that `verify` and `uninstall` operate on all of them.
package/dist/index.js CHANGED
@@ -1368,8 +1368,8 @@ var require_command = __commonJS({
1368
1368
  "use strict";
1369
1369
  var EventEmitter = __require("events").EventEmitter;
1370
1370
  var childProcess = __require("child_process");
1371
- var path39 = __require("path");
1372
- var fs37 = __require("fs");
1371
+ var path40 = __require("path");
1372
+ var fs38 = __require("fs");
1373
1373
  var process5 = __require("process");
1374
1374
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1375
1375
  var { CommanderError: CommanderError2 } = require_error();
@@ -2363,7 +2363,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2363
2363
  * @param {string} subcommandName
2364
2364
  */
2365
2365
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2366
- if (fs37.existsSync(executableFile)) return;
2366
+ if (fs38.existsSync(executableFile)) return;
2367
2367
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2368
2368
  const executableMissing = `'${executableFile}' does not exist
2369
2369
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2381,11 +2381,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2381
2381
  let launchWithNode = false;
2382
2382
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2383
2383
  function findFile(baseDir, baseName) {
2384
- const localBin = path39.resolve(baseDir, baseName);
2385
- if (fs37.existsSync(localBin)) return localBin;
2386
- if (sourceExt.includes(path39.extname(baseName))) return void 0;
2384
+ const localBin = path40.resolve(baseDir, baseName);
2385
+ if (fs38.existsSync(localBin)) return localBin;
2386
+ if (sourceExt.includes(path40.extname(baseName))) return void 0;
2387
2387
  const foundExt = sourceExt.find(
2388
- (ext) => fs37.existsSync(`${localBin}${ext}`)
2388
+ (ext) => fs38.existsSync(`${localBin}${ext}`)
2389
2389
  );
2390
2390
  if (foundExt) return `${localBin}${foundExt}`;
2391
2391
  return void 0;
@@ -2397,21 +2397,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2397
2397
  if (this._scriptPath) {
2398
2398
  let resolvedScriptPath;
2399
2399
  try {
2400
- resolvedScriptPath = fs37.realpathSync(this._scriptPath);
2400
+ resolvedScriptPath = fs38.realpathSync(this._scriptPath);
2401
2401
  } catch {
2402
2402
  resolvedScriptPath = this._scriptPath;
2403
2403
  }
2404
- executableDir = path39.resolve(
2405
- path39.dirname(resolvedScriptPath),
2404
+ executableDir = path40.resolve(
2405
+ path40.dirname(resolvedScriptPath),
2406
2406
  executableDir
2407
2407
  );
2408
2408
  }
2409
2409
  if (executableDir) {
2410
2410
  let localFile = findFile(executableDir, executableFile);
2411
2411
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2412
- const legacyName = path39.basename(
2412
+ const legacyName = path40.basename(
2413
2413
  this._scriptPath,
2414
- path39.extname(this._scriptPath)
2414
+ path40.extname(this._scriptPath)
2415
2415
  );
2416
2416
  if (legacyName !== this._name) {
2417
2417
  localFile = findFile(
@@ -2422,7 +2422,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2422
2422
  }
2423
2423
  executableFile = localFile || executableFile;
2424
2424
  }
2425
- launchWithNode = sourceExt.includes(path39.extname(executableFile));
2425
+ launchWithNode = sourceExt.includes(path40.extname(executableFile));
2426
2426
  let proc;
2427
2427
  if (process5.platform !== "win32") {
2428
2428
  if (launchWithNode) {
@@ -3337,7 +3337,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3337
3337
  * @return {Command}
3338
3338
  */
3339
3339
  nameFromFilename(filename) {
3340
- this._name = path39.basename(filename, path39.extname(filename));
3340
+ this._name = path40.basename(filename, path40.extname(filename));
3341
3341
  return this;
3342
3342
  }
3343
3343
  /**
@@ -3351,9 +3351,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3351
3351
  * @param {string} [path]
3352
3352
  * @return {(string|null|Command)}
3353
3353
  */
3354
- executableDir(path40) {
3355
- if (path40 === void 0) return this._executableDir;
3356
- this._executableDir = path40;
3354
+ executableDir(path41) {
3355
+ if (path41 === void 0) return this._executableDir;
3356
+ this._executableDir = path41;
3357
3357
  return this;
3358
3358
  }
3359
3359
  /**
@@ -3709,17 +3709,17 @@ var require_visit = __commonJS({
3709
3709
  visit.BREAK = BREAK;
3710
3710
  visit.SKIP = SKIP;
3711
3711
  visit.REMOVE = REMOVE;
3712
- function visit_(key, node, visitor, path39) {
3713
- const ctrl = callVisitor(key, node, visitor, path39);
3712
+ function visit_(key, node, visitor, path40) {
3713
+ const ctrl = callVisitor(key, node, visitor, path40);
3714
3714
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
3715
- replaceNode(key, path39, ctrl);
3716
- return visit_(key, ctrl, visitor, path39);
3715
+ replaceNode(key, path40, ctrl);
3716
+ return visit_(key, ctrl, visitor, path40);
3717
3717
  }
3718
3718
  if (typeof ctrl !== "symbol") {
3719
3719
  if (identity.isCollection(node)) {
3720
- path39 = Object.freeze(path39.concat(node));
3720
+ path40 = Object.freeze(path40.concat(node));
3721
3721
  for (let i = 0; i < node.items.length; ++i) {
3722
- const ci2 = visit_(i, node.items[i], visitor, path39);
3722
+ const ci2 = visit_(i, node.items[i], visitor, path40);
3723
3723
  if (typeof ci2 === "number")
3724
3724
  i = ci2 - 1;
3725
3725
  else if (ci2 === BREAK)
@@ -3730,13 +3730,13 @@ var require_visit = __commonJS({
3730
3730
  }
3731
3731
  }
3732
3732
  } else if (identity.isPair(node)) {
3733
- path39 = Object.freeze(path39.concat(node));
3734
- const ck = visit_("key", node.key, visitor, path39);
3733
+ path40 = Object.freeze(path40.concat(node));
3734
+ const ck = visit_("key", node.key, visitor, path40);
3735
3735
  if (ck === BREAK)
3736
3736
  return BREAK;
3737
3737
  else if (ck === REMOVE)
3738
3738
  node.key = null;
3739
- const cv = visit_("value", node.value, visitor, path39);
3739
+ const cv = visit_("value", node.value, visitor, path40);
3740
3740
  if (cv === BREAK)
3741
3741
  return BREAK;
3742
3742
  else if (cv === REMOVE)
@@ -3757,17 +3757,17 @@ var require_visit = __commonJS({
3757
3757
  visitAsync.BREAK = BREAK;
3758
3758
  visitAsync.SKIP = SKIP;
3759
3759
  visitAsync.REMOVE = REMOVE;
3760
- async function visitAsync_(key, node, visitor, path39) {
3761
- const ctrl = await callVisitor(key, node, visitor, path39);
3760
+ async function visitAsync_(key, node, visitor, path40) {
3761
+ const ctrl = await callVisitor(key, node, visitor, path40);
3762
3762
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
3763
- replaceNode(key, path39, ctrl);
3764
- return visitAsync_(key, ctrl, visitor, path39);
3763
+ replaceNode(key, path40, ctrl);
3764
+ return visitAsync_(key, ctrl, visitor, path40);
3765
3765
  }
3766
3766
  if (typeof ctrl !== "symbol") {
3767
3767
  if (identity.isCollection(node)) {
3768
- path39 = Object.freeze(path39.concat(node));
3768
+ path40 = Object.freeze(path40.concat(node));
3769
3769
  for (let i = 0; i < node.items.length; ++i) {
3770
- const ci2 = await visitAsync_(i, node.items[i], visitor, path39);
3770
+ const ci2 = await visitAsync_(i, node.items[i], visitor, path40);
3771
3771
  if (typeof ci2 === "number")
3772
3772
  i = ci2 - 1;
3773
3773
  else if (ci2 === BREAK)
@@ -3778,13 +3778,13 @@ var require_visit = __commonJS({
3778
3778
  }
3779
3779
  }
3780
3780
  } else if (identity.isPair(node)) {
3781
- path39 = Object.freeze(path39.concat(node));
3782
- const ck = await visitAsync_("key", node.key, visitor, path39);
3781
+ path40 = Object.freeze(path40.concat(node));
3782
+ const ck = await visitAsync_("key", node.key, visitor, path40);
3783
3783
  if (ck === BREAK)
3784
3784
  return BREAK;
3785
3785
  else if (ck === REMOVE)
3786
3786
  node.key = null;
3787
- const cv = await visitAsync_("value", node.value, visitor, path39);
3787
+ const cv = await visitAsync_("value", node.value, visitor, path40);
3788
3788
  if (cv === BREAK)
3789
3789
  return BREAK;
3790
3790
  else if (cv === REMOVE)
@@ -3811,23 +3811,23 @@ var require_visit = __commonJS({
3811
3811
  }
3812
3812
  return visitor;
3813
3813
  }
3814
- function callVisitor(key, node, visitor, path39) {
3814
+ function callVisitor(key, node, visitor, path40) {
3815
3815
  if (typeof visitor === "function")
3816
- return visitor(key, node, path39);
3816
+ return visitor(key, node, path40);
3817
3817
  if (identity.isMap(node))
3818
- return visitor.Map?.(key, node, path39);
3818
+ return visitor.Map?.(key, node, path40);
3819
3819
  if (identity.isSeq(node))
3820
- return visitor.Seq?.(key, node, path39);
3820
+ return visitor.Seq?.(key, node, path40);
3821
3821
  if (identity.isPair(node))
3822
- return visitor.Pair?.(key, node, path39);
3822
+ return visitor.Pair?.(key, node, path40);
3823
3823
  if (identity.isScalar(node))
3824
- return visitor.Scalar?.(key, node, path39);
3824
+ return visitor.Scalar?.(key, node, path40);
3825
3825
  if (identity.isAlias(node))
3826
- return visitor.Alias?.(key, node, path39);
3826
+ return visitor.Alias?.(key, node, path40);
3827
3827
  return void 0;
3828
3828
  }
3829
- function replaceNode(key, path39, node) {
3830
- const parent = path39[path39.length - 1];
3829
+ function replaceNode(key, path40, node) {
3830
+ const parent = path40[path40.length - 1];
3831
3831
  if (identity.isCollection(parent)) {
3832
3832
  parent.items[key] = node;
3833
3833
  } else if (identity.isPair(parent)) {
@@ -4435,10 +4435,10 @@ var require_Collection = __commonJS({
4435
4435
  var createNode = require_createNode();
4436
4436
  var identity = require_identity();
4437
4437
  var Node = require_Node();
4438
- function collectionFromPath(schema, path39, value) {
4438
+ function collectionFromPath(schema, path40, value) {
4439
4439
  let v2 = value;
4440
- for (let i = path39.length - 1; i >= 0; --i) {
4441
- const k2 = path39[i];
4440
+ for (let i = path40.length - 1; i >= 0; --i) {
4441
+ const k2 = path40[i];
4442
4442
  if (typeof k2 === "number" && Number.isInteger(k2) && k2 >= 0) {
4443
4443
  const a = [];
4444
4444
  a[k2] = v2;
@@ -4457,7 +4457,7 @@ var require_Collection = __commonJS({
4457
4457
  sourceObjects: /* @__PURE__ */ new Map()
4458
4458
  });
4459
4459
  }
4460
- var isEmptyPath = (path39) => path39 == null || typeof path39 === "object" && !!path39[Symbol.iterator]().next().done;
4460
+ var isEmptyPath = (path40) => path40 == null || typeof path40 === "object" && !!path40[Symbol.iterator]().next().done;
4461
4461
  var Collection = class extends Node.NodeBase {
4462
4462
  constructor(type, schema) {
4463
4463
  super(type);
@@ -4487,11 +4487,11 @@ var require_Collection = __commonJS({
4487
4487
  * be a Pair instance or a `{ key, value }` object, which may not have a key
4488
4488
  * that already exists in the map.
4489
4489
  */
4490
- addIn(path39, value) {
4491
- if (isEmptyPath(path39))
4490
+ addIn(path40, value) {
4491
+ if (isEmptyPath(path40))
4492
4492
  this.add(value);
4493
4493
  else {
4494
- const [key, ...rest] = path39;
4494
+ const [key, ...rest] = path40;
4495
4495
  const node = this.get(key, true);
4496
4496
  if (identity.isCollection(node))
4497
4497
  node.addIn(rest, value);
@@ -4505,8 +4505,8 @@ var require_Collection = __commonJS({
4505
4505
  * Removes a value from the collection.
4506
4506
  * @returns `true` if the item was found and removed.
4507
4507
  */
4508
- deleteIn(path39) {
4509
- const [key, ...rest] = path39;
4508
+ deleteIn(path40) {
4509
+ const [key, ...rest] = path40;
4510
4510
  if (rest.length === 0)
4511
4511
  return this.delete(key);
4512
4512
  const node = this.get(key, true);
@@ -4520,8 +4520,8 @@ var require_Collection = __commonJS({
4520
4520
  * scalar values from their surrounding node; to disable set `keepScalar` to
4521
4521
  * `true` (collections are always returned intact).
4522
4522
  */
4523
- getIn(path39, keepScalar) {
4524
- const [key, ...rest] = path39;
4523
+ getIn(path40, keepScalar) {
4524
+ const [key, ...rest] = path40;
4525
4525
  const node = this.get(key, true);
4526
4526
  if (rest.length === 0)
4527
4527
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -4539,8 +4539,8 @@ var require_Collection = __commonJS({
4539
4539
  /**
4540
4540
  * Checks if the collection includes a value with the key `key`.
4541
4541
  */
4542
- hasIn(path39) {
4543
- const [key, ...rest] = path39;
4542
+ hasIn(path40) {
4543
+ const [key, ...rest] = path40;
4544
4544
  if (rest.length === 0)
4545
4545
  return this.has(key);
4546
4546
  const node = this.get(key, true);
@@ -4550,8 +4550,8 @@ var require_Collection = __commonJS({
4550
4550
  * Sets a value in this collection. For `!!set`, `value` needs to be a
4551
4551
  * boolean to add/remove the item from the set.
4552
4552
  */
4553
- setIn(path39, value) {
4554
- const [key, ...rest] = path39;
4553
+ setIn(path40, value) {
4554
+ const [key, ...rest] = path40;
4555
4555
  if (rest.length === 0) {
4556
4556
  this.set(key, value);
4557
4557
  } else {
@@ -7063,9 +7063,9 @@ var require_Document = __commonJS({
7063
7063
  this.contents.add(value);
7064
7064
  }
7065
7065
  /** Adds a value to the document. */
7066
- addIn(path39, value) {
7066
+ addIn(path40, value) {
7067
7067
  if (assertCollection(this.contents))
7068
- this.contents.addIn(path39, value);
7068
+ this.contents.addIn(path40, value);
7069
7069
  }
7070
7070
  /**
7071
7071
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -7140,14 +7140,14 @@ var require_Document = __commonJS({
7140
7140
  * Removes a value from the document.
7141
7141
  * @returns `true` if the item was found and removed.
7142
7142
  */
7143
- deleteIn(path39) {
7144
- if (Collection.isEmptyPath(path39)) {
7143
+ deleteIn(path40) {
7144
+ if (Collection.isEmptyPath(path40)) {
7145
7145
  if (this.contents == null)
7146
7146
  return false;
7147
7147
  this.contents = null;
7148
7148
  return true;
7149
7149
  }
7150
- return assertCollection(this.contents) ? this.contents.deleteIn(path39) : false;
7150
+ return assertCollection(this.contents) ? this.contents.deleteIn(path40) : false;
7151
7151
  }
7152
7152
  /**
7153
7153
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -7162,10 +7162,10 @@ var require_Document = __commonJS({
7162
7162
  * scalar values from their surrounding node; to disable set `keepScalar` to
7163
7163
  * `true` (collections are always returned intact).
7164
7164
  */
7165
- getIn(path39, keepScalar) {
7166
- if (Collection.isEmptyPath(path39))
7165
+ getIn(path40, keepScalar) {
7166
+ if (Collection.isEmptyPath(path40))
7167
7167
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
7168
- return identity.isCollection(this.contents) ? this.contents.getIn(path39, keepScalar) : void 0;
7168
+ return identity.isCollection(this.contents) ? this.contents.getIn(path40, keepScalar) : void 0;
7169
7169
  }
7170
7170
  /**
7171
7171
  * Checks if the document includes a value with the key `key`.
@@ -7176,10 +7176,10 @@ var require_Document = __commonJS({
7176
7176
  /**
7177
7177
  * Checks if the document includes a value at `path`.
7178
7178
  */
7179
- hasIn(path39) {
7180
- if (Collection.isEmptyPath(path39))
7179
+ hasIn(path40) {
7180
+ if (Collection.isEmptyPath(path40))
7181
7181
  return this.contents !== void 0;
7182
- return identity.isCollection(this.contents) ? this.contents.hasIn(path39) : false;
7182
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path40) : false;
7183
7183
  }
7184
7184
  /**
7185
7185
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -7196,13 +7196,13 @@ var require_Document = __commonJS({
7196
7196
  * Sets a value in this document. For `!!set`, `value` needs to be a
7197
7197
  * boolean to add/remove the item from the set.
7198
7198
  */
7199
- setIn(path39, value) {
7200
- if (Collection.isEmptyPath(path39)) {
7199
+ setIn(path40, value) {
7200
+ if (Collection.isEmptyPath(path40)) {
7201
7201
  this.contents = value;
7202
7202
  } else if (this.contents == null) {
7203
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path39), value);
7203
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path40), value);
7204
7204
  } else if (assertCollection(this.contents)) {
7205
- this.contents.setIn(path39, value);
7205
+ this.contents.setIn(path40, value);
7206
7206
  }
7207
7207
  }
7208
7208
  /**
@@ -9159,9 +9159,9 @@ var require_cst_visit = __commonJS({
9159
9159
  visit.BREAK = BREAK;
9160
9160
  visit.SKIP = SKIP;
9161
9161
  visit.REMOVE = REMOVE;
9162
- visit.itemAtPath = (cst, path39) => {
9162
+ visit.itemAtPath = (cst, path40) => {
9163
9163
  let item = cst;
9164
- for (const [field, index] of path39) {
9164
+ for (const [field, index] of path40) {
9165
9165
  const tok = item?.[field];
9166
9166
  if (tok && "items" in tok) {
9167
9167
  item = tok.items[index];
@@ -9170,23 +9170,23 @@ var require_cst_visit = __commonJS({
9170
9170
  }
9171
9171
  return item;
9172
9172
  };
9173
- visit.parentCollection = (cst, path39) => {
9174
- const parent = visit.itemAtPath(cst, path39.slice(0, -1));
9175
- const field = path39[path39.length - 1][0];
9173
+ visit.parentCollection = (cst, path40) => {
9174
+ const parent = visit.itemAtPath(cst, path40.slice(0, -1));
9175
+ const field = path40[path40.length - 1][0];
9176
9176
  const coll = parent?.[field];
9177
9177
  if (coll && "items" in coll)
9178
9178
  return coll;
9179
9179
  throw new Error("Parent collection not found");
9180
9180
  };
9181
- function _visit(path39, item, visitor) {
9182
- let ctrl = visitor(item, path39);
9181
+ function _visit(path40, item, visitor) {
9182
+ let ctrl = visitor(item, path40);
9183
9183
  if (typeof ctrl === "symbol")
9184
9184
  return ctrl;
9185
9185
  for (const field of ["key", "value"]) {
9186
9186
  const token = item[field];
9187
9187
  if (token && "items" in token) {
9188
9188
  for (let i = 0; i < token.items.length; ++i) {
9189
- const ci2 = _visit(Object.freeze(path39.concat([[field, i]])), token.items[i], visitor);
9189
+ const ci2 = _visit(Object.freeze(path40.concat([[field, i]])), token.items[i], visitor);
9190
9190
  if (typeof ci2 === "number")
9191
9191
  i = ci2 - 1;
9192
9192
  else if (ci2 === BREAK)
@@ -9197,10 +9197,10 @@ var require_cst_visit = __commonJS({
9197
9197
  }
9198
9198
  }
9199
9199
  if (typeof ctrl === "function" && field === "key")
9200
- ctrl = ctrl(item, path39);
9200
+ ctrl = ctrl(item, path40);
9201
9201
  }
9202
9202
  }
9203
- return typeof ctrl === "function" ? ctrl(item, path39) : ctrl;
9203
+ return typeof ctrl === "function" ? ctrl(item, path40) : ctrl;
9204
9204
  }
9205
9205
  exports2.visit = visit;
9206
9206
  }
@@ -10485,14 +10485,14 @@ var require_parser = __commonJS({
10485
10485
  case "scalar":
10486
10486
  case "single-quoted-scalar":
10487
10487
  case "double-quoted-scalar": {
10488
- const fs37 = this.flowScalar(this.type);
10488
+ const fs38 = this.flowScalar(this.type);
10489
10489
  if (atNextItem || it2.value) {
10490
- map.items.push({ start, key: fs37, sep: [] });
10490
+ map.items.push({ start, key: fs38, sep: [] });
10491
10491
  this.onKeyLine = true;
10492
10492
  } else if (it2.sep) {
10493
- this.stack.push(fs37);
10493
+ this.stack.push(fs38);
10494
10494
  } else {
10495
- Object.assign(it2, { key: fs37, sep: [] });
10495
+ Object.assign(it2, { key: fs38, sep: [] });
10496
10496
  this.onKeyLine = true;
10497
10497
  }
10498
10498
  return;
@@ -10620,13 +10620,13 @@ var require_parser = __commonJS({
10620
10620
  case "scalar":
10621
10621
  case "single-quoted-scalar":
10622
10622
  case "double-quoted-scalar": {
10623
- const fs37 = this.flowScalar(this.type);
10623
+ const fs38 = this.flowScalar(this.type);
10624
10624
  if (!it2 || it2.value)
10625
- fc.items.push({ start: [], key: fs37, sep: [] });
10625
+ fc.items.push({ start: [], key: fs38, sep: [] });
10626
10626
  else if (it2.sep)
10627
- this.stack.push(fs37);
10627
+ this.stack.push(fs38);
10628
10628
  else
10629
- Object.assign(it2, { key: fs37, sep: [] });
10629
+ Object.assign(it2, { key: fs38, sep: [] });
10630
10630
  return;
10631
10631
  }
10632
10632
  case "flow-map-end":
@@ -15011,7 +15011,10 @@ function normalizeConfig(config) {
15011
15011
  kit: config.kit,
15012
15012
  kitPath: config.kitPath,
15013
15013
  kitSearchPaths: config.kitSearchPaths,
15014
- scope: config.scope
15014
+ scope: config.scope,
15015
+ // claudeCodeGlobalPaths: pass through as-is; ~ expansion happens at
15016
+ // resolve time so relative entries are not forced absolute here
15017
+ claudeCodeGlobalPaths: config.claudeCodeGlobalPaths
15015
15018
  };
15016
15019
  }
15017
15020
  function findConfigPath(explicitPath) {
@@ -15249,7 +15252,10 @@ function readManifest(scopePath) {
15249
15252
  backupLocation: b.backupLocation,
15250
15253
  integerSuffix: typeof b.integerSuffix === "number" ? b.integerSuffix : 0
15251
15254
  })),
15252
- managedLinks: Array.isArray(parsed.managedLinks) ? parsed.managedLinks : []
15255
+ managedLinks: Array.isArray(parsed.managedLinks) ? parsed.managedLinks : [],
15256
+ claudeCodeGlobalRoots: Array.isArray(parsed.claudeCodeGlobalRoots) ? parsed.claudeCodeGlobalRoots.filter(
15257
+ (r) => typeof r === "string"
15258
+ ) : void 0
15253
15259
  };
15254
15260
  }
15255
15261
  function readStrictManifest(scopePath) {
@@ -15637,6 +15643,66 @@ function resolveClineProfile(scope, projectRoot, mappings) {
15637
15643
  mcp_file: r(slotMap, "mcp_file")
15638
15644
  };
15639
15645
  }
15646
+ var DEFAULT_CLAUDE_ROOT_RAW = "~/.claude";
15647
+ function resolveClaudeGlobalRoots(input) {
15648
+ const rawRoots = (input.flag?.length ?? 0) > 0 ? input.flag : (input.config?.length ?? 0) > 0 ? input.config : [DEFAULT_CLAUDE_ROOT_RAW];
15649
+ const seen = /* @__PURE__ */ new Set();
15650
+ const roots = [];
15651
+ for (const raw of rawRoots) {
15652
+ const abs = path8.resolve(expandHome(raw));
15653
+ if (!seen.has(abs)) {
15654
+ seen.add(abs);
15655
+ roots.push(abs);
15656
+ }
15657
+ }
15658
+ return roots;
15659
+ }
15660
+ var UNDER_ROOT_SLOTS = /* @__PURE__ */ new Set([
15661
+ "instructions",
15662
+ "instructions_shim",
15663
+ "agents",
15664
+ "skills",
15665
+ "commands",
15666
+ "rules",
15667
+ "output_styles",
15668
+ "hooks_shared_file",
15669
+ "settings_shared_file"
15670
+ ]);
15671
+ function resolveClaudeCodeProfileForRoot(root, mappings) {
15672
+ const slotMap = mappings.targets["claude-code"];
15673
+ if (!slotMap) {
15674
+ throw new Error(
15675
+ "platform-paths.yaml: missing claude-code target entry \u2014 run the loader validator first"
15676
+ );
15677
+ }
15678
+ const r = makeResolver("global", void 0, "claude-code");
15679
+ const defaultProfile = {
15680
+ instructions: r(slotMap, "instructions"),
15681
+ instructions_shim: r(slotMap, "instructions_shim"),
15682
+ agents: r(slotMap, "agents"),
15683
+ skills: r(slotMap, "skills"),
15684
+ commands: r(slotMap, "commands"),
15685
+ rules: r(slotMap, "rules"),
15686
+ output_styles: r(slotMap, "output_styles"),
15687
+ hooks_shared_file: r(slotMap, "hooks_shared_file"),
15688
+ settings_shared_file: r(slotMap, "settings_shared_file"),
15689
+ mcp_shared_file: r(slotMap, "mcp_shared_file"),
15690
+ hooks: r(slotMap, "hooks")
15691
+ };
15692
+ const defaultRoot = defaultProfile.agents !== void 0 ? path8.dirname(defaultProfile.agents) : path8.resolve(expandHome(DEFAULT_CLAUDE_ROOT_RAW));
15693
+ if (root === defaultRoot) {
15694
+ return defaultProfile;
15695
+ }
15696
+ const profile = { ...defaultProfile };
15697
+ for (const slot of UNDER_ROOT_SLOTS) {
15698
+ const slotValue = defaultProfile[slot];
15699
+ if (slotValue.startsWith(defaultRoot)) {
15700
+ const suffix = slotValue.slice(defaultRoot.length);
15701
+ profile[slot] = root + suffix;
15702
+ }
15703
+ }
15704
+ return profile;
15705
+ }
15640
15706
  function makeResolver(scope, projectRoot, tool) {
15641
15707
  return (slotMap, slot) => {
15642
15708
  if (scope === "global") {
@@ -20144,6 +20210,10 @@ function removeManagedPath(managedPath) {
20144
20210
  // src/commands/uninstall.ts
20145
20211
  var codexManagedMcpStart = "# tangyr: managed mcp start";
20146
20212
  var codexManagedMcpEnd = "# tangyr: managed mcp end";
20213
+ var SHARED_CLAUDE_SLOTS = /* @__PURE__ */ new Set([
20214
+ "mcp_shared_file",
20215
+ "hooks"
20216
+ ]);
20147
20217
  async function runUninstallCommand(options, logger) {
20148
20218
  const scope = options.scope === "global" ? "global" : "project";
20149
20219
  const scopePath = resolveScopePath(
@@ -20200,19 +20270,38 @@ async function runUninstallCommand(options, logger) {
20200
20270
  return;
20201
20271
  }
20202
20272
  }
20273
+ const claudeRoots = scope === "global" && selectedTools.has("claude-code") ? resolveClaudeRootsForUninstall(manifest, runtime) : [];
20203
20274
  let removedCount = 0;
20204
20275
  for (const file of manifestArtifacts) {
20205
- const actualPath = resolveManifestKeyToPath(
20206
- file.relativePath,
20207
- scope,
20208
- runtime
20209
- );
20210
- if (actualPath && (fs18.existsSync(actualPath) || isSymlink(actualPath))) {
20211
- removeManagedPath(actualPath);
20212
- logger.verbose(` removed: ${actualPath}`);
20213
- removedCount++;
20276
+ const isClaudeGlobal = file.relativePath.startsWith("claude-code/") && scope === "global";
20277
+ if (isClaudeGlobal && claudeRoots.length > 0) {
20278
+ const paths = resolveManifestKeyToMultiPaths(
20279
+ file.relativePath,
20280
+ claudeRoots,
20281
+ runtime
20282
+ );
20283
+ for (const actualPath of paths) {
20284
+ if (actualPath && (fs18.existsSync(actualPath) || isSymlink(actualPath))) {
20285
+ removeManagedPath(actualPath);
20286
+ logger.verbose(` removed: ${actualPath}`);
20287
+ removedCount++;
20288
+ } else {
20289
+ logger.verbose(` skipped (not found): ${actualPath}`);
20290
+ }
20291
+ }
20214
20292
  } else {
20215
- logger.verbose(` skipped (not found): ${file.relativePath}`);
20293
+ const actualPath = resolveManifestKeyToPath(
20294
+ file.relativePath,
20295
+ scope,
20296
+ runtime
20297
+ );
20298
+ if (actualPath && (fs18.existsSync(actualPath) || isSymlink(actualPath))) {
20299
+ removeManagedPath(actualPath);
20300
+ logger.verbose(` removed: ${actualPath}`);
20301
+ removedCount++;
20302
+ } else {
20303
+ logger.verbose(` skipped (not found): ${file.relativePath}`);
20304
+ }
20216
20305
  }
20217
20306
  }
20218
20307
  if (runtime) {
@@ -20442,6 +20531,102 @@ function resolveClaudeCodePaths(runtime, scope, projectRoot) {
20442
20531
  output_styles: path17.join(root, ".claude", "output-styles")
20443
20532
  };
20444
20533
  }
20534
+ function resolveClaudeRootsForUninstall(manifest, runtime) {
20535
+ if (Array.isArray(manifest.claudeCodeGlobalRoots) && manifest.claudeCodeGlobalRoots.length > 0) {
20536
+ return manifest.claudeCodeGlobalRoots;
20537
+ }
20538
+ if (runtime) {
20539
+ try {
20540
+ const mappings = loadMappings(
20541
+ runtime.sourceRoot,
20542
+ runtime.config,
20543
+ runtime.configDir
20544
+ );
20545
+ const profile = resolveTargetPaths({
20546
+ scope: "global",
20547
+ projectRoot: void 0,
20548
+ mappings: mappings.platformPaths
20549
+ })["claude-code"];
20550
+ return [path17.dirname(profile.agents)];
20551
+ } catch {
20552
+ }
20553
+ }
20554
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
20555
+ return [path17.resolve(expandHome(`${home}/.claude`))];
20556
+ }
20557
+ function resolveManifestKeyToMultiPaths(key, claudeRoots, runtime) {
20558
+ if (claudeRoots.length === 0) {
20559
+ return [];
20560
+ }
20561
+ const afterPrefix = key.slice("claude-code/".length);
20562
+ const slotName = afterPrefix.split("/")[0];
20563
+ const subPath = afterPrefix.slice(slotName.length + 1) || void 0;
20564
+ const isShared = SHARED_CLAUDE_SLOTS.has(slotName);
20565
+ const rootsToResolve = isShared ? [claudeRoots[0]] : claudeRoots;
20566
+ if (!runtime) {
20567
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
20568
+ const defaultRoot = path17.resolve(expandHome(`${home}/.claude`));
20569
+ return rootsToResolve.flatMap((root) => {
20570
+ const hardcoded = resolveHardcodedClaudeSlot(root, defaultRoot, slotName);
20571
+ if (!hardcoded) {
20572
+ return [];
20573
+ }
20574
+ return [subPath ? path17.join(hardcoded, subPath) : hardcoded];
20575
+ });
20576
+ }
20577
+ try {
20578
+ const mappings = loadMappings(
20579
+ runtime.sourceRoot,
20580
+ runtime.config,
20581
+ runtime.configDir
20582
+ );
20583
+ return rootsToResolve.flatMap((root) => {
20584
+ const profile = resolveClaudeCodeProfileForRoot(
20585
+ root,
20586
+ mappings.platformPaths
20587
+ );
20588
+ const effectiveSlot = slotName === "CLAUDE.md" ? "instructions" : slotName;
20589
+ const slotPath = profile[effectiveSlot] ?? null;
20590
+ if (!slotPath) {
20591
+ return [];
20592
+ }
20593
+ return [subPath ? path17.join(slotPath, subPath) : slotPath];
20594
+ });
20595
+ } catch {
20596
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
20597
+ const defaultRoot = path17.resolve(expandHome(`${home}/.claude`));
20598
+ return rootsToResolve.flatMap((root) => {
20599
+ const hardcoded = resolveHardcodedClaudeSlot(root, defaultRoot, slotName);
20600
+ if (!hardcoded) {
20601
+ return [];
20602
+ }
20603
+ return [subPath ? path17.join(hardcoded, subPath) : hardcoded];
20604
+ });
20605
+ }
20606
+ }
20607
+ function resolveHardcodedClaudeSlot(root, defaultRoot, slotName) {
20608
+ const slotRelatives = {
20609
+ instructions: "CLAUDE.md",
20610
+ CLAUDE: "CLAUDE.md",
20611
+ "CLAUDE.md": "CLAUDE.md",
20612
+ instructions_shim: "CLAUDE.md",
20613
+ agents: "agents",
20614
+ skills: "skills",
20615
+ commands: "commands",
20616
+ rules: "rules",
20617
+ output_styles: "output-styles",
20618
+ hooks_shared_file: "settings.json",
20619
+ settings_shared_file: "settings.json"
20620
+ };
20621
+ const relative = slotRelatives[slotName];
20622
+ if (!relative) {
20623
+ return null;
20624
+ }
20625
+ if (root === defaultRoot) {
20626
+ return path17.join(root, relative);
20627
+ }
20628
+ return path17.join(root, relative);
20629
+ }
20445
20630
  function resolveRuntimeIfAvailable(options, logger) {
20446
20631
  try {
20447
20632
  return resolveRuntime(options, import.meta.url);
@@ -26307,17 +26492,54 @@ function installForTool(tool, config, kitInfo, manifest, options, mappings, logg
26307
26492
  if (tool !== "claude-code") {
26308
26493
  throwUnrecognizedTarget(tool);
26309
26494
  }
26310
- const placements = getToolPlacements(
26311
- tool,
26312
- kitInfo,
26313
- scope,
26314
- projectRoot,
26315
- mappings
26316
- );
26317
- let count = 0;
26318
26495
  manifest.artifacts = manifest.artifacts.filter(
26319
26496
  (file) => !file.relativePath.startsWith(`${tool}/`)
26320
26497
  );
26498
+ const claudeRoots = scope === "global" && mappings ? resolveClaudeGlobalRoots({
26499
+ flag: options.claudeConfigDir,
26500
+ config: config.claudeCodeGlobalPaths
26501
+ }) : void 0;
26502
+ let count = 0;
26503
+ if (claudeRoots && claudeRoots.length > 0 && mappings) {
26504
+ for (const claudeRoot of claudeRoots) {
26505
+ if (claudeRoots.length > 1) {
26506
+ logger.info(` claude-code: installing to root ${claudeRoot}`);
26507
+ }
26508
+ const rootProfile = resolveClaudeCodeProfileForRoot(
26509
+ claudeRoot,
26510
+ mappings.platformPaths
26511
+ );
26512
+ const placements = getClaudeCodePlacementsFromProfile(
26513
+ kitInfo,
26514
+ rootProfile
26515
+ );
26516
+ count += applyClaudeCodePlacements(
26517
+ placements,
26518
+ manifest,
26519
+ skippedTargetPaths,
26520
+ logger
26521
+ );
26522
+ }
26523
+ manifest.claudeCodeGlobalRoots = claudeRoots;
26524
+ } else {
26525
+ const placements = getToolPlacements(
26526
+ tool,
26527
+ kitInfo,
26528
+ scope,
26529
+ projectRoot,
26530
+ mappings
26531
+ );
26532
+ count += applyClaudeCodePlacements(
26533
+ placements,
26534
+ manifest,
26535
+ skippedTargetPaths,
26536
+ logger
26537
+ );
26538
+ }
26539
+ return count;
26540
+ }
26541
+ function applyClaudeCodePlacements(placements, manifest, skippedTargetPaths, logger) {
26542
+ let count = 0;
26321
26543
  for (const placement of placements) {
26322
26544
  if (skippedTargetPaths.has(placement.destination)) {
26323
26545
  logger.verbose(` skipped (operator choice): ${placement.destination}`);
@@ -26785,6 +27007,38 @@ function getClaudeCodePlacements(kitInfo, scope = "global", projectRoot, mapping
26785
27007
  }
26786
27008
  return placements;
26787
27009
  }
27010
+ function getClaudeCodePlacementsFromProfile(kitInfo, profile) {
27011
+ const placements = [];
27012
+ const claudeMd = path31.join(kitInfo.path, "CLAUDE.md");
27013
+ if (fs30.existsSync(claudeMd)) {
27014
+ placements.push({
27015
+ source: claudeMd,
27016
+ destination: profile.instructions,
27017
+ manifestKey: "claude-code/CLAUDE.md",
27018
+ type: "symlink",
27019
+ isDir: false
27020
+ });
27021
+ }
27022
+ const componentDirMap = {
27023
+ agents: profile.agents,
27024
+ skills: profile.skills,
27025
+ commands: profile.commands,
27026
+ rules: profile.rules
27027
+ };
27028
+ for (const [component, targetPath] of Object.entries(componentDirMap)) {
27029
+ const componentDir = path31.join(kitInfo.path, component);
27030
+ if (fs30.existsSync(componentDir) && fs30.statSync(componentDir).isDirectory()) {
27031
+ placements.push({
27032
+ source: componentDir,
27033
+ destination: targetPath,
27034
+ manifestKey: `claude-code/${component}`,
27035
+ type: "symlink",
27036
+ isDir: true
27037
+ });
27038
+ }
27039
+ }
27040
+ return placements;
27041
+ }
26788
27042
  function collectAllFiles(kitInfo) {
26789
27043
  const files = /* @__PURE__ */ new Map();
26790
27044
  const claudeMd = path31.join(kitInfo.path, "CLAUDE.md");
@@ -27017,18 +27271,13 @@ function runStatusCommand(options, logger) {
27017
27271
  if (target !== "claude-code" && target !== "copilot" && target !== "codex") {
27018
27272
  throwUnrecognizedTarget(target);
27019
27273
  }
27020
- const rows = target === "claude-code" ? [
27021
- ...claudeDirectArtifacts(
27022
- sourceRoot,
27023
- config,
27024
- configDir,
27025
- scope,
27026
- projectRoot
27027
- ).map(
27028
- (artifact) => classifyDirectArtifact(artifact, config, sourceRoot)
27029
- ),
27030
- ...classifyClaudeSharedFiles(config, sourceRoot)
27031
- ] : target === "copilot" ? classifyCopilotFiles(config, sourceRoot, configDir) : classifyCodexFiles(config, sourceRoot, configDir);
27274
+ const rows = target === "claude-code" ? classifyClaudeCodeRows(
27275
+ sourceRoot,
27276
+ config,
27277
+ configDir,
27278
+ scope,
27279
+ projectRoot
27280
+ ) : target === "copilot" ? classifyCopilotFiles(config, sourceRoot, configDir) : classifyCodexFiles(config, sourceRoot, configDir);
27032
27281
  for (const row of rows) {
27033
27282
  logger.info(`${row.component} ${row.className} ${row.path}`);
27034
27283
  if (!row.conformant) {
@@ -27297,6 +27546,64 @@ function classifyDirectArtifact(artifact, config, sourceRoot) {
27297
27546
  conformant: artifactClass === "managed-symlink" || artifactClass === "managed-compiled"
27298
27547
  };
27299
27548
  }
27549
+ function classifyClaudeCodeRows(sourceRoot, config, configDir, scope, projectRoot) {
27550
+ const extraRoots = [];
27551
+ if (scope === "global") {
27552
+ try {
27553
+ const scopePath = resolveScopePath(scope, projectRoot);
27554
+ const manifest = readManifest(scopePath);
27555
+ if (manifest && Array.isArray(manifest.claudeCodeGlobalRoots) && manifest.claudeCodeGlobalRoots.length > 1) {
27556
+ extraRoots.push(...manifest.claudeCodeGlobalRoots.slice(1));
27557
+ }
27558
+ } catch {
27559
+ }
27560
+ }
27561
+ const defaultArtifactRows = claudeDirectArtifacts(
27562
+ sourceRoot,
27563
+ config,
27564
+ configDir,
27565
+ scope,
27566
+ projectRoot
27567
+ ).map((artifact) => classifyDirectArtifact(artifact, config, sourceRoot));
27568
+ const extraRootRows = [];
27569
+ if (extraRoots.length > 0) {
27570
+ try {
27571
+ const mappings = loadMappings(sourceRoot, config, configDir);
27572
+ for (const root of extraRoots) {
27573
+ const profile = resolveClaudeCodeProfileForRoot(
27574
+ root,
27575
+ mappings.platformPaths
27576
+ );
27577
+ for (const [component, targetPath] of Object.entries(profile)) {
27578
+ if (component.endsWith("_shared_file") || component === "hooks") {
27579
+ continue;
27580
+ }
27581
+ const sourceKey = claudeDirectComponentSourceKeys[component];
27582
+ if (!sourceKey || !targetPath) {
27583
+ continue;
27584
+ }
27585
+ extraRootRows.push(
27586
+ classifyDirectArtifact(
27587
+ {
27588
+ component: `${component.replace(/_/gu, "-")} [${root}]`,
27589
+ path: targetPath,
27590
+ sourceKey
27591
+ },
27592
+ config,
27593
+ sourceRoot
27594
+ )
27595
+ );
27596
+ }
27597
+ }
27598
+ } catch {
27599
+ }
27600
+ }
27601
+ return [
27602
+ ...defaultArtifactRows,
27603
+ ...extraRootRows,
27604
+ ...classifyClaudeSharedFiles(config, sourceRoot)
27605
+ ];
27606
+ }
27300
27607
  function classifyClaudeSharedFiles(config, sourceRoot) {
27301
27608
  const settingsPath = resolveTargetPath("~/.claude/settings.json");
27302
27609
  const claudeJsonPath = resolveTargetPath("~/.claude.json");
@@ -28137,7 +28444,9 @@ function syncClaudeCode(config, sourceRoot, options, logger, mappings, manifest,
28137
28444
  const scope = options.scope ?? "global";
28138
28445
  const projectRoot = options.projectRoot;
28139
28446
  let profile;
28140
- if (mappings) {
28447
+ if (options.claudeRoot && mappings) {
28448
+ profile = resolveClaudeCodeProfileForRoot(options.claudeRoot, mappings);
28449
+ } else if (mappings) {
28141
28450
  profile = resolveTargetPaths({
28142
28451
  scope,
28143
28452
  projectRoot,
@@ -28560,19 +28869,42 @@ function runSyncCommand(options, logger) {
28560
28869
  const allMappings = mappings ?? (targets.includes("claude-code") ? loadMappings(sourceRoot, config, configDir) : void 0);
28561
28870
  for (const target of targets) {
28562
28871
  if (target === "claude-code") {
28563
- results.push(
28564
- syncClaudeCode(
28565
- config,
28566
- sourceRoot,
28567
- syncOptions,
28568
- logger,
28569
- allMappings?.platformPaths,
28570
- void 0,
28571
- void 0,
28572
- allMappings,
28573
- lossReport
28574
- )
28575
- );
28872
+ const claudeRoots = syncOptions.scope === "global" && allMappings ? resolveClaudeGlobalRoots({
28873
+ flag: options.claudeConfigDir,
28874
+ config: config.claudeCodeGlobalPaths
28875
+ }) : void 0;
28876
+ if (claudeRoots && claudeRoots.length > 1) {
28877
+ for (const claudeRoot of claudeRoots) {
28878
+ logger.info(` claude-code: syncing root ${claudeRoot}`);
28879
+ results.push(
28880
+ syncClaudeCode(
28881
+ config,
28882
+ sourceRoot,
28883
+ { ...syncOptions, claudeRoot },
28884
+ logger,
28885
+ allMappings?.platformPaths,
28886
+ void 0,
28887
+ void 0,
28888
+ allMappings,
28889
+ lossReport
28890
+ )
28891
+ );
28892
+ }
28893
+ } else {
28894
+ results.push(
28895
+ syncClaudeCode(
28896
+ config,
28897
+ sourceRoot,
28898
+ syncOptions,
28899
+ logger,
28900
+ allMappings?.platformPaths,
28901
+ void 0,
28902
+ void 0,
28903
+ allMappings,
28904
+ lossReport
28905
+ )
28906
+ );
28907
+ }
28576
28908
  } else if (target === "copilot") {
28577
28909
  if (!mappings) {
28578
28910
  throw new Error("Copilot mappings were not loaded");
@@ -29444,8 +29776,13 @@ function checkHumanOnlyCommandInvocation(kit) {
29444
29776
  }
29445
29777
 
29446
29778
  // src/commands/verify.ts
29779
+ import fs37 from "fs";
29780
+ import path39 from "path";
29447
29781
  function runVerifyCommand(options, logger) {
29448
- const { config, kitInfo } = resolveRuntime(options, import.meta.url);
29782
+ const { config, configDir, sourceRoot, kitInfo } = resolveRuntime(
29783
+ options,
29784
+ import.meta.url
29785
+ );
29449
29786
  const { scope, projectRoot } = resolveScopeContext(
29450
29787
  options.scope,
29451
29788
  config.scope,
@@ -29476,6 +29813,27 @@ function runVerifyCommand(options, logger) {
29476
29813
  );
29477
29814
  inconsistent = true;
29478
29815
  }
29816
+ if (scope === "global" && Array.isArray(manifest.claudeCodeGlobalRoots) && manifest.claudeCodeGlobalRoots.length > 1) {
29817
+ try {
29818
+ const mappings = loadMappings(sourceRoot, config, configDir);
29819
+ const manifestKeys = manifest.artifacts.map((a) => a.relativePath);
29820
+ const rootFailures = verifyClaudeCodeGlobalRoots(
29821
+ manifest.claudeCodeGlobalRoots,
29822
+ manifestKeys,
29823
+ mappings.platformPaths
29824
+ );
29825
+ if (rootFailures.length > 0) {
29826
+ for (const failure of rootFailures) {
29827
+ logger.warn(failure);
29828
+ }
29829
+ inconsistent = true;
29830
+ }
29831
+ } catch (err) {
29832
+ logger.warn(
29833
+ `Multi-root verify skipped (mappings unavailable): ${err instanceof Error ? err.message : String(err)}`
29834
+ );
29835
+ }
29836
+ }
29479
29837
  if (inconsistent) {
29480
29838
  logger.error("Verification FAILED. Run 'tangyr sync' to update.");
29481
29839
  process.exit(1);
@@ -29487,6 +29845,39 @@ function runVerifyCommand(options, logger) {
29487
29845
  }
29488
29846
  runStatusCommand(options, logger);
29489
29847
  }
29848
+ function verifyClaudeCodeGlobalRoots(roots, manifestKeys, mappings) {
29849
+ const failures = [];
29850
+ const perRootSlots = [];
29851
+ for (const key of manifestKeys) {
29852
+ if (!key.startsWith("claude-code/")) {
29853
+ continue;
29854
+ }
29855
+ const afterPrefix = key.slice("claude-code/".length);
29856
+ const slotName = afterPrefix.split("/")[0];
29857
+ const subPath = afterPrefix.slice(slotName.length + 1) || void 0;
29858
+ if (SHARED_CLAUDE_SLOTS.has(slotName)) {
29859
+ continue;
29860
+ }
29861
+ const effectiveSlot = slotName === "CLAUDE.md" ? "instructions" : slotName;
29862
+ perRootSlots.push({ slot: effectiveSlot, subPath });
29863
+ }
29864
+ for (const root of roots) {
29865
+ const profile = resolveClaudeCodeProfileForRoot(root, mappings);
29866
+ for (const { slot, subPath } of perRootSlots) {
29867
+ const slotPath = profile[slot] ?? null;
29868
+ if (!slotPath) {
29869
+ continue;
29870
+ }
29871
+ const fullPath = subPath ? path39.join(slotPath, subPath) : slotPath;
29872
+ if (!fs37.existsSync(fullPath)) {
29873
+ failures.push(
29874
+ `claude-code/${slot}${subPath ? `/${subPath}` : ""} missing in root ${root}: ${fullPath}`
29875
+ );
29876
+ }
29877
+ }
29878
+ }
29879
+ return failures;
29880
+ }
29490
29881
 
29491
29882
  // src/utils/logger.ts
29492
29883
  var Logger = class {
@@ -29576,7 +29967,12 @@ function buildProgram() {
29576
29967
  ).option(
29577
29968
  "-y, --yes",
29578
29969
  "Skip interactive confirmation and apply non-interactive conflict handling"
29579
- ).option("-v, --verbose", "Emit detailed output").option("-q, --quiet", "Emit errors only").option("--no-color", "Disable colorized output");
29970
+ ).option("-v, --verbose", "Emit detailed output").option("-q, --quiet", "Emit errors only").option("--no-color", "Disable colorized output").option(
29971
+ "--claude-config-dir <path>",
29972
+ "Claude Code global root directory (repeatable; overrides claudeCodeGlobalPaths in config)",
29973
+ (value, previous) => [...previous, value],
29974
+ []
29975
+ );
29580
29976
  program2.command("assess").description("Pre-installation conflict analysis").option("--tools <list>", "Comma-separated list of target tools to assess").option("--scope <scope>", "Installation scope: global or project").option("--json", "Emit JSON output").action((options) => {
29581
29977
  const context = commandContext(program2);
29582
29978
  const scope = options.scope === "global" || options.scope === "project" ? options.scope : void 0;
@@ -29585,7 +29981,10 @@ function buildProgram() {
29585
29981
  context.logger
29586
29982
  );
29587
29983
  });
29588
- program2.command("install").description("Install the Tangyr operating kit into the target scope").option("--kit <name>", "Name of the operating kit to install").option("--tools <list>", "Comma-separated list of target tools").option("--scope <scope>", "Installation scope: global or project").option("--ignore-errors", "Suppress exit code 3 when error-severity loss entries are present").action(
29984
+ program2.command("install").description("Install the Tangyr operating kit into the target scope").option("--kit <name>", "Name of the operating kit to install").option("--tools <list>", "Comma-separated list of target tools").option("--scope <scope>", "Installation scope: global or project").option(
29985
+ "--ignore-errors",
29986
+ "Suppress exit code 3 when error-severity loss entries are present"
29987
+ ).action(
29589
29988
  async (options) => {
29590
29989
  const context = commandContext(program2);
29591
29990
  await runInstallCommand(
@@ -29622,7 +30021,10 @@ function buildProgram() {
29622
30021
  context.logger
29623
30022
  );
29624
30023
  });
29625
- program2.command("sync").description("Synchronize configured targets with the operating kit").option("--target <tool>", "Limit sync to a single target").option("--component <name>", "Limit sync to a single component type").option("--force", "Rewrite managed artifacts even when provenance matches").option("--loss-report <path>", "Write semantic loss report JSON to a file").option("--scope <scope>", "Installation scope: global or project").option("--ignore-errors", "Suppress exit code 3 when error-severity loss entries are present").action(
30024
+ program2.command("sync").description("Synchronize configured targets with the operating kit").option("--target <tool>", "Limit sync to a single target").option("--component <name>", "Limit sync to a single component type").option("--force", "Rewrite managed artifacts even when provenance matches").option("--loss-report <path>", "Write semantic loss report JSON to a file").option("--scope <scope>", "Installation scope: global or project").option(
30025
+ "--ignore-errors",
30026
+ "Suppress exit code 3 when error-severity loss entries are present"
30027
+ ).action(
29626
30028
  (options) => {
29627
30029
  const context = commandContext(program2);
29628
30030
  const scope = options.scope === "global" || options.scope === "project" ? options.scope : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alessandroraffa/tangyr",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "CLI for the Tangyr discipline — install and manage operating kits for AI coding tools",
5
5
  "license": "MIT",
6
6
  "type": "module",