prodc 0.7.0__tar.gz → 0.7.2__tar.gz

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 (108) hide show
  1. {prodc-0.7.0 → prodc-0.7.2}/CHANGELOG.md +16 -0
  2. {prodc-0.7.0 → prodc-0.7.2}/PKG-INFO +1 -1
  3. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/__init__.py +1 -1
  4. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/adapters.py +20 -1
  5. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/catalog/ux.py +85 -36
  6. {prodc-0.7.0 → prodc-0.7.2}/tests/test_vitest.py +22 -0
  7. {prodc-0.7.0 → prodc-0.7.2}/.gitignore +0 -0
  8. {prodc-0.7.0 → prodc-0.7.2}/LICENSE +0 -0
  9. {prodc-0.7.0 → prodc-0.7.2}/README.md +0 -0
  10. {prodc-0.7.0 → prodc-0.7.2}/examples/quickstart/features/orders.feature +0 -0
  11. {prodc-0.7.0 → prodc-0.7.2}/examples/quickstart/needs/jordan.yaml +0 -0
  12. {prodc-0.7.0 → prodc-0.7.2}/examples/quickstart/prodc.toml +0 -0
  13. {prodc-0.7.0 → prodc-0.7.2}/pyproject.toml +0 -0
  14. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/agent.md +0 -0
  15. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/anchor.py +0 -0
  16. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/board.py +0 -0
  17. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/callbacks.py +0 -0
  18. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/catalog/__init__.py +0 -0
  19. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/cli/__init__.py +0 -0
  20. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/cli/__main__.py +0 -0
  21. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/cli/app.py +0 -0
  22. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/cli/commands.py +0 -0
  23. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/cli/common.py +0 -0
  24. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/cli/render.py +0 -0
  25. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/cli/serialize.py +0 -0
  26. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/config.py +0 -0
  27. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/dedup.py +0 -0
  28. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/glossary.py +0 -0
  29. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/guide.md +0 -0
  30. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/inbox.py +0 -0
  31. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/issues.py +0 -0
  32. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/loader.py +0 -0
  33. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/model.py +0 -0
  34. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/py.typed +0 -0
  35. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/report.py +0 -0
  36. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/report_export.py +0 -0
  37. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/report_html.py +0 -0
  38. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/status.py +0 -0
  39. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/storybook.py +0 -0
  40. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/views.py +0 -0
  41. {prodc-0.7.0 → prodc-0.7.2}/src/prodc/writer.py +0 -0
  42. {prodc-0.7.0 → prodc-0.7.2}/tests/fixtures/storybook/index.json +0 -0
  43. {prodc-0.7.0 → prodc-0.7.2}/tests/test_adapters.py +0 -0
  44. {prodc-0.7.0 → prodc-0.7.2}/tests/test_anchor.py +0 -0
  45. {prodc-0.7.0 → prodc-0.7.2}/tests/test_answer.py +0 -0
  46. {prodc-0.7.0 → prodc-0.7.2}/tests/test_attest_policy.py +0 -0
  47. {prodc-0.7.0 → prodc-0.7.2}/tests/test_awaits.py +0 -0
  48. {prodc-0.7.0 → prodc-0.7.2}/tests/test_callbacks.py +0 -0
  49. {prodc-0.7.0 → prodc-0.7.2}/tests/test_capture_block_scalar.py +0 -0
  50. {prodc-0.7.0 → prodc-0.7.2}/tests/test_catalog_add.py +0 -0
  51. {prodc-0.7.0 → prodc-0.7.2}/tests/test_catalog_lint.py +0 -0
  52. {prodc-0.7.0 → prodc-0.7.2}/tests/test_citation.py +0 -0
  53. {prodc-0.7.0 → prodc-0.7.2}/tests/test_claim_gating.py +0 -0
  54. {prodc-0.7.0 → prodc-0.7.2}/tests/test_cli.py +0 -0
  55. {prodc-0.7.0 → prodc-0.7.2}/tests/test_command_hook.py +0 -0
  56. {prodc-0.7.0 → prodc-0.7.2}/tests/test_confidence.py +0 -0
  57. {prodc-0.7.0 → prodc-0.7.2}/tests/test_config_keys.py +0 -0
  58. {prodc-0.7.0 → prodc-0.7.2}/tests/test_contradicts.py +0 -0
  59. {prodc-0.7.0 → prodc-0.7.2}/tests/test_decisions.py +0 -0
  60. {prodc-0.7.0 → prodc-0.7.2}/tests/test_declared_only.py +0 -0
  61. {prodc-0.7.0 → prodc-0.7.2}/tests/test_dedup.py +0 -0
  62. {prodc-0.7.0 → prodc-0.7.2}/tests/test_dedup_candidates.py +0 -0
  63. {prodc-0.7.0 → prodc-0.7.2}/tests/test_description.py +0 -0
  64. {prodc-0.7.0 → prodc-0.7.2}/tests/test_drafts.py +0 -0
  65. {prodc-0.7.0 → prodc-0.7.2}/tests/test_evalgate.py +0 -0
  66. {prodc-0.7.0 → prodc-0.7.2}/tests/test_expects.py +0 -0
  67. {prodc-0.7.0 → prodc-0.7.2}/tests/test_external_adapter.py +0 -0
  68. {prodc-0.7.0 → prodc-0.7.2}/tests/test_files_key.py +0 -0
  69. {prodc-0.7.0 → prodc-0.7.2}/tests/test_flowsteps.py +0 -0
  70. {prodc-0.7.0 → prodc-0.7.2}/tests/test_gitlab_source.py +0 -0
  71. {prodc-0.7.0 → prodc-0.7.2}/tests/test_glossary.py +0 -0
  72. {prodc-0.7.0 → prodc-0.7.2}/tests/test_importance.py +0 -0
  73. {prodc-0.7.0 → prodc-0.7.2}/tests/test_inbox.py +0 -0
  74. {prodc-0.7.0 → prodc-0.7.2}/tests/test_inventory.py +0 -0
  75. {prodc-0.7.0 → prodc-0.7.2}/tests/test_issues.py +0 -0
  76. {prodc-0.7.0 → prodc-0.7.2}/tests/test_lint_decision.py +0 -0
  77. {prodc-0.7.0 → prodc-0.7.2}/tests/test_loader.py +0 -0
  78. {prodc-0.7.0 → prodc-0.7.2}/tests/test_models.py +0 -0
  79. {prodc-0.7.0 → prodc-0.7.2}/tests/test_needref.py +0 -0
  80. {prodc-0.7.0 → prodc-0.7.2}/tests/test_path.py +0 -0
  81. {prodc-0.7.0 → prodc-0.7.2}/tests/test_path_views.py +0 -0
  82. {prodc-0.7.0 → prodc-0.7.2}/tests/test_playwright.py +0 -0
  83. {prodc-0.7.0 → prodc-0.7.2}/tests/test_principle_withdrawal.py +0 -0
  84. {prodc-0.7.0 → prodc-0.7.2}/tests/test_principles.py +0 -0
  85. {prodc-0.7.0 → prodc-0.7.2}/tests/test_relink.py +0 -0
  86. {prodc-0.7.0 → prodc-0.7.2}/tests/test_report.py +0 -0
  87. {prodc-0.7.0 → prodc-0.7.2}/tests/test_report_export.py +0 -0
  88. {prodc-0.7.0 → prodc-0.7.2}/tests/test_report_html.py +0 -0
  89. {prodc-0.7.0 → prodc-0.7.2}/tests/test_results_reports.py +0 -0
  90. {prodc-0.7.0 → prodc-0.7.2}/tests/test_review.py +0 -0
  91. {prodc-0.7.0 → prodc-0.7.2}/tests/test_roles.py +0 -0
  92. {prodc-0.7.0 → prodc-0.7.2}/tests/test_source_role.py +0 -0
  93. {prodc-0.7.0 → prodc-0.7.2}/tests/test_sources.py +0 -0
  94. {prodc-0.7.0 → prodc-0.7.2}/tests/test_stage.py +0 -0
  95. {prodc-0.7.0 → prodc-0.7.2}/tests/test_staleness.py +0 -0
  96. {prodc-0.7.0 → prodc-0.7.2}/tests/test_status.py +0 -0
  97. {prodc-0.7.0 → prodc-0.7.2}/tests/test_storybook.py +0 -0
  98. {prodc-0.7.0 → prodc-0.7.2}/tests/test_suggest.py +0 -0
  99. {prodc-0.7.0 → prodc-0.7.2}/tests/test_suggest_eval.py +0 -0
  100. {prodc-0.7.0 → prodc-0.7.2}/tests/test_ux_catalog.py +0 -0
  101. {prodc-0.7.0 → prodc-0.7.2}/tests/test_variants.py +0 -0
  102. {prodc-0.7.0 → prodc-0.7.2}/tests/test_view_links.py +0 -0
  103. {prodc-0.7.0 → prodc-0.7.2}/tests/test_views.py +0 -0
  104. {prodc-0.7.0 → prodc-0.7.2}/tests/test_views_report.py +0 -0
  105. {prodc-0.7.0 → prodc-0.7.2}/tests/test_who_variants.py +0 -0
  106. {prodc-0.7.0 → prodc-0.7.2}/tests/test_withdrawn.py +0 -0
  107. {prodc-0.7.0 → prodc-0.7.2}/tests/test_writer.py +0 -0
  108. {prodc-0.7.0 → prodc-0.7.2}/tests/test_writer_comments.py +0 -0
