@alessandroraffa/tangyr 0.18.0 → 0.20.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/dist/index.js CHANGED
@@ -1373,7 +1373,7 @@ var require_command = __commonJS({
1373
1373
  var EventEmitter = __require("events").EventEmitter;
1374
1374
  var childProcess = __require("child_process");
1375
1375
  var path45 = __require("path");
1376
- var fs44 = __require("fs");
1376
+ var fs45 = __require("fs");
1377
1377
  var process5 = __require("process");
1378
1378
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1379
1379
  var { CommanderError: CommanderError2 } = require_error();
@@ -2367,7 +2367,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2367
2367
  * @param {string} subcommandName
2368
2368
  */
2369
2369
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2370
- if (fs44.existsSync(executableFile)) return;
2370
+ if (fs45.existsSync(executableFile)) return;
2371
2371
  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";
2372
2372
  const executableMissing = `'${executableFile}' does not exist
2373
2373
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2386,10 +2386,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2386
2386
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2387
2387
  function findFile(baseDir, baseName) {
2388
2388
  const localBin = path45.resolve(baseDir, baseName);
2389
- if (fs44.existsSync(localBin)) return localBin;
2389
+ if (fs45.existsSync(localBin)) return localBin;
2390
2390
  if (sourceExt.includes(path45.extname(baseName))) return void 0;
2391
2391
  const foundExt = sourceExt.find(
2392
- (ext) => fs44.existsSync(`${localBin}${ext}`)
2392
+ (ext) => fs45.existsSync(`${localBin}${ext}`)
2393
2393
  );
2394
2394
  if (foundExt) return `${localBin}${foundExt}`;
2395
2395
  return void 0;
@@ -2401,7 +2401,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2401
2401
  if (this._scriptPath) {
2402
2402
  let resolvedScriptPath;
2403
2403
  try {
2404
- resolvedScriptPath = fs44.realpathSync(this._scriptPath);
2404
+ resolvedScriptPath = fs45.realpathSync(this._scriptPath);
2405
2405
  } catch {
2406
2406
  resolvedScriptPath = this._scriptPath;
2407
2407
  }
@@ -10489,14 +10489,14 @@ var require_parser = __commonJS({
10489
10489
  case "scalar":
10490
10490
  case "single-quoted-scalar":
10491
10491
  case "double-quoted-scalar": {
10492
- const fs44 = this.flowScalar(this.type);
10492
+ const fs45 = this.flowScalar(this.type);
10493
10493
  if (atNextItem || it2.value) {
10494
- map.items.push({ start, key: fs44, sep: [] });
10494
+ map.items.push({ start, key: fs45, sep: [] });
10495
10495
  this.onKeyLine = true;
10496
10496
  } else if (it2.sep) {
10497
- this.stack.push(fs44);
10497
+ this.stack.push(fs45);
10498
10498
  } else {
10499
- Object.assign(it2, { key: fs44, sep: [] });
10499
+ Object.assign(it2, { key: fs45, sep: [] });
10500
10500
  this.onKeyLine = true;
10501
10501
  }
10502
10502
  return;
@@ -10624,13 +10624,13 @@ var require_parser = __commonJS({
10624
10624
  case "scalar":
10625
10625
  case "single-quoted-scalar":
10626
10626
  case "double-quoted-scalar": {
10627
- const fs44 = this.flowScalar(this.type);
10627
+ const fs45 = this.flowScalar(this.type);
10628
10628
  if (!it2 || it2.value)
10629
- fc.items.push({ start: [], key: fs44, sep: [] });
10629
+ fc.items.push({ start: [], key: fs45, sep: [] });
10630
10630
  else if (it2.sep)
10631
- this.stack.push(fs44);
10631
+ this.stack.push(fs45);
10632
10632
  else
10633
- Object.assign(it2, { key: fs44, sep: [] });
10633
+ Object.assign(it2, { key: fs45, sep: [] });
10634
10634
  return;
10635
10635
  }
10636
10636
  case "flow-map-end":
@@ -12551,22 +12551,22 @@ var require_parse_async = __commonJS({
12551
12551
  const index = 0;
12552
12552
  const blocksize = opts.blocksize || 40960;
12553
12553
  const parser = new TOMLParser();
12554
- return new Promise((resolve, reject) => {
12555
- setImmediate(parseAsyncNext, index, blocksize, resolve, reject);
12554
+ return new Promise((resolve, reject2) => {
12555
+ setImmediate(parseAsyncNext, index, blocksize, resolve, reject2);
12556
12556
  });
12557
- function parseAsyncNext(index2, blocksize2, resolve, reject) {
12557
+ function parseAsyncNext(index2, blocksize2, resolve, reject2) {
12558
12558
  if (index2 >= str.length) {
12559
12559
  try {
12560
12560
  return resolve(parser.finish());
12561
12561
  } catch (err) {
12562
- return reject(prettyError(err, str));
12562
+ return reject2(prettyError(err, str));
12563
12563
  }
12564
12564
  }
12565
12565
  try {
12566
12566
  parser.parse(str.slice(index2, index2 + blocksize2));
12567
- setImmediate(parseAsyncNext, index2 + blocksize2, blocksize2, resolve, reject);
12567
+ setImmediate(parseAsyncNext, index2 + blocksize2, blocksize2, resolve, reject2);
12568
12568
  } catch (err) {
12569
- reject(prettyError(err, str));
12569
+ reject2(prettyError(err, str));
12570
12570
  }
12571
12571
  }
12572
12572
  }
@@ -12590,7 +12590,7 @@ var require_parse_stream = __commonJS({
12590
12590
  function parseReadable(stm) {
12591
12591
  const parser = new TOMLParser();
12592
12592
  stm.setEncoding("utf8");
12593
- return new Promise((resolve, reject) => {
12593
+ return new Promise((resolve, reject2) => {
12594
12594
  let readable;
12595
12595
  let ended = false;
12596
12596
  let errored = false;
@@ -12600,12 +12600,12 @@ var require_parse_stream = __commonJS({
12600
12600
  try {
12601
12601
  resolve(parser.finish());
12602
12602
  } catch (err) {
12603
- reject(err);
12603
+ reject2(err);
12604
12604
  }
12605
12605
  }
12606
12606
  function error(err) {
12607
12607
  errored = true;
12608
- reject(err);
12608
+ reject2(err);
12609
12609
  }
12610
12610
  stm.once("end", finish);
12611
12611
  stm.once("error", error);
@@ -14307,12 +14307,12 @@ var PromisePolyfill = class extends Promise {
14307
14307
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
14308
14308
  static withResolver() {
14309
14309
  let resolve;
14310
- let reject;
14310
+ let reject2;
14311
14311
  const promise = new Promise((res, rej) => {
14312
14312
  resolve = res;
14313
- reject = rej;
14313
+ reject2 = rej;
14314
14314
  });
14315
- return { promise, resolve, reject };
14315
+ return { promise, resolve, reject: reject2 };
14316
14316
  }
14317
14317
  };
14318
14318
 
@@ -14349,10 +14349,10 @@ function createPrompt(view) {
14349
14349
  output
14350
14350
  });
14351
14351
  const screen = new ScreenManager(rl);
14352
- const { promise, resolve, reject } = PromisePolyfill.withResolver();
14353
- const cancel = () => reject(new CancelPromptError());
14352
+ const { promise, resolve, reject: reject2 } = PromisePolyfill.withResolver();
14353
+ const cancel = () => reject2(new CancelPromptError());
14354
14354
  if (signal) {
14355
- const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
14355
+ const abort = () => reject2(new AbortPromptError({ cause: signal.reason }));
14356
14356
  if (signal.aborted) {
14357
14357
  abort();
14358
14358
  return Object.assign(promise, { cancel });
@@ -14361,9 +14361,9 @@ function createPrompt(view) {
14361
14361
  cleanups.add(() => signal.removeEventListener("abort", abort));
14362
14362
  }
14363
14363
  cleanups.add(onExit((code, signal2) => {
14364
- reject(new ExitPromptError(`User force closed the prompt with ${code} ${signal2}`));
14364
+ reject2(new ExitPromptError(`User force closed the prompt with ${code} ${signal2}`));
14365
14365
  }));
14366
- const sigint = () => reject(new ExitPromptError(`User force closed the prompt with SIGINT`));
14366
+ const sigint = () => reject2(new ExitPromptError(`User force closed the prompt with SIGINT`));
14367
14367
  rl.on("SIGINT", sigint);
14368
14368
  cleanups.add(() => rl.removeListener("SIGINT", sigint));
14369
14369
  return withHooks(rl, (cycle) => {
@@ -14397,7 +14397,7 @@ function createPrompt(view) {
14397
14397
  screen.render(content, bottomContent);
14398
14398
  effectScheduler.run();
14399
14399
  } catch (error) {
14400
- reject(error);
14400
+ reject2(error);
14401
14401
  }
14402
14402
  effectsSettled = true;
14403
14403
  if (pendingDone !== null) {
@@ -15667,7 +15667,11 @@ function vscodeUserDataDir() {
15667
15667
  return expandHome(process.env.VSCODE_USER_DATA_DIR);
15668
15668
  }
15669
15669
  if (process.env.VSCODE_PORTABLE) {
15670
- return path7.join(expandHome(process.env.VSCODE_PORTABLE), "user-data", "User");
15670
+ return path7.join(
15671
+ expandHome(process.env.VSCODE_PORTABLE),
15672
+ "user-data",
15673
+ "User"
15674
+ );
15671
15675
  }
15672
15676
  const homeDir = canonicalHomeDir();
15673
15677
  if (process.platform === "darwin") {
@@ -16154,6 +16158,25 @@ var KIT_COMPONENTS = [
16154
16158
  "rules",
16155
16159
  "hooks"
16156
16160
  ];
16161
+ var DEFAULT_ENTRYPOINT = "ORIENTATION.md";
16162
+ var LEGACY_ENTRYPOINT = "CLAUDE.md";
16163
+ function resolveEntrypoint(kit) {
16164
+ const declared = kit.entrypoint;
16165
+ if (declared !== void 0) {
16166
+ const file = path9.join(kit.path, declared);
16167
+ return fs8.existsSync(file) ? { file, name: declared, legacy: false } : null;
16168
+ }
16169
+ const canonical = path9.join(kit.path, DEFAULT_ENTRYPOINT);
16170
+ if (fs8.existsSync(canonical)) {
16171
+ return { file: canonical, name: DEFAULT_ENTRYPOINT, legacy: false };
16172
+ }
16173
+ const legacy = path9.join(kit.path, LEGACY_ENTRYPOINT);
16174
+ if (fs8.existsSync(legacy)) {
16175
+ return { file: legacy, name: LEGACY_ENTRYPOINT, legacy: true };
16176
+ }
16177
+ return null;
16178
+ }
16179
+ var LEGACY_ENTRYPOINT_NOTICE = `This kit has no '${DEFAULT_ENTRYPOINT}' and no 'entrypoint' in tangyr-kit.yaml, so '${LEGACY_ENTRYPOINT}' was used. '${DEFAULT_ENTRYPOINT}' is the contract's canonical orientation document; '${LEGACY_ENTRYPOINT}' is one of the names targets translate it to, and a kit that ships only the translation is naming a target rather than itself. To fix: rename '${LEGACY_ENTRYPOINT}' to '${DEFAULT_ENTRYPOINT}', or add "entrypoint: ${LEGACY_ENTRYPOINT}" to tangyr-kit.yaml to keep the name deliberately.`;
16157
16180
  function discoverKits(searchPaths) {
16158
16181
  const kits = [];
16159
16182
  for (const searchPath of searchPaths) {
@@ -16198,6 +16221,7 @@ function loadKit(kitPath) {
16198
16221
  version: manifestResult.version,
16199
16222
  description: manifestResult.description,
16200
16223
  archetype: manifestResult.archetype,
16224
+ ...manifestResult.entrypoint !== void 0 ? { entrypoint: manifestResult.entrypoint } : {},
16201
16225
  discipline: manifestResult.discipline,
16202
16226
  components
16203
16227
  };
@@ -17845,7 +17869,7 @@ async function runCacheClearCommand(options, logger) {
17845
17869
  }
17846
17870
 
17847
17871
  // src/commands/cleanup.ts
17848
- import fs24 from "fs";
17872
+ import fs25 from "fs";
17849
17873
  import path22 from "path";
17850
17874
 
17851
17875
  // node_modules/glob/dist/esm/index.min.js
@@ -20992,7 +21016,7 @@ function removeManagedEntries(filePath, parser, serializer, removeManaged, logge
20992
21016
  }
20993
21017
 
20994
21018
  // src/commands/uninstall.ts
20995
- import fs23 from "fs";
21019
+ import fs24 from "fs";
20996
21020
  import path21 from "path";
20997
21021
 
20998
21022
  // src/core/managed-sources.ts
@@ -21043,10 +21067,11 @@ function hooksSourceExists(sourceRoot, config) {
21043
21067
  }
21044
21068
  return fs20.readdirSync(hooksDir).some((f) => f.endsWith(".md") && !f.startsWith("."));
21045
21069
  }
21046
- function hooksSourceLabel(config) {
21047
- return normalizeManagedSourcePath(
21048
- path19.posix.join(config.source.hooks.replace(/\\/gu, "/"), "hooks.json")
21070
+ function hooksSourceLabel(sourceRoot, config) {
21071
+ const dir = normalizeManagedSourcePath(
21072
+ config.source.hooks.replace(/\\/gu, "/")
21049
21073
  );
21074
+ return fs20.existsSync(resolveHooksSourceFile(sourceRoot, config)) ? path19.posix.join(dir, "hooks.json") : `${dir}/`;
21050
21075
  }
21051
21076
  function resolveMcpSourceFile(sourceRoot, config) {
21052
21077
  return path19.resolve(sourceRoot, config.source.mcp);
@@ -21060,44 +21085,148 @@ function normalizeManagedSourcePath(rawPath) {
21060
21085
  }
21061
21086
 
21062
21087
  // src/compiler/hook-components.ts
21063
- var import_yaml6 = __toESM(require_dist(), 1);
21064
- import fs21 from "fs";
21088
+ import fs22 from "fs";
21065
21089
  import path20 from "path";
21090
+
21091
+ // src/compiler/loss-report.ts
21092
+ import fs21 from "fs";
21093
+ function createLossReport() {
21094
+ return {
21095
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
21096
+ targets: {},
21097
+ summary: {},
21098
+ informational: []
21099
+ };
21100
+ }
21101
+ function addInformational(report, target, component, source, severity, detail) {
21102
+ report.informational.push({ target, component, source, severity, detail });
21103
+ }
21104
+ function addLoss(report, target, component, loss) {
21105
+ const targetReport = report.targets[target] ?? {};
21106
+ const componentReport = targetReport[component] ?? [];
21107
+ componentReport.push(loss);
21108
+ targetReport[component] = componentReport;
21109
+ report.targets[target] = targetReport;
21110
+ const targetSummary = report.summary[target] ?? {
21111
+ info: 0,
21112
+ warning: 0,
21113
+ error: 0
21114
+ };
21115
+ for (const field of loss.fields) {
21116
+ targetSummary[field.severity] += 1;
21117
+ }
21118
+ report.summary[target] = targetSummary;
21119
+ }
21120
+ var lossSeverityByEntryType = {
21121
+ "command-as-skill": "info",
21122
+ "rule-as-instruction": "info",
21123
+ "rule-mode-downgrade": "warning",
21124
+ "rule-path-scoping": "warning",
21125
+ "hook-event-drop": "warning",
21126
+ // A declaration the parser refuses installs nothing, anywhere, and used to
21127
+ // do so silently. Error severity is what makes install and sync exit 3.
21128
+ "hook-declaration-invalid": "error",
21129
+ // Both source forms present means one of them is inert and nothing says
21130
+ // which. Error severity, because the CLI cannot tell an author which half
21131
+ // of their kit it is ignoring without them deciding first.
21132
+ "hook-source-ambiguous": "error",
21133
+ // A warning, not an error: the hooks that do reach the target are correct,
21134
+ // and the remedy is a choice about scope rather than a defect in the kit.
21135
+ "hook-script-path-unresolved": "warning",
21136
+ "hook-as-plugin": "error",
21137
+ "instructions-shim": "info",
21138
+ "permission-coarsening": "error",
21139
+ "provider-not-representable": "error",
21140
+ "provider-wire-protocol": "error",
21141
+ "model-not-representable": "error",
21142
+ "persona-not-representable": "error",
21143
+ "settings-not-representable": "warning"
21144
+ };
21145
+ function resolveLossSeverity(kind, name, action, explicitSeverity) {
21146
+ if (explicitSeverity) {
21147
+ return explicitSeverity;
21148
+ }
21149
+ return lossSeverityByEntryType[action];
21150
+ }
21151
+ function writeLossReport(filePath, report) {
21152
+ if (!filePath) {
21153
+ return;
21154
+ }
21155
+ fs21.writeFileSync(filePath, `${JSON.stringify(report, null, 2)}
21156
+ `);
21157
+ }
21158
+
21159
+ // src/compiler/hook-components.ts
21160
+ var import_yaml6 = __toESM(require_dist(), 1);
21161
+ function reject(filePath, field, reason) {
21162
+ return { ok: false, rejection: { file: filePath, field, reason } };
21163
+ }
21066
21164
  function parseHookDeclaration(filePath) {
21067
21165
  let raw;
21068
21166
  try {
21069
- raw = fs21.readFileSync(filePath, "utf8");
21167
+ raw = fs22.readFileSync(filePath, "utf8");
21070
21168
  } catch {
21071
- return null;
21169
+ return reject(filePath, "<file>", "the file could not be read");
21072
21170
  }
21073
21171
  const fm = extractFrontmatter(raw);
21074
21172
  if (!fm) {
21075
- return null;
21173
+ return reject(
21174
+ filePath,
21175
+ "<frontmatter>",
21176
+ "no YAML frontmatter was found; a hook declaration opens with a '---' block carrying name, description and events"
21177
+ );
21076
21178
  }
21077
- const { name, description, events } = fm;
21179
+ const { name, description, events, command } = fm;
21078
21180
  if (typeof name !== "string" || !name.trim()) {
21079
- return null;
21181
+ return reject(filePath, "name", "expected a non-empty string");
21080
21182
  }
21081
21183
  if (typeof description !== "string" || !description.trim()) {
21082
- return null;
21184
+ return reject(filePath, "description", "expected a non-empty string");
21185
+ }
21186
+ if (command !== void 0 && (typeof command !== "string" || !command.trim())) {
21187
+ return reject(
21188
+ filePath,
21189
+ "command",
21190
+ "expected a non-empty string naming the command the harness runs; omit the field entirely to accept the synthesized default"
21191
+ );
21083
21192
  }
21084
21193
  if (!Array.isArray(events) || events.length === 0) {
21085
- return null;
21194
+ return reject(
21195
+ filePath,
21196
+ "events",
21197
+ 'expected a non-empty list of event objects, e.g. "- event: PreToolUse\\n timeout: 10\\n statusMessage: \u2026". A list of plain event names is the most common mistake and is rejected: each entry must be an object'
21198
+ );
21086
21199
  }
21087
21200
  const parsedEvents = [];
21088
- for (const entry of events) {
21201
+ for (const [index, entry] of events.entries()) {
21089
21202
  if (!entry || typeof entry !== "object") {
21090
- return null;
21203
+ return reject(
21204
+ filePath,
21205
+ `events[${index}]`,
21206
+ `expected an object with 'event', 'timeout' and 'statusMessage'; found ${typeof entry === "string" ? `the bare string "${entry}"` : typeof entry}`
21207
+ );
21091
21208
  }
21092
21209
  const ev = entry;
21093
21210
  if (typeof ev.event !== "string" || !ev.event.trim()) {
21094
- return null;
21211
+ return reject(
21212
+ filePath,
21213
+ `events[${index}].event`,
21214
+ "expected a non-empty string naming the harness event"
21215
+ );
21095
21216
  }
21096
21217
  if (typeof ev.timeout !== "number") {
21097
- return null;
21218
+ return reject(
21219
+ filePath,
21220
+ `events[${index}].timeout`,
21221
+ "expected a number, in seconds"
21222
+ );
21098
21223
  }
21099
21224
  if (typeof ev.statusMessage !== "string" || !ev.statusMessage.trim()) {
21100
- return null;
21225
+ return reject(
21226
+ filePath,
21227
+ `events[${index}].statusMessage`,
21228
+ "expected a non-empty string shown while the hook runs"
21229
+ );
21101
21230
  }
21102
21231
  const parsed = {
21103
21232
  event: ev.event,
@@ -21110,15 +21239,23 @@ function parseHookDeclaration(filePath) {
21110
21239
  parsedEvents.push(parsed);
21111
21240
  }
21112
21241
  return {
21113
- name: name.trim(),
21114
- description: description.trim(),
21115
- events: parsedEvents
21242
+ ok: true,
21243
+ declaration: {
21244
+ name: name.trim(),
21245
+ description: description.trim(),
21246
+ ...typeof command === "string" ? { command: command.trim() } : {},
21247
+ events: parsedEvents
21248
+ }
21116
21249
  };
21117
21250
  }
21118
- function hookCommand(name) {
21119
- return `npx tsx ~/.agents/hooks/scripts/${name}.ts`;
21251
+ var GLOBAL_HOOK_SCRIPTS_DIR = "~/.agents/hooks/scripts";
21252
+ function synthesizedHookCommand(name, scriptsDir = GLOBAL_HOOK_SCRIPTS_DIR) {
21253
+ return `npx tsx ${scriptsDir}/${name}.ts`;
21254
+ }
21255
+ function resolveHookCommand(declaration, scriptsDir) {
21256
+ return declaration.command ?? synthesizedHookCommand(declaration.name, scriptsDir);
21120
21257
  }
21121
- function synthesizeHooksJson(declarations) {
21258
+ function synthesizeHooksJson(declarations, scriptsDir) {
21122
21259
  const hooks = {};
21123
21260
  for (const decl of declarations) {
21124
21261
  for (const ev of decl.events) {
@@ -21129,7 +21266,7 @@ function synthesizeHooksJson(declarations) {
21129
21266
  hooks: [
21130
21267
  {
21131
21268
  type: "command",
21132
- command: hookCommand(decl.name),
21269
+ command: resolveHookCommand(decl, scriptsDir),
21133
21270
  timeout: ev.timeout,
21134
21271
  statusMessage: ev.statusMessage
21135
21272
  }
@@ -21148,35 +21285,56 @@ function synthesizeHooksJson(declarations) {
21148
21285
  }
21149
21286
  return { hooks };
21150
21287
  }
21151
- function loadSourceHooks(hooksDir, hookNames) {
21288
+ function loadSourceHooks(hooksDir, hookNames, scriptsDir) {
21152
21289
  const hooksJsonPath = path20.join(hooksDir, "hooks.json");
21153
- if (fs21.existsSync(hooksJsonPath)) {
21154
- const raw = fs21.readFileSync(hooksJsonPath, "utf8");
21290
+ if (fs22.existsSync(hooksJsonPath)) {
21291
+ const raw = fs22.readFileSync(hooksJsonPath, "utf8");
21155
21292
  return JSON.parse(raw);
21156
21293
  }
21157
21294
  const declarations = loadHookDeclarations(hooksDir, hookNames);
21158
- return synthesizeHooksJson(declarations);
21295
+ return synthesizeHooksJson(declarations, scriptsDir);
21159
21296
  }
21160
21297
  function loadHookDeclarations(hooksDir, hookNames) {
21161
21298
  let names;
21162
21299
  if (hookNames && hookNames.length > 0) {
21163
21300
  names = hookNames;
21164
21301
  } else {
21165
- if (!fs21.existsSync(hooksDir) || !fs21.statSync(hooksDir).isDirectory()) {
21302
+ if (!fs22.existsSync(hooksDir) || !fs22.statSync(hooksDir).isDirectory()) {
21166
21303
  return [];
21167
21304
  }
21168
- names = fs21.readdirSync(hooksDir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => f.slice(0, -3)).sort();
21305
+ names = fs22.readdirSync(hooksDir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => f.slice(0, -3)).sort();
21169
21306
  }
21170
21307
  const declarations = [];
21171
21308
  for (const name of names) {
21172
21309
  const filePath = path20.join(hooksDir, `${name}.md`);
21173
- const decl = parseHookDeclaration(filePath);
21174
- if (decl) {
21175
- declarations.push(decl);
21310
+ const result = parseHookDeclaration(filePath);
21311
+ if (result.ok) {
21312
+ declarations.push(result.declaration);
21176
21313
  }
21177
21314
  }
21178
21315
  return declarations;
21179
21316
  }
21317
+ function collectHookRejections(hooksDir, hookNames) {
21318
+ if (fs22.existsSync(path20.join(hooksDir, "hooks.json"))) {
21319
+ return [];
21320
+ }
21321
+ if (!fs22.existsSync(hooksDir) || !fs22.statSync(hooksDir).isDirectory()) {
21322
+ return [];
21323
+ }
21324
+ const names = hookNames && hookNames.length > 0 ? hookNames : fs22.readdirSync(hooksDir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => f.slice(0, -3)).sort();
21325
+ const rejections = [];
21326
+ for (const name of names) {
21327
+ const filePath = path20.join(hooksDir, `${name}.md`);
21328
+ if (!fs22.existsSync(filePath)) {
21329
+ continue;
21330
+ }
21331
+ const result = parseHookDeclaration(filePath);
21332
+ if (!result.ok) {
21333
+ rejections.push(result.rejection);
21334
+ }
21335
+ }
21336
+ return rejections;
21337
+ }
21180
21338
  function extractFrontmatter(content) {
21181
21339
  if (!content.startsWith("---\n")) {
21182
21340
  return null;
@@ -21197,22 +21355,179 @@ function extractFrontmatter(content) {
21197
21355
  }
21198
21356
  return parsed;
21199
21357
  }
21358
+ function reportHookRejections(report, targets, hooksDir, hookNames) {
21359
+ const rejections = collectHookRejections(hooksDir, hookNames);
21360
+ for (const rejection of rejections) {
21361
+ const name = path20.basename(rejection.file, ".md");
21362
+ for (const target of targets) {
21363
+ addLoss(report, target, "hooks", {
21364
+ source: `hooks/${path20.basename(rejection.file)}`,
21365
+ fields: [
21366
+ {
21367
+ field: rejection.field,
21368
+ action: "hook-declaration-invalid",
21369
+ severity: "error",
21370
+ detail: `Hook '${name}' was not installed: its declaration was rejected at '${rejection.field}' \u2014 ${rejection.reason}. To fix: correct that field in this file, then re-run. Until then this hook reaches no target.`
21371
+ }
21372
+ ]
21373
+ });
21374
+ }
21375
+ }
21376
+ return rejections;
21377
+ }
21378
+ function declarationFileNames(hooksDir) {
21379
+ if (!fs22.existsSync(hooksDir) || !fs22.statSync(hooksDir).isDirectory()) {
21380
+ return [];
21381
+ }
21382
+ return fs22.readdirSync(hooksDir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).sort();
21383
+ }
21384
+ function detectHooksSourceForm(hooksDir) {
21385
+ const hasJson = fs22.existsSync(path20.join(hooksDir, "hooks.json"));
21386
+ const hasDeclarations = declarationFileNames(hooksDir).length > 0;
21387
+ if (hasJson && hasDeclarations) return { kind: "both" };
21388
+ if (hasJson) return { kind: "hooks-json" };
21389
+ if (hasDeclarations) return { kind: "declarations" };
21390
+ return { kind: "absent" };
21391
+ }
21392
+ function registrations(hooksJson) {
21393
+ const out = /* @__PURE__ */ new Set();
21394
+ if (!hooksJson || typeof hooksJson !== "object") return out;
21395
+ const hooks = hooksJson.hooks;
21396
+ if (!hooks || typeof hooks !== "object") return out;
21397
+ for (const [event, entries] of Object.entries(
21398
+ hooks
21399
+ )) {
21400
+ if (!Array.isArray(entries)) continue;
21401
+ for (const entry of entries) {
21402
+ if (!entry || typeof entry !== "object") continue;
21403
+ const e = entry;
21404
+ const matcher = typeof e.matcher === "string" ? e.matcher : "*";
21405
+ const inner = Array.isArray(e.hooks) ? e.hooks : [];
21406
+ for (const h of inner) {
21407
+ if (!h || typeof h !== "object") continue;
21408
+ const command = h.command;
21409
+ out.add(`${event} [${matcher}] ${String(command)}`);
21410
+ }
21411
+ }
21412
+ }
21413
+ return out;
21414
+ }
21415
+ function describeHooksSourceConflict(hooksDir, hookNames) {
21416
+ if (detectHooksSourceForm(hooksDir).kind !== "both") {
21417
+ return null;
21418
+ }
21419
+ const jsonPath = path20.join(hooksDir, "hooks.json");
21420
+ const declared = declarationFileNames(hooksDir);
21421
+ let divergence = "";
21422
+ try {
21423
+ const fromJson = registrations(
21424
+ JSON.parse(fs22.readFileSync(jsonPath, "utf8"))
21425
+ );
21426
+ const fromDeclarations = registrations(
21427
+ synthesizeHooksJson(loadHookDeclarations(hooksDir, hookNames))
21428
+ );
21429
+ const onlyJson = [...fromJson].filter((r) => !fromDeclarations.has(r));
21430
+ const onlyDeclarations = [...fromDeclarations].filter(
21431
+ (r) => !fromJson.has(r)
21432
+ );
21433
+ if (onlyJson.length === 0 && onlyDeclarations.length === 0) {
21434
+ divergence = " The two forms currently register the same hooks, so nothing is being lost yet \u2014 but only one of them is read, and the next edit to the other will be silent.";
21435
+ } else {
21436
+ const lines = [
21437
+ ...onlyJson.map((r) => ` shipped, not declared: ${r}`),
21438
+ ...onlyDeclarations.map((r) => ` declared, not shipped: ${r}`)
21439
+ ];
21440
+ divergence = " The two forms disagree. Only hooks.json is read, so:\n" + lines.join("\n");
21441
+ }
21442
+ } catch (error) {
21443
+ divergence = ` The two forms could not be compared: ${error instanceof Error ? error.message : String(error)}.`;
21444
+ }
21445
+ return `This kit declares its hooks twice: '${jsonPath}' and ${declared.length} declaration file${declared.length === 1 ? "" : "s"} (${declared.join(", ")}) in the same directory. A kit must write one form or the other.${divergence}
21446
+ To fix: keep the declarations and delete hooks.json \u2014 the declarations are the documented form, they carry the description and the per-event statusMessage, and they are what 'tangyr validate' checks. Before deleting, fold anything above that hooks.json ships and the declarations do not into the matching hooks/<name>.md, and add every hook name to 'components.hooks' in tangyr-kit.yaml. To keep hooks.json instead, delete the .md files \u2014 but note that no target then receives a hook description, and the manifest's hook list stops being checked against anything.`;
21447
+ }
21448
+ function loadKitHooksSource(sourceRoot, config, scriptsDir) {
21449
+ if (!hooksSourceExists(sourceRoot, config)) {
21450
+ return null;
21451
+ }
21452
+ const hooksDir = resolveHooksSourceDir(sourceRoot, config);
21453
+ const hooks = loadSourceHooks(
21454
+ hooksDir,
21455
+ resolveKitHookNames(sourceRoot),
21456
+ scriptsDir
21457
+ );
21458
+ const form = detectHooksSourceForm(hooksDir);
21459
+ return {
21460
+ hooks,
21461
+ path: form.kind === "declarations" ? hooksDir : path20.join(hooksDir, "hooks.json"),
21462
+ label: hooksSourceLabel(sourceRoot, config),
21463
+ // Hashed over the compiled input rather than over one file's bytes: the
21464
+ // declaration form has no single file to hash, and what provenance needs
21465
+ // to identify is what was compiled.
21466
+ hash: computeBytesHash(Buffer.from(JSON.stringify(hooks), "utf8"))
21467
+ };
21468
+ }
21469
+ function reportHooksSourceConflict(report, targets, hooksDir, hookNames) {
21470
+ const message = describeHooksSourceConflict(hooksDir, hookNames);
21471
+ if (!message) {
21472
+ return null;
21473
+ }
21474
+ for (const target of targets) {
21475
+ addLoss(report, target, "hooks", {
21476
+ source: `${path20.basename(hooksDir)}/`,
21477
+ fields: [
21478
+ {
21479
+ field: "hooks.json",
21480
+ action: "hook-source-ambiguous",
21481
+ severity: "error",
21482
+ detail: message
21483
+ }
21484
+ ]
21485
+ });
21486
+ }
21487
+ return message;
21488
+ }
21489
+ function reportProjectScopeScriptPath(report, targets, hooksDir, hookNames) {
21490
+ const affected = targets.filter((t) => t !== "claude-code");
21491
+ if (affected.length === 0) {
21492
+ return [];
21493
+ }
21494
+ const synthesized = loadHookDeclarations(hooksDir, hookNames).filter(
21495
+ (d) => d.command === void 0
21496
+ );
21497
+ if (synthesized.length === 0) {
21498
+ return [];
21499
+ }
21500
+ for (const target of affected) {
21501
+ addLoss(report, target, "hooks", {
21502
+ source: `${path20.basename(hooksDir)}/`,
21503
+ fields: [
21504
+ {
21505
+ field: "command",
21506
+ action: "hook-script-path-unresolved",
21507
+ severity: "warning",
21508
+ detail: `The ${synthesized.length} hook${synthesized.length === 1 ? "" : "s"} that take the synthesized command name '${GLOBAL_HOOK_SCRIPTS_DIR}/<name>.ts', which a project-scope install does not write. Only the claude-code adapter delivers hook scripts into a project, and only its own commands are resolved against the project. To fix, choose one: install this kit at global scope, where the path is correct; install claude-code alongside ${target} so the scripts reach the project, and give each hook an explicit 'command' pointing at them; or give each hook a 'command' of its own. Until then these hooks do not run under ${target}.`
21509
+ }
21510
+ ]
21511
+ });
21512
+ }
21513
+ return affected;
21514
+ }
21200
21515
 
21201
21516
  // src/core/remove.ts
21202
- import fs22 from "fs";
21517
+ import fs23 from "fs";
21203
21518
  function removeManagedPath(managedPath) {
21204
21519
  let stat;
21205
21520
  try {
21206
- stat = fs22.lstatSync(managedPath);
21521
+ stat = fs23.lstatSync(managedPath);
21207
21522
  } catch {
21208
21523
  return;
21209
21524
  }
21210
21525
  if (stat.isSymbolicLink() || stat.isFile()) {
21211
- fs22.unlinkSync(managedPath);
21526
+ fs23.unlinkSync(managedPath);
21212
21527
  return;
21213
21528
  }
21214
21529
  if (stat.isDirectory()) {
21215
- fs22.rmSync(managedPath, { recursive: true, force: true });
21530
+ fs23.rmSync(managedPath, { recursive: true, force: true });
21216
21531
  }
21217
21532
  }
21218
21533
 
@@ -21320,7 +21635,7 @@ async function runUninstallCommand(options, logger) {
21320
21635
  runtime
21321
21636
  );
21322
21637
  for (const actualPath of paths) {
21323
- if (actualPath && (fs23.existsSync(actualPath) || isSymlink(actualPath))) {
21638
+ if (actualPath && (fs24.existsSync(actualPath) || isSymlink(actualPath))) {
21324
21639
  removeManagedPath(actualPath);
21325
21640
  logger.verbose(` removed: ${actualPath}`);
21326
21641
  removedCount++;
@@ -21334,7 +21649,7 @@ async function runUninstallCommand(options, logger) {
21334
21649
  scope,
21335
21650
  runtime
21336
21651
  );
21337
- if (actualPath && (fs23.existsSync(actualPath) || isSymlink(actualPath))) {
21652
+ if (actualPath && (fs24.existsSync(actualPath) || isSymlink(actualPath))) {
21338
21653
  removeManagedPath(actualPath);
21339
21654
  logger.verbose(` removed: ${actualPath}`);
21340
21655
  removedCount++;
@@ -21368,14 +21683,14 @@ async function runUninstallCommand(options, logger) {
21368
21683
  restoreBackup(backup, logger);
21369
21684
  }
21370
21685
  const manifestPath = path21.join(scopePath, "manifest.json");
21371
- if (fs23.existsSync(manifestPath)) {
21372
- fs23.unlinkSync(manifestPath);
21686
+ if (fs24.existsSync(manifestPath)) {
21687
+ fs24.unlinkSync(manifestPath);
21373
21688
  logger.verbose(` removed manifest: ${manifestPath}`);
21374
21689
  }
21375
21690
  try {
21376
- const remaining = fs23.readdirSync(scopePath);
21691
+ const remaining = fs24.readdirSync(scopePath);
21377
21692
  if (remaining.length === 0) {
21378
- fs23.rmdirSync(scopePath);
21693
+ fs24.rmdirSync(scopePath);
21379
21694
  logger.verbose(` removed empty scope directory: ${scopePath}`);
21380
21695
  }
21381
21696
  } catch {
@@ -21619,9 +21934,9 @@ function resolveAllClaudeRoots(manifest, runtime, claudeConfigDirs) {
21619
21934
  })
21620
21935
  ].map((root) => path21.resolve(root))
21621
21936
  );
21622
- if (home && fs23.existsSync(home)) {
21937
+ if (home && fs24.existsSync(home)) {
21623
21938
  try {
21624
- for (const entry of fs23.readdirSync(home, { withFileTypes: true })) {
21939
+ for (const entry of fs24.readdirSync(home, { withFileTypes: true })) {
21625
21940
  if (entry.isDirectory() && entry.name.startsWith(".claude-")) {
21626
21941
  roots.add(path21.resolve(home, entry.name));
21627
21942
  }
@@ -21748,7 +22063,7 @@ function addCompiledGlobPlans(artifacts, pattern, expectedTarget, component) {
21748
22063
  for (const artifactPath of ts(pattern).sort()) {
21749
22064
  let marker = null;
21750
22065
  try {
21751
- marker = extractProvenanceMarker(fs23.readFileSync(artifactPath, "utf8"));
22066
+ marker = extractProvenanceMarker(fs24.readFileSync(artifactPath, "utf8"));
21752
22067
  } catch {
21753
22068
  continue;
21754
22069
  }
@@ -21768,7 +22083,7 @@ function addCompiledGlobPlans(artifacts, pattern, expectedTarget, component) {
21768
22083
  function addManagedSymlinkChildren(artifacts, directory, runtime, component) {
21769
22084
  let entries;
21770
22085
  try {
21771
- entries = fs23.readdirSync(directory, { withFileTypes: true });
22086
+ entries = fs24.readdirSync(directory, { withFileTypes: true });
21772
22087
  } catch {
21773
22088
  return;
21774
22089
  }
@@ -21958,12 +22273,8 @@ function resolveKitManagedArtifacts(runtime, scope, selectedTools, manifest, cla
21958
22273
  expectedTarget: "copilot"
21959
22274
  }
21960
22275
  );
21961
- const hooksSourcePath = resolveHooksSourceFile(
21962
- runtime.sourceRoot,
21963
- runtime.config
21964
- );
21965
- const hooksSource = hooksSourceLabel(runtime.config);
21966
- if (fs23.existsSync(hooksSourcePath)) {
22276
+ const hooksSource = hooksSourceLabel(runtime.sourceRoot, runtime.config);
22277
+ if (hooksSourceExists(runtime.sourceRoot, runtime.config)) {
21967
22278
  artifacts.push({
21968
22279
  component: "copilot hooks",
21969
22280
  path: path21.join(copilotPaths.hooks, "tangyr-managed.json"),
@@ -21977,7 +22288,7 @@ function resolveKitManagedArtifacts(runtime, scope, selectedTools, manifest, cla
21977
22288
  runtime.config
21978
22289
  );
21979
22290
  const mcpSource = mcpSourceLabel(runtime.config);
21980
- if (fs23.existsSync(mcpSourcePath)) {
22291
+ if (fs24.existsSync(mcpSourcePath)) {
21981
22292
  artifacts.push({
21982
22293
  component: "copilot mcp",
21983
22294
  path: copilotPaths.mcpSharedFile,
@@ -22038,12 +22349,8 @@ function resolveKitManagedArtifacts(runtime, scope, selectedTools, manifest, cla
22038
22349
  kind: "direct"
22039
22350
  });
22040
22351
  }
22041
- const hooksSourcePath = resolveHooksSourceFile(
22042
- runtime.sourceRoot,
22043
- runtime.config
22044
- );
22045
- const hooksSource = hooksSourceLabel(runtime.config);
22046
- if (fs23.existsSync(hooksSourcePath)) {
22352
+ const hooksSource = hooksSourceLabel(runtime.sourceRoot, runtime.config);
22353
+ if (hooksSourceExists(runtime.sourceRoot, runtime.config)) {
22047
22354
  artifacts.push({
22048
22355
  component: "codex hooks",
22049
22356
  path: codexPaths.hooksSharedFile,
@@ -22056,7 +22363,7 @@ function resolveKitManagedArtifacts(runtime, scope, selectedTools, manifest, cla
22056
22363
  runtime.config
22057
22364
  );
22058
22365
  const mcpSource = mcpSourceLabel(runtime.config);
22059
- if (fs23.existsSync(mcpSourcePath)) {
22366
+ if (fs24.existsSync(mcpSourcePath)) {
22060
22367
  artifacts.push({
22061
22368
  component: "codex mcp",
22062
22369
  path: codexPaths.mcpSharedFile,
@@ -22133,7 +22440,7 @@ function resolveKitManagedArtifacts(runtime, scope, selectedTools, manifest, cla
22133
22440
  runtime.config
22134
22441
  );
22135
22442
  const mcpSource = mcpSourceLabel(runtime.config);
22136
- if (fs23.existsSync(mcpSourcePath)) {
22443
+ if (fs24.existsSync(mcpSourcePath)) {
22137
22444
  artifacts.push({
22138
22445
  component: "opencode config",
22139
22446
  path: opencodePaths.configDoc,
@@ -22214,7 +22521,7 @@ function resolveKitManagedArtifacts(runtime, scope, selectedTools, manifest, cla
22214
22521
  runtime.config
22215
22522
  );
22216
22523
  const mcpSource = mcpSourceLabel(runtime.config);
22217
- if (fs23.existsSync(mcpSourcePath)) {
22524
+ if (fs24.existsSync(mcpSourcePath)) {
22218
22525
  artifacts.push({
22219
22526
  component: "cursor mcp",
22220
22527
  path: cursorPaths.mcpFile,
@@ -22294,7 +22601,7 @@ function resolveKitManagedArtifacts(runtime, scope, selectedTools, manifest, cla
22294
22601
  runtime.config
22295
22602
  );
22296
22603
  const mcpSource = mcpSourceLabel(runtime.config);
22297
- if (fs23.existsSync(mcpSourcePath)) {
22604
+ if (fs24.existsSync(mcpSourcePath)) {
22298
22605
  artifacts.push({
22299
22606
  component: "cline mcp",
22300
22607
  path: clinePaths.mcpFile,
@@ -22376,12 +22683,12 @@ function isKitManagedArtifact(artifact, runtime) {
22376
22683
  return artifactClass === "managed-symlink" || artifactClass === "managed-stale" || artifactClass === "managed-compiled";
22377
22684
  }
22378
22685
  if (artifact.kind === "compiled") {
22379
- if (!fs23.existsSync(artifact.path)) {
22686
+ if (!fs24.existsSync(artifact.path)) {
22380
22687
  return false;
22381
22688
  }
22382
22689
  try {
22383
22690
  const marker = extractProvenanceMarker(
22384
- fs23.readFileSync(artifact.path, "utf8")
22691
+ fs24.readFileSync(artifact.path, "utf8")
22385
22692
  );
22386
22693
  return marker?.target === artifact.expectedTarget && marker?.source === artifact.expectedSource;
22387
22694
  } catch {
@@ -22389,7 +22696,7 @@ function isKitManagedArtifact(artifact, runtime) {
22389
22696
  }
22390
22697
  }
22391
22698
  if (artifact.kind === "recorded") {
22392
- return fs23.existsSync(artifact.path) || isSymlink(artifact.path);
22699
+ return fs24.existsSync(artifact.path) || isSymlink(artifact.path);
22393
22700
  }
22394
22701
  if (artifact.kind === "kit-pointer") {
22395
22702
  return isKitPointer(artifact);
@@ -22398,11 +22705,11 @@ function isKitManagedArtifact(artifact, runtime) {
22398
22705
  return isLegacyOperatingGuideCopy(artifact.path);
22399
22706
  }
22400
22707
  if (artifact.kind === "codex-mcp") {
22401
- if (!fs23.existsSync(artifact.path)) {
22708
+ if (!fs24.existsSync(artifact.path)) {
22402
22709
  return false;
22403
22710
  }
22404
22711
  const section = readManagedTomlSection(
22405
- fs23.readFileSync(artifact.path, "utf8")
22712
+ fs24.readFileSync(artifact.path, "utf8")
22406
22713
  );
22407
22714
  const marker = section ? extractProvenanceMarker(section) : null;
22408
22715
  return marker?.target === artifact.expectedTarget && marker?.source === artifact.expectedSource;
@@ -22428,16 +22735,16 @@ function isKitManagedArtifact(artifact, runtime) {
22428
22735
  return hasManagedCodexHooks(parsed, artifact.expectedSource);
22429
22736
  }
22430
22737
  function removeManagedCompiledArtifact(artifact, logger) {
22431
- if (!fs23.existsSync(artifact.path)) {
22738
+ if (!fs24.existsSync(artifact.path)) {
22432
22739
  return false;
22433
22740
  }
22434
22741
  const marker = extractProvenanceMarker(
22435
- fs23.readFileSync(artifact.path, "utf8")
22742
+ fs24.readFileSync(artifact.path, "utf8")
22436
22743
  );
22437
22744
  if (marker?.target !== artifact.expectedTarget || marker?.source !== artifact.expectedSource) {
22438
22745
  return false;
22439
22746
  }
22440
- fs23.rmSync(artifact.path, { force: true });
22747
+ fs24.rmSync(artifact.path, { force: true });
22441
22748
  logger.verbose(` removed ${artifact.component}: ${artifact.path}`);
22442
22749
  return true;
22443
22750
  }
@@ -22508,12 +22815,12 @@ function removeManagedJsonArtifact(artifact, logger) {
22508
22815
  if (!parsed || !hasManagedRootMetadata(parsed, artifact.expectedTarget)) {
22509
22816
  return false;
22510
22817
  }
22511
- fs23.rmSync(artifact.path, { force: true });
22818
+ fs24.rmSync(artifact.path, { force: true });
22512
22819
  logger.verbose(` removed ${artifact.component}: ${artifact.path}`);
22513
22820
  return true;
22514
22821
  }
22515
22822
  function removeRecordedArtifact(artifact, logger) {
22516
- if (!fs23.existsSync(artifact.path) && !isSymlink(artifact.path)) {
22823
+ if (!fs24.existsSync(artifact.path) && !isSymlink(artifact.path)) {
22517
22824
  return false;
22518
22825
  }
22519
22826
  removeManagedPath(artifact.path);
@@ -22521,10 +22828,10 @@ function removeRecordedArtifact(artifact, logger) {
22521
22828
  return true;
22522
22829
  }
22523
22830
  function removeExactManagedFile(artifact, logger) {
22524
- if (!fs23.existsSync(artifact.path)) {
22831
+ if (!fs24.existsSync(artifact.path)) {
22525
22832
  return false;
22526
22833
  }
22527
- fs23.rmSync(artifact.path, { force: true });
22834
+ fs24.rmSync(artifact.path, { force: true });
22528
22835
  logger.verbose(` removed ${artifact.component}: ${artifact.path}`);
22529
22836
  return true;
22530
22837
  }
@@ -22558,10 +22865,10 @@ function removeManagedCodexHooksArtifact(artifact, logger) {
22558
22865
  return true;
22559
22866
  }
22560
22867
  function removeManagedCodexMcpArtifact(artifact, logger) {
22561
- if (!fs23.existsSync(artifact.path)) {
22868
+ if (!fs24.existsSync(artifact.path)) {
22562
22869
  return false;
22563
22870
  }
22564
- const content = fs23.readFileSync(artifact.path, "utf8");
22871
+ const content = fs24.readFileSync(artifact.path, "utf8");
22565
22872
  const managedSection = readManagedTomlSection(content);
22566
22873
  if (!managedSection) {
22567
22874
  return false;
@@ -22576,9 +22883,9 @@ function removeManagedCodexMcpArtifact(artifact, logger) {
22576
22883
  codexManagedMcpEnd
22577
22884
  );
22578
22885
  if (cleaned.trim().length === 0) {
22579
- fs23.rmSync(artifact.path, { force: true });
22886
+ fs24.rmSync(artifact.path, { force: true });
22580
22887
  } else {
22581
- fs23.writeFileSync(artifact.path, cleaned);
22888
+ fs24.writeFileSync(artifact.path, cleaned);
22582
22889
  }
22583
22890
  logger.verbose(` removed ${artifact.component}: ${artifact.path}`);
22584
22891
  return true;
@@ -22771,11 +23078,11 @@ function managedArtifactPlanPriority(artifact) {
22771
23078
  return 2;
22772
23079
  }
22773
23080
  function readJsonObject2(filePath) {
22774
- if (!fs23.existsSync(filePath)) {
23081
+ if (!fs24.existsSync(filePath)) {
22775
23082
  return null;
22776
23083
  }
22777
23084
  try {
22778
- const parsed = JSON.parse(fs23.readFileSync(filePath, "utf8"));
23085
+ const parsed = JSON.parse(fs24.readFileSync(filePath, "utf8"));
22779
23086
  return isRecord(parsed) ? parsed : null;
22780
23087
  } catch {
22781
23088
  return null;
@@ -22823,22 +23130,22 @@ function removeLegacyClaudeSharedPermission(value) {
22823
23130
  return Object.keys(cleaned).length === 0 ? void 0 : cleaned;
22824
23131
  }
22825
23132
  function isKitPointer(artifact) {
22826
- if (!artifact.expectedPath || !fs23.existsSync(artifact.path)) {
23133
+ if (!artifact.expectedPath || !fs24.existsSync(artifact.path)) {
22827
23134
  return false;
22828
23135
  }
22829
23136
  try {
22830
- const recordedPath = fs23.readFileSync(artifact.path, "utf8").trim();
23137
+ const recordedPath = fs24.readFileSync(artifact.path, "utf8").trim();
22831
23138
  return recordedPath.length > 0 && path21.resolve(recordedPath) === path21.resolve(artifact.expectedPath);
22832
23139
  } catch {
22833
23140
  return false;
22834
23141
  }
22835
23142
  }
22836
23143
  function isLegacyOperatingGuideCopy(filePath) {
22837
- if (!fs23.existsSync(filePath)) {
23144
+ if (!fs24.existsSync(filePath)) {
22838
23145
  return false;
22839
23146
  }
22840
23147
  try {
22841
- return fs23.readFileSync(filePath, "utf8").startsWith(legacyOperatingGuideSignature);
23148
+ return fs24.readFileSync(filePath, "utf8").startsWith(legacyOperatingGuideSignature);
22842
23149
  } catch {
22843
23150
  return false;
22844
23151
  }
@@ -22860,10 +23167,10 @@ function matchesManagedMetadata(metadata, artifact) {
22860
23167
  }
22861
23168
  function writeJsonOrRemove(filePath, parsed) {
22862
23169
  if (isEmptyJsonValue(parsed)) {
22863
- fs23.rmSync(filePath, { force: true });
23170
+ fs24.rmSync(filePath, { force: true });
22864
23171
  return;
22865
23172
  }
22866
- fs23.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
23173
+ fs24.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
22867
23174
  `);
22868
23175
  }
22869
23176
  function isEmptyJsonValue(value) {
@@ -22989,20 +23296,20 @@ function removeDelimitedManagedSection(content, startMarker, endMarker) {
22989
23296
  return `${content.slice(0, start)}${content.slice(removalEnd)}`;
22990
23297
  }
22991
23298
  function restoreBackup(backup, logger) {
22992
- if (!fs23.existsSync(backup.backupLocation)) {
23299
+ if (!fs24.existsSync(backup.backupLocation)) {
22993
23300
  logger.warn(` backup not found: ${backup.backupLocation}`);
22994
23301
  return;
22995
23302
  }
22996
23303
  const destDir = path21.dirname(backup.originalPath);
22997
- fs23.mkdirSync(destDir, { recursive: true });
22998
- fs23.renameSync(backup.backupLocation, backup.originalPath);
23304
+ fs24.mkdirSync(destDir, { recursive: true });
23305
+ fs24.renameSync(backup.backupLocation, backup.originalPath);
22999
23306
  logger.verbose(
23000
23307
  ` restored: ${backup.backupLocation} \u2192 ${backup.originalPath}`
23001
23308
  );
23002
23309
  }
23003
23310
  function isSymlink(p) {
23004
23311
  try {
23005
- return fs23.lstatSync(p).isSymbolicLink();
23312
+ return fs24.lstatSync(p).isSymbolicLink();
23006
23313
  } catch {
23007
23314
  return false;
23008
23315
  }
@@ -23076,7 +23383,7 @@ function cleanupClaudeCode(config, sourceRoot, configDir, logger, scope) {
23076
23383
  )) {
23077
23384
  const artifactClass = classifyArtifact(artifact.path, config, sourceRoot);
23078
23385
  if (artifactClass === "managed-symlink" || artifactClass === "managed-compiled" || artifactClass === "managed-stale") {
23079
- fs24.rmSync(artifact.path, { recursive: true, force: true });
23386
+ fs25.rmSync(artifact.path, { recursive: true, force: true });
23080
23387
  logger.info(`${artifact.component}: removed ${artifact.path}`);
23081
23388
  }
23082
23389
  }
@@ -23289,7 +23596,7 @@ function removeManagedValueByKey2(value, keyName, keyValue) {
23289
23596
  function removeManagedDirectPath(artifactPath, config, sourceRoot, component, logger) {
23290
23597
  const artifactClass = classifyArtifact(artifactPath, config, sourceRoot);
23291
23598
  if (artifactClass === "managed-symlink" || artifactClass === "managed-compiled" || artifactClass === "managed-stale") {
23292
- fs24.rmSync(artifactPath, { recursive: true, force: true });
23599
+ fs25.rmSync(artifactPath, { recursive: true, force: true });
23293
23600
  logger.info(`${component}: removed ${artifactPath}`);
23294
23601
  }
23295
23602
  }
@@ -23299,30 +23606,30 @@ function removeManagedCompiledFiles(dir, pattern, expectedTarget, logger) {
23299
23606
  }
23300
23607
  }
23301
23608
  function removeManagedCompiledFile(filePath, expectedTarget, logger) {
23302
- if (!fs24.existsSync(filePath)) {
23609
+ if (!fs25.existsSync(filePath)) {
23303
23610
  return;
23304
23611
  }
23305
- const marker = extractProvenanceMarker(fs24.readFileSync(filePath, "utf8"));
23612
+ const marker = extractProvenanceMarker(fs25.readFileSync(filePath, "utf8"));
23306
23613
  if (marker?.target === expectedTarget) {
23307
- fs24.rmSync(filePath, { force: true });
23614
+ fs25.rmSync(filePath, { force: true });
23308
23615
  logger.info(`removed ${filePath}`);
23309
23616
  }
23310
23617
  }
23311
23618
  function removeManagedJsonFile(filePath, markerKey, logger) {
23312
- if (!fs24.existsSync(filePath)) {
23619
+ if (!fs25.existsSync(filePath)) {
23313
23620
  return;
23314
23621
  }
23315
23622
  const parsed = readJsonObject(filePath);
23316
23623
  if (isRecord(parsed[markerKey])) {
23317
- fs24.rmSync(filePath, { force: true });
23624
+ fs25.rmSync(filePath, { force: true });
23318
23625
  logger.info(`removed ${filePath}`);
23319
23626
  }
23320
23627
  }
23321
23628
  function removeCodexManagedTomlSection(filePath, logger) {
23322
- if (!fs24.existsSync(filePath)) {
23629
+ if (!fs25.existsSync(filePath)) {
23323
23630
  return;
23324
23631
  }
23325
- const content = fs24.readFileSync(filePath, "utf8");
23632
+ const content = fs25.readFileSync(filePath, "utf8");
23326
23633
  const next = removeDelimitedManagedSection2(
23327
23634
  content,
23328
23635
  codexManagedMcpStart2,
@@ -23331,7 +23638,7 @@ function removeCodexManagedTomlSection(filePath, logger) {
23331
23638
  if (next === content) {
23332
23639
  return;
23333
23640
  }
23334
- fs24.writeFileSync(filePath, next);
23641
+ fs25.writeFileSync(filePath, next);
23335
23642
  logger.info(`mcp: removed managed section from ${filePath}`);
23336
23643
  }
23337
23644
  function removeDelimitedManagedSection2(content, startMarker, endMarker) {
@@ -23403,7 +23710,7 @@ function toVerbosity(value) {
23403
23710
  }
23404
23711
 
23405
23712
  // src/commands/detect.ts
23406
- import fs25 from "fs";
23713
+ import fs26 from "fs";
23407
23714
  import os5 from "os";
23408
23715
  import path23 from "path";
23409
23716
  import { spawnSync } from "child_process";
@@ -23444,7 +23751,7 @@ var bundledCursorExecutablePathsByPlatform = {
23444
23751
  };
23445
23752
  function detectTargets(logger, dependencies = {}) {
23446
23753
  const spawnSyncImpl = dependencies.spawnSyncImpl ?? defaultSpawnSync;
23447
- const pathExists = dependencies.pathExists ?? fs25.existsSync;
23754
+ const pathExists = dependencies.pathExists ?? fs26.existsSync;
23448
23755
  const homeDirectory = dependencies.homeDirectory ?? os5.homedir();
23449
23756
  const platform = dependencies.platform ?? process.platform;
23450
23757
  const locator = platform === "win32" ? "where.exe" : "which";
@@ -23479,8 +23786,8 @@ function detectsCopilot(logger, dependencies = {}) {
23479
23786
  return detectCopilot(logger, dependencies);
23480
23787
  }
23481
23788
  function detectCopilot(logger, dependencies = {}) {
23482
- const pathExists = dependencies.pathExists ?? fs25.existsSync;
23483
- const readFile = dependencies.readFile ?? ((filePath) => fs25.readFileSync(filePath, "utf8"));
23789
+ const pathExists = dependencies.pathExists ?? fs26.existsSync;
23790
+ const readFile = dependencies.readFile ?? ((filePath) => fs26.readFileSync(filePath, "utf8"));
23484
23791
  const platform = dependencies.platform ?? process.platform;
23485
23792
  if (detectEditorExtension(
23486
23793
  "Copilot",
@@ -23508,8 +23815,8 @@ function detectCopilot(logger, dependencies = {}) {
23508
23815
  }
23509
23816
  function detectEditorExtension(label, matchesExtension, logger, dependencies = {}) {
23510
23817
  const spawnSyncImpl = dependencies.spawnSyncImpl ?? defaultSpawnSync;
23511
- const pathExists = dependencies.pathExists ?? fs25.existsSync;
23512
- const readDirectory = dependencies.readDirectory ?? fs25.readdirSync;
23818
+ const pathExists = dependencies.pathExists ?? fs26.existsSync;
23819
+ const readDirectory = dependencies.readDirectory ?? fs26.readdirSync;
23513
23820
  const homeDirectory = dependencies.homeDirectory ?? os5.homedir();
23514
23821
  const platform = dependencies.platform ?? process.platform;
23515
23822
  const commandCandidates = [
@@ -23620,45 +23927,55 @@ function logDetectionError(attemptedCommand, error, logger) {
23620
23927
  }
23621
23928
 
23622
23929
  // src/commands/doctor.ts
23623
- import fs27 from "fs";
23930
+ import fs28 from "fs";
23624
23931
  import path25 from "path";
23625
23932
 
23626
23933
  // src/utils/fs.ts
23627
- import fs26 from "fs";
23934
+ import fs27 from "fs";
23628
23935
  import path24 from "path";
23629
23936
  function ensureDir(dirPath) {
23630
- fs26.mkdirSync(dirPath, { recursive: true });
23937
+ fs27.mkdirSync(dirPath, { recursive: true });
23631
23938
  }
23632
23939
  function createRelativeSymlink(source, destination, type, options) {
23633
23940
  const absoluteSource = path24.resolve(source);
23634
- const relative = path24.relative(realPathOrInput(path24.dirname(destination)), absoluteSource);
23941
+ const relative = path24.relative(
23942
+ realPathOrInput(path24.dirname(destination)),
23943
+ absoluteSource
23944
+ );
23635
23945
  if (process.platform === "win32" && type === "dir") {
23636
- fs26.symlinkSync(absoluteSource, destination, "junction");
23946
+ fs27.symlinkSync(absoluteSource, destination, "junction");
23637
23947
  return;
23638
23948
  }
23639
23949
  if (process.platform === "win32" && type === "file") {
23640
23950
  try {
23641
- fs26.symlinkSync(relative, destination, "file");
23951
+ fs27.symlinkSync(relative, destination, "file");
23642
23952
  } catch (error) {
23643
23953
  if (!hasErrorCode(error, "EPERM")) {
23644
23954
  throw error;
23645
23955
  }
23646
23956
  if (!options?.provenanceMarker) {
23647
- throw new Error(`${destination}: file symlink unavailable on Windows and no provenance marker was provided.`);
23957
+ throw new Error(
23958
+ `${destination}: file symlink unavailable on Windows and no provenance marker was provided.`
23959
+ );
23648
23960
  }
23649
- createManagedCopy(source, destination, options.provenanceMarker, options.logger);
23961
+ createManagedCopy(
23962
+ source,
23963
+ destination,
23964
+ options.provenanceMarker,
23965
+ options.logger
23966
+ );
23650
23967
  options.logger?.warn(
23651
23968
  `${destination}: file symlink unavailable on Windows \u2014 using managed copy with provenance metadata (behavioral contract C-09)`
23652
23969
  );
23653
23970
  }
23654
23971
  return;
23655
23972
  }
23656
- fs26.symlinkSync(relative, destination, type);
23973
+ fs27.symlinkSync(relative, destination, type);
23657
23974
  }
23658
23975
  function createManagedCopy(source, destination, provenanceMarker, _logger) {
23659
23976
  ensureDir(path24.dirname(destination));
23660
- const content = fs26.readFileSync(source, "utf8");
23661
- fs26.writeFileSync(destination, `${provenanceMarker}
23977
+ const content = fs27.readFileSync(source, "utf8");
23978
+ fs27.writeFileSync(destination, `${provenanceMarker}
23662
23979
  ${content}`);
23663
23980
  }
23664
23981
  function hasErrorCode(error, expectedCode) {
@@ -23666,7 +23983,7 @@ function hasErrorCode(error, expectedCode) {
23666
23983
  }
23667
23984
  function realPathOrInput(filePath) {
23668
23985
  try {
23669
- return fs26.realpathSync(filePath);
23986
+ return fs27.realpathSync(filePath);
23670
23987
  } catch {
23671
23988
  return filePath;
23672
23989
  }
@@ -23679,7 +23996,7 @@ function runDoctorCommand(options, logger) {
23679
23996
  import.meta.url
23680
23997
  );
23681
23998
  const findings = [];
23682
- if (!fs27.existsSync(configPath)) {
23999
+ if (!fs28.existsSync(configPath)) {
23683
24000
  findings.push(`${configPath}: missing`);
23684
24001
  }
23685
24002
  const hooksJsonPath = path25.join(
@@ -23687,9 +24004,9 @@ function runDoctorCommand(options, logger) {
23687
24004
  config.source.hooks,
23688
24005
  "hooks.json"
23689
24006
  );
23690
- if (fs27.existsSync(hooksJsonPath)) {
24007
+ if (fs28.existsSync(hooksJsonPath)) {
23691
24008
  try {
23692
- JSON.parse(fs27.readFileSync(hooksJsonPath, "utf8"));
24009
+ JSON.parse(fs28.readFileSync(hooksJsonPath, "utf8"));
23693
24010
  } catch (err) {
23694
24011
  findings.push(
23695
24012
  `hooks/hooks.json: ${err instanceof Error ? err.message : String(err)}`
@@ -23764,9 +24081,9 @@ function checkClaudeCode(options, findings, config, sourceRoot, configDir, logge
23764
24081
  /* @__PURE__ */ new Set([path25.resolve(artifact.path)]),
23765
24082
  config
23766
24083
  )) {
23767
- fs27.unlinkSync(artifact.path);
24084
+ fs28.unlinkSync(artifact.path);
23768
24085
  ensureDir(path25.dirname(artifact.path));
23769
- const symlinkType = fs27.statSync(sourcePath).isDirectory() ? "dir" : "file";
24086
+ const symlinkType = fs28.statSync(sourcePath).isDirectory() ? "dir" : "file";
23770
24087
  createRelativeSymlink(sourcePath, artifact.path, symlinkType, {
23771
24088
  logger,
23772
24089
  provenanceMarker: symlinkType === "file" && artifact.sourceKey ? formatProvenanceMarker(
@@ -23779,7 +24096,7 @@ function checkClaudeCode(options, findings, config, sourceRoot, configDir, logge
23779
24096
  logger.info(`${artifact.component}: recreated managed symlink`);
23780
24097
  }
23781
24098
  }
23782
- if (options.fix && !fs27.existsSync(path25.dirname(artifact.path))) {
24099
+ if (options.fix && !fs28.existsSync(path25.dirname(artifact.path))) {
23783
24100
  ensureDir(path25.dirname(artifact.path));
23784
24101
  }
23785
24102
  checkParentWritablePath(artifact.path, artifact.component, findings);
@@ -23841,24 +24158,24 @@ function checkParentWritablePath(targetPath, label, findings) {
23841
24158
  checkWritablePath(path25.dirname(targetPath), `${label} parent`, findings);
23842
24159
  }
23843
24160
  function checkWritablePath(checkPath, label, findings) {
23844
- if (!fs27.existsSync(checkPath)) {
24161
+ if (!fs28.existsSync(checkPath)) {
23845
24162
  findings.push(`${label}: path does not exist`);
23846
24163
  return;
23847
24164
  }
23848
24165
  try {
23849
- fs27.accessSync(checkPath, fs27.constants.R_OK | fs27.constants.W_OK);
24166
+ fs28.accessSync(checkPath, fs28.constants.R_OK | fs28.constants.W_OK);
23850
24167
  } catch {
23851
24168
  findings.push(`${label}: path is not readable and writable`);
23852
24169
  }
23853
24170
  }
23854
24171
  function walkHookScripts(dir) {
23855
- if (!fs27.existsSync(dir)) {
24172
+ if (!fs28.existsSync(dir)) {
23856
24173
  return [];
23857
24174
  }
23858
- return fs27.readdirSync(dir).map((entry) => path25.join(dir, entry)).filter((entryPath) => fs27.statSync(entryPath).isFile());
24175
+ return fs28.readdirSync(dir).map((entry) => path25.join(dir, entry)).filter((entryPath) => fs28.statSync(entryPath).isFile());
23859
24176
  }
23860
24177
  function fixManagedHookScriptModes(scriptRoot, sourceRoot, logger) {
23861
- if (!fs27.existsSync(scriptRoot)) {
24178
+ if (!fs28.existsSync(scriptRoot)) {
23862
24179
  return;
23863
24180
  }
23864
24181
  if (hasSymlinkAncestor(scriptRoot, resolveTargetPath("~/.agents")) || isInside(realPathOrInput2(scriptRoot), realPathOrInput2(sourceRoot))) {
@@ -23868,7 +24185,7 @@ function fixManagedHookScriptModes(scriptRoot, sourceRoot, logger) {
23868
24185
  return;
23869
24186
  }
23870
24187
  for (const scriptPath of walkHookScripts(scriptRoot)) {
23871
- fs27.chmodSync(scriptPath, 493);
24188
+ fs28.chmodSync(scriptPath, 493);
23872
24189
  }
23873
24190
  }
23874
24191
  function hasSymlinkAncestor(targetPath, stopAt) {
@@ -23885,7 +24202,7 @@ function hasSymlinkAncestor(targetPath, stopAt) {
23885
24202
  ...segments.slice(0, index + 1)
23886
24203
  );
23887
24204
  try {
23888
- return fs27.lstatSync(candidate).isSymbolicLink();
24205
+ return fs28.lstatSync(candidate).isSymbolicLink();
23889
24206
  } catch {
23890
24207
  return false;
23891
24208
  }
@@ -23897,14 +24214,14 @@ function isInside(childPath, parentPath) {
23897
24214
  }
23898
24215
  function realPathOrInput2(filePath) {
23899
24216
  try {
23900
- return fs27.realpathSync(filePath);
24217
+ return fs28.realpathSync(filePath);
23901
24218
  } catch {
23902
24219
  return filePath;
23903
24220
  }
23904
24221
  }
23905
24222
 
23906
24223
  // src/commands/init.ts
23907
- import fs28 from "fs";
24224
+ import fs29 from "fs";
23908
24225
  import path26 from "path";
23909
24226
  var import_yaml7 = __toESM(require_dist(), 1);
23910
24227
  async function runInitCommand(_options, logger) {
@@ -23920,16 +24237,18 @@ async function runInitCommand(_options, logger) {
23920
24237
  "rules",
23921
24238
  "hooks"
23922
24239
  ].filter(
23923
- (relativePath) => fs28.existsSync(path26.join(sourceRoot, relativePath))
24240
+ (relativePath) => fs29.existsSync(path26.join(sourceRoot, relativePath))
23924
24241
  ).length;
23925
24242
  const detectedTargets = detectTargets(logger);
23926
24243
  const enabledTargets = await dist_default4({
23927
24244
  message: `Detected ${componentCount} source components. Select targets`,
23928
- choices: Object.entries(detectedTargets).map(([name, detected]) => ({
23929
- name,
23930
- value: name,
23931
- checked: detected
23932
- }))
24245
+ choices: Object.entries(detectedTargets).map(
24246
+ ([name, detected]) => ({
24247
+ name,
24248
+ value: name,
24249
+ checked: detected
24250
+ })
24251
+ )
23933
24252
  });
23934
24253
  const onConflict = await dist_default8({
23935
24254
  message: "Conflict policy",
@@ -23939,7 +24258,7 @@ async function runInitCommand(_options, logger) {
23939
24258
  }))
23940
24259
  });
23941
24260
  const configPath = path26.join(sourceRoot, "tangyr.config.yaml");
23942
- if (fs28.existsSync(configPath)) {
24261
+ if (fs29.existsSync(configPath)) {
23943
24262
  const overwrite = await dist_default5({
23944
24263
  message: `${configPath} exists. Overwrite?`,
23945
24264
  default: false
@@ -23949,7 +24268,7 @@ async function runInitCommand(_options, logger) {
23949
24268
  return;
23950
24269
  }
23951
24270
  }
23952
- fs28.writeFileSync(
24271
+ fs29.writeFileSync(
23953
24272
  configPath,
23954
24273
  (0, import_yaml7.stringify)({
23955
24274
  schema: 1,
@@ -23992,11 +24311,11 @@ function toConflictPolicy2(value) {
23992
24311
  }
23993
24312
 
23994
24313
  // src/commands/install.ts
23995
- import fs37 from "fs";
24314
+ import fs38 from "fs";
23996
24315
  import path37 from "path";
23997
24316
 
23998
24317
  // src/core/channel-state.ts
23999
- import fs29 from "fs";
24318
+ import fs30 from "fs";
24000
24319
  import path27 from "path";
24001
24320
  var CHANNEL_STATE_FILENAME = ".tangyr-channels.json";
24002
24321
  function stateFilePath(cacheRoot) {
@@ -24009,7 +24328,7 @@ function readState(cacheRoot) {
24009
24328
  const filePath = stateFilePath(cacheRoot);
24010
24329
  let raw;
24011
24330
  try {
24012
- raw = fs29.readFileSync(filePath, "utf8");
24331
+ raw = fs30.readFileSync(filePath, "utf8");
24013
24332
  } catch (err) {
24014
24333
  if (err.code === "ENOENT") {
24015
24334
  return { state: {} };
@@ -24056,13 +24375,13 @@ function writeChannelSequence(params) {
24056
24375
  [key]: { sequence, version, seenAt: now.toISOString() }
24057
24376
  };
24058
24377
  const filePath = stateFilePath(cacheRoot);
24059
- fs29.mkdirSync(path27.dirname(filePath), { recursive: true, mode: 448 });
24378
+ fs30.mkdirSync(path27.dirname(filePath), { recursive: true, mode: 448 });
24060
24379
  const tempPath = `${filePath}.${process.pid}.tmp`;
24061
- fs29.writeFileSync(tempPath, `${JSON.stringify(state, null, 2)}
24380
+ fs30.writeFileSync(tempPath, `${JSON.stringify(state, null, 2)}
24062
24381
  `, {
24063
24382
  mode: 384
24064
24383
  });
24065
- fs29.renameSync(tempPath, filePath);
24384
+ fs30.renameSync(tempPath, filePath);
24066
24385
  }
24067
24386
  function assertChannelNotRolledBack(params) {
24068
24387
  const { originUrl, kit, channel, seen, seenVersion, incoming } = params;
@@ -24078,7 +24397,7 @@ var CHANNEL_POINTER_TYPE = "tangyr.channel-pointer.v1";
24078
24397
  var DEFAULT_CHANNEL = "stable";
24079
24398
 
24080
24399
  // src/core/materialize.ts
24081
- import fs30 from "fs";
24400
+ import fs31 from "fs";
24082
24401
  import path28 from "path";
24083
24402
 
24084
24403
  // src/core/integrity.ts
@@ -24489,10 +24808,10 @@ function verifyBundleFileHashes(bundle) {
24489
24808
  function writeStagedFiles(stagingDir, decoded) {
24490
24809
  for (const [rawPath, bytes] of decoded) {
24491
24810
  const target = sanitizeBundleFilePath(rawPath, stagingDir);
24492
- fs30.mkdirSync(path28.dirname(target), { recursive: true, mode: 448 });
24811
+ fs31.mkdirSync(path28.dirname(target), { recursive: true, mode: 448 });
24493
24812
  let existingStat;
24494
24813
  try {
24495
- existingStat = fs30.lstatSync(target);
24814
+ existingStat = fs31.lstatSync(target);
24496
24815
  } catch (error) {
24497
24816
  if (error.code !== "ENOENT") {
24498
24817
  throw error;
@@ -24503,7 +24822,7 @@ function writeStagedFiles(stagingDir, decoded) {
24503
24822
  `Refusing to write "${rawPath}" \u2014 a symbolic link already exists at the target path.`
24504
24823
  );
24505
24824
  }
24506
- fs30.writeFileSync(target, bytes, { mode: 384, flag: "wx" });
24825
+ fs31.writeFileSync(target, bytes, { mode: 384, flag: "wx" });
24507
24826
  }
24508
24827
  }
24509
24828
  function verifyWarmCacheEntry(entryDir, reference, keys, now) {
@@ -24559,14 +24878,14 @@ async function materializeRemoteKit(params) {
24559
24878
  writeStagedFiles(stagingDir, decoded);
24560
24879
  writeBundleDocument(stagingDir, bundle);
24561
24880
  writeCompletionMarker(stagingDir, reference);
24562
- if (replace && fs30.existsSync(entryDir)) {
24563
- fs30.rmSync(entryDir, { recursive: true, force: true });
24881
+ if (replace && fs31.existsSync(entryDir)) {
24882
+ fs31.rmSync(entryDir, { recursive: true, force: true });
24564
24883
  }
24565
24884
  publishCacheEntry(stagingDir, entryDir);
24566
24885
  return entryDir;
24567
24886
  } catch (error) {
24568
24887
  if (stagingDir !== void 0) {
24569
- fs30.rmSync(stagingDir, { recursive: true, force: true });
24888
+ fs31.rmSync(stagingDir, { recursive: true, force: true });
24570
24889
  }
24571
24890
  throw error;
24572
24891
  }
@@ -24902,65 +25221,13 @@ function sleep(ms2) {
24902
25221
  // src/adapters/codex.ts
24903
25222
  var TOML3 = __toESM(require_toml(), 1);
24904
25223
  import crypto5 from "crypto";
24905
- import fs32 from "fs";
25224
+ import fs33 from "fs";
24906
25225
  import path31 from "path";
24907
25226
 
24908
25227
  // src/compiler/agents.ts
24909
25228
  var TOML = __toESM(require_toml(), 1);
24910
25229
  var import_yaml8 = __toESM(require_dist(), 1);
24911
25230
  import path29 from "path";
24912
-
24913
- // src/compiler/loss-report.ts
24914
- function createLossReport() {
24915
- return {
24916
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
24917
- targets: {},
24918
- summary: {},
24919
- informational: []
24920
- };
24921
- }
24922
- function addInformational(report, target, component, source, severity, detail) {
24923
- report.informational.push({ target, component, source, severity, detail });
24924
- }
24925
- function addLoss(report, target, component, loss) {
24926
- const targetReport = report.targets[target] ?? {};
24927
- const componentReport = targetReport[component] ?? [];
24928
- componentReport.push(loss);
24929
- targetReport[component] = componentReport;
24930
- report.targets[target] = targetReport;
24931
- const targetSummary = report.summary[target] ?? {
24932
- info: 0,
24933
- warning: 0,
24934
- error: 0
24935
- };
24936
- for (const field of loss.fields) {
24937
- targetSummary[field.severity] += 1;
24938
- }
24939
- report.summary[target] = targetSummary;
24940
- }
24941
- var lossSeverityByEntryType = {
24942
- "command-as-skill": "info",
24943
- "rule-as-instruction": "info",
24944
- "rule-mode-downgrade": "warning",
24945
- "rule-path-scoping": "warning",
24946
- "hook-event-drop": "warning",
24947
- "hook-as-plugin": "error",
24948
- "instructions-shim": "info",
24949
- "permission-coarsening": "error",
24950
- "provider-not-representable": "error",
24951
- "provider-wire-protocol": "error",
24952
- "model-not-representable": "error",
24953
- "persona-not-representable": "error",
24954
- "settings-not-representable": "warning"
24955
- };
24956
- function resolveLossSeverity(kind, name, action, explicitSeverity) {
24957
- if (explicitSeverity) {
24958
- return explicitSeverity;
24959
- }
24960
- return lossSeverityByEntryType[action];
24961
- }
24962
-
24963
- // src/compiler/agents.ts
24964
25231
  var writeCapabilityClasses = /* @__PURE__ */ new Set(["filesystem-write", "shell-execution"]);
24965
25232
  var copilotToolAliasesByClass = {
24966
25233
  delegation: "agent",
@@ -25380,12 +25647,37 @@ function compileCursorHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
25380
25647
  let dropped = 0;
25381
25648
  for (const [sourceEvent, payload] of Object.entries(sourceHooks)) {
25382
25649
  const mapping = normalizeHookEventRule(
25383
- targetMapping.source_to_target[sourceEvent]
25650
+ targetMapping.source_to_target[sourceEvent],
25651
+ "cursor",
25652
+ sourceEvent
25384
25653
  );
25385
25654
  if (mapping.status === "mapped" && mapping.target_event) {
25386
- const sourceCommand = isRecord(payload) && typeof payload.command === "string" ? payload.command : Array.isArray(payload) && isRecord(payload[0]) && typeof payload[0].command === "string" ? payload[0].command : "";
25387
- hooks[mapping.target_event] = { command: sourceCommand };
25655
+ const extracted = extractHookCommand(payload);
25656
+ if (!extracted.command) {
25657
+ droppedEvents.push(sourceEvent);
25658
+ addHookLoss(
25659
+ lossReport,
25660
+ "cursor",
25661
+ sourceEvent,
25662
+ sourceLabel,
25663
+ "error",
25664
+ `${sourceEvent}: no command could be read from the source entry, so nothing was written for Cursor. To fix: check that the hook declares a command \u2014 'tangyr validate' names the file and the field.`
25665
+ );
25666
+ dropped += 1;
25667
+ continue;
25668
+ }
25669
+ hooks[mapping.target_event] = { command: extracted.command };
25388
25670
  translated += 1;
25671
+ if (extracted.matcher) {
25672
+ addInformational(
25673
+ lossReport,
25674
+ "cursor",
25675
+ "hooks",
25676
+ sourceLabel,
25677
+ "warning",
25678
+ `${sourceEvent}: the source scopes this hook to '${extracted.matcher}', and Cursor's hook entry carries no matcher. It will run for every tool call on this event, not only the matched ones.`
25679
+ );
25680
+ }
25389
25681
  continue;
25390
25682
  }
25391
25683
  droppedEvents.push(sourceEvent);
@@ -25504,7 +25796,9 @@ function compileCopilotHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
25504
25796
  }
25505
25797
  for (const [sourceEvent, payload] of Object.entries(sourceHooks)) {
25506
25798
  const mapping = normalizeHookEventRule(
25507
- targetMapping.source_to_target[sourceEvent]
25799
+ targetMapping.source_to_target[sourceEvent],
25800
+ "copilot",
25801
+ sourceEvent
25508
25802
  );
25509
25803
  if (mapping.status === "mapped" && mapping.target_event) {
25510
25804
  hooks[mapping.target_event] = payload;
@@ -25539,11 +25833,35 @@ function compileCopilotHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
25539
25833
  }
25540
25834
  return { payload: { hooks }, translated, dropped, droppedEvents };
25541
25835
  }
25542
- function normalizeHookEventRule(value) {
25836
+ function extractHookCommand(payload) {
25837
+ const entries = Array.isArray(payload) ? payload : [payload];
25838
+ for (const entry of entries) {
25839
+ if (!isRecord(entry)) continue;
25840
+ const matcher = typeof entry.matcher === "string" && entry.matcher ? { matcher: entry.matcher } : {};
25841
+ if (typeof entry.command === "string" && entry.command) {
25842
+ return { command: entry.command, ...matcher };
25843
+ }
25844
+ const inner = Array.isArray(entry.hooks) ? entry.hooks : [];
25845
+ for (const hook of inner) {
25846
+ if (isRecord(hook) && typeof hook.command === "string" && hook.command) {
25847
+ return { command: hook.command, ...matcher };
25848
+ }
25849
+ }
25850
+ }
25851
+ return { command: "" };
25852
+ }
25853
+ function normalizeHookEventRule(value, target, sourceEvent) {
25543
25854
  if (typeof value === "string") {
25544
25855
  return { status: "mapped", target_event: value };
25545
25856
  }
25546
- return value ?? { status: "drop" };
25857
+ if (value) {
25858
+ return value;
25859
+ }
25860
+ return {
25861
+ status: "unmapped",
25862
+ severity: "warning",
25863
+ reason: `mappings/hook-events.yaml has no entry for '${sourceEvent}' under target '${target}', so it was dropped without a decision behind it. To fix: add '${sourceEvent}' to that file \u2014 to source_events, and to every target's source_to_target with either a target_event or a stated reason for dropping it.`
25864
+ };
25547
25865
  }
25548
25866
  function compileCodexHooks(sourceHooksJson, mappings, lossReport, sourceLabel) {
25549
25867
  const targetMapping = mappings.hookEvents.targets.codex;
@@ -25557,7 +25875,9 @@ function compileCodexHooks(sourceHooksJson, mappings, lossReport, sourceLabel) {
25557
25875
  let dropped = 0;
25558
25876
  for (const [sourceEvent, payload] of Object.entries(sourceHooks)) {
25559
25877
  const mapping = normalizeHookEventRule(
25560
- targetMapping.source_to_target[sourceEvent]
25878
+ targetMapping.source_to_target[sourceEvent],
25879
+ "codex",
25880
+ sourceEvent
25561
25881
  );
25562
25882
  if (mapping.status === "mapped" && mapping.target_event) {
25563
25883
  hooks[mapping.target_event] = markCodexManaged(payload, sourceLabel);
@@ -25890,7 +26210,7 @@ function compileOpenCodeMcp(sourceMcpJson, lossReport, sourceLabel) {
25890
26210
  // src/compiler/rules.ts
25891
26211
  import path30 from "path";
25892
26212
  var import_yaml9 = __toESM(require_dist(), 1);
25893
- import fs31 from "fs";
26213
+ import fs32 from "fs";
25894
26214
  function compileRules(sourceRulesDir, target, _mappings, lossReport) {
25895
26215
  if (target === "copilot") {
25896
26216
  addLoss(lossReport, "copilot", "rules", {
@@ -25960,7 +26280,7 @@ ${parsed.body.trim()}`);
25960
26280
  return { agentsAppendix: appendixParts.join("\n\n"), dotRulesFiles };
25961
26281
  }
25962
26282
  function parseRule(ruleFile) {
25963
- const content = fs31.readFileSync(ruleFile, "utf8");
26283
+ const content = fs32.readFileSync(ruleFile, "utf8");
25964
26284
  if (!content.startsWith("---\n")) {
25965
26285
  return { mode: "guidance", body: content };
25966
26286
  }
@@ -26011,7 +26331,7 @@ ${parsed.body.trim()}`);
26011
26331
  function compileCursorRules(sourceRulesDir, _lossReport) {
26012
26332
  const dotRulesFiles = [];
26013
26333
  for (const ruleFile of ts(path30.join(sourceRulesDir, "*.md")).sort()) {
26014
- const content = fs31.readFileSync(ruleFile, "utf8");
26334
+ const content = fs32.readFileSync(ruleFile, "utf8");
26015
26335
  const name = path30.basename(ruleFile, ".md");
26016
26336
  let frontmatterData = {};
26017
26337
  let body = content;
@@ -26058,7 +26378,7 @@ ${body}`;
26058
26378
  function compileClineRules(sourceRulesDir, lossReport) {
26059
26379
  const dotRulesFiles = [];
26060
26380
  for (const ruleFile of ts(path30.join(sourceRulesDir, "*.md")).sort()) {
26061
- const content = fs31.readFileSync(ruleFile, "utf8");
26381
+ const content = fs32.readFileSync(ruleFile, "utf8");
26062
26382
  const name = path30.basename(ruleFile, ".md");
26063
26383
  let frontmatterData = {};
26064
26384
  let body = content;
@@ -26283,7 +26603,7 @@ function buildProviderTomlSection(config, sourceRoot, mappings, scope, lossRepor
26283
26603
  const cp = mappings.modelTiers.custom_provider;
26284
26604
  if (!cp) return "";
26285
26605
  const agentsDir = path31.join(sourceRoot, config.source.agents);
26286
- if (!fs32.existsSync(agentsDir)) return "";
26606
+ if (!fs33.existsSync(agentsDir)) return "";
26287
26607
  const agentFiles = ts(path31.join(agentsDir, "*.md")).sort();
26288
26608
  const hasProviderAgent = agentFiles.some((agentFile) => {
26289
26609
  const { data } = parseMarkdownFrontmatter(agentFile);
@@ -26379,7 +26699,7 @@ function syncRules(config, sourceRoot, paths, rules, options, logger, manifest,
26379
26699
  function syncInstructions(config, sourceRoot, paths, agentsAppendix, options, logger, manifest, scopePath) {
26380
26700
  const source = path31.join(sourceRoot, config.source.instructions);
26381
26701
  const destination = resolveTargetPath(paths.instructions);
26382
- const composed = `${fs32.readFileSync(source, "utf8").trim()}
26702
+ const composed = `${fs33.readFileSync(source, "utf8").trim()}
26383
26703
 
26384
26704
  --- tangyr managed rules ---
26385
26705
  ${agentsAppendix}`.trim();
@@ -26414,7 +26734,7 @@ ${composed}
26414
26734
  function syncSkills(config, sourceRoot, paths, options, logger, manifest, scopePath) {
26415
26735
  const source = path31.resolve(sourceRoot, config.source.skills);
26416
26736
  const destination = resolveTargetPath(paths.skills);
26417
- if (!fs32.existsSync(source)) {
26737
+ if (!fs33.existsSync(source)) {
26418
26738
  return skippedMissingSource("skills", source);
26419
26739
  }
26420
26740
  if (options.dryRun) {
@@ -26430,12 +26750,12 @@ function syncSkills(config, sourceRoot, paths, options, logger, manifest, scopeP
26430
26750
  };
26431
26751
  }
26432
26752
  if (artifactClass === "managed-stale" && isOurStaleSymlink(destination, /* @__PURE__ */ new Set([destination]), config)) {
26433
- fs32.unlinkSync(destination);
26753
+ fs33.unlinkSync(destination);
26434
26754
  logger.verbose(`Skills: created skills path ${destination}`);
26435
26755
  } else if (artifactClass === "unmanaged-conflict" && !handleConflict(destination, config, options, logger, manifest, scopePath)) {
26436
26756
  return { component: "skills", status: "conflict", path: destination };
26437
26757
  } else if (artifactClass === "managed-symlink") {
26438
- fs32.unlinkSync(destination);
26758
+ fs33.unlinkSync(destination);
26439
26759
  }
26440
26760
  ensureDir(path31.dirname(destination));
26441
26761
  createRelativeSymlink(source, destination, "dir");
@@ -26444,7 +26764,7 @@ function syncSkills(config, sourceRoot, paths, options, logger, manifest, scopeP
26444
26764
  }
26445
26765
  function syncAgents(config, sourceRoot, paths, mappings, extensions, options, lossReport, logger, manifest, scopePath) {
26446
26766
  const sourceAgentsRoot = path31.join(sourceRoot, config.source.agents);
26447
- if (!fs32.existsSync(sourceAgentsRoot)) {
26767
+ if (!fs33.existsSync(sourceAgentsRoot)) {
26448
26768
  return [skippedMissingSource("agents", sourceAgentsRoot)];
26449
26769
  }
26450
26770
  const sourceAgents = ts(path31.join(sourceAgentsRoot, "*.md")).sort();
@@ -26486,14 +26806,16 @@ function syncHooks(config, sourceRoot, paths, mappings, options, lossReport, log
26486
26806
  logger.warn(message);
26487
26807
  return { component: "hooks", status: "skipped", path: message };
26488
26808
  }
26489
- const source = resolveHooksSourceFile(sourceRoot, config);
26490
- const sourceLabel = hooksSourceLabel(config);
26491
- if (!fs32.existsSync(source)) {
26492
- return skippedMissingSource("hooks", source);
26809
+ const hooksSource = loadKitHooksSource(sourceRoot, config);
26810
+ if (!hooksSource) {
26811
+ return skippedMissingSource(
26812
+ "hooks",
26813
+ resolveHooksSourceDir(sourceRoot, config)
26814
+ );
26493
26815
  }
26494
- const sourceHooksJson = JSON.parse(fs32.readFileSync(source, "utf8"));
26816
+ const { path: source, label: sourceLabel } = hooksSource;
26495
26817
  const compiled = compileHooks(
26496
- sourceHooksJson,
26818
+ hooksSource.hooks,
26497
26819
  "codex",
26498
26820
  mappings,
26499
26821
  lossReport,
@@ -26513,7 +26835,7 @@ function syncHooks(config, sourceRoot, paths, mappings, options, lossReport, log
26513
26835
  destination,
26514
26836
  hostHooks,
26515
26837
  stringifyJson2,
26516
- !fs32.existsSync(destination),
26838
+ !fs33.existsSync(destination),
26517
26839
  logger
26518
26840
  );
26519
26841
  return {
@@ -26526,7 +26848,7 @@ function syncMcp(config, sourceRoot, paths, options, lossReport, logger, provide
26526
26848
  const source = resolveMcpSourceFile(sourceRoot, config);
26527
26849
  const sourceLabel = mcpSourceLabel(config);
26528
26850
  const destination = resolveTargetPath(paths.mcp_shared_file);
26529
- if (!fs32.existsSync(source)) {
26851
+ if (!fs33.existsSync(source)) {
26530
26852
  if (providerToml && !options.dryRun) {
26531
26853
  const changed2 = writeTomlManagedSection(
26532
26854
  destination,
@@ -26541,7 +26863,7 @@ function syncMcp(config, sourceRoot, paths, options, lossReport, logger, provide
26541
26863
  }
26542
26864
  return skippedMissingSource("mcp", source);
26543
26865
  }
26544
- const sourceMcpJson = JSON.parse(fs32.readFileSync(source, "utf8"));
26866
+ const sourceMcpJson = JSON.parse(fs33.readFileSync(source, "utf8"));
26545
26867
  const compiled = compileMcp(sourceMcpJson, "codex", lossReport, sourceLabel);
26546
26868
  const managedContent = providerToml ? `${compiled.trimEnd()}${providerToml}` : compiled;
26547
26869
  if (options.dryRun) {
@@ -26564,11 +26886,11 @@ function writeCompiledArtifact(component, destination, content, expectedHash, co
26564
26886
  return { component, status: "conflict", path: destination };
26565
26887
  }
26566
26888
  ensureDir(path31.dirname(destination));
26567
- fs32.writeFileSync(destination, content);
26889
+ fs33.writeFileSync(destination, content);
26568
26890
  return { component, status: "success", path: destination };
26569
26891
  }
26570
26892
  function writeTomlManagedSection(destination, managedContent, logger) {
26571
- const current = fs32.existsSync(destination) ? fs32.readFileSync(destination, "utf8") : "";
26893
+ const current = fs33.existsSync(destination) ? fs33.readFileSync(destination, "utf8") : "";
26572
26894
  const isFirstWrite = current.length > 0 && !current.includes(managedMcpStart);
26573
26895
  if (isFirstWrite) {
26574
26896
  logger.warn(
@@ -26585,7 +26907,7 @@ ${managedMcpEnd}
26585
26907
  return false;
26586
26908
  }
26587
26909
  ensureDir(path31.dirname(destination));
26588
- fs32.writeFileSync(destination, next.trimStart());
26910
+ fs33.writeFileSync(destination, next.trimStart());
26589
26911
  return true;
26590
26912
  }
26591
26913
  function removeManagedTomlSection(content) {
@@ -26612,16 +26934,16 @@ function handleConflict(destination, config, options, logger, manifest, scopePat
26612
26934
  backupFile(destination, manifest);
26613
26935
  writeManifest(scopePath, manifest);
26614
26936
  } else {
26615
- fs32.renameSync(destination, `${destination}.bak.${Date.now()}`);
26937
+ fs33.renameSync(destination, `${destination}.bak.${Date.now()}`);
26616
26938
  }
26617
26939
  return true;
26618
26940
  }
26619
- fs32.rmSync(destination, { recursive: true, force: true });
26941
+ fs33.rmSync(destination, { recursive: true, force: true });
26620
26942
  return true;
26621
26943
  }
26622
26944
  function isCompiledCurrent(destination, expectedContent, expectedHash) {
26623
26945
  try {
26624
- const current = fs32.readFileSync(destination, "utf8");
26946
+ const current = fs33.readFileSync(destination, "utf8");
26625
26947
  const marker = extractProvenanceMarker(current);
26626
26948
  return expectedHash ? marker?.hash === expectedHash && marker.target === "codex" && current === expectedContent : current === expectedContent;
26627
26949
  } catch {
@@ -26640,16 +26962,16 @@ function shouldSkipComponent(options, component) {
26640
26962
  }
26641
26963
  function isSymlinkTo(linkPath, source) {
26642
26964
  try {
26643
- return fs32.realpathSync(linkPath) === fs32.realpathSync(source);
26965
+ return fs33.realpathSync(linkPath) === fs33.realpathSync(source);
26644
26966
  } catch {
26645
26967
  return false;
26646
26968
  }
26647
26969
  }
26648
26970
  function readJsonObject3(filePath) {
26649
- if (!fs32.existsSync(filePath)) {
26971
+ if (!fs33.existsSync(filePath)) {
26650
26972
  return {};
26651
26973
  }
26652
- const parsed = JSON.parse(fs32.readFileSync(filePath, "utf8"));
26974
+ const parsed = JSON.parse(fs33.readFileSync(filePath, "utf8"));
26653
26975
  return isRecord4(parsed) ? parsed : {};
26654
26976
  }
26655
26977
  function stringifyJson2(value) {
@@ -26665,7 +26987,7 @@ function hashString(value) {
26665
26987
 
26666
26988
  // src/adapters/copilot.ts
26667
26989
  import crypto6 from "crypto";
26668
- import fs33 from "fs";
26990
+ import fs34 from "fs";
26669
26991
  import path33 from "path";
26670
26992
 
26671
26993
  // src/compiler/commands.ts
@@ -26850,7 +27172,7 @@ function syncCopilot(config, sourceRoot, mappings, extensions, options, lossRepo
26850
27172
  function syncProviderLoss(config, sourceRoot, mappings, lossReport) {
26851
27173
  if (!mappings.modelTiers.custom_provider) return;
26852
27174
  const agentsDir = path33.join(sourceRoot, config.source.agents);
26853
- if (!fs33.existsSync(agentsDir)) return;
27175
+ if (!fs34.existsSync(agentsDir)) return;
26854
27176
  for (const agentFile of ts(path33.join(agentsDir, "*.md")).sort()) {
26855
27177
  const { data } = parseMarkdownFrontmatter(agentFile);
26856
27178
  const hasProvider = typeof data.provider === "string" || typeof data.provider === "object" && data.provider !== null && Object.keys(data.provider).length > 0;
@@ -26881,10 +27203,10 @@ function syncCopilotInstructions(config, sourceRoot, options, lossReport, logger
26881
27203
  return [{ component: "instructions", status: "skipped", path: message }];
26882
27204
  }
26883
27205
  const source = path33.resolve(sourceRoot, config.source.instructions);
26884
- if (!fs33.existsSync(source)) {
27206
+ if (!fs34.existsSync(source)) {
26885
27207
  return [skippedMissingSource2("instructions", source)];
26886
27208
  }
26887
- const rawContent = fs33.readFileSync(source, "utf8");
27209
+ const rawContent = fs34.readFileSync(source, "utf8");
26888
27210
  const contentHash = hashString2(rawContent);
26889
27211
  const marker = formatProvenanceMarker(
26890
27212
  "markdown",
@@ -26951,7 +27273,7 @@ function writeInstructionsFile(component, destination, content, contentHash, con
26951
27273
  return { component, status: "conflict", path: destination };
26952
27274
  }
26953
27275
  ensureDir(path33.dirname(destination));
26954
- fs33.writeFileSync(destination, content);
27276
+ fs34.writeFileSync(destination, content);
26955
27277
  return { component, status: "success", path: destination };
26956
27278
  }
26957
27279
  function hashString2(value) {
@@ -26960,7 +27282,7 @@ function hashString2(value) {
26960
27282
  function syncSkills2(config, sourceRoot, paths, options, logger, manifest, scopePath) {
26961
27283
  const source = path33.resolve(sourceRoot, config.source.skills);
26962
27284
  const destination = resolveTargetPath(paths.skills);
26963
- if (!fs33.existsSync(source)) {
27285
+ if (!fs34.existsSync(source)) {
26964
27286
  return skippedMissingSource2("skills", source);
26965
27287
  }
26966
27288
  if (options.dryRun) {
@@ -26976,11 +27298,11 @@ function syncSkills2(config, sourceRoot, paths, options, logger, manifest, scope
26976
27298
  };
26977
27299
  }
26978
27300
  if (artifactClass === "managed-stale" && isOurStaleSymlink(destination, /* @__PURE__ */ new Set([destination]), config)) {
26979
- fs33.unlinkSync(destination);
27301
+ fs34.unlinkSync(destination);
26980
27302
  } else if (artifactClass === "unmanaged-conflict" && !handleConflict2(destination, config, options, logger, manifest, scopePath)) {
26981
27303
  return { component: "skills", status: "conflict", path: destination };
26982
27304
  } else if (artifactClass === "managed-symlink") {
26983
- fs33.unlinkSync(destination);
27305
+ fs34.unlinkSync(destination);
26984
27306
  }
26985
27307
  ensureDir(path33.dirname(destination));
26986
27308
  createRelativeSymlink(source, destination, "dir");
@@ -26988,7 +27310,7 @@ function syncSkills2(config, sourceRoot, paths, options, logger, manifest, scope
26988
27310
  }
26989
27311
  function syncAgents2(config, sourceRoot, paths, mappings, extensions, options, lossReport, logger, manifest, scopePath) {
26990
27312
  const sourceAgentsRoot = path33.join(sourceRoot, config.source.agents);
26991
- if (!fs33.existsSync(sourceAgentsRoot)) {
27313
+ if (!fs34.existsSync(sourceAgentsRoot)) {
26992
27314
  return [skippedMissingSource2("agents", sourceAgentsRoot)];
26993
27315
  }
26994
27316
  const sourceAgents = ts(path33.join(sourceAgentsRoot, "*.md")).sort();
@@ -27025,7 +27347,7 @@ function syncAgents2(config, sourceRoot, paths, mappings, extensions, options, l
27025
27347
  }
27026
27348
  function syncCommands(config, sourceRoot, paths, options, logger, manifest, scopePath) {
27027
27349
  const sourceCommandsRoot = path33.join(sourceRoot, config.source.commands);
27028
- if (!fs33.existsSync(sourceCommandsRoot)) {
27350
+ if (!fs34.existsSync(sourceCommandsRoot)) {
27029
27351
  return [skippedMissingSource2("commands", sourceCommandsRoot)];
27030
27352
  }
27031
27353
  const sourceCommands = ts(path33.join(sourceCommandsRoot, "*.md")).sort();
@@ -27057,18 +27379,20 @@ function syncCommands(config, sourceRoot, paths, options, logger, manifest, scop
27057
27379
  });
27058
27380
  }
27059
27381
  function syncHooks2(config, sourceRoot, paths, mappings, options, lossReport, logger) {
27060
- const source = resolveHooksSourceFile(sourceRoot, config);
27061
- const sourceLabel = hooksSourceLabel(config);
27062
27382
  const destination = path33.join(
27063
27383
  resolveTargetPath(paths.hooks),
27064
27384
  "tangyr-managed.json"
27065
27385
  );
27066
- if (!fs33.existsSync(source)) {
27067
- return skippedMissingSource2("hooks", source);
27386
+ const hooksSource = loadKitHooksSource(sourceRoot, config);
27387
+ if (!hooksSource) {
27388
+ return skippedMissingSource2(
27389
+ "hooks",
27390
+ resolveHooksSourceDir(sourceRoot, config)
27391
+ );
27068
27392
  }
27069
- const sourceHooksJson = JSON.parse(fs33.readFileSync(source, "utf8"));
27393
+ const { path: source, label: sourceLabel, hash: sourceHash } = hooksSource;
27070
27394
  const compiled = compileHooks(
27071
- sourceHooksJson,
27395
+ hooksSource.hooks,
27072
27396
  "copilot",
27073
27397
  mappings,
27074
27398
  lossReport,
@@ -27077,7 +27401,7 @@ function syncHooks2(config, sourceRoot, paths, mappings, options, lossReport, lo
27077
27401
  const payload = {
27078
27402
  _tangyr: {
27079
27403
  source: sourceLabel,
27080
- hash: computeSourceHash(source),
27404
+ hash: sourceHash,
27081
27405
  target: "copilot"
27082
27406
  },
27083
27407
  ...compiled.payload
@@ -27092,7 +27416,7 @@ function syncHooks2(config, sourceRoot, paths, mappings, options, lossReport, lo
27092
27416
  "hooks",
27093
27417
  destination,
27094
27418
  serialized,
27095
- computeSourceHash(source),
27419
+ sourceHash,
27096
27420
  config,
27097
27421
  sourceRoot,
27098
27422
  options,
@@ -27103,10 +27427,10 @@ function syncMcp2(config, sourceRoot, paths, options, lossReport, logger) {
27103
27427
  const source = resolveMcpSourceFile(sourceRoot, config);
27104
27428
  const sourceLabel = mcpSourceLabel(config);
27105
27429
  const destination = resolveTargetPath(paths.mcp_shared_file);
27106
- if (!fs33.existsSync(source)) {
27430
+ if (!fs34.existsSync(source)) {
27107
27431
  return skippedMissingSource2("mcp", source);
27108
27432
  }
27109
- const sourceMcpJson = JSON.parse(fs33.readFileSync(source, "utf8"));
27433
+ const sourceMcpJson = JSON.parse(fs34.readFileSync(source, "utf8"));
27110
27434
  const compiled = compileMcp(
27111
27435
  sourceMcpJson,
27112
27436
  "copilot",
@@ -27137,12 +27461,12 @@ function writeCompiledArtifact2(component, destination, content, expectedHash, c
27137
27461
  return { component, status: "conflict", path: destination };
27138
27462
  }
27139
27463
  ensureDir(path33.dirname(destination));
27140
- fs33.writeFileSync(destination, content);
27464
+ fs34.writeFileSync(destination, content);
27141
27465
  return { component, status: "success", path: destination };
27142
27466
  }
27143
27467
  function isCompiledCurrent2(destination, expectedContent, expectedHash) {
27144
27468
  try {
27145
- const current = fs33.readFileSync(destination, "utf8");
27469
+ const current = fs34.readFileSync(destination, "utf8");
27146
27470
  const marker = extractProvenanceMarker(current);
27147
27471
  return expectedHash ? marker?.hash === expectedHash && marker.target === "copilot" && current === expectedContent : current === expectedContent;
27148
27472
  } catch {
@@ -27165,11 +27489,11 @@ function handleConflict2(destination, config, options, logger, manifest, scopePa
27165
27489
  backupFile(destination, manifest);
27166
27490
  writeManifest(scopePath, manifest);
27167
27491
  } else {
27168
- fs33.renameSync(destination, `${destination}.bak.${Date.now()}`);
27492
+ fs34.renameSync(destination, `${destination}.bak.${Date.now()}`);
27169
27493
  }
27170
27494
  return true;
27171
27495
  }
27172
- fs33.rmSync(destination, { recursive: true, force: true });
27496
+ fs34.rmSync(destination, { recursive: true, force: true });
27173
27497
  return true;
27174
27498
  }
27175
27499
  function shouldSkipComponent2(options, component) {
@@ -27193,14 +27517,14 @@ function cleanupLegacyAgents(paths, options, logger) {
27193
27517
  outcomes.push({ component: "agents", status: "dry-run", path: filePath });
27194
27518
  continue;
27195
27519
  }
27196
- fs33.rmSync(filePath, { force: true });
27520
+ fs34.rmSync(filePath, { force: true });
27197
27521
  outcomes.push({ component: "agents", status: "success", path: filePath });
27198
27522
  }
27199
27523
  return outcomes;
27200
27524
  }
27201
27525
  function isCopilotManagedCompiledFile(filePath) {
27202
27526
  try {
27203
- return extractProvenanceMarker(fs33.readFileSync(filePath, "utf8"))?.target === "copilot";
27527
+ return extractProvenanceMarker(fs34.readFileSync(filePath, "utf8"))?.target === "copilot";
27204
27528
  } catch {
27205
27529
  return false;
27206
27530
  }
@@ -27214,7 +27538,7 @@ function skippedMissingSource2(component, source) {
27214
27538
  }
27215
27539
  function isSymlinkTo2(linkPath, source) {
27216
27540
  try {
27217
- return fs33.realpathSync(linkPath) === fs33.realpathSync(source);
27541
+ return fs34.realpathSync(linkPath) === fs34.realpathSync(source);
27218
27542
  } catch {
27219
27543
  return false;
27220
27544
  }
@@ -27222,7 +27546,7 @@ function isSymlinkTo2(linkPath, source) {
27222
27546
 
27223
27547
  // src/adapters/cursor.ts
27224
27548
  import crypto7 from "crypto";
27225
- import fs34 from "fs";
27549
+ import fs35 from "fs";
27226
27550
  import path34 from "path";
27227
27551
  function syncCursor(config, sourceRoot, mappings, extensions, options, lossReport, logger, manifest, scopePath) {
27228
27552
  const scope = options.scope ?? "global";
@@ -27376,7 +27700,7 @@ function syncCursor(config, sourceRoot, mappings, extensions, options, lossRepor
27376
27700
  function syncInstructions2(config, sourceRoot, paths, options, lossReport, logger, manifest, scopePath) {
27377
27701
  const source = path34.join(sourceRoot, config.source.instructions);
27378
27702
  const destination = resolveTargetPath(paths.instructions);
27379
- const baseContent = fs34.existsSync(source) ? fs34.readFileSync(source, "utf8").trim() : "";
27703
+ const baseContent = fs35.existsSync(source) ? fs35.readFileSync(source, "utf8").trim() : "";
27380
27704
  const personaContent = extractPrimaryPersonaContent(
27381
27705
  config,
27382
27706
  sourceRoot,
@@ -27415,7 +27739,7 @@ ${composed}
27415
27739
  }
27416
27740
  function extractPrimaryPersonaContent(config, sourceRoot, lossReport) {
27417
27741
  const agentsDir = path34.join(sourceRoot, config.source.agents);
27418
- if (!fs34.existsSync(agentsDir)) {
27742
+ if (!fs35.existsSync(agentsDir)) {
27419
27743
  return null;
27420
27744
  }
27421
27745
  const agentFiles = ts(path34.join(agentsDir, "*.md")).sort();
@@ -27459,7 +27783,7 @@ function extractPrimaryPersonaContent(config, sourceRoot, lossReport) {
27459
27783
  }
27460
27784
  function syncAgents3(config, sourceRoot, paths, mappings, extensions, options, lossReport, logger, manifest, scopePath) {
27461
27785
  const sourceAgentsRoot = path34.join(sourceRoot, config.source.agents);
27462
- if (!fs34.existsSync(sourceAgentsRoot)) {
27786
+ if (!fs35.existsSync(sourceAgentsRoot)) {
27463
27787
  return [skippedMissingSource3("agents", sourceAgentsRoot)];
27464
27788
  }
27465
27789
  const sourceAgents = ts(path34.join(sourceAgentsRoot, "*.md")).sort();
@@ -27509,7 +27833,7 @@ function syncAgents3(config, sourceRoot, paths, mappings, extensions, options, l
27509
27833
  }
27510
27834
  function syncRules2(config, sourceRoot, paths, mappings, options, lossReport, logger, manifest, scopePath) {
27511
27835
  const sourceRulesDir = path34.join(sourceRoot, config.source.rules);
27512
- if (!fs34.existsSync(sourceRulesDir)) {
27836
+ if (!fs35.existsSync(sourceRulesDir)) {
27513
27837
  return [skippedMissingSource3("rules", sourceRulesDir)];
27514
27838
  }
27515
27839
  const compiled = compileRules(sourceRulesDir, "cursor", mappings, lossReport);
@@ -27540,7 +27864,7 @@ function syncRules2(config, sourceRoot, paths, mappings, options, lossReport, lo
27540
27864
  }
27541
27865
  function syncCommands2(config, sourceRoot, paths, options, logger, manifest, scopePath) {
27542
27866
  const sourceCommandsRoot = path34.join(sourceRoot, config.source.commands);
27543
- if (!fs34.existsSync(sourceCommandsRoot)) {
27867
+ if (!fs35.existsSync(sourceCommandsRoot)) {
27544
27868
  return [skippedMissingSource3("commands", sourceCommandsRoot)];
27545
27869
  }
27546
27870
  const sourceCommands = ts(path34.join(sourceCommandsRoot, "*.md")).sort();
@@ -27548,7 +27872,7 @@ function syncCommands2(config, sourceRoot, paths, options, logger, manifest, sco
27548
27872
  return sourceCommands.map((commandFile) => {
27549
27873
  const baseName = path34.basename(commandFile, ".md");
27550
27874
  const destination = path34.join(skillsRoot, baseName, "SKILL.md");
27551
- const commandContent = fs34.readFileSync(commandFile, "utf8");
27875
+ const commandContent = fs35.readFileSync(commandFile, "utf8");
27552
27876
  const relativeSource = `commands/${path34.basename(commandFile)}`;
27553
27877
  const marker = formatProvenanceMarker(
27554
27878
  "markdown",
@@ -27589,7 +27913,7 @@ ${commandContent.trim()}
27589
27913
  }
27590
27914
  function syncSkills3(config, sourceRoot, options, logger, manifest, scopePath, component = "skills", destination = "", asSymlink = false) {
27591
27915
  const source = path34.resolve(sourceRoot, config.source.skills);
27592
- if (!fs34.existsSync(source)) {
27916
+ if (!fs35.existsSync(source)) {
27593
27917
  return skippedMissingSource3(component, source);
27594
27918
  }
27595
27919
  if (options.dryRun) {
@@ -27603,18 +27927,18 @@ function syncSkills3(config, sourceRoot, options, logger, manifest, scopePath, c
27603
27927
  return { component, status: "skipped-current", path: destination };
27604
27928
  }
27605
27929
  if (artifactClass === "managed-stale" && isOurStaleSymlink(destination, /* @__PURE__ */ new Set([destination]), config)) {
27606
- fs34.unlinkSync(destination);
27930
+ fs35.unlinkSync(destination);
27607
27931
  } else if (artifactClass === "unmanaged-conflict" && !handleConflict3(destination, config, options, logger, manifest, scopePath)) {
27608
27932
  return { component, status: "conflict", path: destination };
27609
27933
  } else if (artifactClass === "managed-symlink") {
27610
- fs34.unlinkSync(destination);
27934
+ fs35.unlinkSync(destination);
27611
27935
  }
27612
27936
  ensureDir(path34.dirname(destination));
27613
27937
  createRelativeSymlink(source, destination, "dir");
27614
27938
  logger.verbose(`Skills: created shared skills symlink ${destination}`);
27615
27939
  return { component, status: "success", path: destination };
27616
27940
  }
27617
- if (fs34.existsSync(destination)) {
27941
+ if (fs35.existsSync(destination)) {
27618
27942
  return { component, status: "skipped-current", path: destination };
27619
27943
  }
27620
27944
  ensureDir(destination);
@@ -27622,10 +27946,11 @@ function syncSkills3(config, sourceRoot, options, logger, manifest, scopePath, c
27622
27946
  return { component, status: "success", path: destination };
27623
27947
  }
27624
27948
  function syncHooks3(config, sourceRoot, paths, mappings, options, lossReport, logger) {
27625
- const source = resolveHooksSourceFile(sourceRoot, config);
27626
- const sourceLabel = hooksSourceLabel(config);
27949
+ const hooksSource = loadKitHooksSource(sourceRoot, config);
27950
+ const source = hooksSource?.path ?? resolveHooksSourceDir(sourceRoot, config);
27951
+ const sourceLabel = hooksSource?.label ?? hooksSourceLabel(sourceRoot, config);
27627
27952
  const destination = resolveTargetPath(paths.hooks_file);
27628
- const sourceHooksJson = fs34.existsSync(source) ? JSON.parse(fs34.readFileSync(source, "utf8")) : { hooks: {} };
27953
+ const sourceHooksJson = hooksSource?.hooks ?? { hooks: {} };
27629
27954
  const compiled = compileHooks(
27630
27955
  sourceHooksJson,
27631
27956
  "cursor",
@@ -27640,17 +27965,17 @@ function syncHooks3(config, sourceRoot, paths, mappings, options, lossReport, lo
27640
27965
  const hooksFileContent = `${JSON.stringify({ version: 1, hooks: compiled.payload }, null, 2)}
27641
27966
  `;
27642
27967
  ensureDir(path34.dirname(destination));
27643
- fs34.writeFileSync(destination, hooksFileContent);
27968
+ fs35.writeFileSync(destination, hooksFileContent);
27644
27969
  return { component: "hooks", status: "success", path: destination };
27645
27970
  }
27646
27971
  function syncMcp3(config, sourceRoot, paths, options, lossReport, logger, manifest, scopePath) {
27647
27972
  const source = resolveMcpSourceFile(sourceRoot, config);
27648
27973
  const sourceLabel = mcpSourceLabel(config);
27649
27974
  const destination = resolveTargetPath(paths.mcp_file);
27650
- if (!fs34.existsSync(source)) {
27975
+ if (!fs35.existsSync(source)) {
27651
27976
  return skippedMissingSource3("mcp", source);
27652
27977
  }
27653
- const sourceMcpJson = JSON.parse(fs34.readFileSync(source, "utf8"));
27978
+ const sourceMcpJson = JSON.parse(fs35.readFileSync(source, "utf8"));
27654
27979
  const mcpJson = compileMcp(sourceMcpJson, "cursor", lossReport, sourceLabel);
27655
27980
  if (options.dryRun) {
27656
27981
  logger.info(`[dry-run] mcp: compile ${source} -> ${destination}`);
@@ -27702,7 +28027,7 @@ function syncPermissions(config, paths, options, _lossReport, logger, manifest,
27702
28027
  }
27703
28028
  function syncProviderPin(config, sourceRoot, lossReport) {
27704
28029
  const agentsDir = path34.join(sourceRoot, config.source.agents);
27705
- if (!fs34.existsSync(agentsDir)) return;
28030
+ if (!fs35.existsSync(agentsDir)) return;
27706
28031
  for (const agentFile of ts(path34.join(agentsDir, "*.md")).sort()) {
27707
28032
  const { data } = parseMarkdownFrontmatter(agentFile);
27708
28033
  const hasCustomProvider = typeof data.provider === "string" || typeof data.provider === "object" && data.provider !== null && Object.keys(data.provider).length > 0;
@@ -27735,7 +28060,7 @@ function writeCompiledArtifact3(component, destination, content, expectedHash, c
27735
28060
  return { component, status: "conflict", path: destination };
27736
28061
  }
27737
28062
  ensureDir(path34.dirname(destination));
27738
- fs34.writeFileSync(destination, content);
28063
+ fs35.writeFileSync(destination, content);
27739
28064
  return { component, status: "success", path: destination };
27740
28065
  }
27741
28066
  function handleConflict3(destination, config, options, logger, manifest, scopePath) {
@@ -27754,16 +28079,16 @@ function handleConflict3(destination, config, options, logger, manifest, scopePa
27754
28079
  backupFile(destination, manifest);
27755
28080
  writeManifest(scopePath, manifest);
27756
28081
  } else {
27757
- fs34.renameSync(destination, `${destination}.bak.${Date.now()}`);
28082
+ fs35.renameSync(destination, `${destination}.bak.${Date.now()}`);
27758
28083
  }
27759
28084
  return true;
27760
28085
  }
27761
- fs34.rmSync(destination, { recursive: true, force: true });
28086
+ fs35.rmSync(destination, { recursive: true, force: true });
27762
28087
  return true;
27763
28088
  }
27764
28089
  function isCompiledCurrent3(destination, expectedContent, expectedHash) {
27765
28090
  try {
27766
- const current = fs34.readFileSync(destination, "utf8");
28091
+ const current = fs35.readFileSync(destination, "utf8");
27767
28092
  const marker = extractProvenanceMarker(current);
27768
28093
  return expectedHash ? marker?.hash === expectedHash && marker.target === "cursor" && current === expectedContent : current === expectedContent;
27769
28094
  } catch {
@@ -27782,7 +28107,7 @@ function shouldSkipComponent3(options, component) {
27782
28107
  }
27783
28108
  function isSymlinkTo3(linkPath, source) {
27784
28109
  try {
27785
- return fs34.realpathSync(linkPath) === fs34.realpathSync(source);
28110
+ return fs35.realpathSync(linkPath) === fs35.realpathSync(source);
27786
28111
  } catch {
27787
28112
  return false;
27788
28113
  }
@@ -27793,7 +28118,7 @@ function hashString3(value) {
27793
28118
 
27794
28119
  // src/adapters/cline.ts
27795
28120
  import crypto8 from "crypto";
27796
- import fs35 from "fs";
28121
+ import fs36 from "fs";
27797
28122
  import path35 from "path";
27798
28123
  function syncCline(config, sourceRoot, mappings, extensions, options, lossReport, logger, manifest, scopePath) {
27799
28124
  const scope = options.scope ?? "global";
@@ -27929,7 +28254,7 @@ function syncInstructions3(config, sourceRoot, paths, scope, options, lossReport
27929
28254
  }
27930
28255
  const source = path35.join(sourceRoot, config.source.instructions);
27931
28256
  const destination = resolveTargetPath(paths.instructions);
27932
- const baseContent = fs35.existsSync(source) ? fs35.readFileSync(source, "utf8").trim() : "";
28257
+ const baseContent = fs36.existsSync(source) ? fs36.readFileSync(source, "utf8").trim() : "";
27933
28258
  const marker = formatProvenanceMarker(
27934
28259
  "markdown",
27935
28260
  path35.basename(config.source.instructions),
@@ -27958,7 +28283,7 @@ ${baseContent}
27958
28283
  }
27959
28284
  function syncAgents4(config, sourceRoot, mappings, extensions, options, lossReport, logger) {
27960
28285
  const sourceAgentsRoot = path35.join(sourceRoot, config.source.agents);
27961
- if (!fs35.existsSync(sourceAgentsRoot)) {
28286
+ if (!fs36.existsSync(sourceAgentsRoot)) {
27962
28287
  return [skippedMissingSource4("agents", sourceAgentsRoot)];
27963
28288
  }
27964
28289
  const sourceAgents = ts(path35.join(sourceAgentsRoot, "*.md")).sort();
@@ -27978,7 +28303,7 @@ function syncAgents4(config, sourceRoot, mappings, extensions, options, lossRepo
27978
28303
  }
27979
28304
  function syncRules3(config, sourceRoot, paths, mappings, options, lossReport, logger, manifest, scopePath) {
27980
28305
  const sourceRulesDir = path35.join(sourceRoot, config.source.rules);
27981
- if (!fs35.existsSync(sourceRulesDir)) {
28306
+ if (!fs36.existsSync(sourceRulesDir)) {
27982
28307
  return [skippedMissingSource4("rules", sourceRulesDir)];
27983
28308
  }
27984
28309
  const compiled = compileRules(sourceRulesDir, "cline", mappings, lossReport);
@@ -28009,7 +28334,7 @@ function syncRules3(config, sourceRoot, paths, mappings, options, lossReport, lo
28009
28334
  }
28010
28335
  function syncCommands3(config, sourceRoot, paths, options, logger, manifest, scopePath) {
28011
28336
  const sourceCommandsRoot = path35.join(sourceRoot, config.source.commands);
28012
- if (!fs35.existsSync(sourceCommandsRoot)) {
28337
+ if (!fs36.existsSync(sourceCommandsRoot)) {
28013
28338
  return [skippedMissingSource4("commands", sourceCommandsRoot)];
28014
28339
  }
28015
28340
  const sourceCommands = ts(path35.join(sourceCommandsRoot, "*.md")).sort();
@@ -28017,7 +28342,7 @@ function syncCommands3(config, sourceRoot, paths, options, logger, manifest, sco
28017
28342
  return sourceCommands.map((commandFile) => {
28018
28343
  const baseName = path35.basename(commandFile);
28019
28344
  const destination = path35.join(workflowsRoot, baseName);
28020
- const commandContent = fs35.readFileSync(commandFile, "utf8");
28345
+ const commandContent = fs36.readFileSync(commandFile, "utf8");
28021
28346
  const relativeSource = `commands/${path35.basename(commandFile)}`;
28022
28347
  const marker = formatProvenanceMarker(
28023
28348
  "markdown",
@@ -28055,7 +28380,7 @@ ${commandContent.trim()}
28055
28380
  function syncSkills4(config, sourceRoot, paths, options, logger, manifest, scopePath) {
28056
28381
  const source = path35.resolve(sourceRoot, config.source.skills);
28057
28382
  const destination = resolveTargetPath(paths.skills);
28058
- if (!fs35.existsSync(source)) {
28383
+ if (!fs36.existsSync(source)) {
28059
28384
  return skippedMissingSource4("skills", source);
28060
28385
  }
28061
28386
  if (options.dryRun) {
@@ -28075,7 +28400,7 @@ function syncSkills4(config, sourceRoot, paths, options, logger, manifest, scope
28075
28400
  return { component: "skills", status: "conflict", path: destination };
28076
28401
  }
28077
28402
  } else if (artifactClass === "managed-symlink") {
28078
- fs35.unlinkSync(destination);
28403
+ fs36.unlinkSync(destination);
28079
28404
  }
28080
28405
  ensureDir(path35.dirname(destination));
28081
28406
  createRelativeSymlink(source, destination, "dir");
@@ -28083,9 +28408,10 @@ function syncSkills4(config, sourceRoot, paths, options, logger, manifest, scope
28083
28408
  return { component: "skills", status: "success", path: destination };
28084
28409
  }
28085
28410
  function syncHooks4(config, sourceRoot, mappings, options, lossReport, logger) {
28086
- const source = resolveHooksSourceFile(sourceRoot, config);
28087
- const sourceLabel = hooksSourceLabel(config);
28088
- const sourceHooksJson = fs35.existsSync(source) ? JSON.parse(fs35.readFileSync(source, "utf8")) : { hooks: {} };
28411
+ const hooksSource = loadKitHooksSource(sourceRoot, config);
28412
+ const source = hooksSource?.path ?? resolveHooksSourceDir(sourceRoot, config);
28413
+ const sourceLabel = hooksSource?.label ?? hooksSourceLabel(sourceRoot, config);
28414
+ const sourceHooksJson = hooksSource?.hooks ?? { hooks: {} };
28089
28415
  compileHooks(sourceHooksJson, "cline", mappings, lossReport, sourceLabel);
28090
28416
  if (options.dryRun) {
28091
28417
  logger.info(`[dry-run] hooks: record hook-as-plugin loss for ${source}`);
@@ -28100,10 +28426,10 @@ function syncMcp4(config, sourceRoot, paths, options, lossReport, logger, manife
28100
28426
  const source = resolveMcpSourceFile(sourceRoot, config);
28101
28427
  const sourceLabel = mcpSourceLabel(config);
28102
28428
  const destination = resolveTargetPath(paths.mcp_file);
28103
- if (!fs35.existsSync(source)) {
28429
+ if (!fs36.existsSync(source)) {
28104
28430
  return skippedMissingSource4("mcp", source);
28105
28431
  }
28106
- const sourceMcpJson = JSON.parse(fs35.readFileSync(source, "utf8"));
28432
+ const sourceMcpJson = JSON.parse(fs36.readFileSync(source, "utf8"));
28107
28433
  const mcpJson = compileMcp(sourceMcpJson, "cline", lossReport, sourceLabel);
28108
28434
  if (options.dryRun) {
28109
28435
  logger.info(`[dry-run] mcp: compile ${source} -> ${destination}`);
@@ -28147,7 +28473,7 @@ function syncSettings(lossReport) {
28147
28473
  }
28148
28474
  function syncProviderAndModel(config, sourceRoot, lossReport) {
28149
28475
  const agentsDir = path35.join(sourceRoot, config.source.agents);
28150
- if (!fs35.existsSync(agentsDir)) return;
28476
+ if (!fs36.existsSync(agentsDir)) return;
28151
28477
  for (const agentFile of ts(path35.join(agentsDir, "*.md")).sort()) {
28152
28478
  const { data } = parseMarkdownFrontmatter(agentFile);
28153
28479
  const relativeSource = `agents/${path35.basename(agentFile)}`;
@@ -28198,7 +28524,7 @@ function writeCompiledArtifact4(component, destination, content, expectedHash, c
28198
28524
  return { component, status: "conflict", path: destination };
28199
28525
  }
28200
28526
  ensureDir(path35.dirname(destination));
28201
- fs35.writeFileSync(destination, content);
28527
+ fs36.writeFileSync(destination, content);
28202
28528
  return { component, status: "success", path: destination };
28203
28529
  }
28204
28530
  function handleConflict4(destination, config, options, logger, manifest, scopePath) {
@@ -28217,16 +28543,16 @@ function handleConflict4(destination, config, options, logger, manifest, scopePa
28217
28543
  backupFile(destination, manifest);
28218
28544
  writeManifest(scopePath, manifest);
28219
28545
  } else {
28220
- fs35.renameSync(destination, `${destination}.bak.${Date.now()}`);
28546
+ fs36.renameSync(destination, `${destination}.bak.${Date.now()}`);
28221
28547
  }
28222
28548
  return true;
28223
28549
  }
28224
- fs35.rmSync(destination, { recursive: true, force: true });
28550
+ fs36.rmSync(destination, { recursive: true, force: true });
28225
28551
  return true;
28226
28552
  }
28227
28553
  function isCompiledCurrent4(destination, expectedContent, expectedHash) {
28228
28554
  try {
28229
- const current = fs35.readFileSync(destination, "utf8");
28555
+ const current = fs36.readFileSync(destination, "utf8");
28230
28556
  const marker = extractProvenanceMarker(current);
28231
28557
  return expectedHash ? marker?.hash === expectedHash && marker.target === "cline" && current === expectedContent : current === expectedContent;
28232
28558
  } catch {
@@ -28245,7 +28571,7 @@ function shouldSkipComponent4(options, component) {
28245
28571
  }
28246
28572
  function isSymlinkTo4(linkPath, source) {
28247
28573
  try {
28248
- return fs35.realpathSync(linkPath) === fs35.realpathSync(source);
28574
+ return fs36.realpathSync(linkPath) === fs36.realpathSync(source);
28249
28575
  } catch {
28250
28576
  return false;
28251
28577
  }
@@ -28256,7 +28582,7 @@ function hashString4(value) {
28256
28582
 
28257
28583
  // src/adapters/opencode.ts
28258
28584
  import crypto9 from "crypto";
28259
- import fs36 from "fs";
28585
+ import fs37 from "fs";
28260
28586
  import os6 from "os";
28261
28587
  import path36 from "path";
28262
28588
  var import_yaml11 = __toESM(require_dist(), 1);
@@ -28380,7 +28706,7 @@ function syncOpenCode(config, sourceRoot, mappings, extensions, options, lossRep
28380
28706
  function syncInstructions4(config, sourceRoot, paths, agentsAppendix, options, logger, manifest, scopePath) {
28381
28707
  const source = path36.join(sourceRoot, config.source.instructions);
28382
28708
  const destination = resolveTargetPath(paths.instructions);
28383
- const baseContent = fs36.existsSync(source) ? fs36.readFileSync(source, "utf8").trim() : "";
28709
+ const baseContent = fs37.existsSync(source) ? fs37.readFileSync(source, "utf8").trim() : "";
28384
28710
  const composed = agentsAppendix ? `${baseContent}
28385
28711
 
28386
28712
  --- tangyr managed rules ---
@@ -28415,7 +28741,7 @@ ${composed}
28415
28741
  }
28416
28742
  function syncAgents5(config, sourceRoot, paths, mappings, extensions, options, lossReport, logger, manifest, scopePath) {
28417
28743
  const sourceAgentsRoot = path36.join(sourceRoot, config.source.agents);
28418
- if (!fs36.existsSync(sourceAgentsRoot)) {
28744
+ if (!fs37.existsSync(sourceAgentsRoot)) {
28419
28745
  return [skippedMissingSource5("agents", sourceAgentsRoot)];
28420
28746
  }
28421
28747
  const sourceAgents = ts(path36.join(sourceAgentsRoot, "*.md")).sort();
@@ -28488,7 +28814,7 @@ function applyProviderModelBinding(compiled, mappings) {
28488
28814
  }
28489
28815
  function syncCommands4(config, sourceRoot, paths, options, logger, manifest, scopePath) {
28490
28816
  const sourceCommandsRoot = path36.join(sourceRoot, config.source.commands);
28491
- if (!fs36.existsSync(sourceCommandsRoot)) {
28817
+ if (!fs37.existsSync(sourceCommandsRoot)) {
28492
28818
  return [skippedMissingSource5("commands", sourceCommandsRoot)];
28493
28819
  }
28494
28820
  const sourceCommands = ts(path36.join(sourceCommandsRoot, "*.md")).sort();
@@ -28551,7 +28877,7 @@ ${body.trimEnd()}
28551
28877
  }
28552
28878
  function syncSkills5(config, sourceRoot, _paths, options, logger, manifest, scopePath, component = "skills", destination = "") {
28553
28879
  const source = path36.resolve(sourceRoot, config.source.skills);
28554
- if (!fs36.existsSync(source)) {
28880
+ if (!fs37.existsSync(source)) {
28555
28881
  return skippedMissingSource5(component, source);
28556
28882
  }
28557
28883
  if (options.dryRun) {
@@ -28563,11 +28889,11 @@ function syncSkills5(config, sourceRoot, _paths, options, logger, manifest, scop
28563
28889
  return { component, status: "skipped-current", path: destination };
28564
28890
  }
28565
28891
  if (artifactClass === "managed-stale" && isOurStaleSymlink(destination, /* @__PURE__ */ new Set([destination]), config)) {
28566
- fs36.unlinkSync(destination);
28892
+ fs37.unlinkSync(destination);
28567
28893
  } else if (artifactClass === "unmanaged-conflict" && !handleConflict5(destination, config, options, logger, manifest, scopePath)) {
28568
28894
  return { component, status: "conflict", path: destination };
28569
28895
  } else if (artifactClass === "managed-symlink") {
28570
- fs36.unlinkSync(destination);
28896
+ fs37.unlinkSync(destination);
28571
28897
  }
28572
28898
  ensureDir(path36.dirname(destination));
28573
28899
  createRelativeSymlink(source, destination, "dir");
@@ -28578,13 +28904,13 @@ function syncMcp5(config, sourceRoot, paths, mappings, options, lossReport, logg
28578
28904
  const source = resolveMcpSourceFile(sourceRoot, config);
28579
28905
  const sourceLabel = mcpSourceLabel(config);
28580
28906
  const destination = resolveTargetPath(paths.config_doc);
28581
- if (!fs36.existsSync(source)) {
28907
+ if (!fs37.existsSync(source)) {
28582
28908
  if (!options.dryRun) {
28583
28909
  syncConfigDoc(paths, {}, lossReport, {}, mappings);
28584
28910
  }
28585
28911
  return skippedMissingSource5("mcp", source);
28586
28912
  }
28587
- const sourceMcpJson = JSON.parse(fs36.readFileSync(source, "utf8"));
28913
+ const sourceMcpJson = JSON.parse(fs37.readFileSync(source, "utf8"));
28588
28914
  const mcpJson = compileMcp(
28589
28915
  sourceMcpJson,
28590
28916
  "opencode",
@@ -28613,9 +28939,10 @@ function syncPermissions2(config, _sourceRoot, paths, options, lossReport, logge
28613
28939
  return { component: "settings", status: "success", path: destination };
28614
28940
  }
28615
28941
  function syncHooks5(config, sourceRoot, mappings, options, lossReport) {
28616
- const source = resolveHooksSourceFile(sourceRoot, config);
28617
- const sourceLabel = hooksSourceLabel(config);
28618
- const sourceHooksJson = fs36.existsSync(source) ? JSON.parse(fs36.readFileSync(source, "utf8")) : { hooks: {} };
28942
+ const hooksSource = loadKitHooksSource(sourceRoot, config);
28943
+ const source = hooksSource?.path ?? resolveHooksSourceDir(sourceRoot, config);
28944
+ const sourceLabel = hooksSource?.label ?? hooksSourceLabel(sourceRoot, config);
28945
+ const sourceHooksJson = hooksSource?.hooks ?? { hooks: {} };
28619
28946
  compileHooks(sourceHooksJson, "opencode", mappings, lossReport, sourceLabel);
28620
28947
  const message = "hooks: Skip \u2014 OpenCode exposes hooks only as plugin code (hook-as-plugin loss recorded)";
28621
28948
  return { component: "hooks", status: "skipped", path: message };
@@ -28649,9 +28976,9 @@ function syncProviderBlock(mappings) {
28649
28976
  function syncConfigDoc(paths, mcpEntries, _lossReport, permissionMap, mappings) {
28650
28977
  const destination = resolveTargetPath(paths.config_doc);
28651
28978
  let existing = {};
28652
- if (fs36.existsSync(destination)) {
28979
+ if (fs37.existsSync(destination)) {
28653
28980
  try {
28654
- existing = JSON.parse(fs36.readFileSync(destination, "utf8"));
28981
+ existing = JSON.parse(fs37.readFileSync(destination, "utf8"));
28655
28982
  } catch {
28656
28983
  existing = {};
28657
28984
  }
@@ -28677,7 +29004,7 @@ function syncConfigDoc(paths, mcpEntries, _lossReport, permissionMap, mappings)
28677
29004
  }
28678
29005
  }
28679
29006
  ensureDir(path36.dirname(destination));
28680
- fs36.writeFileSync(destination, `${JSON.stringify(config, null, 2)}
29007
+ fs37.writeFileSync(destination, `${JSON.stringify(config, null, 2)}
28681
29008
  `);
28682
29009
  }
28683
29010
  function writeCompiledArtifact5(component, destination, content, expectedHash, config, sourceRoot, options, logger, manifest, scopePath) {
@@ -28689,7 +29016,7 @@ function writeCompiledArtifact5(component, destination, content, expectedHash, c
28689
29016
  return { component, status: "conflict", path: destination };
28690
29017
  }
28691
29018
  ensureDir(path36.dirname(destination));
28692
- fs36.writeFileSync(destination, content);
29019
+ fs37.writeFileSync(destination, content);
28693
29020
  return { component, status: "success", path: destination };
28694
29021
  }
28695
29022
  function handleConflict5(destination, config, options, logger, manifest, scopePath) {
@@ -28708,16 +29035,16 @@ function handleConflict5(destination, config, options, logger, manifest, scopePa
28708
29035
  backupFile(destination, manifest);
28709
29036
  writeManifest(scopePath, manifest);
28710
29037
  } else {
28711
- fs36.renameSync(destination, `${destination}.bak.${Date.now()}`);
29038
+ fs37.renameSync(destination, `${destination}.bak.${Date.now()}`);
28712
29039
  }
28713
29040
  return true;
28714
29041
  }
28715
- fs36.rmSync(destination, { recursive: true, force: true });
29042
+ fs37.rmSync(destination, { recursive: true, force: true });
28716
29043
  return true;
28717
29044
  }
28718
29045
  function isCompiledCurrent5(destination, expectedContent, expectedHash) {
28719
29046
  try {
28720
- const current = fs36.readFileSync(destination, "utf8");
29047
+ const current = fs37.readFileSync(destination, "utf8");
28721
29048
  const marker = extractProvenanceMarker(current);
28722
29049
  return expectedHash ? marker?.hash === expectedHash && marker.target === "opencode" && current === expectedContent : current === expectedContent;
28723
29050
  } catch {
@@ -28736,7 +29063,7 @@ function shouldSkipComponent5(options, component) {
28736
29063
  }
28737
29064
  function isSymlinkTo5(linkPath, source) {
28738
29065
  try {
28739
- return fs36.realpathSync(linkPath) === fs36.realpathSync(source);
29066
+ return fs37.realpathSync(linkPath) === fs37.realpathSync(source);
28740
29067
  } catch {
28741
29068
  return false;
28742
29069
  }
@@ -28745,7 +29072,7 @@ function hashString5(value) {
28745
29072
  return `sha256:${crypto9.createHash("sha256").update(value).digest("hex")}`;
28746
29073
  }
28747
29074
  function parseCommandFile(commandFile) {
28748
- const content = fs36.readFileSync(commandFile, "utf8");
29075
+ const content = fs37.readFileSync(commandFile, "utf8");
28749
29076
  if (!content.startsWith("---\n")) {
28750
29077
  return { data: {}, body: content };
28751
29078
  }
@@ -28765,10 +29092,10 @@ function parseCommandFile(commandFile) {
28765
29092
  // src/commands/install.ts
28766
29093
  function isBackupableFile(targetPath) {
28767
29094
  try {
28768
- const lstat = fs37.lstatSync(targetPath);
29095
+ const lstat = fs38.lstatSync(targetPath);
28769
29096
  if (lstat.isDirectory()) return false;
28770
29097
  if (lstat.isSymbolicLink()) {
28771
- const stat = fs37.statSync(targetPath);
29098
+ const stat = fs38.statSync(targetPath);
28772
29099
  return stat.isFile();
28773
29100
  }
28774
29101
  return lstat.isFile();
@@ -28780,8 +29107,8 @@ function backupConflictingTarget(targetPath, scopePath) {
28780
29107
  const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/gu, "").replace("T", "T").replace(/\.\d+Z$/u, "Z");
28781
29108
  const relativeToRoot = path37.relative("/", targetPath).replace(/^\.\.\/+/gu, "");
28782
29109
  const backupPath = path37.join(scopePath, "backups", ts2, relativeToRoot);
28783
- fs37.mkdirSync(path37.dirname(backupPath), { recursive: true });
28784
- fs37.copyFileSync(targetPath, backupPath);
29110
+ fs38.mkdirSync(path37.dirname(backupPath), { recursive: true });
29111
+ fs38.copyFileSync(targetPath, backupPath);
28785
29112
  return backupPath;
28786
29113
  }
28787
29114
  async function runInstallCommand(options, logger) {
@@ -28837,6 +29164,10 @@ async function runInstallCommand(options, logger) {
28837
29164
  logger.info(
28838
29165
  `Installing kit '${kitInfo.name}' (${kitInfo.archetype}) into ${scope} scope for: ${tools.join(", ")}`
28839
29166
  );
29167
+ const resolvedEntrypoint = resolveEntrypoint(kitInfo);
29168
+ if (resolvedEntrypoint?.legacy) {
29169
+ logger.warn(LEGACY_ENTRYPOINT_NOTICE);
29170
+ }
28840
29171
  const scopePath = resolveScopePath(
28841
29172
  scope,
28842
29173
  scope === "project" ? process.cwd() : void 0
@@ -28951,7 +29282,7 @@ ACCEPTED CONFLICTS (backup-and-replace applied to ${conflictingFindings.length}
28951
29282
  logger.info(` source: ${finding.tool}`);
28952
29283
  logger.info(` action: backup-and-replace`);
28953
29284
  logger.info(` backup: ${backupLocation}`);
28954
- } else if (fs37.existsSync(finding.path)) {
29285
+ } else if (fs38.existsSync(finding.path)) {
28955
29286
  logger.info(` - ${finding.path}`);
28956
29287
  logger.info(` source: ${finding.tool}`);
28957
29288
  logger.info(
@@ -29066,6 +29397,30 @@ Installation complete: ${installedCount} files installed.`);
29066
29397
  logger.info(`Kit: ${kitInfo.name} (archetype: ${kitInfo.archetype})`);
29067
29398
  logger.info(`Scope: ${scope}`);
29068
29399
  logger.info(`Tools: ${tools.join(", ")}`);
29400
+ reportHookRejections(
29401
+ installLossReport,
29402
+ tools,
29403
+ path37.resolve(kitInfo.path, "hooks"),
29404
+ kitInfo.components.hooks.declared
29405
+ );
29406
+ const hooksConflict = reportHooksSourceConflict(
29407
+ installLossReport,
29408
+ tools,
29409
+ path37.resolve(kitInfo.path, "hooks"),
29410
+ kitInfo.components.hooks.declared
29411
+ );
29412
+ if (hooksConflict) {
29413
+ logger.warn(hooksConflict);
29414
+ }
29415
+ if (scope === "project") {
29416
+ reportProjectScopeScriptPath(
29417
+ installLossReport,
29418
+ tools,
29419
+ path37.resolve(kitInfo.path, "hooks"),
29420
+ kitInfo.components.hooks.declared
29421
+ );
29422
+ }
29423
+ writeLossReport(options.lossReport, installLossReport);
29069
29424
  exitOnErrorSeverityLoss(installLossReport, logger, {
29070
29425
  ignoreErrors: options.ignoreErrors ?? false
29071
29426
  });
@@ -29205,12 +29560,12 @@ function applyClaudeCodePlacements(placements, manifest, skippedTargetPaths, log
29205
29560
  continue;
29206
29561
  }
29207
29562
  const destDir = path37.dirname(placement.destination);
29208
- fs37.mkdirSync(destDir, { recursive: true });
29563
+ fs38.mkdirSync(destDir, { recursive: true });
29209
29564
  if (placement.type === "symlink") {
29210
- if (fs37.existsSync(placement.destination) || isSymlink2(placement.destination)) {
29565
+ if (fs38.existsSync(placement.destination) || isSymlink2(placement.destination)) {
29211
29566
  removeManagedPath(placement.destination);
29212
29567
  }
29213
- fs37.symlinkSync(
29568
+ fs38.symlinkSync(
29214
29569
  placement.source,
29215
29570
  placement.destination,
29216
29571
  placement.isDir ? "dir" : "file"
@@ -29219,12 +29574,12 @@ function applyClaudeCodePlacements(placements, manifest, skippedTargetPaths, log
29219
29574
  ` symlink: ${placement.source} \u2192 ${placement.destination}`
29220
29575
  );
29221
29576
  } else {
29222
- fs37.copyFileSync(placement.source, placement.destination);
29577
+ fs38.copyFileSync(placement.source, placement.destination);
29223
29578
  logger.verbose(` copy: ${placement.source} \u2192 ${placement.destination}`);
29224
29579
  }
29225
29580
  addArtifactToManifest(manifest, {
29226
29581
  relativePath: placement.manifestKey,
29227
- hash: fs37.statSync(placement.source).isDirectory() ? `dir:${placement.source}` : computeSourceHash(placement.source),
29582
+ hash: fs38.statSync(placement.source).isDirectory() ? `dir:${placement.source}` : computeSourceHash(placement.source),
29228
29583
  origin: "tangyr-managed"
29229
29584
  });
29230
29585
  count++;
@@ -29636,12 +29991,12 @@ function getClaudeCodePlacements(kitInfo, scope = "global", projectRoot, mapping
29636
29991
  };
29637
29992
  }
29638
29993
  const placements = [];
29639
- const claudeMd = path37.join(kitInfo.path, "CLAUDE.md");
29640
- if (fs37.existsSync(claudeMd)) {
29994
+ const entrypoint = resolveEntrypoint(kitInfo);
29995
+ if (entrypoint) {
29641
29996
  placements.push({
29642
- source: claudeMd,
29997
+ source: entrypoint.file,
29643
29998
  destination: resolvedPaths.instructions,
29644
- manifestKey: "claude-code/CLAUDE.md",
29999
+ manifestKey: `claude-code/${entrypoint.name}`,
29645
30000
  type: "symlink",
29646
30001
  isDir: false
29647
30002
  });
@@ -29654,7 +30009,7 @@ function getClaudeCodePlacements(kitInfo, scope = "global", projectRoot, mapping
29654
30009
  };
29655
30010
  for (const [component, targetPath] of Object.entries(componentDirMap)) {
29656
30011
  const componentDir = path37.join(kitInfo.path, component);
29657
- if (fs37.existsSync(componentDir) && fs37.statSync(componentDir).isDirectory()) {
30012
+ if (fs38.existsSync(componentDir) && fs38.statSync(componentDir).isDirectory()) {
29658
30013
  placements.push({
29659
30014
  source: componentDir,
29660
30015
  destination: targetPath,
@@ -29668,12 +30023,12 @@ function getClaudeCodePlacements(kitInfo, scope = "global", projectRoot, mapping
29668
30023
  }
29669
30024
  function getClaudeCodePlacementsFromProfile(kitInfo, profile) {
29670
30025
  const placements = [];
29671
- const claudeMd = path37.join(kitInfo.path, "CLAUDE.md");
29672
- if (fs37.existsSync(claudeMd)) {
30026
+ const entrypoint = resolveEntrypoint(kitInfo);
30027
+ if (entrypoint) {
29673
30028
  placements.push({
29674
- source: claudeMd,
30029
+ source: entrypoint.file,
29675
30030
  destination: profile.instructions,
29676
- manifestKey: "claude-code/CLAUDE.md",
30031
+ manifestKey: `claude-code/${entrypoint.name}`,
29677
30032
  type: "symlink",
29678
30033
  isDir: false
29679
30034
  });
@@ -29686,7 +30041,7 @@ function getClaudeCodePlacementsFromProfile(kitInfo, profile) {
29686
30041
  };
29687
30042
  for (const [component, targetPath] of Object.entries(componentDirMap)) {
29688
30043
  const componentDir = path37.join(kitInfo.path, component);
29689
- if (fs37.existsSync(componentDir) && fs37.statSync(componentDir).isDirectory()) {
30044
+ if (fs38.existsSync(componentDir) && fs38.statSync(componentDir).isDirectory()) {
29690
30045
  placements.push({
29691
30046
  source: componentDir,
29692
30047
  destination: targetPath,
@@ -29700,9 +30055,9 @@ function getClaudeCodePlacementsFromProfile(kitInfo, profile) {
29700
30055
  }
29701
30056
  function collectAllFiles(kitInfo) {
29702
30057
  const files = /* @__PURE__ */ new Map();
29703
- const claudeMd = path37.join(kitInfo.path, "CLAUDE.md");
29704
- if (fs37.existsSync(claudeMd)) {
29705
- files.set("CLAUDE.md", claudeMd);
30058
+ const entrypoint = resolveEntrypoint(kitInfo);
30059
+ if (entrypoint) {
30060
+ files.set(entrypoint.name, entrypoint.file);
29706
30061
  }
29707
30062
  for (const [component, reconciliation] of Object.entries(
29708
30063
  kitInfo.components
@@ -29719,7 +30074,7 @@ function collectAllFiles(kitInfo) {
29719
30074
  }
29720
30075
  function isSymlink2(p) {
29721
30076
  try {
29722
- return fs37.lstatSync(p).isSymbolicLink();
30077
+ return fs38.lstatSync(p).isSymbolicLink();
29723
30078
  } catch {
29724
30079
  return false;
29725
30080
  }
@@ -29854,10 +30209,13 @@ function withDirectKit(kits, directKitPath) {
29854
30209
 
29855
30210
  // src/commands/probe.ts
29856
30211
  import { spawnSync as spawnSync2 } from "child_process";
29857
- import fs38 from "fs";
30212
+ import fs39 from "fs";
29858
30213
  import path39 from "path";
29859
30214
  function runProbeCommand(options, logger) {
29860
- const { config, configDir, sourceRoot } = resolveRuntime(options, import.meta.url);
30215
+ const { config, configDir, sourceRoot } = resolveRuntime(
30216
+ options,
30217
+ import.meta.url
30218
+ );
29861
30219
  const platformPaths = readYamlObject(
29862
30220
  path39.join(
29863
30221
  resolveConfiguredPath(config.mappings, configDir, sourceRoot, "mappings"),
@@ -29885,7 +30243,9 @@ function runProbeCommand(options, logger) {
29885
30243
  console.log(JSON.stringify(rows, null, 2));
29886
30244
  } else {
29887
30245
  for (const row of rows) {
29888
- logger.info(`${row.target} ${row.key} ${row.exists ? "exists" : "missing"} ${row.path}`);
30246
+ logger.info(
30247
+ `${row.target} ${row.key} ${row.exists ? "exists" : "missing"} ${row.path}`
30248
+ );
29889
30249
  }
29890
30250
  }
29891
30251
  }
@@ -29893,15 +30253,26 @@ function toProbeRow(target, key, rawPath) {
29893
30253
  const resolved = resolveTargetPath(rawPath);
29894
30254
  let accessible = false;
29895
30255
  try {
29896
- fs38.accessSync(resolved);
30256
+ fs39.accessSync(resolved);
29897
30257
  accessible = true;
29898
30258
  } catch {
29899
30259
  accessible = false;
29900
30260
  }
29901
- return { target, key, path: resolved, exists: fs38.existsSync(resolved), accessible };
30261
+ return {
30262
+ target,
30263
+ key,
30264
+ path: resolved,
30265
+ exists: fs39.existsSync(resolved),
30266
+ accessible
30267
+ };
29902
30268
  }
29903
30269
  function codexRuntimeProbeRows() {
29904
- const rows = commandProbeRows("codex", "binary", process.platform === "win32" ? "where.exe" : "which", ["codex"]);
30270
+ const rows = commandProbeRows(
30271
+ "codex",
30272
+ "binary",
30273
+ process.platform === "win32" ? "where.exe" : "which",
30274
+ ["codex"]
30275
+ );
29905
30276
  if (process.platform !== "linux") {
29906
30277
  return rows;
29907
30278
  }
@@ -29909,7 +30280,7 @@ function codexRuntimeProbeRows() {
29909
30280
  ["binary_snap", "/snap/bin/codex"],
29910
30281
  ["binary_flatpak", "/var/lib/flatpak/exports/bin/codex"]
29911
30282
  ]) {
29912
- if (fs38.existsSync(candidatePath)) {
30283
+ if (fs39.existsSync(candidatePath)) {
29913
30284
  rows.push(toProbeRow("codex", key, candidatePath));
29914
30285
  }
29915
30286
  }
@@ -29925,7 +30296,7 @@ function commandProbeRows(target, key, command, args) {
29925
30296
 
29926
30297
  // src/commands/status.ts
29927
30298
  import crypto10 from "crypto";
29928
- import fs39 from "fs";
30299
+ import fs40 from "fs";
29929
30300
  import path40 from "path";
29930
30301
  function runStatusCommand(options, logger) {
29931
30302
  const { config, configDir, sourceRoot } = resolveRuntime(
@@ -30010,7 +30381,7 @@ function classifyCodexFiles(config, sourceRoot, configDir) {
30010
30381
  mappings,
30011
30382
  createLossReport()
30012
30383
  );
30013
- const instructionsSource = fs39.readFileSync(path40.join(sourceRoot, config.source.instructions), "utf8").trim();
30384
+ const instructionsSource = fs40.readFileSync(path40.join(sourceRoot, config.source.instructions), "utf8").trim();
30014
30385
  const composedInstructions = `${instructionsSource}
30015
30386
 
30016
30387
  --- tangyr managed rules ---
@@ -30075,11 +30446,11 @@ ${rules.agentsAppendix}`.trim();
30075
30446
  config,
30076
30447
  sourceRoot
30077
30448
  ),
30078
- fs39.existsSync(hooksSource) ? classifyCodexHooksFile(resolveTargetPath(codexPaths.hooksSharedFile)) : notConfiguredRow(
30449
+ fs40.existsSync(hooksSource) ? classifyCodexHooksFile(resolveTargetPath(codexPaths.hooksSharedFile)) : notConfiguredRow(
30079
30450
  "hooks",
30080
30451
  resolveTargetPath(codexPaths.hooksSharedFile)
30081
30452
  ),
30082
- fs39.existsSync(mcpSource) ? classifyCompiledFile(
30453
+ fs40.existsSync(mcpSource) ? classifyCompiledFile(
30083
30454
  "mcp",
30084
30455
  resolveTargetPath(codexPaths.mcpSharedFile),
30085
30456
  "codex",
@@ -30099,7 +30470,7 @@ function classifyCodexHooksFile(filePath) {
30099
30470
  conformant: true
30100
30471
  };
30101
30472
  }
30102
- if (!fs39.existsSync(filePath)) {
30473
+ if (!fs40.existsSync(filePath)) {
30103
30474
  return {
30104
30475
  component: "hooks",
30105
30476
  className: "missing",
@@ -30182,7 +30553,7 @@ function classifyCopilotFiles(config, sourceRoot, configDir) {
30182
30553
  config,
30183
30554
  sourceRoot
30184
30555
  ),
30185
- fs39.existsSync(hooksSource) ? classifyCompiledFile(
30556
+ fs40.existsSync(hooksSource) ? classifyCompiledFile(
30186
30557
  "hooks",
30187
30558
  path40.join(
30188
30559
  resolveTargetPath(copilotPaths.hooks),
@@ -30199,7 +30570,7 @@ function classifyCopilotFiles(config, sourceRoot, configDir) {
30199
30570
  "tangyr-managed.json"
30200
30571
  )
30201
30572
  ),
30202
- fs39.existsSync(mcpSource) ? classifyCompiledFile(
30573
+ fs40.existsSync(mcpSource) ? classifyCompiledFile(
30203
30574
  "mcp",
30204
30575
  resolveTargetPath(copilotPaths.mcpSharedFile),
30205
30576
  "copilot",
@@ -30374,14 +30745,14 @@ function classifyMcp(filePath, sourcePath) {
30374
30745
  }
30375
30746
  function isSymlinkCurrent(linkPath, sourcePath) {
30376
30747
  try {
30377
- return fs39.realpathSync(linkPath) === fs39.realpathSync(sourcePath);
30748
+ return fs40.realpathSync(linkPath) === fs40.realpathSync(sourcePath);
30378
30749
  } catch {
30379
30750
  return false;
30380
30751
  }
30381
30752
  }
30382
30753
  function isCompiledCurrent6(filePath, sourcePath) {
30383
30754
  try {
30384
- const marker = extractProvenanceMarker(fs39.readFileSync(filePath, "utf8"));
30755
+ const marker = extractProvenanceMarker(fs40.readFileSync(filePath, "utf8"));
30385
30756
  return marker?.hash === computeSourceHash(sourcePath) && marker.target === "claude-code";
30386
30757
  } catch {
30387
30758
  return false;
@@ -30411,7 +30782,7 @@ function classifyCompiledFile(component, filePath, target, expectedHash, config,
30411
30782
  }
30412
30783
  function readCompiledContent(filePath) {
30413
30784
  try {
30414
- return fs39.readFileSync(filePath, "utf8");
30785
+ return fs40.readFileSync(filePath, "utf8");
30415
30786
  } catch {
30416
30787
  return null;
30417
30788
  }
@@ -30442,7 +30813,7 @@ function hashString6(value) {
30442
30813
  }
30443
30814
 
30444
30815
  // src/commands/sync.ts
30445
- import fs41 from "fs";
30816
+ import fs42 from "fs";
30446
30817
  import path42 from "path";
30447
30818
 
30448
30819
  // node_modules/chalk/source/vendor/ansi-styles/index.js
@@ -30941,7 +31312,7 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
30941
31312
  var source_default = chalk;
30942
31313
 
30943
31314
  // src/adapters/claude-code.ts
30944
- import fs40 from "fs";
31315
+ import fs41 from "fs";
30945
31316
  import path41 from "path";
30946
31317
 
30947
31318
  // src/core/shim.ts
@@ -31080,7 +31451,14 @@ function syncClaudeCode(config, sourceRoot, options, logger, mappings, manifest,
31080
31451
  )
31081
31452
  );
31082
31453
  outcomes.push(
31083
- syncHooks6(config, sourceRoot, options, logger, profile.hooks_shared_file)
31454
+ syncHooks6(
31455
+ config,
31456
+ sourceRoot,
31457
+ options,
31458
+ logger,
31459
+ profile.hooks_shared_file,
31460
+ profile.hooks
31461
+ )
31084
31462
  );
31085
31463
  }
31086
31464
  if (!shouldSkipComponent6(options, "mcp")) {
@@ -31107,7 +31485,7 @@ function syncClaudeCode(config, sourceRoot, options, logger, mappings, manifest,
31107
31485
  function syncProviderLoss2(config, sourceRoot, mappings, lossReport) {
31108
31486
  if (!mappings.modelTiers.custom_provider) return;
31109
31487
  const agentsDir = path41.join(sourceRoot, config.source.agents);
31110
- if (!fs40.existsSync(agentsDir)) return;
31488
+ if (!fs41.existsSync(agentsDir)) return;
31111
31489
  for (const agentFile of ts(path41.join(agentsDir, "*.md")).sort()) {
31112
31490
  const { data } = parseMarkdownFrontmatter(agentFile);
31113
31491
  const hasProvider = typeof data.provider === "string" || typeof data.provider === "object" && data.provider !== null && Object.keys(data.provider).length > 0;
@@ -31137,19 +31515,19 @@ function shouldSkipComponent6(options, component) {
31137
31515
  function applyClaudeCodeShimUpgrade(shimPath, agentsMdPath, manifest, scopePath, logger) {
31138
31516
  const expectedBodyHash = shimBodyHash();
31139
31517
  const shimContent = buildAgentsMdShim(shimPath, agentsMdPath, "claude-code");
31140
- if (!fs40.existsSync(shimPath)) {
31518
+ if (!fs41.existsSync(shimPath)) {
31141
31519
  manifest.artifacts.push({
31142
31520
  relativePath: path41.relative(scopePath, shimPath),
31143
31521
  hash: expectedBodyHash,
31144
31522
  origin: "tangyr-managed"
31145
31523
  });
31146
31524
  writeManifest(scopePath, manifest);
31147
- fs40.mkdirSync(path41.dirname(shimPath), { recursive: true });
31148
- fs40.writeFileSync(shimPath, shimContent);
31525
+ fs41.mkdirSync(path41.dirname(shimPath), { recursive: true });
31526
+ fs41.writeFileSync(shimPath, shimContent);
31149
31527
  logger?.verbose(`instructions-shim: wrote @AGENTS.md shim -> ${shimPath}`);
31150
31528
  return;
31151
31529
  }
31152
- const existing = fs40.readFileSync(shimPath, "utf8");
31530
+ const existing = fs41.readFileSync(shimPath, "utf8");
31153
31531
  const firstNonEmptyLine = existing.split("\n").find((l) => l.trim().length > 0) ?? "";
31154
31532
  const marker = extractProvenanceMarker(firstNonEmptyLine);
31155
31533
  if (marker !== null && marker.hash === expectedBodyHash) {
@@ -31160,7 +31538,7 @@ function applyClaudeCodeShimUpgrade(shimPath, agentsMdPath, manifest, scopePath,
31160
31538
  }
31161
31539
  backupFile(shimPath, manifest);
31162
31540
  writeManifest(scopePath, manifest);
31163
- fs40.writeFileSync(shimPath, shimContent);
31541
+ fs41.writeFileSync(shimPath, shimContent);
31164
31542
  manifest.artifacts.push({
31165
31543
  relativePath: path41.relative(scopePath, shimPath),
31166
31544
  hash: expectedBodyHash,
@@ -31174,7 +31552,7 @@ function syncDirectComponent(config, sourceRoot, options, logger, directComponen
31174
31552
  config.source[directComponent.sourceKey]
31175
31553
  );
31176
31554
  const destination = directComponent.destination;
31177
- if (!fs40.existsSync(source)) {
31555
+ if (!fs41.existsSync(source)) {
31178
31556
  return skippedMissingSource6(directComponent.component, source);
31179
31557
  }
31180
31558
  if (options.dryRun) {
@@ -31196,7 +31574,7 @@ function syncDirectComponent(config, sourceRoot, options, logger, directComponen
31196
31574
  };
31197
31575
  }
31198
31576
  if (artifactClass === "managed-stale" && isOurStaleSymlink(destination, /* @__PURE__ */ new Set([destination]), config)) {
31199
- fs40.unlinkSync(destination);
31577
+ fs41.unlinkSync(destination);
31200
31578
  } else if (artifactClass === "unmanaged-conflict" && !handleConflict6(destination, config, options, logger, manifest, scopePath)) {
31201
31579
  return {
31202
31580
  component: directComponent.component,
@@ -31204,7 +31582,7 @@ function syncDirectComponent(config, sourceRoot, options, logger, directComponen
31204
31582
  path: destination
31205
31583
  };
31206
31584
  } else if (artifactClass === "managed-symlink") {
31207
- fs40.unlinkSync(destination);
31585
+ fs41.unlinkSync(destination);
31208
31586
  }
31209
31587
  ensureDir(path41.dirname(destination));
31210
31588
  createRelativeSymlink(source, destination, directComponent.type, {
@@ -31224,7 +31602,7 @@ function syncDirectComponent(config, sourceRoot, options, logger, directComponen
31224
31602
  }
31225
31603
  function syncClaudeCodeSkills(config, sourceRoot, options, logger, destination, manifest, scopePath) {
31226
31604
  const source = path41.resolve(sourceRoot, config.source.skills);
31227
- if (!fs40.existsSync(source)) {
31605
+ if (!fs41.existsSync(source)) {
31228
31606
  return [skippedMissingSource6("skills", source)];
31229
31607
  }
31230
31608
  const declaredNames = resolveKitSkillNames(sourceRoot);
@@ -31279,7 +31657,7 @@ function syncClaudeCodeSkills(config, sourceRoot, options, logger, destination,
31279
31657
  function syncSingleSkill(config, sourceRoot, options, logger, source, destination, name, manifest, scopePath) {
31280
31658
  const skillSource = path41.join(source, name);
31281
31659
  const skillDestination = path41.join(destination, name);
31282
- if (!fs40.existsSync(skillSource)) {
31660
+ if (!fs41.existsSync(skillSource)) {
31283
31661
  return skippedMissingSource6("skills", skillSource);
31284
31662
  }
31285
31663
  const artifactClass = classifyArtifact(skillDestination, config, sourceRoot);
@@ -31291,7 +31669,7 @@ function syncSingleSkill(config, sourceRoot, options, logger, source, destinatio
31291
31669
  };
31292
31670
  }
31293
31671
  if (artifactClass === "managed-stale" && isOurStaleSymlink(skillDestination, /* @__PURE__ */ new Set([skillDestination]), config)) {
31294
- fs40.unlinkSync(skillDestination);
31672
+ fs41.unlinkSync(skillDestination);
31295
31673
  } else if (artifactClass === "unmanaged-conflict" && !handleConflict6(
31296
31674
  skillDestination,
31297
31675
  config,
@@ -31302,26 +31680,28 @@ function syncSingleSkill(config, sourceRoot, options, logger, source, destinatio
31302
31680
  )) {
31303
31681
  return { component: "skills", status: "conflict", path: skillDestination };
31304
31682
  } else if (artifactClass === "managed-symlink") {
31305
- fs40.unlinkSync(skillDestination);
31683
+ fs41.unlinkSync(skillDestination);
31306
31684
  }
31307
31685
  createRelativeSymlink(skillSource, skillDestination, "dir");
31308
31686
  return { component: "skills", status: "success", path: skillDestination };
31309
31687
  }
31310
31688
  function migrateWholesaleSkillsSymlink(source, destination, declaredNames, logger) {
31311
31689
  const foreignNames = foreignSkillNames(source, declaredNames);
31312
- fs40.unlinkSync(destination);
31690
+ fs41.unlinkSync(destination);
31313
31691
  ensureDir(destination);
31314
31692
  for (const name of foreignNames) {
31315
31693
  const from = path41.join(source, name);
31316
31694
  const to = path41.join(destination, name);
31317
- if (fs40.existsSync(to)) {
31695
+ if (fs41.existsSync(to)) {
31318
31696
  logger.warn(
31319
- `skills migration: ${to} already exists \u2014 skipping move of ${from} to avoid overwrite`
31697
+ `skills migration: ${to} already exists \u2014 skipping ${from} to avoid overwrite. Nothing in the kit was changed.`
31320
31698
  );
31321
31699
  continue;
31322
31700
  }
31323
- fs40.renameSync(from, to);
31324
- logger.info(`skills migration: moved ${from} -> ${to}`);
31701
+ fs41.cpSync(from, to, { recursive: true, verbatimSymlinks: true });
31702
+ logger.warn(
31703
+ `skills migration: '${name}' is present under the kit's skills/ but is not declared in its manifest. Copied ${from} -> ${to} so the harness can read it. The original is left in the kit deliberately: this command does not modify a kit. Remove ${from} yourself if it does not belong there, or declare it in tangyr-kit.yaml if it does.`
31704
+ );
31325
31705
  }
31326
31706
  }
31327
31707
  function foreignSkillNames(source, declaredNames) {
@@ -31332,21 +31712,21 @@ function foreignSkillNames(source, declaredNames) {
31332
31712
  return listSkillDirNames(source).filter((name) => !declaredSet.has(name));
31333
31713
  }
31334
31714
  function listSkillDirNames(dir) {
31335
- if (!fs40.existsSync(dir)) {
31715
+ if (!fs41.existsSync(dir)) {
31336
31716
  return [];
31337
31717
  }
31338
- return fs40.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
31718
+ return fs41.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
31339
31719
  }
31340
31720
  function isSymlinkPath(filePath) {
31341
31721
  try {
31342
- return fs40.lstatSync(filePath).isSymbolicLink();
31722
+ return fs41.lstatSync(filePath).isSymbolicLink();
31343
31723
  } catch {
31344
31724
  return false;
31345
31725
  }
31346
31726
  }
31347
31727
  function syncHookScriptsBridge(config, sourceRoot, options, logger, destination, manifest, scopePath) {
31348
31728
  const source = path41.resolve(sourceRoot, config.source.hooks);
31349
- if (!fs40.existsSync(source)) {
31729
+ if (!fs41.existsSync(source)) {
31350
31730
  return skippedMissingSource6("hooks", source);
31351
31731
  }
31352
31732
  if (options.dryRun) {
@@ -31358,17 +31738,25 @@ function syncHookScriptsBridge(config, sourceRoot, options, logger, destination,
31358
31738
  return { component: "hooks", status: "skipped-current", path: destination };
31359
31739
  }
31360
31740
  if (artifactClass === "managed-stale" && isOurStaleSymlink(destination, /* @__PURE__ */ new Set([destination]), config)) {
31361
- fs40.unlinkSync(destination);
31741
+ fs41.unlinkSync(destination);
31362
31742
  } else if (artifactClass === "unmanaged-conflict" && !handleConflict6(destination, config, options, logger, manifest, scopePath)) {
31363
31743
  return { component: "hooks", status: "conflict", path: destination };
31364
31744
  } else if (artifactClass === "managed-symlink") {
31365
- fs40.unlinkSync(destination);
31745
+ fs41.unlinkSync(destination);
31366
31746
  }
31367
31747
  ensureDir(path41.dirname(destination));
31368
31748
  createRelativeSymlink(source, destination, "dir");
31369
31749
  return { component: "hooks", status: "success", path: destination };
31370
31750
  }
31371
- function syncHooks6(config, sourceRoot, options, logger, destination) {
31751
+ function hookScriptsDir(scriptsBridge) {
31752
+ const globalBridge = resolveTargetPath("~/.agents/hooks");
31753
+ if (path41.resolve(scriptsBridge) === path41.resolve(globalBridge)) {
31754
+ return GLOBAL_HOOK_SCRIPTS_DIR;
31755
+ }
31756
+ const relative = path41.relative(process.cwd(), scriptsBridge);
31757
+ return relative.startsWith("..") || path41.isAbsolute(relative) ? path41.join(scriptsBridge, "scripts") : `"$CLAUDE_PROJECT_DIR"/${path41.posix.join(relative.split(path41.sep).join("/"), "scripts")}`;
31758
+ }
31759
+ function syncHooks6(config, sourceRoot, options, logger, destination, scriptsBridge) {
31372
31760
  const hooksDir = resolveHooksSourceDir(sourceRoot, config);
31373
31761
  if (!hooksSourceExists(sourceRoot, config)) {
31374
31762
  return skippedMissingSource6("hooks", hooksDir);
@@ -31380,14 +31768,15 @@ function syncHooks6(config, sourceRoot, options, logger, destination) {
31380
31768
  const settings = readJsonObject4(destination);
31381
31769
  const sourceHooks = loadSourceHooks(
31382
31770
  hooksDir,
31383
- resolveKitHookNames(sourceRoot)
31771
+ resolveKitHookNames(sourceRoot),
31772
+ hookScriptsDir(scriptsBridge)
31384
31773
  );
31385
31774
  settings.hooks = markManaged(sourceHooks.hooks ?? {});
31386
31775
  const changed = writeSharedFile(
31387
31776
  destination,
31388
31777
  settings,
31389
31778
  stringifyJson3,
31390
- !fs40.existsSync(destination),
31779
+ !fs41.existsSync(destination),
31391
31780
  logger
31392
31781
  );
31393
31782
  return {
@@ -31398,7 +31787,7 @@ function syncHooks6(config, sourceRoot, options, logger, destination) {
31398
31787
  }
31399
31788
  function syncMcp6(config, sourceRoot, options, logger, destination) {
31400
31789
  const source = path41.resolve(sourceRoot, config.source.mcp);
31401
- if (!fs40.existsSync(source)) {
31790
+ if (!fs41.existsSync(source)) {
31402
31791
  return skippedMissingSource6("mcp", source);
31403
31792
  }
31404
31793
  if (options.dryRun) {
@@ -31423,7 +31812,7 @@ function syncMcp6(config, sourceRoot, options, logger, destination) {
31423
31812
  destination,
31424
31813
  hostConfig,
31425
31814
  stringifyJson3,
31426
- !fs40.existsSync(destination),
31815
+ !fs41.existsSync(destination),
31427
31816
  logger
31428
31817
  );
31429
31818
  return {
@@ -31434,7 +31823,7 @@ function syncMcp6(config, sourceRoot, options, logger, destination) {
31434
31823
  }
31435
31824
  function syncSettings2(config, sourceRoot, options, logger, destination) {
31436
31825
  const source = path41.resolve(sourceRoot, config.source.settings);
31437
- if (!fs40.existsSync(source)) {
31826
+ if (!fs41.existsSync(source)) {
31438
31827
  return skippedMissingSource6("settings", source);
31439
31828
  }
31440
31829
  if (options.dryRun) {
@@ -31452,7 +31841,7 @@ function syncSettings2(config, sourceRoot, options, logger, destination) {
31452
31841
  destination,
31453
31842
  settings,
31454
31843
  stringifyJson3,
31455
- !fs40.existsSync(destination),
31844
+ !fs41.existsSync(destination),
31456
31845
  logger
31457
31846
  );
31458
31847
  return {
@@ -31477,11 +31866,11 @@ function handleConflict6(destination, config, options, logger, manifest, scopePa
31477
31866
  backupFile(destination, manifest);
31478
31867
  writeManifest(scopePath, manifest);
31479
31868
  } else {
31480
- fs40.renameSync(destination, `${destination}.bak.${Date.now()}`);
31869
+ fs41.renameSync(destination, `${destination}.bak.${Date.now()}`);
31481
31870
  }
31482
31871
  return true;
31483
31872
  }
31484
- fs40.rmSync(destination, { recursive: true, force: true });
31873
+ fs41.rmSync(destination, { recursive: true, force: true });
31485
31874
  return true;
31486
31875
  }
31487
31876
  function skippedMissingSource6(component, source) {
@@ -31493,16 +31882,16 @@ function skippedMissingSource6(component, source) {
31493
31882
  }
31494
31883
  function isSymlinkTo6(linkPath, source) {
31495
31884
  try {
31496
- return fs40.realpathSync(linkPath) === fs40.realpathSync(source);
31885
+ return fs41.realpathSync(linkPath) === fs41.realpathSync(source);
31497
31886
  } catch {
31498
31887
  return false;
31499
31888
  }
31500
31889
  }
31501
31890
  function readJsonObject4(filePath) {
31502
- if (!fs40.existsSync(filePath)) {
31891
+ if (!fs41.existsSync(filePath)) {
31503
31892
  return {};
31504
31893
  }
31505
- const parsed = JSON.parse(fs40.readFileSync(filePath, "utf8"));
31894
+ const parsed = JSON.parse(fs41.readFileSync(filePath, "utf8"));
31506
31895
  return isRecord5(parsed) ? parsed : {};
31507
31896
  }
31508
31897
  function markManaged(value) {
@@ -31698,6 +32087,31 @@ async function runSyncCommand(options, logger) {
31698
32087
  throwUnrecognizedTarget(target);
31699
32088
  }
31700
32089
  }
32090
+ if (kitInfo) {
32091
+ reportHookRejections(
32092
+ lossReport,
32093
+ targets,
32094
+ path42.resolve(kitInfo.path, "hooks"),
32095
+ kitInfo.components.hooks.declared
32096
+ );
32097
+ const hooksConflict = reportHooksSourceConflict(
32098
+ lossReport,
32099
+ targets,
32100
+ path42.resolve(kitInfo.path, "hooks"),
32101
+ kitInfo.components.hooks.declared
32102
+ );
32103
+ if (hooksConflict) {
32104
+ logger.warn(hooksConflict);
32105
+ }
32106
+ if (syncOptions.scope === "project") {
32107
+ reportProjectScopeScriptPath(
32108
+ lossReport,
32109
+ targets,
32110
+ path42.resolve(kitInfo.path, "hooks"),
32111
+ kitInfo.components.hooks.declared
32112
+ );
32113
+ }
32114
+ }
31701
32115
  writeLossReport(options.lossReport, lossReport);
31702
32116
  printOutcomeTable(results);
31703
32117
  if (kitInfo && kitSync && !options.dryRun) {
@@ -31764,13 +32178,6 @@ function colorStatus(status) {
31764
32178
  }
31765
32179
  return source_default.yellow(status);
31766
32180
  }
31767
- function writeLossReport(filePath, report) {
31768
- if (!filePath) {
31769
- return;
31770
- }
31771
- fs41.writeFileSync(filePath, `${JSON.stringify(report, null, 2)}
31772
- `);
31773
- }
31774
32181
  function prepareKitManifest(scope, projectRoot, kitInfo) {
31775
32182
  const scopePath = resolveScopePath(scope, projectRoot);
31776
32183
  const manifest = readManifest(scopePath) ?? createEmptyManifest(kitInfo.archetype, kitInfo.kitFormat, scope);
@@ -31810,7 +32217,7 @@ function updateManifest(kitInfo, kitSync) {
31810
32217
  }
31811
32218
  function resolveKitFilePath(kitPath, relativePath) {
31812
32219
  const fullPath = path42.join(kitPath, relativePath);
31813
- return fs41.existsSync(fullPath) ? fullPath : null;
32220
+ return fs42.existsSync(fullPath) ? fullPath : null;
31814
32221
  }
31815
32222
  function cleanupRemovedManagedArtifacts(config, mappings, targets, options, removedSourcePaths, logger) {
31816
32223
  if (removedSourcePaths.length === 0 || !mappings) {
@@ -31857,7 +32264,9 @@ function componentForRemovedSource(relativePath, config) {
31857
32264
  if (relativePath.startsWith("rules/")) {
31858
32265
  return "rules";
31859
32266
  }
31860
- if (relativePath === hooksSourceLabel(config)) {
32267
+ if (relativePath.startsWith(
32268
+ `${normalizeManagedSourcePath(config.source.hooks.replace(/\\/gu, "/"))}/`
32269
+ )) {
31861
32270
  return "hooks";
31862
32271
  }
31863
32272
  if (relativePath === mcpSourceLabel(config)) {
@@ -32126,17 +32535,17 @@ function cleanupRemovedClineArtifact(relativePath, paths, logger) {
32126
32535
  }
32127
32536
  }
32128
32537
  function removeManagedCompiledArtifact2(artifactPath, expectedTarget, expectedSource, logger) {
32129
- if (!fs41.existsSync(artifactPath)) {
32538
+ if (!fs42.existsSync(artifactPath)) {
32130
32539
  return;
32131
32540
  }
32132
- if (!fs41.statSync(artifactPath).isFile()) {
32541
+ if (!fs42.statSync(artifactPath).isFile()) {
32133
32542
  return;
32134
32543
  }
32135
- const marker = extractProvenanceMarker(fs41.readFileSync(artifactPath, "utf8"));
32544
+ const marker = extractProvenanceMarker(fs42.readFileSync(artifactPath, "utf8"));
32136
32545
  if (marker?.source !== expectedSource || marker.target !== expectedTarget) {
32137
32546
  return;
32138
32547
  }
32139
- fs41.rmSync(artifactPath, { force: true });
32548
+ fs42.rmSync(artifactPath, { force: true });
32140
32549
  logger.verbose(` removed stale target artifact: ${artifactPath}`);
32141
32550
  }
32142
32551
  function removeManagedCopilotMcpArtifact2(artifactPath, expectedSource, logger) {
@@ -32167,10 +32576,10 @@ function removeManagedCodexHooksArtifact2(artifactPath, expectedSource, logger)
32167
32576
  logger.verbose(` removed stale target artifact: ${artifactPath}`);
32168
32577
  }
32169
32578
  function removeManagedCodexMcpArtifact2(artifactPath, expectedSource, logger) {
32170
- if (!fs41.existsSync(artifactPath)) {
32579
+ if (!fs42.existsSync(artifactPath)) {
32171
32580
  return;
32172
32581
  }
32173
- const content = fs41.readFileSync(artifactPath, "utf8");
32582
+ const content = fs42.readFileSync(artifactPath, "utf8");
32174
32583
  const startMarker = "# tangyr: managed mcp start";
32175
32584
  const endMarker = "# tangyr: managed mcp end";
32176
32585
  const startIndex = content.indexOf(startMarker);
@@ -32187,19 +32596,19 @@ function removeManagedCodexMcpArtifact2(artifactPath, expectedSource, logger) {
32187
32596
  const after = content.slice(endIndex + endMarker.length).trimStart();
32188
32597
  const cleaned = [before, after].filter((entry) => entry.length > 0).join("\n\n");
32189
32598
  if (cleaned.length === 0) {
32190
- fs41.rmSync(artifactPath, { force: true });
32599
+ fs42.rmSync(artifactPath, { force: true });
32191
32600
  } else {
32192
- fs41.writeFileSync(artifactPath, `${cleaned}
32601
+ fs42.writeFileSync(artifactPath, `${cleaned}
32193
32602
  `);
32194
32603
  }
32195
32604
  logger.verbose(` removed stale target artifact: ${artifactPath}`);
32196
32605
  }
32197
32606
  function readJsonObject5(filePath) {
32198
- if (!fs41.existsSync(filePath)) {
32607
+ if (!fs42.existsSync(filePath)) {
32199
32608
  return null;
32200
32609
  }
32201
32610
  try {
32202
- const parsed = JSON.parse(fs41.readFileSync(filePath, "utf8"));
32611
+ const parsed = JSON.parse(fs42.readFileSync(filePath, "utf8"));
32203
32612
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
32204
32613
  } catch {
32205
32614
  return null;
@@ -32213,10 +32622,10 @@ function readJsonMetadata(value) {
32213
32622
  }
32214
32623
  function writeJsonOrRemove2(filePath, parsed) {
32215
32624
  if (isEmptyJsonValue2(parsed)) {
32216
- fs41.rmSync(filePath, { force: true });
32625
+ fs42.rmSync(filePath, { force: true });
32217
32626
  return;
32218
32627
  }
32219
- fs41.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
32628
+ fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
32220
32629
  `);
32221
32630
  }
32222
32631
  function isEmptyJsonValue2(value) {
@@ -32276,7 +32685,7 @@ function removeManagedHookEntries(value, expectedSource) {
32276
32685
  }
32277
32686
 
32278
32687
  // src/commands/validate.ts
32279
- import fs42 from "fs";
32688
+ import fs43 from "fs";
32280
32689
  import path43 from "path";
32281
32690
  var SUPPORTED_KIT_FORMATS2 = [1];
32282
32691
  var SUPPORTED_DISCIPLINE_VERSIONS = [
@@ -32303,6 +32712,14 @@ var FRONTMATTER_REQUIRED_FIELDS = {
32303
32712
  rules: [],
32304
32713
  hooks: ["name", "description", "events"]
32305
32714
  };
32715
+ var CANONICAL_SKILL_SUBFOLDERS = [
32716
+ "assets",
32717
+ "references",
32718
+ "schemas",
32719
+ "scripts",
32720
+ "tests",
32721
+ "evals"
32722
+ ];
32306
32723
  function runValidateCommand(options, logger) {
32307
32724
  const { kitInfo } = resolveRuntime(options, import.meta.url);
32308
32725
  if (!kitInfo) {
@@ -32315,16 +32732,26 @@ function runValidateCommand(options, logger) {
32315
32732
  const result = validateKit(kitInfo);
32316
32733
  if (options.json) {
32317
32734
  logger.info(JSON.stringify(result, null, 2));
32318
- } else if (result.conformant) {
32319
- logger.info(
32320
- `validate: OK (kit '${kitInfo.name}' conforms to kitFormat ${kitInfo.kitFormat})`
32321
- );
32322
32735
  } else {
32323
- logger.error(
32324
- `validate: FAILED (${result.violations.length} violation${result.violations.length === 1 ? "" : "s"})`
32325
- );
32326
- for (const v2 of result.violations) {
32327
- logger.error(` [${v2.code}] ${v2.location}: ${v2.message}`);
32736
+ if (result.conformant) {
32737
+ logger.info(
32738
+ `validate: OK (kit '${kitInfo.name}' conforms to kitFormat ${kitInfo.kitFormat})`
32739
+ );
32740
+ } else {
32741
+ logger.error(
32742
+ `validate: FAILED (${result.violations.length} violation${result.violations.length === 1 ? "" : "s"})`
32743
+ );
32744
+ for (const v2 of result.violations) {
32745
+ logger.error(` [${v2.code}] ${v2.location}: ${v2.message}`);
32746
+ }
32747
+ }
32748
+ if (result.notices.length > 0) {
32749
+ logger.warn(
32750
+ `validate: ${result.notices.length} notice${result.notices.length === 1 ? "" : "s"} (not violations; the verdict above stands)`
32751
+ );
32752
+ for (const n7 of result.notices) {
32753
+ logger.warn(` [${n7.code}] ${n7.location}: ${n7.message}`);
32754
+ }
32328
32755
  }
32329
32756
  }
32330
32757
  if (!result.conformant) {
@@ -32341,11 +32768,60 @@ function validateKit(kitInfo) {
32341
32768
  violations.push(...checkComponentFrontmatter(kitInfo));
32342
32769
  violations.push(...checkReservedRootNames(kitInfo));
32343
32770
  violations.push(...checkHumanOnlyCommandInvocation(kitInfo));
32771
+ violations.push(...checkHookDeclarations(kitInfo));
32772
+ collect(violations, checkHooksSourceForm(kitInfo));
32344
32773
  return {
32345
32774
  conformant: violations.length === 0,
32346
- violations
32775
+ violations,
32776
+ notices: checkSkillSubfolderNames(kitInfo)
32347
32777
  };
32348
32778
  }
32779
+ function checkSkillSubfolderNames(kit) {
32780
+ const notices = [];
32781
+ const canonical = new Set(CANONICAL_SKILL_SUBFOLDERS);
32782
+ for (const name of kit.components.skills.declared) {
32783
+ const skillDir = path43.join(kit.path, "skills", name);
32784
+ let entries;
32785
+ try {
32786
+ entries = fs43.readdirSync(skillDir, { withFileTypes: true });
32787
+ } catch {
32788
+ continue;
32789
+ }
32790
+ for (const entry of entries) {
32791
+ if (!entry.isDirectory() || entry.name.startsWith(".") || canonical.has(entry.name)) {
32792
+ continue;
32793
+ }
32794
+ notices.push({
32795
+ code: "skill-subfolder-unrecognized",
32796
+ location: `skills/${name}/${entry.name}/`,
32797
+ message: `skills: '${name}' contains a subfolder '${entry.name}' that the contract does not enumerate. The recognized names are ${CANONICAL_SKILL_SUBFOLDERS.join(", ")}. This is not a violation, and nothing is rejected: the CLI carries the whole skill folder to local targets either way. It is reported so a convention stays visible rather than drifting silently.`
32798
+ });
32799
+ }
32800
+ }
32801
+ return notices;
32802
+ }
32803
+ function checkHooksSourceForm(kit) {
32804
+ const hooksDir = path43.join(kit.path, "hooks");
32805
+ const message = describeHooksSourceConflict(
32806
+ hooksDir,
32807
+ kit.components.hooks.declared
32808
+ );
32809
+ return message ? {
32810
+ code: "hooks-source-ambiguous",
32811
+ location: "hooks/",
32812
+ message
32813
+ } : null;
32814
+ }
32815
+ function checkHookDeclarations(kit) {
32816
+ const hooksDir = path43.join(kit.path, "hooks");
32817
+ return collectHookRejections(hooksDir, kit.components.hooks.declared).map(
32818
+ (r) => ({
32819
+ code: "hook-declaration-invalid",
32820
+ location: `hooks/${path43.basename(r.file)}`,
32821
+ message: `hooks: '${path43.basename(r.file, ".md")}' was rejected at '${r.field}' \u2014 ${r.reason}. Until this is fixed the hook is not installed on any target; install and sync report it as an error-severity loss and exit 3. To fix: correct the field named above in this file.`
32822
+ })
32823
+ );
32824
+ }
32349
32825
  function collect(violations, v2) {
32350
32826
  if (v2) violations.push(v2);
32351
32827
  }
@@ -32360,16 +32836,19 @@ function checkKitFormatSupported(kit) {
32360
32836
  return null;
32361
32837
  }
32362
32838
  function checkEntrypointExists(kit) {
32363
- const entrypoint = "CLAUDE.md";
32839
+ const declared = kit.entrypoint;
32840
+ const entrypoint = declared ?? DEFAULT_ENTRYPOINT;
32364
32841
  const entrypointPath = path43.join(kit.path, entrypoint);
32365
- if (!fs42.existsSync(entrypointPath)) {
32366
- return {
32367
- code: "entrypoint-missing",
32368
- location: entrypoint,
32369
- message: `entrypoint '${entrypoint}' does not exist at the kit root`
32370
- };
32842
+ if (fs43.existsSync(entrypointPath)) {
32843
+ return null;
32371
32844
  }
32372
- return null;
32845
+ const legacyPresent = declared === void 0 && fs43.existsSync(path43.join(kit.path, LEGACY_ENTRYPOINT));
32846
+ const remedy = legacyPresent ? `This kit has '${LEGACY_ENTRYPOINT}' but not '${DEFAULT_ENTRYPOINT}'. Until now validate looked for '${LEGACY_ENTRYPOINT}' unconditionally; it now reads the manifest's 'entrypoint', defaulting to '${DEFAULT_ENTRYPOINT}' \u2014 the contract's canonical orientation document, of which '${LEGACY_ENTRYPOINT}' is a target translation. To fix: either add "entrypoint: ${LEGACY_ENTRYPOINT}" to tangyr-kit.yaml to keep the current filename, or rename '${LEGACY_ENTRYPOINT}' to '${DEFAULT_ENTRYPOINT}'.` : declared === void 0 ? `No 'entrypoint' is declared in tangyr-kit.yaml, so '${DEFAULT_ENTRYPOINT}' is expected at the kit root. To fix: create it, or declare the file you use with "entrypoint: <filename>".` : `tangyr-kit.yaml declares "entrypoint: ${declared}", and no such file exists at the kit root. To fix: create it, or correct the declaration.`;
32847
+ return {
32848
+ code: "entrypoint-missing",
32849
+ location: entrypoint,
32850
+ message: `entrypoint '${entrypoint}' does not exist at the kit root. ${remedy}`
32851
+ };
32373
32852
  }
32374
32853
  function checkDisciplineVersionSupported(kit) {
32375
32854
  if (!SUPPORTED_DISCIPLINE_VERSIONS.includes(kit.discipline.version)) {
@@ -32420,7 +32899,7 @@ function checkComponentFrontmatter(kit) {
32420
32899
  for (const name of kit.components[cat].declared) {
32421
32900
  if (missingSet.has(name)) continue;
32422
32901
  const filePath = cat === "skills" ? path43.join(kit.path, cat, name, "SKILL.md") : path43.join(kit.path, cat, `${name}.md`);
32423
- if (!fs42.existsSync(filePath)) continue;
32902
+ if (!fs43.existsSync(filePath)) continue;
32424
32903
  const relPath = path43.relative(kit.path, filePath);
32425
32904
  try {
32426
32905
  const { data } = parseMarkdownFrontmatter(filePath);
@@ -32446,10 +32925,10 @@ function checkComponentFrontmatter(kit) {
32446
32925
  }
32447
32926
  function checkReservedRootNames(kit) {
32448
32927
  const violations = [];
32449
- if (!fs42.existsSync(kit.path) || !fs42.statSync(kit.path).isDirectory()) {
32928
+ if (!fs43.existsSync(kit.path) || !fs43.statSync(kit.path).isDirectory()) {
32450
32929
  return violations;
32451
32930
  }
32452
- const entries = fs42.readdirSync(kit.path, { withFileTypes: true });
32931
+ const entries = fs43.readdirSync(kit.path, { withFileTypes: true });
32453
32932
  for (const entry of entries) {
32454
32933
  if (RESERVED_ROOT_NAMES.includes(entry.name)) {
32455
32934
  violations.push({
@@ -32467,7 +32946,7 @@ function checkHumanOnlyCommandInvocation(kit) {
32467
32946
  for (const name of kit.components.commands.declared) {
32468
32947
  if (missingSet.has(name)) continue;
32469
32948
  const filePath = path43.join(kit.path, "commands", `${name}.md`);
32470
- if (!fs42.existsSync(filePath)) continue;
32949
+ if (!fs43.existsSync(filePath)) continue;
32471
32950
  let data;
32472
32951
  try {
32473
32952
  ({ data } = parseMarkdownFrontmatter(filePath));
@@ -32489,7 +32968,7 @@ function checkHumanOnlyCommandInvocation(kit) {
32489
32968
  }
32490
32969
 
32491
32970
  // src/commands/verify.ts
32492
- import fs43 from "fs";
32971
+ import fs44 from "fs";
32493
32972
  import path44 from "path";
32494
32973
  function runVerifyCommand(options, logger) {
32495
32974
  const { config, configDir, sourceRoot, kitInfo } = resolveRuntime(
@@ -32582,7 +33061,7 @@ function verifyClaudeCodeGlobalRoots(roots, manifestKeys, mappings) {
32582
33061
  continue;
32583
33062
  }
32584
33063
  const fullPath = subPath ? path44.join(slotPath, subPath) : slotPath;
32585
- if (!fs43.existsSync(fullPath)) {
33064
+ if (!fs44.existsSync(fullPath)) {
32586
33065
  failures.push(
32587
33066
  `claude-code/${slot}${subPath ? `/${subPath}` : ""} missing in root ${root}: ${fullPath}`
32588
33067
  );
@@ -32712,7 +33191,7 @@ function buildProgram() {
32712
33191
  ).option(
32713
33192
  "--json",
32714
33193
  `Emit {"degraded": true} on stderr only when this resolution fell back to a warm cache after a network failure; otherwise this flag has no effect and output is unchanged \u2014 unlike --json on other commands, which always replaces the command's entire output.`
32715
- ).option("--tools <list>", "Comma-separated list of target tools").option("--scope <scope>", "Installation scope: global or project").option(
33194
+ ).option("--tools <list>", "Comma-separated list of target tools").option("--loss-report <path>", "Write semantic loss report JSON to a file").option("--scope <scope>", "Installation scope: global or project").option(
32716
33195
  "--ignore-errors",
32717
33196
  "Suppress exit code 3 when error-severity loss entries are present"
32718
33197
  ).action(