@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.
- package/README.md +14 -0
- package/dist/index.js +75 -28
- 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
|
|
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 =
|
|
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 =
|
|
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.
|
|
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 =
|
|
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 =
|
|
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
|
|
35695
|
-
|
|
35696
|
-
|
|
35697
|
-
|
|
35698
|
-
|
|
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(
|
|
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(
|