@@ -4,6 +4,22 @@ All notable changes are documented here. Versioning is [SemVer](https://semver.o
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Fixed
8
+
9
+ - A vitest or Playwright proof now finds a test whose title is a template literal: the locator
10
+ `at most 50 rows` matches `it(`at most ${LIMIT} rows`)`. Before, the proof was BROKEN.
11
+
12
+ ## [0.7.1] - 2026-11-09
13
+
14
+ ### Changed
15
+
16
+ - `ux-miller` recipe: replaced placeholder with a source-scanning vitest guard that tests
17
+ a named page-size constant against the agreed threshold (derived from crm
18
+ `apps/website/src/design/millerLaw.guard.test.ts`).
19
+ - `ux-jakob` recipe: replaced placeholder playwright test with a source-scanning vitest guard
20
+ that checks every dialog/modal for the shared Escape hook and verifies the logo link in
21
+ Nav.tsx (derived from crm `apps/website/src/design/jakobLaw.guard.test.ts`).
22
+
7
23
  ## [0.7.0] - 2026-11-09
8
24
 
9
25
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: prodc
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Product management as code: roles, flows, and features traced to evidence.
5
5
  Project-URL: Homepage, https://gitlab.com/cardona-oss/prodc
6
6
  Project-URL: Repository, https://gitlab.com/cardona-oss/prodc
@@ -1,3 +1,3 @@
1
1
  """prodc: product management as code."""
2
2
 
3
- __version__ = "0.7.0" # the single source of truth; pyproject reads it via [tool.hatch.version]
3
+ __version__ = "0.7.2" # the single source of truth; pyproject reads it via [tool.hatch.version]
@@ -580,18 +580,37 @@ class VitestAdapter:
580
580
  def _titles_missing(text: str, title: str, sep: str) -> str | None:
581
581
  """The first segment of ``title`` that does not appear in a test file's source, else None.
582
582
 
583
- Each title segment must appear in the source. A quote escaped in the literal is tolerated.
583
+ Each title segment must appear in the source. A quote escaped in the literal is tolerated. A
584
+ segment also matches a template-literal title when it is that title with each ``${...}``
585
+ filled in, as vitest and Playwright render it (``at most ${LIMIT} rows`` matches
586
+ ``at most 50 rows``).
584
587
  """
588
+ templates = _template_titles(text)
585
589
  for seg in (s.strip() for s in title.split(sep)):
586
590
  if (
587
591
  seg not in text
588
592
  and seg.replace("'", "\\'") not in text
589
593
  and seg.replace('"', '\\"') not in text
594
+ and not any(t.fullmatch(seg) for t in templates)
590
595
  ):
591
596
  return seg
592
597
  return None
593
598
 
594
599
 
600
+ #: A backtick string holding at least one ``${...}`` substitution.
601
+ _TEMPLATE_RE = re.compile(r"`(?P<body>(?:\\.|[^`\\])*\$\{(?:\\.|[^`\\])*)`")
602
+ _SUBST_RE = re.compile(r"\$\{[^}]*\}")
603
+
604
+
605
+ def _template_titles(text: str) -> list[re.Pattern[str]]:
606
+ """A regex per template literal in ``text``: literal parts escaped, each ``${...}`` as ``.+``."""
607
+ out: list[re.Pattern[str]] = []
608
+ for m in _TEMPLATE_RE.finditer(text):
609
+ parts = _SUBST_RE.split(m["body"])
610
+ out.append(re.compile(".+".join(re.escape(part) for part in parts), re.DOTALL))
611
+ return out
612
+
613
+
595
614
  def _js_inventory(root: Path, patterns: Sequence[str], test_re: re.Pattern[str]) -> list[str]:
596
615
  """Every ``file#title`` that ``test_re`` finds; a title with ``${`` is computed, so skipped."""
597
616
  out: set[str] = set()
@@ -212,24 +212,35 @@ _MILLER = CatalogEntry(
212
212
  checkable=True,
213
213
  recipe=Recipe(
214
214
  kind="vitest",
215
- summary="Assert that any list beyond the threshold has pagination or section headers.",
216
- # TODO: replace with the guard from crm/tyko once it is extracted.
215
+ summary="Assert that the page-size constant used by your list components stays at or below the agreed threshold.",
216
+ # Real guard from crm: apps/website/src/design/millerLaw.guard.test.ts
217
+ # The guard tests a named constant, not a rendered component; jsdom has no virtual scroll.
218
+ # Adapt PAGE_SIZE_CONSTANT and the import path for your app.
217
219
  snippet="""\
218
- // tests/design/list-paging.test.ts
219
- import { render } from '@testing-library/react'
220
- import { ItemList } from '../src/ItemList'
221
-
222
- const THRESHOLD = {N}
223
- const items = Array.from({ length: THRESHOLD + 1 }, (_, i) => ({ id: i, label: `Item ${i}` }))
224
-
225
- test('long list shows a pager or section headers', () => {
226
- const { container } = render(<ItemList items={items} />)
227
- const hasPager = container.querySelector('[aria-label="pagination"]') !== null
228
- const hasGroups = container.querySelectorAll('[role="group"]').length > 1
229
- expect(hasPager || hasGroups).toBe(true)
230
- })
220
+ // src/design/millerLaw.guard.test.ts
221
+ import { describe, expect, it } from "vitest";
222
+ import { PAGE_SIZE_CONSTANT } from "./primitives/pageSize";
223
+ // Replace PAGE_SIZE_CONSTANT with the constant your list component uses, e.g. DESKTOP_PAGE_SIZE.
224
+
225
+ const LIMIT = {N};
226
+
227
+ describe("Miller's Law: list page cap stays within agreed bounds", () => {
228
+ it(`page cap is at most ${LIMIT} rows`, () => {
229
+ expect(
230
+ PAGE_SIZE_CONSTANT,
231
+ `PAGE_SIZE_CONSTANT (${PAGE_SIZE_CONSTANT}) exceeds the ${LIMIT}-row cap — ` +
232
+ "update this guard and the ux-miller principle YAML if the change is intentional",
233
+ ).toBeLessThanOrEqual(LIMIT);
234
+ });
235
+
236
+ it("watches a real module: PAGE_SIZE_CONSTANT is defined and positive", () => {
237
+ // Proves the import resolved and the module exports a real value.
238
+ expect(typeof PAGE_SIZE_CONSTANT).toBe("number");
239
+ expect(PAGE_SIZE_CONSTANT).toBeGreaterThan(0);
240
+ });
241
+ });
231
242
  """,
232
- proof_locator_hint="vitest:tests/design/list-paging.test.ts::long list shows a pager or section headers",
243
+ proof_locator_hint="vitest:src/design/millerLaw.guard.test.ts::Miller's Law: list page cap stays within agreed bounds > page cap is at most {N} rows",
233
244
  ),
234
245
  )
235
246
 
@@ -244,28 +255,66 @@ _JAKOB = CatalogEntry(
244
255
  ),
245
256
  checkable=True,
246
257
  recipe=Recipe(
247
- kind="playwright",
248
- summary="Verify Escape closes dialogs, Enter submits forms, and the logo links home.",
249
- # TODO: replace with the guard from crm/tyko once it is extracted.
258
+ kind="vitest",
259
+ summary="Source-scan every dialog/modal file for Escape handling and check the logo link in the nav component.",
260
+ # Real guard from crm: apps/website/src/design/jakobLaw.guard.test.ts
261
+ # jsdom has no global keydown dispatch in this setup; source scanning is the only way
262
+ # to verify the structural invariants without spinning up a browser.
263
+ # Adapt DIALOG_HOOK and the logo assertion to your app's conventions.
250
264
  snippet="""\
251
- // tests/design/conventions.spec.ts
252
- import { test, expect } from '@playwright/test'
253
-
254
- test('Escape closes the dialog', async ({ page }) => {
255
- await page.goto('/')
256
- await page.click('[data-testid="open-dialog"]')
257
- await expect(page.locator('[role="dialog"]')).toBeVisible()
258
- await page.keyboard.press('Escape')
259
- await expect(page.locator('[role="dialog"]')).not.toBeVisible()
260
- })
261
-
262
- test('logo links to home', async ({ page }) => {
263
- await page.goto('/about')
264
- await page.click('[data-testid="site-logo"]')
265
- await expect(page).toHaveURL('/')
266
- })
265
+ // src/design/jakobLaw.guard.test.ts
266
+ import { readdirSync, readFileSync, statSync } from "node:fs";
267
+ import { join, relative } from "node:path";
268
+ import { describe, expect, it } from "vitest";
269
+
270
+ const SRC = join(import.meta.dirname, "..");
271
+ const DIALOG_PATTERN = /(?:Dialog|Modal)\\.tsx$/;
272
+
273
+ // The shared hook (or keyword) every dialog must use to wire Escape.
274
+ // Adapt to match your app: a hook name, an imported function, or a bare string like "Escape".
275
+ const DIALOG_HOOK = "useDialogFocus";
276
+
277
+ function dialogFiles(dir: string): string[] {
278
+ const out: string[] = [];
279
+ for (const entry of readdirSync(dir)) {
280
+ const full = join(dir, entry);
281
+ if (statSync(full).isDirectory()) out.push(...dialogFiles(full));
282
+ else if (DIALOG_PATTERN.test(entry)) out.push(full);
283
+ }
284
+ return out;
285
+ }
286
+
287
+ describe("Jakob's Law: app conventions match user expectations", () => {
288
+ it("every dialog and modal file uses the shared Escape handler", () => {
289
+ const offenders: string[] = [];
290
+ for (const file of dialogFiles(SRC)) {
291
+ const src = readFileSync(file, "utf8");
292
+ if (!src.includes(DIALOG_HOOK)) offenders.push(relative(SRC, file));
293
+ }
294
+ expect(
295
+ offenders,
296
+ `These dialogs do not use ${DIALOG_HOOK}; Escape will not close them:\\n ${offenders.join("\\n ")}`,
297
+ ).toEqual([]);
298
+ });
299
+
300
+ it("the logo in Nav.tsx links to the home route", () => {
301
+ const nav = readFileSync(join(SRC, "components/Nav.tsx"), "utf8");
302
+ // Adapt the regex to match your logo link. The pattern matches the home
303
+ // route href within 300 characters before the logo element.
304
+ expect(nav, "logo in Nav.tsx must link to the home route").toMatch(
305
+ /to="\\/[^"]*"[\\s\\S]{1,300}<(?:Logo|Wordmark|SiteLogo)/,
306
+ );
307
+ });
308
+
309
+ it("watches a real tree: at least 4 dialog/modal files must exist", () => {
310
+ expect(
311
+ dialogFiles(SRC).length,
312
+ "fewer than 4 dialog/modal files found — the guard may not be scanning the right directory",
313
+ ).toBeGreaterThanOrEqual(4);
314
+ });
315
+ });
267
316
  """,
268
- proof_locator_hint="playwright:tests/design/conventions.spec.ts::Escape closes the dialog",
317
+ proof_locator_hint="vitest:src/design/jakobLaw.guard.test.ts::Jakob's Law: app conventions match user expectations > every dialog and modal file uses the shared Escape handler",
269
318
  ),
270
319
  )
