@alessandroraffa/tangyr 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +152 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17378,21 +17378,33 @@ function collectProjectScopeFindings(projectRoot, tools, options) {
17378
17378
  ) : void 0;
17379
17379
  const probePaths = buildProjectProbes(tool, projectRoot, profile);
17380
17380
  for (const { resolvedPath, description, classification } of probePaths) {
17381
- if (!fs11.existsSync(resolvedPath)) {
17381
+ let stat;
17382
+ try {
17383
+ stat = fs11.lstatSync(resolvedPath);
17384
+ } catch {
17382
17385
  continue;
17383
17386
  }
17384
- const stat = fs11.lstatSync(resolvedPath);
17387
+ const type = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
17388
+ const ours = type === "file" && carriesTangyrMarker(resolvedPath);
17385
17389
  findings.push({
17386
17390
  path: resolvedPath,
17387
17391
  tool,
17388
- type: stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file",
17389
- classification,
17390
- description
17392
+ type,
17393
+ classification: ours ? "compatible" : classification,
17394
+ description: ours ? `${description} (written by Tangyr)` : description
17391
17395
  });
17392
17396
  }
17393
17397
  }
17394
17398
  return findings;
17395
17399
  }
17400
+ function carriesTangyrMarker(filePath) {
17401
+ try {
17402
+ const firstNonEmpty = fs11.readFileSync(filePath, "utf8").split("\n").find((line) => line.trim().length > 0) ?? "";
17403
+ return extractProvenanceMarker(firstNonEmpty) !== null;
17404
+ } catch {
17405
+ return false;
17406
+ }
17407
+ }
17396
17408
  function requestedAssessTools(toolFilter) {
17397
17409
  return toolFilter ?? [...DEFAULT_ASSESS_TARGETS];
17398
17410
  }
@@ -22182,11 +22194,23 @@ async function runUninstallCommand(options, logger) {
22182
22194
  }
22183
22195
  const claudeRoots = scope === "global" && selectedTools.has("claude-code") ? resolveClaudeRootsForUninstall(manifest, runtime) : [];
22184
22196
  let removedCount = 0;
