@alessandroraffa/tangyr 4.2.0 → 5.0.1

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 CHANGED
@@ -363,6 +363,26 @@ acceptedLoss:
363
363
 
364
364
  `--ignore-errors` remains the blunt instrument: it suppresses the exit code for every error entry, including ones nobody has looked at yet.
365
365
 
366
+ Not every error entry is structural. `rule-not-delivered` (below) reports a rule that did not reach a target on this run, which is usually fixable — by giving the target an instructions path of its own, or by not enabling it. Accept it when you have decided to live with it, not to quiet it.
367
+
368
+ ## Rules that did not arrive
369
+
370
+ A rule reaches a target either as a file in that target's own rules slot or as a section in the instructions document it reads. Several targets resolve their instructions to one project-root `AGENTS.md` and write it in turn, so a rule folded into that file survives only for whoever wrote last.
371
+
372
+ After every target has written, the run measures what is actually on disk and says so — one line per target, the count first, and the rules that did **not** arrive named:
373
+
374
+ ```text
375
+ Rules reaching each target (3 declared, measured on disk):
376
+ claude-code 3/3
377
+ copilot 3/3
378
+ codex 0/3 missing: style, testing, review
379
+ its rules are folded into AGENTS.md, which cline wrote last
380
+ opencode 3/3
381
+ cline 3/3
382
+ ```
383
+
384
+ Nothing is printed when every rule arrived; `--verbose` shows the block either way. The same fact reaches `--loss-report <path>` as a `rule-not-delivered` entry per rule per target, at `error` severity, so a run that loses a rule exits `3` rather than reporting success.
385
+
366
386
  ## Exit codes
367
387
 
368
388
  The full exit-code taxonomy, in numeric order. Every code below `5`
@@ -404,9 +424,9 @@ reaches them).
404
424
  ## Supported tools
405
425
 