271
320
 
@@ -108,3 +108,25 @@ def test_config_builds_vitest_adapter(tmp_path: Path) -> None:
108
108
  )
109
109
  cfg = Config.load(tmp_path / "prodc.toml")
110
110
  assert isinstance(cfg.adapters["fe"], VitestAdapter)
111
+
112
+
113
+ _TEMPLATE_TEST = """import { it } from 'vitest'
114
+ const DESKTOP_LIMIT = 50
115
+ it(`desktop page cap is at most ${DESKTOP_LIMIT} rows`, () => {})
116
+ """
117
+
118
+
119
+ def test_template_literal_title_matches_its_rendered_text(tmp_path: Path) -> None:
120
+ (tmp_path / "m.test.ts").write_text(_TEMPLATE_TEST, encoding="utf-8")
121
+ adapter = VitestAdapter(tmp_path, None)
122
+ assert adapter.resolve("m.test.ts#desktop page cap is at most 50 rows").state is (
123
+ ProofState.DELIVERED
124
+ )
125
+
126
+
127
+ def test_template_literal_title_does_not_match_other_text(tmp_path: Path) -> None:
128
+ (tmp_path / "m.test.ts").write_text(_TEMPLATE_TEST, encoding="utf-8")
129
+ adapter = VitestAdapter(tmp_path, None)
130
+ assert adapter.resolve("m.test.ts#mobile page cap is at most 50 rows").state is (
131
+ ProofState.BROKEN
132
+ )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes