@alessandroraffa/tangyr 1.0.1 → 1.0.2

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 +135 -18
  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
 
@@ -26398,6 +26503,12 @@ function applyClaudeCodeShimUpgrade(shimPath, agentsMdPath, manifest, scopePath,
26398
26503
  const firstNonEmptyLine = existing.split("\n").find((l) => l.trim().length > 0) ?? "";
26399
26504
  const marker = extractProvenanceMarker(firstNonEmptyLine);
26400
26505
  if (marker !== null && marker.hash === expectedBodyHash) {
26506
+ addArtifactToManifest(manifest, {
26507
+ relativePath: SHIM_MANIFEST_KEY,
26508
+ path: shimPath,
26509
+ hash: expectedBodyHash,
26510
+ origin: "tangyr-managed"
26511
+ });
26401
26512
  logger?.verbose(
26402
26513
  `instructions-shim: already current, skipped -> ${shimPath}`
26403
26514
  );
@@ -30731,7 +30842,12 @@ function isBackupablePath(targetPath) {
30731
30842
  }
30732
30843
  function backupConflictingTarget(targetPath, scopePath) {
30733
30844
  const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/gu, "").replace("T", "T").replace(/\.\d+Z$/u, "Z");
30734
- const relativeToRoot = path43.relative("/", targetPath).replace(/^\.\.\/+/gu, "");
30845
+ const scopeRoot = path43.dirname(scopePath);
30846
+ let relativeToRoot = path43.relative(scopeRoot, targetPath);
30847
+ if (relativeToRoot.startsWith("..") || path43.isAbsolute(relativeToRoot)) {
30848
+ const digest = crypto11.createHash("sha256").update(path43.dirname(targetPath)).digest("hex").slice(0, 12);
30849
+ relativeToRoot = path43.join(`_outside-${digest}`, path43.basename(targetPath));
30850
+ }
30735
30851
  const backupPath = path43.join(scopePath, "backups", ts2, relativeToRoot);
30736
30852
  fs43.mkdirSync(path43.dirname(backupPath), { recursive: true });
30737
30853
  const stat = fs43.lstatSync(targetPath);
@@ -30953,14 +31069,14 @@ async function runInstallCommand(options, logger) {
30953
31069
  return;
30954
31070
  }
30955
31071
  if (conflictingFindings.length > 0) {
31072
+ const toolsByPath = /* @__PURE__ */ new Map();
31073
+ for (const finding of conflictingFindings) {
31074
+ const tools2 = toolsByPath.get(finding.path) ?? [];
31075
+ tools2.push(finding.tool);
31076
+ toolsByPath.set(finding.path, tools2);
31077
+ }
30956
31078
  if (options.yes) {
30957
31079
  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
31080
  const posture = config.onConflict === "ask" ? "skip" : config.onConflict;
30965
31081
  const replaceable = Array.from(toolsByPath.keys()).filter(
30966
31082
  (p) => !foreignSkillsSlots.has(p)
@@ -31039,10 +31155,11 @@ ACCEPTED CONFLICTS (posture: ${posture}; ${replaceable.length} target(s)):`
31039
31155
  } else {
31040
31156
  logger.info(
31041
31157
  `
31042
- Found ${conflictingFindings.length} conflicting target(s). Please choose an action for each:
31158
+ Found ${toolsByPath.size} conflicting target(s). Please choose an action for each:
31043
31159
  `
31044
31160
  );
31045
- for (const finding of conflictingFindings) {
31161
+ for (const [conflictPath, claimingTools] of toolsByPath) {
31162
+ const finding = { path: conflictPath, tool: claimingTools.join(", ") };
31046
31163
  const choice = await dist_default8({
31047
31164
  message: `Conflict: ${finding.path} (${finding.tool})
31048
31165
  Choose an action:`,
@@ -32358,7 +32475,7 @@ function commandProbeRows(target, key, command, args) {
32358
32475
  }
32359
32476
 
32360
32477
  // src/commands/status.ts
32361
- import crypto11 from "crypto";
32478
+ import crypto12 from "crypto";
32362
32479
  import fs45 from "fs";
32363
32480
  import path46 from "path";
32364
32481
  function runStatusCommand(options, logger) {
@@ -33482,7 +33599,7 @@ function containsTangyrSource(value) {
33482
33599
  return typeof value._tangyr_source === "string" || typeof value._proteus_source === "string" || Object.values(value).some((entry) => containsTangyrSource(entry));
33483
33600
  }
33484
33601
  function hashString6(value) {
33485
- return `sha256:${crypto11.createHash("sha256").update(value).digest("hex")}`;
33602
+ return `sha256:${crypto12.createHash("sha256").update(value).digest("hex")}`;
33486
33603
  }
33487
33604
 
33488
33605
  // src/commands/sync.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alessandroraffa/tangyr",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "CLI for the Tangyr discipline — install and manage operating kits for AI coding tools",
5
5
  "license": "MIT",
6
6
  "engines": {