22197
+ const removedParentDirs = /* @__PURE__ */ new Set();
22185
22198
  for (const file of useManifestPathRemoval ? manifestArtifacts : []) {
22186
22199
  if (isMergeSurfaceKey(file.relativePath)) {
22187
- logger.verbose(
22188
- ` left in place (merge surface \u2014 the block is stripped, not the file): ${file.path ?? file.relativePath}`
22200
+ const surfacePath = file.path ?? resolveManifestKeyToPath(
22201
+ file.relativePath,
22202
+ scope,
22203
+ runtime,
22204
+ uninstallProjectRoot
22189
22205
  );
22206
+ if (surfacePath && stripTangyrFromMergeSurface(surfacePath, logger)) {
22207
+ removedParentDirs.add(path24.dirname(surfacePath));
22208
+ removedCount++;
22209
+ } else {
22210
+ logger.verbose(
22211
+ ` left in place (merge surface, nothing of ours in it): ${surfacePath ?? file.relativePath}`
22212
+ );
22213
+ }
22190
22214
  continue;
22191
22215
  }
22192
22216
  const isClaudeGlobal = file.relativePath.startsWith("claude-code/") && scope === "global";
@@ -22199,6 +22223,7 @@ async function runUninstallCommand(options, logger) {
22199
22223
  for (const actualPath of paths) {
22200
22224
  if (actualPath && (fs27.existsSync(actualPath) || isSymlink(actualPath))) {
22201
22225
  removeManagedPath(actualPath);
22226
+ removedParentDirs.add(path24.dirname(actualPath));
22202
22227
  logger.verbose(` removed: ${actualPath}`);
22203
22228
  removedCount++;
22204
22229
  } else {
@@ -22220,6 +22245,7 @@ async function runUninstallCommand(options, logger) {
22220
22245
  continue;
22221
22246
  }
22222
22247
  removeManagedPath(actualPath);
22248
+ removedParentDirs.add(path24.dirname(actualPath));
22223
22249
  logger.verbose(` removed: ${actualPath}`);
22224
22250
  removedCount++;
22225
22251
  } else {
@@ -22270,6 +22296,11 @@ async function runUninstallCommand(options, logger) {
22270
22296
  fs27.unlinkSync(manifestPath);
22271
22297
  logger.verbose(` removed manifest: ${manifestPath}`);
22272
22298
  }
22299
+ pruneEmptyDirs(path24.join(scopePath, "backups"), logger);
22300
+ const pruneBoundary = path24.dirname(scopePath);
22301
+ for (const dir of removedParentDirs) {
22302
+ pruneEmptyAncestors(dir, pruneBoundary, logger);
22303
+ }
22273
22304
  try {
22274
22305
  const remaining = fs27.readdirSync(scopePath);
22275
22306
  if (remaining.length === 0) {
@@ -22649,6 +22680,79 @@ var MERGE_SURFACE_SLOTS = /* @__PURE__ */ new Set([
22649
22680
  "mcp_shared_file",
22650
22681
  "mcp_file"
22651
22682
  ]);
22683
+ function stripTangyrFromMergeSurface(filePath, logger) {
22684
+ const parsed = readJsonObject2(filePath);
22685
+ if (!parsed) {
22686
+ return false;
22687
+ }
22688
+ const cleaned = { ...parsed };
22689
+ const hooks = removeManagedClaudeHookEntries(
22690
+ parsed.hooks,
22691
+ (entry) => entry._tangyr === true || entry._proteus === true
22692
+ );
22693
+ if (hooks === void 0) {
22694
+ delete cleaned.hooks;
22695
+ } else {
22696
+ cleaned.hooks = hooks;
22697
+ }
22698
+ if (isRecord(parsed.mcpServers)) {
22699
+ const kept = Object.fromEntries(
22700
+ Object.entries(parsed.mcpServers).filter(
22701
+ ([, server]) => !isRecord(server) || !isRecord(server._tangyr) && !isRecord(server._proteus)
22702
+ )
22703
+ );
22704
+ if (Object.keys(kept).length === 0) {
22705
+ delete cleaned.mcpServers;
22706
+ } else {
22707
+ cleaned.mcpServers = kept;
22708
+ }
22709
+ }
22710
+ delete cleaned._tangyr;
22711
+ delete cleaned._proteus;
22712
+ if (JSON.stringify(cleaned) === JSON.stringify(parsed)) {
22713
+ return false;
22714
+ }
22715
+ writeJsonOrRemove(filePath, cleaned);
22716
+ logger.verbose(` stripped Tangyr's block from: ${filePath}`);
22717
+ return true;
22718
+ }
22719
+ function pruneEmptyAncestors(startDir, stopAt, logger) {
22720
+ const boundary = path24.resolve(stopAt);
22721
+ let current = path24.resolve(startDir);
22722
+ while (current !== boundary && current.startsWith(`${boundary}${path24.sep}`)) {
22723
+ try {
22724
+ if (fs27.readdirSync(current).length > 0) {
22725
+ return;
22726
+ }
22727
+ fs27.rmdirSync(current);
22728
+ logger.verbose(` removed empty directory: ${current}`);
22729
+ } catch {
22730
+ return;
22731
+ }
22732
+ current = path24.dirname(current);
22733
+ }
22734
+ }
22735
+ function pruneEmptyDirs(dir, logger) {
22736
+ let stat;
22737
+ try {
22738
+ stat = fs27.lstatSync(dir);
22739
+ } catch {
22740
+ return;
22741
+ }
22742
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
22743
+ return;
22744
+ }
22745
+ for (const entry of fs27.readdirSync(dir, { withFileTypes: true })) {
22746
+ if (entry.isDirectory() && !entry.isSymbolicLink()) {
22747
+ pruneEmptyDirs(path24.join(dir, entry.name), logger);
22748
+ }
22749
+ }
22750
+ try {
22751
+ fs27.rmdirSync(dir);
22752
+ logger.verbose(` removed empty directory: ${dir}`);
22753
+ } catch {
22754
+ }
22755
+ }
22652
22756
  function stillMatchesRecord(artifactPath, recorded) {
22653
22757
  if (isLegacyArtifactState(recorded)) {
22654
22758
  return true;
@@ -24987,6 +25091,7 @@ function toConflictPolicy2(value) {
24987
25091
  }
24988
25092
 
24989
25093
  // src/commands/install.ts
25094
+ import crypto11 from "crypto";
24990
25095
  import fs43 from "fs";
24991
25096
  import path43 from "path";
24992
25097
 
@@ -26127,7 +26232,11 @@ function resolveInstructionsSource(kitRoot, config) {
26127
26232
  "source.instructions in tangyr.config.yaml"
26128
26233
  );
26129
26234
  }
26130
- return { file, label: normalizeLabel(kitRoot, file), origin: "config-override" };
26235
+ return {
26236
+ file,
26237
+ label: normalizeLabel(kitRoot, file),
26238
+ origin: "config-override"
26239
+ };
26131
26240
  }
26132
26241
  const declared = resolveKitEntrypointName(kitRoot);
26133
26242
  if (declared !== void 0) {
@@ -26139,7 +26248,11 @@ function resolveInstructionsSource(kitRoot, config) {
26139
26248
  "entrypoint in tangyr-kit.yaml"
26140
26249
  );
26141
26250
  }
26142
- return { file, label: normalizeLabel(kitRoot, file), origin: "manifest-entrypoint" };
26251
+ return {
26252
+ file,
26253
+ label: normalizeLabel(kitRoot, file),
26254
+ origin: "manifest-entrypoint"
26255
+ };
26143
26256
  }
26144
26257
  for (const name of [DEFAULT_ENTRYPOINT, PORTABLE_INSTRUCTIONS_NAME]) {
26145
26258
  const file = path33.resolve(kitRoot, name);
@@ -26398,6 +26511,12 @@ function applyClaudeCodeShimUpgrade(shimPath, agentsMdPath, manifest, scopePath,
26398
26511
  const firstNonEmptyLine = existing.split("\n").find((l) => l.trim().length > 0) ?? "";
26399
26512
  const marker = extractProvenanceMarker(firstNonEmptyLine);
26400
26513
  if (marker !== null && marker.hash === expectedBodyHash) {
26514
+ addArtifactToManifest(manifest, {
26515
+ relativePath: SHIM_MANIFEST_KEY,
26516
+ path: shimPath,
26517
+ hash: expectedBodyHash,
26518
+ origin: "tangyr-managed"
26519
+ });
26401
26520
  logger?.verbose(
26402
26521
  `instructions-shim: already current, skipped -> ${shimPath}`
26403
26522
  );
@@ -30731,7 +30850,12 @@ function isBackupablePath(targetPath) {
30731
30850
  }
30732
30851
  function backupConflictingTarget(targetPath, scopePath) {
30733
30852
  const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/gu, "").replace("T", "T").replace(/\.\d+Z$/u, "Z");
30734
- const relativeToRoot = path43.relative("/", targetPath).replace(/^\.\.\/+/gu, "");
30853
+ const scopeRoot = path43.dirname(scopePath);
30854
+ let relativeToRoot = path43.relative(scopeRoot, targetPath);
30855
+ if (relativeToRoot.startsWith("..") || path43.isAbsolute(relativeToRoot)) {
30856
+ const digest = crypto11.createHash("sha256").update(path43.dirname(targetPath)).digest("hex").slice(0, 12);
30857
+ relativeToRoot = path43.join(`_outside-${digest}`, path43.basename(targetPath));
30858
+ }
30735
30859
  const backupPath = path43.join(scopePath, "backups", ts2, relativeToRoot);
30736
30860
  fs43.mkdirSync(path43.dirname(backupPath), { recursive: true });
30737
30861
  const stat = fs43.lstatSync(targetPath);
@@ -30953,14 +31077,14 @@ async function runInstallCommand(options, logger) {
30953
31077
  return;
30954
31078
  }
30955
31079
  if (conflictingFindings.length > 0) {
31080
+ const toolsByPath = /* @__PURE__ */ new Map();
31081
+ for (const finding of conflictingFindings) {
31082
+ const tools2 = toolsByPath.get(finding.path) ?? [];
31083
+ tools2.push(finding.tool);
31084
+ toolsByPath.set(finding.path, tools2);
31085
+ }
30956
31086
  if (options.yes) {
30957
31087
  const decidedPaths = /* @__PURE__ */ new Set();
30958
- const toolsByPath = /* @__PURE__ */ new Map();
30959
- for (const finding of conflictingFindings) {
30960
- const tools2 = toolsByPath.get(finding.path) ?? [];
30961
- tools2.push(finding.tool);
30962
- toolsByPath.set(finding.path, tools2);
30963
- }
30964
31088
  const posture = config.onConflict === "ask" ? "skip" : config.onConflict;
30965
31089
  const replaceable = Array.from(toolsByPath.keys()).filter(
30966
31090
  (p) => !foreignSkillsSlots.has(p)
@@ -31039,10 +31163,11 @@ ACCEPTED CONFLICTS (posture: ${posture}; ${replaceable.length} target(s)):`
31039
31163
  } else {
31040
31164
  logger.info(
31041
31165
  `
31042
- Found ${conflictingFindings.length} conflicting target(s). Please choose an action for each:
31166
+ Found ${toolsByPath.size} conflicting target(s). Please choose an action for each:
31043
31167
  `
31044
31168
  );
31045
- for (const finding of conflictingFindings) {
31169
+ for (const [conflictPath, claimingTools] of toolsByPath) {
31170
+ const finding = { path: conflictPath, tool: claimingTools.join(", ") };
31046
31171
  const choice = await dist_default8({
31047
31172
  message: `Conflict: ${finding.path} (${finding.tool})
31048
31173
  Choose an action:`,
@@ -31440,7 +31565,12 @@ function installClaudeCodeAdapterSlots(args) {
31440
31565
  if (outcome.status !== "success" && outcome.status !== "skipped-current") {
31441
31566
  continue;
31442
31567
  }
31443
- const key = claudeCodeOutcomeToManifestKey(profile, outcome, scope, projectRoot);
31568
+ const key = claudeCodeOutcomeToManifestKey(
31569
+ profile,
31570
+ outcome,
31571
+ scope,
31572
+ projectRoot
31573
+ );
31444
31574
  if (!key) {
31445
31575
  continue;
31446
31576
  }
@@ -32358,7 +32488,7 @@ function commandProbeRows(target, key, command, args) {
32358
32488
  }
32359
32489
 
32360
32490
  // src/commands/status.ts
32361
- import crypto11 from "crypto";
32491
+ import crypto12 from "crypto";
32362
32492
  import fs45 from "fs";
32363
32493
  import path46 from "path";
32364
32494
  function runStatusCommand(options, logger) {
@@ -33482,7 +33612,7 @@ function containsTangyrSource(value) {
33482
33612
  return typeof value._tangyr_source === "string" || typeof value._proteus_source === "string" || Object.values(value).some((entry) => containsTangyrSource(entry));
33483
33613
  }
33484
33614
  function hashString6(value) {
33485
- return `sha256:${crypto11.createHash("sha256").update(value).digest("hex")}`;
33615
+ return `sha256:${crypto12.createHash("sha256").update(value).digest("hex")}`;
33486
33616
  }
33487
33617
 
33488
33618
  // src/commands/sync.ts
@@ -34020,10 +34150,7 @@ async function runSyncCommand(options, logger) {
34020
34150
  }
34021
34151
  options.kitPath = kitPathOverride;
34022
34152
  options.kit = kitName;
34023
- const { config, configDir, configSource, sourceRoot, kitInfo } = resolveRuntime(
34024
- options,
34025
- import.meta.url
34026
- );
34153
+ const { config, configDir, configSource, sourceRoot, kitInfo } = resolveRuntime(options, import.meta.url);
34027
34154
  const syncOptions = toSyncOptions(options, config, configDir, configSource);
34028
34155
  const targets = activeTargets(config, options.target);
34029
34156
  const results = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alessandroraffa/tangyr",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "CLI for the Tangyr discipline — install and manage operating kits for AI coding tools",
5
5
  "license": "MIT",
6
6
  "engines": {