@alessandroraffa/tangyr 6.0.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +14 -0
  2. package/dist/index.js +75 -28
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -60,6 +60,7 @@ Options declared on individual commands in addition to the [global flags](#globa
60
60
  | Command | Option | Description |
61
61
  | -------------------------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
62
62
  | `install`, `sync` | `--loss-report <path>` | Write an **extra copy** of this run's semantic loss report to `<path>`. The report itself is scope state and is rewritten at `<scope>/loss.json` by every `install` and `sync` regardless, whether or not the run exits `3`. |
63
+ | `install` | `--replace` | Install this kit over a different one already in this scope: the installed kit is uninstalled first, which restores what it displaced. Without it, a change of kit is refused rather than performed as an update |
63
64
  | `install`, `sync` | `--ignore-errors` | Suppress exit code `3` when the loss report contains `error`-severity entries |
64
65
  | `install`, `sync` | `--kit <name>` | Kit to install/synchronize: `<name>` for a local source, `<name>@<version>` for a remote one |
65
66
  | `install`, `uninstall`, `assess` | `--tools <list>` | Comma-separated list of target tools |
@@ -509,3 +510,16 @@ Precedence: `--claude-config-dir` flag > `TANGYR_CLAUDE_CONFIG_DIRS` env > `clau
509
510
  Per-root artifacts (instructions, agents, skills, commands, rules, output-styles, hooks settings) are written under each root. Shared artifacts (`~/.claude.json` for MCP, `~/.agents/hooks` for hooks) are resolved from the platform default and written once regardless of how many roots are configured.
510
511
 
511
512
  The manifest records the full set of roots written so that `verify` and `uninstall` operate on all of them.
513
+
514
+ ## Documentation
515
+
516
+ | Document | For |
517
+ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
518
+ | [Operating Tangyr in a project](docs/guides/operating-tangyr-in-a-project.md) | Running the CLI in a repository you did not set up: what it copies, rewrites and deletes, how to read a run, and how to tell a defect from a target that changed |
519
+ | [Authoring a project-local kit](docs/guides/authoring-a-project-local-kit.md) | Writing a kit and installing it into a repository |
520
+ | [Installing a private kit](docs/guides/kit-consumer-onboarding.md) | Consuming a kit from a signed remote origin |
521
+ | [Functional specification](docs/specifications/tangyr-cli-functional-spec-v1.0.md) | What the tool must do: the canonical taxonomy, the per-target contracts, the loss and conflict models |
522
+ | [Maintaining this CLI](docs/maintaining-the-cli.md) | Working on the tool itself: the decisions the code cannot explain, and the traps that have already cost something |
523
+ | [Mapping declarations](mappings/README.md) | The declarative translation inputs under `mappings/` |
524
+
525
+ The dated records under `docs/stepledgers/`, `docs/plans/`, `docs/reports/`, `docs/initiatives/` and `docs/archive/` describe work as it was done and are not maintained against the current behaviour.
package/dist/index.js CHANGED
@@ -15930,7 +15930,8 @@ function readManifest(scopePath) {
15930
15930
  // and `uninstall` is a different process from `install`. The shared-surface
15931
15931
  // records went out to disk correctly and arrived as `undefined`, which put
15932
15932
  // removal back on the marker-based guess this exists to replace.
15933
- sharedSurfaces: Array.isArray(parsed.sharedSurfaces) ? parsed.sharedSurfaces : void 0
15933
+ sharedSurfaces: Array.isArray(parsed.sharedSurfaces) ? parsed.sharedSurfaces : void 0,
15934
+ kit: typeof parsed.kit === "object" && parsed.kit !== null && typeof parsed.kit.name === "string" ? parsed.kit : void 0
15934
15935
  };
15935
15936
  }
15936
15937
  function readStrictManifest(scopePath) {
@@ -16710,6 +16711,21 @@ function makeResolver(scope, projectRoot, tool) {
16710
16711
  return resolved;
16711
16712
  };
16712
16713
  }
16714
+ function slotsForScope(scopeMap, scope, projectRoot) {
16715
+ if (scope !== "project") {
16716
+ return scopeMap?.global;
16717
+ }
16718
+ const projectSlots = scopeMap?.project;
16719
+ if (!projectSlots) {
16720
+ return void 0;
16721
+ }
16722
+ const root = projectRoot ?? process.cwd();
16723
+ const resolved = {};
16724
+ for (const [slot, value] of Object.entries(projectSlots)) {
16725
+ resolved[slot] = typeof value === "string" ? path9.resolve(root, value) : value;
16726
+ }
16727
+ return resolved;
16728
+ }
16713
16729
 
16714
16730
  // src/mappings/loader.ts
16715
16731
  var import_yaml4 = __toESM(require_dist(), 1);
@@ -25383,7 +25399,7 @@ function checkClaudeCode(options, findings, config, sourceRoot, configDir, logge
25383
25399
  checkParentWritablePath(artifact.path, artifact.component, findings);
25384
25400
  }
25385
25401
  }
25386
- function slotsForScope(config, sourceRoot, configDir, target, options) {
25402
+ function slotsForScope2(config, sourceRoot, configDir, target, options) {
25387
25403
  const scopeMap = loadMappings(sourceRoot, config, configDir).platformPaths.targets[target];
25388
25404
  const scope = options.scope ?? config.scope ?? "global";
25389
25405
  if (scope !== "project") {
@@ -25405,7 +25421,7 @@ function slotsForScope(config, sourceRoot, configDir, target, options) {
25405
25421
  return resolved;
25406
25422
  }
25407
25423
  function checkCopilot(config, sourceRoot, configDir, findings, options) {
25408
- const globalPaths = slotsForScope(
25424
+ const globalPaths = slotsForScope2(
25409
25425
  config,
25410
25426
  sourceRoot,
25411
25427
  configDir,
@@ -25428,7 +25444,7 @@ function checkCopilot(config, sourceRoot, configDir, findings, options) {
25428
25444
  }
25429
25445
  }
25430
25446
  function checkCodex(config, sourceRoot, configDir, findings, logger, options) {
25431
- const globalPaths = slotsForScope(
25447
+ const globalPaths = slotsForScope2(
25432
25448
  config,
25433
25449
  sourceRoot,
25434
25450
  configDir,
@@ -32032,9 +32048,35 @@ async function runInstallCommand(options, logger) {
32032
32048
  logger.warn(
32033
32049
  `Existing Tangyr installation found in ${scopePath} (archetype: ${existingManifest.archetype}, version: ${existingManifest.version})`
32034
32050
  );
32051
+ const installedKit = existingManifest.kit;
32052
+ const switchingKit = installedKit !== void 0 && installedKit.name !== kitInfo.name;
32035
32053
  let action;
32036
- if (options.yes) {
32054
+ if (switchingKit && options.replace) {
32055
+ logger.warn(
32056
+ `Replacing '${installedKit.name}' (${installedKit.version}) with '${kitInfo.name}' (${kitInfo.version}): the installed kit is uninstalled first, which restores what it displaced.`
32057
+ );
32058
+ action = "clean-install";
32059
+ } else if (switchingKit && options.yes) {
32060
+ logger.error(
32061
+ `This scope holds the kit '${installedKit.name}' (${installedKit.version}), not '${kitInfo.name}'.`
32062
+ );
32063
+ logger.error(
32064
+ `Installing over it would leave both kits' artifacts in place, and every target would read both. Pass --replace to uninstall '${installedKit.name}' first and install '${kitInfo.name}' in its place, or run \`tangyr uninstall\` yourself. If you meant to update '${installedKit.name}', check kitPath in your configuration.`
32065
+ );
32066
+ process.exit(1);
32067
+ } else if (options.yes) {
32037
32068
  action = "update";
32069
+ } else if (switchingKit) {
32070
+ action = await dist_default8({
32071
+ message: `This scope holds '${installedKit.name}'. Installing '${kitInfo.name}' over it would leave both. Choose an action:`,
32072
+ choices: [
32073
+ {
32074
+ name: `Replace (uninstall '${installedKit.name}', then install '${kitInfo.name}')`,
32075
+ value: "clean-install"
32076
+ },
32077
+ { name: "Abort", value: "abort" }
32078
+ ]
32079
+ });
32038
32080
  } else if (!process.stdin.isTTY) {
32039
32081
  logger.error(
32040
32082
  "Existing installation requires an explicit action. Pass --yes to update, or run interactively."
@@ -32293,6 +32335,7 @@ SKIPPED CONFLICTS (${skippedTargetPaths.size} target(s) left unchanged):`
32293
32335
  ensureScope(scopePath);
32294
32336
  const manifest = existingManifest ?? createEmptyManifest(kitInfo.archetype, kitInfo.kitFormat, scope);
32295
32337
  manifest.tools = [.../* @__PURE__ */ new Set([...manifest.tools, ...tools])];
32338
+ manifest.kit = { name: kitInfo.name, version: kitInfo.version };
32296
32339
  manifest.files = [];
32297
32340
  for (const backup of backupsToAdd) {
32298
32341
  manifest.backups.push(backup);
@@ -34221,25 +34264,10 @@ function classifyDirectoryPresence(component, dirPath) {
34221
34264
  conformant: present
34222
34265
  };
34223
34266
  }
34224
- function slotsForScope2(scopeMap, scope, projectRoot) {
34225
- if (scope !== "project") {
34226
- return scopeMap?.global;
34227
- }
34228
- const projectSlots = scopeMap?.project;
34229
- if (!projectSlots) {
34230
- return void 0;
34231
- }
34232
- const root = projectRoot ?? process.cwd();
34233
- const resolved = {};
34234
- for (const [slot, value] of Object.entries(projectSlots)) {
34235
- resolved[slot] = typeof value === "string" ? path51.resolve(root, value) : value;
34236
- }
34237
- return resolved;
34238
- }
34239
34267
  function classifyCodexFiles(config, sourceRoot, configDir, scope, projectRoot) {
34240
34268
  const mappings = loadMappings(sourceRoot, config, configDir);
34241
34269
  const scopeMap = mappings.platformPaths.targets.codex;
34242
- const paths = slotsForScope2(scopeMap, scope, projectRoot);
34270
+ const paths = slotsForScope(scopeMap, scope, projectRoot);
34243
34271
  if (!paths?.instructions || !paths.agents || !paths.skills || !paths.hooks_shared_file || !paths.mcp_shared_file || !paths.rules) {
34244
34272
  throw new Error("platform-paths.yaml: incomplete codex path mapping");
34245
34273
  }
@@ -34387,7 +34415,7 @@ function classifyCodexHooksFile(filePath) {
34387
34415
  function classifyCopilotFiles(config, sourceRoot, configDir, scope, projectRoot) {
34388
34416
  const mappings = loadMappings(sourceRoot, config, configDir);
34389
34417
  const scopeMap = mappings.platformPaths.targets.copilot;
34390
- const paths = slotsForScope2(scopeMap, scope, projectRoot);
34418
+ const paths = slotsForScope(scopeMap, scope, projectRoot);
34391
34419
  if (!paths?.agents || !paths.skills || !paths.prompts || !paths.hooks || !paths.mcp_shared_file) {
34392
34420
  throw new Error("platform-paths.yaml: incomplete copilot path mapping");
34393
34421
  }
@@ -35327,6 +35355,16 @@ async function runSyncCommand(options, logger) {
35327
35355
  const lossReport = createLossReport();
35328
35356
  const mappings = targets.includes("copilot") || targets.includes("codex") || targets.includes("opencode") || targets.includes("cursor") || targets.includes("cline") || targets.some((target) => isDeferredTarget(target)) ? loadMappings(sourceRoot, config, configDir) : void 0;
35329
35357
  const kitSync = kitInfo ? prepareKitManifest(syncOptions.scope, syncOptions.projectRoot, kitInfo) : void 0;
35358
+ const installedKit = kitSync?.manifest.kit;
35359
+ if (kitInfo && installedKit && installedKit.name !== kitInfo.name) {
35360
+ logger.error(
35361
+ `This scope holds the kit '${installedKit.name}' (${installedKit.version}), and the configuration names '${kitInfo.name}'.`
35362
+ );
35363
+ logger.error(
35364
+ `sync updates the installed kit; it does not replace it. Run \`tangyr install --replace\` to put '${kitInfo.name}' in its place, or check kitPath if you meant to update '${installedKit.name}'.`
35365
+ );
35366
+ process.exit(1);
35367
+ }
35330
35368
  if (kitInfo && kitSync && options.dryRun) {
35331
35369
  printManifestDiff(logger, kitInfo.name, kitSync.diff);
35332
35370
  }