406
426
  - **Claude Code** — instructions (`AGENTS.md` plus the `CLAUDE.md` shim), agents, skills, commands, rules, output styles, hooks, settings, and MCP, placed in scope-resolved Claude Code paths (fanned out across every configured global root — see [Multiple Claude Code global roots](#multiple-claude-code-global-roots))
407
- - **GitHub Copilot** — instructions (`AGENTS.md` plus the always-read `.github/copilot-instructions.md` mirror; project scope only, since Copilot has no global instructions slot), agents, prompts (compiled from commands), skills, hooks (`~/.copilot/hooks` or `.github/hooks`), and MCP. Hook matchers are rewritten to Copilot's own lowercase tool names (`bash`, `view`, `create`, `edit`, `grep`) — an untranslated `Bash` matcher never fires
408
- - **OpenAI Codex** — `AGENTS.md`, agent TOML files, rules (`.rules` files for `exec-policy`, appended to the instructions file as guidance otherwise), hooks (`hooks.json` plus the shared `~/.agents/hooks` script bridge; macOS and Linux only), and MCP in `config.toml`
409
- - **OpenCode** — instructions (`AGENTS.md`), agents, commands, skills (OpenCode-local plus the shared `~/.agents/skills` link), and `opencode.json` (MCP, permissions, custom provider), in XDG config paths. Hooks are **not** delivered: OpenCode exposes hooks only as plugin code, so every hook event is dropped and recorded as a `hook-as-plugin` loss
427
+ - **GitHub Copilot** — instructions (`AGENTS.md` plus the always-read `.github/copilot-instructions.md` mirror; project scope only, since Copilot has no global instructions slot), agents, prompts (compiled from commands), rules (`.github/instructions/<name>.instructions.md`, each carrying the rule's path scope as an `applyTo` glob; project scope only, since that is a repository path with no user-level equivalent), skills, hooks (`~/.copilot/hooks` or `.github/hooks`), and MCP. Hook matchers are rewritten to Copilot's own lowercase tool names (`bash`, `view`, `create`, `edit`, `grep`) — an untranslated `Bash` matcher never fires
428
+ - **OpenAI Codex** — `AGENTS.md`, agent TOML files, rules (`.rules` files for `exec-policy`, appended to the instructions file as guidance otherwise), skills and commands (both as skill directories at Codex's documented skills path — `~/.agents/skills` globally, `.agents/skills` inside a repository — because Codex deprecated its custom prompts in favour of skills), hooks (`hooks.json` plus the shared `~/.agents/hooks` script bridge; macOS and Linux only), and MCP in `config.toml`
429
+ - **OpenCode** — instructions (`~/.config/opencode/AGENTS.md` globally; `.opencode/AGENTS.md` inside a project, declared in the `instructions` field of `opencode.json`, because the project-root `AGENTS.md` is written by four other targets in turn and only the last writer's content survives), agents, commands, skills (OpenCode-local plus the shared `~/.agents/skills` link), and `opencode.json` (MCP, permissions, custom provider), in XDG config paths. Hooks are **not** delivered: OpenCode exposes hooks only as plugin code, so a declared hook event is dropped and recorded as a `hook-as-plugin` loss
410
430
  - **Cursor** — instructions, subagents, `.mdc` rules, commands, skills (Cursor-local plus shared), `hooks.json` plus the shared hook-script bridge (matchers rewritten to Cursor's tool names — `Shell`, `Read`, `Write`, `Grep`), `mcp.json`, and `permissions.json`
411
431
  - **Cline** — instructions (`AGENTS.md`, project scope only), rules, workflows (compiled from commands), skills, and MCP. Agents and hooks are **not** delivered: Cline has no committable persona file (`persona-not-representable`) and exposes hooks only as `@cline/sdk` plugin code (`hook-as-plugin`); both are recorded as losses rather than silently skipped
412
432
 
package/dist/index.js CHANGED
@@ -21625,6 +21625,19 @@ var lossSeverityByEntryType = {
21625
21625
  "rule-as-instruction": "info",
21626
21626
  "rule-mode-downgrade": "warning",
21627
21627
  "rule-path-scoping": "warning",
21628
+ // The rule is not there. Measured on the files each target reads, after every
21629
+ // target has written, so this says what arrived rather than what a compiler
21630
+ // intended — the two have disagreed, most often where several targets write
21631
+ // one instructions file and only the last writer's content survives.
21632
+ //
21633
+ // Error by the severity ladder's own definition: `warning` is "a real
21634
+ // reduction in fidelity that still produces a working artifact", and an
21635
+ // absent rule is not reduced, it is absent. `error` is "an intent that could
21636
+ // not be emitted at all and requires a manual step or a kit change", which is
21637
+ // exactly the remedy the run prints: give the target an instructions path of
21638
+ // its own, or disable it. `acceptedLoss` is how a project that has decided to
21639
+ // live with it says so.
21640
+ "rule-not-delivered": "error",
21628
21641
  "hook-event-drop": "warning",
21629
21642
  // A declaration the parser refuses installs nothing, anywhere, and used to
21630
21643
  // do so silently. Error severity is what makes install and sync exit 3.
@@ -26421,6 +26434,26 @@ Rules reaching each target (${String(declared)} declared, measured on disk):`
26421
26434
  function missingRulesByTarget(coverage) {
26422
26435
  return new Map(coverage.map((entry) => [entry.target, entry.missing]));
26423
26436
  }
26437
+ function recordRulesCoverageLoss(coverage, lossReport) {
26438
+ for (const entry of coverage) {
26439
+ if (entry.missing.length === 0) {
26440
+ continue;
26441
+ }
26442
+ addLoss(lossReport, entry.target, "rules", {
26443
+ source: "rules",
26444
+ fields: entry.missing.map((rule) => ({
26445
+ field: rule,
26446
+ action: "rule-not-delivered",
26447
+ severity: resolveLossSeverity(
26448
+ "agent-field",
26449
+ rule,
26450
+ "rule-not-delivered"
26451
+ ),
26452
+ detail: `${rule}: not readable by ${entry.target}${entry.reason ? ` \u2014 ${entry.reason}` : ""}`
26453
+ }))
26454
+ });
26455
+ }
26456
+ }
26424
26457
 
26425
26458
  // src/adapters/claude-code.ts
26426
26459
  import fs38 from "fs";
@@ -27725,23 +27758,6 @@ function compileOpenCodeHooks(sourceHooksJson, lossReport, sourceLabel) {
27725
27758
  ]
27726
27759
  });
27727
27760
  }
27728
- if (Object.keys(sourceHooks).length === 0) {
27729
- addLoss(lossReport, "opencode", "hooks", {
27730
- source: sourceLabel,
27731
- fields: [
27732
- {
27733
- field: "hooks",
27734
- action: "hook-as-plugin",
27735
- severity: resolveLossSeverity(
27736
- "agent-field",
27737
- "hooks",
27738
- "hook-as-plugin"
27739
- ),
27740
- detail: "OpenCode exposes hooks only as plugin code \u2014 no declarative hook file emitted"
27741
- }
27742
- ]
27743
- });
27744
- }
27745
27761
  return { payload: {}, translated: 0, dropped: 0, droppedEvents: [] };
27746
27762
  }
27747
27763
  function compileClineHooks(sourceHooksJson, lossReport, sourceLabel) {
@@ -27763,23 +27779,6 @@ function compileClineHooks(sourceHooksJson, lossReport, sourceLabel) {
27763
27779
  ]
27764
27780
  });
27765
27781
  }
27766
- if (Object.keys(sourceHooks).length === 0) {
27767
- addLoss(lossReport, "cline", "hooks", {
27768
- source: sourceLabel,
27769
- fields: [
27770
- {
27771
- field: "hooks",
27772
- action: "hook-as-plugin",
27773
- severity: resolveLossSeverity(
27774
- "agent-field",
27775
- "hooks",
27776
- "hook-as-plugin"
27777
- ),
27778
- detail: "Cline exposes hooks only as @cline/sdk AgentPlugin TypeScript/JS code \u2014 no declarative hook file emitted"
27779
- }
27780
- ]
27781
- });
27782
- }
27783
27782
  return { payload: {}, translated: 0, dropped: 0, droppedEvents: [] };
27784
27783
  }
27785
27784
  function compileCopilotHooks(sourceHooksJson, mappings, lossReport, sourceLabel) {
@@ -28321,24 +28320,6 @@ import path39 from "path";
28321
28320
  var import_yaml10 = __toESM(require_dist(), 1);
28322
28321
  import fs40 from "fs";
28323
28322
  function compileRules(sourceRulesDir, target, _mappings, lossReport) {
28324
- if (target === "copilot") {
28325
- addLoss(lossReport, "copilot", "rules", {
28326
- source: "rules",
28327
- fields: [
28328
- {
28329
- field: "rules",
28330
- action: "rule-path-scoping",
28331
- severity: resolveLossSeverity(
28332
- "agent-field",
28333
- "rules",
28334
- "rule-path-scoping"
28335
- ),
28336
- detail: "Rules: Skip \u2014 no verified global user-level rules surface for Copilot in v1"
28337
- }
28338
- ]
28339
- });
28340
- return { agentsAppendix: "", dotRulesFiles: [] };
28341
- }
28342
28323
  if (target === "opencode") {
28343
28324
  return compileOpenCodeRules(sourceRulesDir, lossReport);
28344
28325
  }
@@ -28430,7 +28411,7 @@ ${parsed.body.trim()}`);
28430
28411
  "rule",
28431
28412
  "rule-path-scoping"
28432
28413
  ),
28433
- detail: `${path39.basename(ruleFile)}: OpenCode has no per-agent rule scoping \u2014 rule appended to AGENTS.md instructions`
28414
+ detail: `${path39.basename(ruleFile)}: OpenCode has no per-agent rule scoping \u2014 rule appended to its instructions file`
28434
28415
  }
28435
28416
  ]
28436
28417
  });
@@ -30113,7 +30094,7 @@ function extractPrimaryPersonaContent(config, sourceRoot, lossReport) {
30113
30094
  "persona",
30114
30095
  "instructions-shim"
30115
30096
  ),
30116
- detail: `Primary persona from ${path43.basename(primaryFile)} folded into AGENTS.md (Cursor has no custom modes surface)`
30097
+ detail: `Primary persona from ${path43.basename(primaryFile)} folded into its instructions file (Cursor has no custom modes surface)`
30117
30098
  }
