prodc 0.7.1__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.1 → prodc-0.7.2}/CHANGELOG.md +7 -0
  2. {prodc-0.7.1 → prodc-0.7.2}/PKG-INFO +1 -1
  3. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/__init__.py +1 -1
  4. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/adapters.py +20 -1
  5. {prodc-0.7.1 → prodc-0.7.2}/tests/test_vitest.py +22 -0
  6. {prodc-0.7.1 → prodc-0.7.2}/.gitignore +0 -0
  7. {prodc-0.7.1 → prodc-0.7.2}/LICENSE +0 -0
  8. {prodc-0.7.1 → prodc-0.7.2}/README.md +0 -0
  9. {prodc-0.7.1 → prodc-0.7.2}/examples/quickstart/features/orders.feature +0 -0
  10. {prodc-0.7.1 → prodc-0.7.2}/examples/quickstart/needs/jordan.yaml +0 -0
  11. {prodc-0.7.1 → prodc-0.7.2}/examples/quickstart/prodc.toml +0 -0
  12. {prodc-0.7.1 → prodc-0.7.2}/pyproject.toml +0 -0
  13. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/agent.md +0 -0
  14. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/anchor.py +0 -0
  15. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/board.py +0 -0
  16. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/callbacks.py +0 -0
  17. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/catalog/__init__.py +0 -0
  18. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/catalog/ux.py +0 -0
  19. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/cli/__init__.py +0 -0
  20. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/cli/__main__.py +0 -0
  21. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/cli/app.py +0 -0
  22. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/cli/commands.py +0 -0
  23. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/cli/common.py +0 -0
  24. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/cli/render.py +0 -0
  25. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/cli/serialize.py +0 -0
  26. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/config.py +0 -0
  27. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/dedup.py +0 -0
  28. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/glossary.py +0 -0
  29. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/guide.md +0 -0
  30. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/inbox.py +0 -0
  31. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/issues.py +0 -0
  32. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/loader.py +0 -0
  33. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/model.py +0 -0
  34. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/py.typed +0 -0
  35. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/report.py +0 -0
  36. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/report_export.py +0 -0
  37. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/report_html.py +0 -0
  38. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/status.py +0 -0
  39. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/storybook.py +0 -0
  40. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/views.py +0 -0
  41. {prodc-0.7.1 → prodc-0.7.2}/src/prodc/writer.py +0 -0
  42. {prodc-0.7.1 → prodc-0.7.2}/tests/fixtures/storybook/index.json +0 -0
  43. {prodc-0.7.1 → prodc-0.7.2}/tests/test_adapters.py +0 -0
  44. {prodc-0.7.1 → prodc-0.7.2}/tests/test_anchor.py +0 -0
  45. {prodc-0.7.1 → prodc-0.7.2}/tests/test_answer.py +0 -0
  46. {prodc-0.7.1 → prodc-0.7.2}/tests/test_attest_policy.py +0 -0
  47. {prodc-0.7.1 → prodc-0.7.2}/tests/test_awaits.py +0 -0
  48. {prodc-0.7.1 → prodc-0.7.2}/tests/test_callbacks.py +0 -0
  49. {prodc-0.7.1 → prodc-0.7.2}/tests/test_capture_block_scalar.py +0 -0
  50. {prodc-0.7.1 → prodc-0.7.2}/tests/test_catalog_add.py +0 -0
  51. {prodc-0.7.1 → prodc-0.7.2}/tests/test_catalog_lint.py +0 -0
  52. {prodc-0.7.1 → prodc-0.7.2}/tests/test_citation.py +0 -0
  53. {prodc-0.7.1 → prodc-0.7.2}/tests/test_claim_gating.py +0 -0
  54. {prodc-0.7.1 → prodc-0.7.2}/tests/test_cli.py +0 -0
  55. {prodc-0.7.1 → prodc-0.7.2}/tests/test_command_hook.py +0 -0
  56. {prodc-0.7.1 → prodc-0.7.2}/tests/test_confidence.py +0 -0
  57. {prodc-0.7.1 → prodc-0.7.2}/tests/test_config_keys.py +0 -0
  58. {prodc-0.7.1 → prodc-0.7.2}/tests/test_contradicts.py +0 -0
  59. {prodc-0.7.1 → prodc-0.7.2}/tests/test_decisions.py +0 -0
  60. {prodc-0.7.1 → prodc-0.7.2}/tests/test_declared_only.py +0 -0
  61. {prodc-0.7.1 → prodc-0.7.2}/tests/test_dedup.py +0 -0
  62. {prodc-0.7.1 → prodc-0.7.2}/tests/test_dedup_candidates.py +0 -0
  63. {prodc-0.7.1 → prodc-0.7.2}/tests/test_description.py +0 -0
  64. {prodc-0.7.1 → prodc-0.7.2}/tests/test_drafts.py +0 -0
  65. {prodc-0.7.1 → prodc-0.7.2}/tests/test_evalgate.py +0 -0
  66. {prodc-0.7.1 → prodc-0.7.2}/tests/test_expects.py +0 -0
  67. {prodc-0.7.1 → prodc-0.7.2}/tests/test_external_adapter.py +0 -0
  68. {prodc-0.7.1 → prodc-0.7.2}/tests/test_files_key.py +0 -0
  69. {prodc-0.7.1 → prodc-0.7.2}/tests/test_flowsteps.py +0 -0
  70. {prodc-0.7.1 → prodc-0.7.2}/tests/test_gitlab_source.py +0 -0
  71. {prodc-0.7.1 → prodc-0.7.2}/tests/test_glossary.py +0 -0
  72. {prodc-0.7.1 → prodc-0.7.2}/tests/test_importance.py +0 -0
  73. {prodc-0.7.1 → prodc-0.7.2}/tests/test_inbox.py +0 -0
  74. {prodc-0.7.1 → prodc-0.7.2}/tests/test_inventory.py +0 -0
  75. {prodc-0.7.1 → prodc-0.7.2}/tests/test_issues.py +0 -0
  76. {prodc-0.7.1 → prodc-0.7.2}/tests/test_lint_decision.py +0 -0
  77. {prodc-0.7.1 → prodc-0.7.2}/tests/test_loader.py +0 -0
  78. {prodc-0.7.1 → prodc-0.7.2}/tests/test_models.py +0 -0
  79. {prodc-0.7.1 → prodc-0.7.2}/tests/test_needref.py +0 -0
  80. {prodc-0.7.1 → prodc-0.7.2}/tests/test_path.py +0 -0
  81. {prodc-0.7.1 → prodc-0.7.2}/tests/test_path_views.py +0 -0
  82. {prodc-0.7.1 → prodc-0.7.2}/tests/test_playwright.py +0 -0
  83. {prodc-0.7.1 → prodc-0.7.2}/tests/test_principle_withdrawal.py +0 -0
  84. {prodc-0.7.1 → prodc-0.7.2}/tests/test_principles.py +0 -0
  85. {prodc-0.7.1 → prodc-0.7.2}/tests/test_relink.py +0 -0
  86. {prodc-0.7.1 → prodc-0.7.2}/tests/test_report.py +0 -0
  87. {prodc-0.7.1 → prodc-0.7.2}/tests/test_report_export.py +0 -0
  88. {prodc-0.7.1 → prodc-0.7.2}/tests/test_report_html.py +0 -0
  89. {prodc-0.7.1 → prodc-0.7.2}/tests/test_results_reports.py +0 -0
  90. {prodc-0.7.1 → prodc-0.7.2}/tests/test_review.py +0 -0
  91. {prodc-0.7.1 → prodc-0.7.2}/tests/test_roles.py +0 -0
  92. {prodc-0.7.1 → prodc-0.7.2}/tests/test_source_role.py +0 -0
  93. {prodc-0.7.1 → prodc-0.7.2}/tests/test_sources.py +0 -0
  94. {prodc-0.7.1 → prodc-0.7.2}/tests/test_stage.py +0 -0
  95. {prodc-0.7.1 → prodc-0.7.2}/tests/test_staleness.py +0 -0
  96. {prodc-0.7.1 → prodc-0.7.2}/tests/test_status.py +0 -0
  97. {prodc-0.7.1 → prodc-0.7.2}/tests/test_storybook.py +0 -0
  98. {prodc-0.7.1 → prodc-0.7.2}/tests/test_suggest.py +0 -0
  99. {prodc-0.7.1 → prodc-0.7.2}/tests/test_suggest_eval.py +0 -0
  100. {prodc-0.7.1 → prodc-0.7.2}/tests/test_ux_catalog.py +0 -0
  101. {prodc-0.7.1 → prodc-0.7.2}/tests/test_variants.py +0 -0
  102. {prodc-0.7.1 → prodc-0.7.2}/tests/test_view_links.py +0 -0
  103. {prodc-0.7.1 → prodc-0.7.2}/tests/test_views.py +0 -0
  104. {prodc-0.7.1 → prodc-0.7.2}/tests/test_views_report.py +0 -0
  105. {prodc-0.7.1 → prodc-0.7.2}/tests/test_who_variants.py +0 -0
  106. {prodc-0.7.1 → prodc-0.7.2}/tests/test_withdrawn.py +0 -0
  107. {prodc-0.7.1 → prodc-0.7.2}/tests/test_writer.py +0 -0
  108. {prodc-0.7.1 → prodc-0.7.2}/tests/test_writer_comments.py +0 -0
@@ -4,6 +4,13 @@ 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
+
7
14
  ### Changed
8
15
 
9
16
  - `ux-miller` recipe: replaced placeholder with a source-scanning vitest guard that tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: prodc
3
- Version: 0.7.1
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.1" # 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()
@@ -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
File without changes