@@ -35609,6 +35647,7 @@ function updateManifest(kitInfo, kitSync) {
35609
35647
  });
35610
35648
  }
35611
35649
  }
35650
+ kitSync.manifest.kit = { name: kitInfo.name, version: kitInfo.version };
35612
35651
  refreshArtifactStates(kitSync.manifest.artifacts);
35613
35652
  writeManifest(kitSync.scopePath, kitSync.manifest);
35614
35653
  }
@@ -35691,11 +35730,16 @@ function cleanupRemovedManagedArtifacts(config, mappings, targets, options, remo
35691
35730
  return;
35692
35731
  }
35693
35732
  const currentComponent = options.component;
35694
- const copilotPaths = mappings.platformPaths.targets.copilot.global;
35695
- const codexPaths = mappings.platformPaths.targets.codex.global;
35696
- const opencodePaths = mappings.platformPaths.targets.opencode.global;
35697
- const cursorPaths = mappings.platformPaths.targets.cursor.global;
35698
- const clinePaths = mappings.platformPaths.targets.cline.global;
35733
+ const scopeOf = (target) => slotsForScope(
35734
+ mappings.platformPaths.targets[target],
35735
+ options.scope,
35736
+ options.projectRoot
35737
+ );
35738
+ const copilotPaths = scopeOf("copilot");
35739
+ const codexPaths = scopeOf("codex");
35740
+ const opencodePaths = scopeOf("opencode");
35741
+ const cursorPaths = scopeOf("cursor");
35742
+ const clinePaths = scopeOf("cline");
35699
35743
  for (const relativePath of removedSourcePaths) {
35700
35744
  const component = componentForRemovedSource(relativePath, config);
35701
35745
  if (currentComponent && component !== currentComponent) {
@@ -36829,7 +36873,10 @@ function buildProgram() {
36829
36873
  ).option(
36830
36874
  "--json",
36831
36875
  `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.`
36832
- ).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(
36876
+ ).option("--tools <list>", "Comma-separated list of target tools").option("--loss-report <path>", "Write semantic loss report JSON to a file").option(
36877
+ "--replace",
36878
+ "Install this kit over a different one already in this scope, uninstalling that one first"
36879
+ ).option("--scope <scope>", "Installation scope: global or project").option(
36833
36880
  "--ignore-errors",
36834
36881
  "Suppress exit code 3 when error-severity loss entries are present"
36835
36882
  ).action(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alessandroraffa/tangyr",
3
- "version": "6.0.1",
3
+ "version": "7.0.0",
4
4
  "description": "CLI for the Tangyr discipline — install and manage operating kits for AI coding tools",
5
5
  "license": "MIT",
6
6
  "engines": {