30118
30099
  ]
30119
30100
  });
@@ -31877,6 +31858,7 @@ SKIPPED CONFLICTS (${skippedTargetPaths.size} target(s) left unchanged):`
31877
31858
  missingRulesByTarget(rulesCoverage)
31878
31859
  );
31879
31860
  reportRulesCoverage(rulesCoverage, logger);
31861
+ recordRulesCoverageLoss(rulesCoverage, installLossReport);
31880
31862
  for (const [relativePath, sourcePath] of collectAllFiles(kitInfo, config)) {
31881
31863
  addFileToManifest(manifest, {
31882
31864
  relativePath,
@@ -35009,6 +34991,7 @@ async function runSyncCommand(options, logger) {
35009
34991
  missingRulesByTarget(rulesCoverage)
35010
34992
  );
35011
34993
  reportRulesCoverage(rulesCoverage, logger);
34994
+ recordRulesCoverageLoss(rulesCoverage, lossReport);
35012
34995
  }
35013
34996
  }
35014
34997
  writeLossReport(options.lossReport, lossReport);
@@ -5,9 +5,9 @@ Declarative translation inputs for `tangyr`.
5
5
  These files are part of the canonical runtime bundle, but they are design-time metadata rather than user-facing runtime artifacts. They define the current active v1 translation baseline for:
6
6
 
7
7
  - `Claude Code`
8
- - `GitHub Copilot`
9
- - `Codex`
10
- - `OpenCode` — full six-slot profile: `instructions` (`AGENTS.md` at the XDG-based `~/.config/opencode/` path), `agents`, `commands`, `skills` (OpenCode-local), `skills_shared` (cross-tool `~/.agents/skills`), and `config_doc` (`opencode.json`)
8
+ - `GitHub Copilot` — `rules` is a project-only slot (`.github/instructions/`), `null` at global scope: that path is a repository surface with no documented user-level equivalent
9
+ - `Codex` — skills and commands both land at Codex's documented skills path (`~/.agents/skills` globally, `.agents/skills` in a repository), since Codex deprecated custom prompts in favour of skills; `exec-policy` rules emit as `.rules` files and guidance rules are folded into the instructions document
10
+ - `OpenCode` — full six-slot profile: `instructions` (`AGENTS.md` at the XDG-based `~/.config/opencode/` path globally; `.opencode/AGENTS.md` in a project, declared in the `instructions` field of `opencode.json`, because the project-root `AGENTS.md` is claimed by four other targets), `agents`, `commands`, `skills` (OpenCode-local), `skills_shared` (cross-tool `~/.agents/skills`), and `config_doc` (`opencode.json`)
11
11
  - `Cursor` — full eight-slot profile: `instructions` (`AGENTS.md` at `~/.cursor/rules/`), `agents` (`~/.cursor/agents/`), `rules` (`~/.cursor/rules/`), `skills` (Cursor-local `~/.cursor/skills/`), `skills_shared` (cross-tool `~/.agents/skills`), `hooks_file` (`~/.cursor/hooks.json`), `mcp_file` (`~/.cursor/mcp.json`), and `permissions_file` (`~/.cursor/permissions.json`)
12
12
  - `Cline` — five-slot profile: `instructions` (`AGENTS.md` at project root — project scope only; global `instructions` is `null` because Cline has no documented global `AGENTS.md` read location and the global custom-instructions field is interface-only), `rules` (project: `.clinerules/`; global: `~/Documents/Cline/Rules` — macOS primary), `workflows` (project: `.clinerules/workflows/`; global: `~/Documents/Cline/Workflows/`), `skills` (project: `.cline/skills/`; global: `~/.cline/skills/`), and `mcp_file` (project: `cline-mcp.json`; global: `~/.cline/mcp.json`)
13
13
 
@@ -18,13 +18,23 @@ notes:
18
18
  - Severity is resolved per entry type, not per target - `resolveLossSeverity`
19
19
  (compiler/loss-report.ts) keys only on the entry type, with an explicit severity from
20
20
  hook-events.yaml overriding it.
21
+ - "`rule-not-delivered` was added on 2026-09-16 and is the one entry measured on the
22
+ files rather than recorded by the compiler that wrote them. It is emitted after every
23
+ target has run, once per rule that did not reach a target, which is the machine-readable
24
+ half of the per-target coverage block the run prints."
25
+ - "`hook-declaration-invalid` and `hook-source-ambiguous` were in the code's table and
26
+ missing from this one until 2026-09-16. Nothing compared the two, so the drift was
27
+ silent; tests/unit/mappings/loss-severity-matches-code.test.ts now does."
21
28
 
22
29
  entry_types:
23
30
  command-as-skill: info
24
31
  rule-as-instruction: info
25
32
  rule-mode-downgrade: warning
26
33
  rule-path-scoping: warning
34
+ rule-not-delivered: error
27
35
  hook-event-drop: warning
36
+ hook-declaration-invalid: error
37
+ hook-source-ambiguous: error
28
38
  hook-as-plugin: error
29
39
  instructions-shim: info
30
40
  permission-coarsening: error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alessandroraffa/tangyr",
3
- "version": "4.2.0",
3
+ "version": "5.0.1",
4
4
  "description": "CLI for the Tangyr discipline — install and manage operating kits for AI coding tools",
5
5
  "license": "MIT",
6
6
  "engines": {