@jterrazz/test 10.1.0 → 11.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/dist/oxlint.js CHANGED
@@ -251,6 +251,20 @@ const RULE_DOCS = {
251
251
  id: "C1",
252
252
  rationale: "Une profondeur fixe rend la place de chaque fichier prévisible et détectable statiquement."
253
253
  },
254
+ "c10-contracts-boundary": {
255
+ channel: "statique",
256
+ convention: "Hors de `specs/**/contracts/`, un import qui résout dans un dossier de provider (`contracts/{http,openai,anthropic}/…`) est une erreur : un test n’importe que les façades `*.contracts.ts`.",
257
+ family: "C",
258
+ id: "C10",
259
+ rationale: "Les contrats unitaires sont des détails de composition ; passer par la façade garde chaque scénario nommé au même endroit que le monde dont il dérive."
260
+ },
261
+ "c11-contract-data-pairing": {
262
+ channel: "statique",
263
+ convention: "Dans un dossier de provider, tout `*.response.json` et tout `*.request.ts` a un frère `<souche>.ts` — la souche est le nom jusqu’au PREMIER point (`events.fr.response.json` → `events.ts`) ; une donnée orpheline est une erreur.",
264
+ family: "C",
265
+ id: "C11",
266
+ rationale: "Une donnée n’existe que servie par un contrat — un fichier sans propriétaire est du poids mort qu’aucun test ne charge."
267
+ },
254
268
  "c2-http-only-requests": {
255
269
  channel: "statique",
256
270
  convention: "`requests/` ne contient que des fichiers `.http` ; toute autre extension est une erreur.",
@@ -260,10 +274,10 @@ const RULE_DOCS = {
260
274
  },
261
275
  "c4-contract-shape": {
262
276
  channel: "statique",
263
- convention: "Un fichier de `contracts/` respecte `<nom>.<provider>.ts` (provider openai|anthropic|http), à plat, `export default defineContract(...)`, imports depuis le point d’entrée public.",
277
+ convention: "Sous `specs/**/contracts/` : la RACINE ne porte que des façades `*.contracts.ts` (export default construit par `defineContracts(...)` ou une re-export de composition ; exports nommés = scénarios) et les dossiers de provider `http` | `openai` | `anthropic`. Un contrat unitaire est `<provider>/<nom-kebab>.ts` avec un `export default defineContract(...)` (ou une factory qui en retourne un), et son builder de `request` doit désigner le provider de son dossier. Un dossier de provider est plat et ne contient que des `*.ts` et des `*.response.json`.",
264
278
  family: "C",
265
279
  id: "C4",
266
- rationale: "Une forme figée rend les contrats découvrables et typables sans convention locale."
280
+ rationale: "Une arborescence figée sépare la façade publique des unités internes et fait porter le provider par le DOSSIER — les noms de fichiers redeviennent du métier, et la donnée volumineuse se range à côté du contrat qui la sert."
267
281
  },
268
282
  "c6-tomatch-extension": {
269
283
  channel: "statique",
@@ -490,7 +504,7 @@ const CHECKER_PASSES = [
490
504
  },
491
505
  {
492
506
  channel: "checker",
493
- convention: "Aucune fixture morte : tout fichier sous `seeds/`/`requests/`/`intercepts/`/`fixtures/` et toute entrée de premier niveau de `expected/` doit être référencée ; un dossier de feature sans `*.test.ts` est orphelin (warning si argument non littéral).",
507
+ convention: "Aucune fixture morte : tout fichier sous `seeds/`/`requests/`/`fixtures/` et toute entrée de premier niveau de `expected/` doit être référencée ; un dossier de feature sans `*.test.ts` est orphelin (warning si argument non littéral).",
494
508
  family: "C",
495
509
  id: "C9",
496
510
  name: "c9-dead-fixtures",
@@ -1432,8 +1446,94 @@ const c1DomainStructure = {
1432
1446
  }
1433
1447
  };
1434
1448
  //#endregion
1449
+ //#region src/lint/rules/c10-contracts-boundary.ts
1450
+ /** A specifier reaching into a provider folder — the internal half of contracts/. */
1451
+ const INTERNAL_UNIT = /(?:^|\/)contracts\/(?:anthropic|http|openai)\//u;
1452
+ /**
1453
+ * CONVENTIONS C10 — the contracts boundary. A feature's `contracts/` folder has
1454
+ * a PUBLIC half (`*.contracts.ts` facades: the default export is the world, the
1455
+ * named exports are its scenarios) and an INTERNAL half (the `http/`, `openai/`
1456
+ * and `anthropic/` unit contracts the facades compose). Outside `contracts/`,
1457
+ * importing a unit is an error — a test routes through the facade, so a
1458
+ * scenario is named once, next to the world it derives from.
1459
+ */
1460
+ const c10ContractsBoundary = {
1461
+ create(context) {
1462
+ const parts = segments(context.physicalFilename);
1463
+ if (!parts.includes("specs")) return {};
1464
+ if (parts.slice(0, -1).includes("contracts")) return {};
1465
+ return importSourceVisitor(({ node, source }) => {
1466
+ if (INTERNAL_UNIT.test(source)) context.report({
1467
+ data: { source },
1468
+ messageId: "internal",
1469
+ node
1470
+ });
1471
+ });
1472
+ },
1473
+ meta: {
1474
+ docs: RULE_DOCS["c10-contracts-boundary"],
1475
+ messages: { internal: "Import \"{{source}}\" reaches into a provider folder — only contracts/*.contracts.ts is importable from a test; add a named scenario export there instead (C10 — see docs/10-linting.md)." },
1476
+ type: "problem"
1477
+ }
1478
+ };
1479
+ //#endregion
1480
+ //#region src/lint/rules/c11-contract-data-pairing.ts
1481
+ const TEST_FILE$11 = /\.test\.[cm]?[jt]sx?$/;
1482
+ /** The provider folders that may carry data files. */
1483
+ const PROVIDERS$1 = [
1484
+ "anthropic",
1485
+ "http",
1486
+ "openai"
1487
+ ];
1488
+ /** Data files: served payloads and matched inputs, both owned by a contract. */
1489
+ const DATA_SUFFIXES = [".request.ts", ".response.json"];
1490
+ /** The owning contract of a data file: its name up to the FIRST dot, `.ts`. */
1491
+ function ownerOf(entry) {
1492
+ return `${entry.slice(0, entry.indexOf("."))}.ts`;
1493
+ }
1494
+ /**
1495
+ * CONVENTIONS C11 — data is owned by a contract. Inside a provider folder every
1496
+ * `*.response.json` (served payload) and `*.request.ts` (matched prompt/body)
1497
+ * pairs with the sibling `<stem>.ts` that serves it, where the stem is the name
1498
+ * up to the FIRST dot: `events.fr.response.json` belongs to `events.ts`. A data
1499
+ * file with no owner is dead weight no test can reach — reported on the feature's
1500
+ * test file, since oxlint never visits a `.json`.
1501
+ */
1502
+ const c11ContractDataPairing = {
1503
+ create(context) {
1504
+ const file = context.physicalFilename;
1505
+ if (!TEST_FILE$11.test(file) || !segments(file).includes("specs")) return {};
1506
+ return { Program(node) {
1507
+ const contractsDir = join(dirname(file), "contracts");
1508
+ for (const provider of PROVIDERS$1) {
1509
+ const entries = listDirectory(join(contractsDir, provider));
1510
+ if (entries === null) continue;
1511
+ const present = new Set(entries);
1512
+ for (const entry of entries) {
1513
+ if (!DATA_SUFFIXES.some((suffix) => entry.endsWith(suffix))) continue;
1514
+ const owner = ownerOf(entry);
1515
+ if (!present.has(owner)) context.report({
1516
+ data: {
1517
+ entry,
1518
+ owner,
1519
+ provider
1520
+ },
1521
+ messageId: "orphan",
1522
+ node
1523
+ });
1524
+ }
1525
+ }
1526
+ } };
1527
+ },
1528
+ meta: {
1529
+ docs: RULE_DOCS["c11-contract-data-pairing"],
1530
+ messages: { orphan: "contracts/{{provider}}/{{entry}} has no owning contract — data pairs with the sibling {{owner}} (stem = name up to the first dot) (C11 — see docs/10-linting.md)." },
1531
+ type: "problem"
1532
+ }
1533
+ };
1534
+ //#endregion
1435
1535
  //#region src/lint/rules/c2-http-only-requests.ts
1436
- const TEST_FILE$9 = /\.test\.[cm]?[jt]sx?$/;
1536
+ const TEST_FILE$10 = /\.test\.[cm]?[jt]sx?$/;
1437
1537
  /**
1438
1538
  * CONVENTIONS C2 — `requests/` contains only `.http` files (a request file is
1439
1539
  * a COMPLETE request: method, path, headers, body). Anchored on the feature's
@@ -1444,7 +1544,7 @@ const TEST_FILE$9 = /\.test\.[cm]?[jt]sx?$/;
1444
1544
  const c2HttpOnlyRequests = {
1445
1545
  create(context) {
1446
1546
  const file = context.physicalFilename;
1447
- if (!TEST_FILE$9.test(file) || !segments(file).includes("specs")) return {};
1547
+ if (!TEST_FILE$10.test(file) || !segments(file).includes("specs")) return {};
1448
1548
  return { Program(node) {
1449
1549
  const requestsDir = join(dirname(file), "requests");
1450
1550
  for (const entry of listDirectory(requestsDir) ?? []) if (!entry.endsWith(".http")) context.report({
@@ -1462,65 +1562,217 @@ const c2HttpOnlyRequests = {
1462
1562
  };
1463
1563
  //#endregion
1464
1564
  //#region src/lint/rules/c4-contract-shape.ts
1465
- /** Providers a contract file may declare (C4 see docs/10-linting.md). */
1565
+ /** The only directories a `contracts/` root may holdthe provider carriers. */
1466
1566
  const PROVIDERS = /* @__PURE__ */ new Set([
1467
1567
  "anthropic",
1468
1568
  "http",
1469
1569
  "openai"
1470
1570
  ]);
1471
- const CONTRACT_NAME = /^[A-Za-z0-9][\w-]*\.(?<provider>[a-z]+)\.[cm]?ts$/;
1472
- /** Is the import source the framework's public entry (`@jterrazz/test` / `src/index`)? */
1473
- function isPublicEntry(source) {
1474
- return source === "@jterrazz/test" || source.endsWith("/src/index.js") || source.endsWith("/src/index.ts");
1571
+ const TEST_FILE$9 = /\.test\.[cm]?[jt]sx?$/;
1572
+ /** A file oxlint itself visits — the AST half of the rule reports on those. */
1573
+ const SOURCE_FILE = /\.[cm]?[jt]sx?$/;
1574
+ /** The public facade of a feature: `contracts/<kebab>.contracts.ts`. */
1575
+ const COMPOSITE_FILE = /^[a-z0-9]+(?:-[a-z0-9]+)*\.contracts\.[cm]?ts$/u;
1576
+ /** An internal unit contract: `contracts/<provider>/<kebab>.ts`. */
1577
+ const UNIT_FILE = /^[a-z0-9]+(?:-[a-z0-9]+)*\.[cm]?ts$/u;
1578
+ /** Matched data next to its contract: `contracts/<provider>/<stem>.request.ts`. */
1579
+ const REQUEST_DATA_FILE = /^[a-z0-9]+(?:-[a-z0-9]+)*\.request\.[cm]?ts$/u;
1580
+ /** Served data next to its contract: `contracts/<provider>/<stem>[.<qualifier>].response.json`. */
1581
+ const RESPONSE_DATA_FILE = /\.response\.json$/;
1582
+ /** The name a specifier/declaration exports under, when it is `default`. */
1583
+ function exportsDefault(node) {
1584
+ return (node.specifiers ?? []).some((specifier) => {
1585
+ const exported = specifier.exported;
1586
+ return exported?.type === "Identifier" && exported.name === "default" || stringValue(exported) === "default";
1587
+ });
1588
+ }
1589
+ /** Is this node a `defineContract(...)` call? */
1590
+ function isDefineContract(node) {
1591
+ const callee = node.callee;
1592
+ return node.type === "CallExpression" && callee?.type === "Identifier" && callee.name === "defineContract";
1593
+ }
1594
+ /** A default export that may legitimately produce a contract (value or factory). */
1595
+ function producesContract(declaration) {
1596
+ if (declaration === void 0) return false;
1597
+ return isDefineContract(declaration) || declaration.type === "ArrowFunctionExpression" || declaration.type === "FunctionDeclaration" || declaration.type === "FunctionExpression" || declaration.type === "Identifier";
1475
1598
  }
1476
1599
  /**
1477
- * CONVENTIONS C4 a contract file (`contracts/<name>.<provider>.ts`) has a
1478
- * rigid shape: flat under `contracts/` (no subfolders), `provider { openai,
1479
- * anthropic, http }`, a single `export default defineContract(...)`, no named
1480
- * exports, and imports only from the public entry. The runtime channel (the
1481
- * loader) only catches a bad default export; this rule closes the rest.
1600
+ * The provider a `request:` value implies, when the builder is written as
1601
+ * `<provider>.<verb>(…)` the only statically decidable form.
1482
1602
  */
1483
- const c4ContractShape = {
1484
- create(context) {
1485
- const parts = segments(context.physicalFilename);
1486
- const contractsIndex = parts.lastIndexOf("contracts");
1487
- if (contractsIndex === -1 || contractsIndex >= parts.length - 1 || !parts.includes("specs")) return {};
1488
- const base = parts.at(-1) ?? "";
1489
- return { Program(node) {
1490
- if (contractsIndex !== parts.length - 2) context.report({
1491
- data: { base },
1492
- messageId: "subfolder",
1603
+ function requestProvider(request) {
1604
+ if (request?.type !== "CallExpression") return;
1605
+ const callee = request.callee;
1606
+ if (callee?.type !== "MemberExpression") return;
1607
+ const object = callee.object;
1608
+ return object?.type === "Identifier" ? object.name : void 0;
1609
+ }
1610
+ /**
1611
+ * The layout half — walks the feature's `contracts/` tree from the test file
1612
+ * that owns it. Covers what oxlint never visits: a stray `.json` at the root,
1613
+ * a directory that is not a provider, a nested folder or a foreign extension
1614
+ * inside a provider folder.
1615
+ */
1616
+ function checkLayout(context, node, contractsDir) {
1617
+ const entries = listDirectory(contractsDir);
1618
+ if (entries === null) return;
1619
+ for (const entry of entries) {
1620
+ const path = join(contractsDir, entry);
1621
+ if (!isDirectory(path)) {
1622
+ if (!SOURCE_FILE.test(entry)) context.report({
1623
+ data: { entry },
1624
+ messageId: "rootEntry",
1493
1625
  node
1494
1626
  });
1495
- const match = CONTRACT_NAME.exec(base);
1496
- if (match === null || !PROVIDERS.has(match.groups?.provider ?? "")) context.report({
1497
- data: { base },
1498
- messageId: "badName",
1627
+ continue;
1628
+ }
1629
+ if (!PROVIDERS.has(entry)) {
1630
+ context.report({
1631
+ data: { entry },
1632
+ messageId: "badProviderDir",
1499
1633
  node
1500
1634
  });
1501
- let hasDefault = false;
1502
- for (const statement of node.body ?? []) if (statement.type === "ImportDeclaration") {
1503
- const source = stringValue(statement.source);
1504
- if (source !== void 0 && !isPublicEntry(source)) context.report({
1505
- data: { source },
1506
- messageId: "foreignImport",
1507
- node: statement
1508
- });
1509
- } else if (statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") context.report({
1510
- messageId: "namedExport",
1511
- node: statement
1512
- });
1513
- else if (statement.type === "ExportDefaultDeclaration") {
1635
+ continue;
1636
+ }
1637
+ for (const child of listDirectory(path) ?? []) if (isDirectory(join(path, child))) context.report({
1638
+ data: {
1639
+ child,
1640
+ provider: entry
1641
+ },
1642
+ messageId: "providerSubfolder",
1643
+ node
1644
+ });
1645
+ else if (!/\.[cm]?ts$/u.test(child) && !RESPONSE_DATA_FILE.test(child)) context.report({
1646
+ data: {
1647
+ child,
1648
+ provider: entry
1649
+ },
1650
+ messageId: "providerEntry",
1651
+ node
1652
+ });
1653
+ }
1654
+ }
1655
+ /** The facade: `<kebab>.contracts.ts`, default-exporting a composition. */
1656
+ function checkComposite(context, program, base) {
1657
+ if (!COMPOSITE_FILE.test(base)) {
1658
+ context.report({
1659
+ data: { base },
1660
+ messageId: "rootFile",
1661
+ node: program
1662
+ });
1663
+ return;
1664
+ }
1665
+ let hasDefault = false;
1666
+ let composed = false;
1667
+ walk(program, (node) => {
1668
+ if (node.type === "ExportDefaultDeclaration") hasDefault = true;
1669
+ else if (node.type === "ExportAllDeclaration" || node.type === "ExportNamedDeclaration") {
1670
+ if (exportsDefault(node)) {
1514
1671
  hasDefault = true;
1515
- const declaration = statement.declaration;
1516
- const callee = declaration?.type === "CallExpression" ? declaration.callee : void 0;
1517
- if (callee?.type !== "Identifier" || callee.name !== "defineContract") context.report({
1518
- messageId: "notDefineContract",
1519
- node: statement
1520
- });
1672
+ composed = stringValue(node.source) !== void 0 || composed;
1521
1673
  }
1522
- if (!hasDefault) context.report({
1523
- messageId: "missingDefault",
1674
+ } else if (node.type === "CallExpression") {
1675
+ const callee = node.callee;
1676
+ if (callee?.type === "Identifier" && callee.name === "defineContracts" || callee !== void 0 && memberPropertyName(callee) === "with") composed = true;
1677
+ }
1678
+ });
1679
+ if (!hasDefault) context.report({
1680
+ data: { base },
1681
+ messageId: "missingComposite",
1682
+ node: program
1683
+ });
1684
+ else if (!composed) context.report({
1685
+ data: { base },
1686
+ messageId: "notDefineContracts",
1687
+ node: program
1688
+ });
1689
+ }
1690
+ /** A unit contract under its provider folder. */
1691
+ function checkUnit(context, program, base, provider) {
1692
+ if (REQUEST_DATA_FILE.test(base)) return;
1693
+ if (!UNIT_FILE.test(base)) {
1694
+ context.report({
1695
+ data: { base },
1696
+ messageId: "badName",
1697
+ node: program
1698
+ });
1699
+ return;
1700
+ }
1701
+ let hasDefault = false;
1702
+ let wellFormed = false;
1703
+ walk(program, (node) => {
1704
+ if (node.type === "ExportDefaultDeclaration") {
1705
+ hasDefault = true;
1706
+ wellFormed = producesContract(node.declaration) || wellFormed;
1707
+ } else if (node.type === "ExportNamedDeclaration" && exportsDefault(node)) {
1708
+ hasDefault = true;
1709
+ wellFormed = true;
1710
+ }
1711
+ if (isDefineContract(node)) {
1712
+ const argument = node.arguments?.[0];
1713
+ const declared = requestProvider(argument === void 0 ? void 0 : findProperty(argument, "request")?.value);
1714
+ if (declared !== void 0 && PROVIDERS.has(declared) && declared !== provider) context.report({
1715
+ data: {
1716
+ declared,
1717
+ provider
1718
+ },
1719
+ messageId: "providerMismatch",
1720
+ node
1721
+ });
1722
+ }
1723
+ });
1724
+ if (!hasDefault) context.report({
1725
+ data: { base },
1726
+ messageId: "missingDefault",
1727
+ node: program
1728
+ });
1729
+ else if (!wellFormed) context.report({
1730
+ data: { base },
1731
+ messageId: "notDefineContract",
1732
+ node: program
1733
+ });
1734
+ }
1735
+ /**
1736
+ * CONVENTIONS C4 — the structure of a feature's `contracts/` tree. The ROOT
1737
+ * holds only `*.contracts.ts` facades (default export = a `defineContracts`
1738
+ * composition, named exports = scenario factories) and the provider
1739
+ * directories `http` / `openai` / `anthropic`. A provider directory holds
1740
+ * `<kebab>.ts` unit contracts (default export = `defineContract(...)` or a
1741
+ * factory returning one) plus their sibling data (`*.response.json`,
1742
+ * `*.request.ts`) — the FOLDER carries the provider, so a unit whose `request`
1743
+ * builder names another provider is an error.
1744
+ *
1745
+ * Two halves: the AST half reports on each contract file it visits, the layout
1746
+ * half walks the tree from the feature's test file (oxlint never visits a
1747
+ * `.json` fixture or an empty directory).
1748
+ */
1749
+ const c4ContractShape = {
1750
+ create(context) {
1751
+ const file = context.physicalFilename;
1752
+ const parts = segments(file);
1753
+ if (!parts.includes("specs")) return {};
1754
+ const contractsIndex = parts.lastIndexOf("contracts");
1755
+ if (contractsIndex === -1) {
1756
+ if (!TEST_FILE$9.test(file)) return {};
1757
+ return { Program(node) {
1758
+ checkLayout(context, node, join(dirname(file), "contracts"));
1759
+ } };
1760
+ }
1761
+ const base = parts.at(-1) ?? "";
1762
+ const depth = parts.length - 1 - contractsIndex;
1763
+ if (depth === 1) return { Program(node) {
1764
+ checkComposite(context, node, base);
1765
+ } };
1766
+ if (depth === 2) {
1767
+ const provider = parts[contractsIndex + 1];
1768
+ return PROVIDERS.has(provider) ? { Program(node) {
1769
+ checkUnit(context, node, base, provider);
1770
+ } } : {};
1771
+ }
1772
+ return { Program(node) {
1773
+ context.report({
1774
+ data: { base },
1775
+ messageId: "tooDeep",
1524
1776
  node
1525
1777
  });
1526
1778
  } };
@@ -1528,12 +1780,18 @@ const c4ContractShape = {
1528
1780
  meta: {
1529
1781
  docs: RULE_DOCS["c4-contract-shape"],
1530
1782
  messages: {
1531
- badName: "Contract file \"{{base}}\" must be named <name>.<provider>.ts with provider openai | anthropic | http (C4 — see docs/10-linting.md).",
1532
- foreignImport: "Contract imports \"{{source}}\" — a contract imports only from the public entry (@jterrazz/test) (C4 — see docs/10-linting.md).",
1533
- missingDefault: "Contract file has no `export default defineContract(...)` (C4 — see docs/10-linting.md).",
1534
- namedExport: "Contract files have no named exportsonly `export default defineContract(...)` (C4 — see docs/10-linting.md).",
1535
- notDefineContract: "The default export of a contract file must be `defineContract(...)` syntactically (C4 — see docs/10-linting.md).",
1536
- subfolder: "Contract file \"{{base}}\" is nested contracts/ is flat, no subfolders (C4 — see docs/10-linting.md)."
1783
+ badName: "Contract unit \"{{base}}\" must be named <kebab-name>.ts the folder already carries the provider (C4 — see docs/10-linting.md).",
1784
+ badProviderDir: "contracts/{{entry}}/ is not a provider directory — a contracts/ root holds only http/, openai/, anthropic/ and *.contracts.ts files (C4 — see docs/10-linting.md).",
1785
+ missingComposite: "Contract facade \"{{base}}\" has no default export — it must default-export the composed world (C4 — see docs/10-linting.md).",
1786
+ missingDefault: "Contract unit \"{{base}}\" has no default exporta unit default-exports `defineContract(...)` or a factory returning one (C4 — see docs/10-linting.md).",
1787
+ notDefineContract: "The default export of a contract unit must be `defineContract(...)` or a factory returning a contract (C4 — see docs/10-linting.md).",
1788
+ notDefineContracts: "The default export of \"{{base}}\" must be built from `defineContracts(...)` (or a composition re-export) (C4 — see docs/10-linting.md).",
1789
+ providerEntry: "contracts/{{provider}}/{{child}} is neither a *.ts contract nor a *.response.json payload (C4 — see docs/10-linting.md).",
1790
+ providerMismatch: "This contract lives in contracts/{{provider}}/ but its request is a `{{declared}}.*` builder — the folder carries the provider (C4 — see docs/10-linting.md).",
1791
+ providerSubfolder: "contracts/{{provider}}/{{child}}/ is nested — a provider folder is flat (C4 — see docs/10-linting.md).",
1792
+ rootEntry: "contracts/{{entry}} is not a *.contracts.ts facade — data files live in a provider folder next to their contract (C4 — see docs/10-linting.md).",
1793
+ rootFile: "Contract file \"{{base}}\" sits at the contracts/ root, which holds only *.contracts.ts facades — a unit contract belongs in http/, openai/ or anthropic/ (C4 — see docs/10-linting.md).",
1794
+ tooDeep: "Contract file \"{{base}}\" is nested deeper than contracts/<provider>/ (C4 — see docs/10-linting.md)."
1537
1795
  },
1538
1796
  type: "problem"
1539
1797
  }
@@ -2950,6 +3208,8 @@ const plugin = {
2950
3208
  "b8-kebab-trigger": b8KebabTrigger,
2951
3209
  "b9w-product-command": b9wProductCommand,
2952
3210
  "c1-domain-structure": c1DomainStructure,
3211
+ "c10-contracts-boundary": c10ContractsBoundary,
3212
+ "c11-contract-data-pairing": c11ContractDataPairing,
2953
3213
  "c2-http-only-requests": c2HttpOnlyRequests,
2954
3214
  "c4-contract-shape": c4ContractShape,
2955
3215
  "c6-tomatch-extension": c6ToMatchExtension,