docassert 0.9.0__tar.gz → 0.10.0__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 (119) hide show
  1. {docassert-0.9.0/docassert.egg-info → docassert-0.10.0}/PKG-INFO +1 -1
  2. {docassert-0.9.0 → docassert-0.10.0}/docassert/__init__.py +1 -1
  3. {docassert-0.9.0 → docassert-0.10.0}/docassert/bridge/ops.py +22 -3
  4. {docassert-0.9.0 → docassert-0.10.0}/docassert/cli.py +11 -0
  5. {docassert-0.9.0 → docassert-0.10.0}/docassert/status/render.py +35 -0
  6. {docassert-0.9.0 → docassert-0.10.0/docassert.egg-info}/PKG-INFO +1 -1
  7. {docassert-0.9.0 → docassert-0.10.0}/tests/test_badge.py +22 -0
  8. {docassert-0.9.0 → docassert-0.10.0}/tests/test_bridge.py +15 -0
  9. {docassert-0.9.0 → docassert-0.10.0}/LICENSE +0 -0
  10. {docassert-0.9.0 → docassert-0.10.0}/NOTICE +0 -0
  11. {docassert-0.9.0 → docassert-0.10.0}/README.md +0 -0
  12. {docassert-0.9.0 → docassert-0.10.0}/docassert/__main__.py +0 -0
  13. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/consistency.yaml +0 -0
  14. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/adr.criteria.yaml +0 -0
  15. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/benefits-realization.criteria.yaml +0 -0
  16. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/brd.criteria.yaml +0 -0
  17. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/business-case.criteria.yaml +0 -0
  18. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/charter.criteria.yaml +0 -0
  19. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/data-migration-plan.criteria.yaml +0 -0
  20. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/frnfr.criteria.yaml +0 -0
  21. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/hypercare-plan.criteria.yaml +0 -0
  22. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/post-implementation-review.criteria.yaml +0 -0
  23. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/prd.criteria.yaml +0 -0
  24. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/project.criteria.yaml +0 -0
  25. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/qa-test-plan.criteria.yaml +0 -0
  26. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/raci-stakeholder.criteria.yaml +0 -0
  27. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/release-cutover-plan.criteria.yaml +0 -0
  28. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/risk-register.criteria.yaml +0 -0
  29. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/rollback-plan.criteria.yaml +0 -0
  30. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/runbook.criteria.yaml +0 -0
  31. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/status-report.criteria.yaml +0 -0
  32. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/test-cases.criteria.yaml +0 -0
  33. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/criteria/user-story.criteria.yaml +0 -0
  34. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/profiles/agile-delivery.yaml +0 -0
  35. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/profiles/lean-startup.yaml +0 -0
  36. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/profiles/regulated-industry.yaml +0 -0
  37. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/adr.schema.json +0 -0
  38. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/benefits-realization.schema.json +0 -0
  39. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/brd.schema.json +0 -0
  40. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/business-case.schema.json +0 -0
  41. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/charter.schema.json +0 -0
  42. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/data-migration-plan.schema.json +0 -0
  43. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/frnfr.schema.json +0 -0
  44. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/hypercare-plan.schema.json +0 -0
  45. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/post-implementation-review.schema.json +0 -0
  46. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/prd.schema.json +0 -0
  47. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/project.schema.json +0 -0
  48. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/qa-test-plan.schema.json +0 -0
  49. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/raci-stakeholder.schema.json +0 -0
  50. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/release-cutover-plan.schema.json +0 -0
  51. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/risk-register.schema.json +0 -0
  52. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/rollback-plan.schema.json +0 -0
  53. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/runbook.schema.json +0 -0
  54. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/status-report.schema.json +0 -0
  55. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/test-cases.schema.json +0 -0
  56. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/schema/user-story.schema.json +0 -0
  57. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/skills/doc-to-pmo/SKILL.md +0 -0
  58. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/adr.template.md +0 -0
  59. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/benefits-realization.template.md +0 -0
  60. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/brd.template.md +0 -0
  61. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/business-case.template.md +0 -0
  62. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/charter.template.md +0 -0
  63. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/data-migration-plan.template.md +0 -0
  64. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/frnfr.template.md +0 -0
  65. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/hypercare-plan.template.md +0 -0
  66. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/post-implementation-review.template.md +0 -0
  67. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/prd.template.md +0 -0
  68. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/project.template.md +0 -0
  69. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/qa-test-plan.template.md +0 -0
  70. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/raci-stakeholder.template.md +0 -0
  71. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/release-cutover-plan.template.md +0 -0
  72. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/risk-register.template.md +0 -0
  73. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/rollback-plan.template.md +0 -0
  74. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/runbook.template.md +0 -0
  75. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/status-report.template.md +0 -0
  76. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/test-cases.template.md +0 -0
  77. {docassert-0.9.0 → docassert-0.10.0}/docassert/_data/templates/user-story.template.md +0 -0
  78. {docassert-0.9.0 → docassert-0.10.0}/docassert/bridge/__init__.py +0 -0
  79. {docassert-0.9.0 → docassert-0.10.0}/docassert/bridge/board.py +0 -0
  80. {docassert-0.9.0 → docassert-0.10.0}/docassert/bridge/gh.py +0 -0
  81. {docassert-0.9.0 → docassert-0.10.0}/docassert/bridge/plan.py +0 -0
  82. {docassert-0.9.0 → docassert-0.10.0}/docassert/config.py +0 -0
  83. {docassert-0.9.0 → docassert-0.10.0}/docassert/consistency.py +0 -0
  84. {docassert-0.9.0 → docassert-0.10.0}/docassert/extract.py +0 -0
  85. {docassert-0.9.0 → docassert-0.10.0}/docassert/graph.py +0 -0
  86. {docassert-0.9.0 → docassert-0.10.0}/docassert/loader.py +0 -0
  87. {docassert-0.9.0 → docassert-0.10.0}/docassert/models.py +0 -0
  88. {docassert-0.9.0 → docassert-0.10.0}/docassert/profiles.py +0 -0
  89. {docassert-0.9.0 → docassert-0.10.0}/docassert/projects.py +0 -0
  90. {docassert-0.9.0 → docassert-0.10.0}/docassert/report.py +0 -0
  91. {docassert-0.9.0 → docassert-0.10.0}/docassert/rtm.py +0 -0
  92. {docassert-0.9.0 → docassert-0.10.0}/docassert/scaffold.py +0 -0
  93. {docassert-0.9.0 → docassert-0.10.0}/docassert/semantic.py +0 -0
  94. {docassert-0.9.0 → docassert-0.10.0}/docassert/status/__init__.py +0 -0
  95. {docassert-0.9.0 → docassert-0.10.0}/docassert/status/derive.py +0 -0
  96. {docassert-0.9.0 → docassert-0.10.0}/docassert/structural.py +0 -0
  97. {docassert-0.9.0 → docassert-0.10.0}/docassert.egg-info/SOURCES.txt +0 -0
  98. {docassert-0.9.0 → docassert-0.10.0}/docassert.egg-info/dependency_links.txt +0 -0
  99. {docassert-0.9.0 → docassert-0.10.0}/docassert.egg-info/entry_points.txt +0 -0
  100. {docassert-0.9.0 → docassert-0.10.0}/docassert.egg-info/requires.txt +0 -0
  101. {docassert-0.9.0 → docassert-0.10.0}/docassert.egg-info/top_level.txt +0 -0
  102. {docassert-0.9.0 → docassert-0.10.0}/pyproject.toml +0 -0
  103. {docassert-0.9.0 → docassert-0.10.0}/setup.cfg +0 -0
  104. {docassert-0.9.0 → docassert-0.10.0}/tests/test_config.py +0 -0
  105. {docassert-0.9.0 → docassert-0.10.0}/tests/test_consistency.py +0 -0
  106. {docassert-0.9.0 → docassert-0.10.0}/tests/test_defects.py +0 -0
  107. {docassert-0.9.0 → docassert-0.10.0}/tests/test_draft_relaxation.py +0 -0
  108. {docassert-0.9.0 → docassert-0.10.0}/tests/test_extract.py +0 -0
  109. {docassert-0.9.0 → docassert-0.10.0}/tests/test_graph.py +0 -0
  110. {docassert-0.9.0 → docassert-0.10.0}/tests/test_json_report.py +0 -0
  111. {docassert-0.9.0 → docassert-0.10.0}/tests/test_kinds_delivery.py +0 -0
  112. {docassert-0.9.0 → docassert-0.10.0}/tests/test_kinds_governance.py +0 -0
  113. {docassert-0.9.0 → docassert-0.10.0}/tests/test_kinds_operate.py +0 -0
  114. {docassert-0.9.0 → docassert-0.10.0}/tests/test_kinds_reporting.py +0 -0
  115. {docassert-0.9.0 → docassert-0.10.0}/tests/test_profiles.py +0 -0
  116. {docassert-0.9.0 → docassert-0.10.0}/tests/test_projects.py +0 -0
  117. {docassert-0.9.0 → docassert-0.10.0}/tests/test_scaffold.py +0 -0
  118. {docassert-0.9.0 → docassert-0.10.0}/tests/test_status.py +0 -0
  119. {docassert-0.9.0 → docassert-0.10.0}/tests/test_structural.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Unit testing for business documents — validate structured Markdown docs against a configurable audit standard.
5
5
  Author: C4G Enterprises Inc.
6
6
  License: Apache-2.0
@@ -5,4 +5,4 @@ standard: deterministic structural checks that gate a merge, plus optional
5
5
  AI-graded semantic checks that advise.
6
6
  """
7
7
 
8
- __version__ = "0.9.0"
8
+ __version__ = "0.10.0"
@@ -171,9 +171,28 @@ def reconcile(plan: BridgePlan, gh: G.GhRunner, repo: str) -> tuple[list[str], i
171
171
 
172
172
 
173
173
  def status(plan: BridgePlan, gh: G.GhRunner, repo: str) -> dict:
174
- """Read-only: delivery progress per feature from issue states."""
175
- by_key = _index(G.list_issues(gh, repo))
176
- out: dict = {"projects": []}
174
+ """Read-only: delivery progress per feature, plus scope classification.
175
+
176
+ Unlike reconcile, this never labels or comments; it only reports, so it is
177
+ safe to run from the dashboard build.
178
+ """
179
+ issues = G.list_issues(gh, repo)
180
+ by_key = _index(issues)
181
+ managed = plan.managed_ids
182
+ unverified, orphaned = [], []
183
+ for issue in issues:
184
+ if issue.get("state") != "open":
185
+ continue
186
+ key = _marker_of(issue)
187
+ if key == SCOPE_REPORT_KEY:
188
+ continue
189
+ if key is None:
190
+ unverified.append({"number": issue["number"], "title": issue["title"]})
191
+ elif key not in managed:
192
+ orphaned.append({"number": issue["number"], "title": issue["title"],
193
+ "doc": key})
194
+ out: dict = {"repo": repo, "projects": [],
195
+ "scope": {"unverified": unverified, "orphaned": orphaned}}
177
196
  for p in plan.projects:
178
197
  feats = []
179
198
  for f in p["features"]:
@@ -232,9 +232,19 @@ def cmd_pages(args: argparse.Namespace) -> int:
232
232
  status_mod.render_badge_json(index["overall"]["rag"]))
233
233
  (out / "badges").mkdir(exist_ok=True)
234
234
 
235
+ execution = {}
236
+ if getattr(args, "execution", None):
237
+ import json as _json
238
+ data = _json.loads(Path(args.execution).read_text())
239
+ for proj in data.get("projects", []):
240
+ execution[proj["id"]] = {**proj, "scope": data.get("scope"),
241
+ "repo": data.get("repo")}
242
+
235
243
  plist = projects_mod.load_projects(docs_dir)
236
244
  for p in plist:
237
245
  model = status_mod.build_status(docs_dir, project=p["id"])
246
+ if p["id"] in execution:
247
+ model["execution"] = execution[p["id"]]
238
248
  (out / f"{p['id']}.html").write_text(status_mod.render_html(model))
239
249
  (out / "badges" / f"{p['id']}.json").write_text(
240
250
  status_mod.render_badge_json(model["rag"], label=p["code"].lower()))
@@ -398,6 +408,7 @@ def main(argv: list[str] | None = None) -> int:
398
408
 
399
409
  pg = sub.add_parser("pages", help="Build the full Pages site (portfolio index + a page per project).")
400
410
  pg.add_argument("--out", default="_site", help="Output directory (default: _site).")
411
+ pg.add_argument("--execution", help="Optional bridge-status JSON; adds Delivery and Scope panels to project pages.")
401
412
  docs_dir_opt(pg)
402
413
  pg.set_defaults(func=cmd_pages)
403
414
 
@@ -205,6 +205,40 @@ def render_html(model) -> str:
205
205
  f'{note}<table><thead><tr><th>Kind</th><th>State</th></tr></thead>'
206
206
  f'<tbody>{req_rows}{sep}{rec_rows}</tbody></table></section>')
207
207
 
208
+ execution_html = ""
209
+ ex = model.get("execution")
210
+ if ex:
211
+ feat_rows: list[str] = []
212
+ for f in ex.get("features", []):
213
+ total = f.get("stories_total", 0) or 0
214
+ done = f.get("stories_closed", 0) or 0
215
+ pct = (100 * done // total) if total else 0
216
+ mark = "✓" if f.get("closed") else ""
217
+ issue = f' · <a href="https://github.com/{esc(ex.get("repo", ""))}/issues/{f["issue"]}">#{f["issue"]}</a>' if f.get("issue") else ""
218
+ feat_rows.append(
219
+ f'<div class="sig"><div class="sig-h"><span>{esc(f["id"])} {mark}</span>'
220
+ f'<span>{done}/{total} stories{issue}</span></div>'
221
+ f'<div class="bar"><div class="bar-f" style="width:{pct}%;background:var(--ok)"></div></div></div>')
222
+ overall_t = ex.get("stories_total", 0) or 0
223
+ overall_d = ex.get("stories_closed", 0) or 0
224
+ execution_html = (
225
+ f'<section><h2>Delivery · {overall_d}/{overall_t} stories closed</h2>'
226
+ '<p style="color:var(--muted);font-size:13px;margin:0 0 10px">'
227
+ "Read from the bridge-managed issues. Scope lives in these documents; "
228
+ "delivery lives on the board.</p>" + "".join(feat_rows) + "</section>")
229
+ scope = ex.get("scope") or {}
230
+ bad = scope.get("unverified", []) + scope.get("orphaned", [])
231
+ if bad:
232
+ items = "".join(
233
+ f'<li><a href="https://github.com/{esc(ex.get("repo", ""))}/issues/{i["number"]}">#{i["number"]}</a> {esc(i["title"])}</li>'
234
+ for i in bad)
235
+ execution_html += (
236
+ f'<section><h2 style="color:var(--bad)">Scope · {len(bad)} unmatched item(s)</h2>'
237
+ f'<ul>{items}</ul></section>')
238
+ else:
239
+ execution_html += ('<section><h2>Scope</h2><p style="color:var(--ok)">'
240
+ "Every open issue matches an approved item in the documents.</p></section>")
241
+
208
242
  title = esc(model.get("title", "Project Status"))
209
243
  pid = model.get("project")
210
244
  back = '<a class="back" href="index.html">← all projects</a>' if pid else ""
@@ -226,6 +260,7 @@ def render_html(model) -> str:
226
260
  {c['approved']} approved{scope} · generated {gen}. Do not edit — regenerated from the documents.</p>
227
261
  <section><h2>Traceability coverage</h2>{coverage}</section>
228
262
  {document_set}
263
+ {execution_html}
229
264
  {problems}
230
265
  <section><h2>Open risks ({len(model['risks'])})</h2><ul>{risks}</ul></section>
231
266
  <section><h2>Latest status report</h2><p>{report}</p></section>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Unit testing for business documents — validate structured Markdown docs against a configurable audit standard.
5
5
  Author: C4G Enterprises Inc.
6
6
  License: Apache-2.0
@@ -24,3 +24,25 @@ def test_pages_emits_badges(tmp_path, monkeypatch):
24
24
  assert overall["schemaVersion"] == 1 and overall["message"] in {"green", "amber", "red"}
25
25
  aur = json.loads((out / "badges" / "PRJ-001-AUR.json").read_text())
26
26
  assert aur["label"] == "aur" and aur["message"] in {"green", "amber", "red"}
27
+
28
+
29
+ def test_pages_execution_panels(tmp_path, monkeypatch):
30
+ import json
31
+ monkeypatch.chdir(ROOT)
32
+ exec_file = tmp_path / "execution.json"
33
+ exec_file.write_text(json.dumps({
34
+ "repo": "o/r",
35
+ "projects": [{"id": "PRJ-001-AUR",
36
+ "features": [{"id": "AUR-PR-014", "issue": 5,
37
+ "stories_total": 2, "stories_closed": 1,
38
+ "closed": False}],
39
+ "stories_closed": 1, "stories_total": 2}],
40
+ "scope": {"unverified": [], "orphaned": []},
41
+ }))
42
+ out = tmp_path / "site"
43
+ assert main(["pages", "--out", str(out), "--execution", str(exec_file)]) == 0
44
+ html = (out / "PRJ-001-AUR.html").read_text()
45
+ assert "Delivery · 1/2 stories closed" in html
46
+ assert "AUR-PR-014" in html and "Every open issue matches" in html
47
+ other = (out / "PRJ-002-ATL.html").read_text()
48
+ assert "Delivery ·" not in other # no execution data, no panel
@@ -320,3 +320,18 @@ def test_scaffold_syncs_board(tmp_path):
320
320
  assert any("5 item(s) synced" in a for a in actions)
321
321
  adds = [q for q in bgh.gql if "addProjectV2ItemById" in q]
322
322
  assert len(adds) == 5
323
+
324
+
325
+ def test_status_includes_scope_classification(tmp_path):
326
+ plan = build_bridge_plan(_tree(tmp_path))
327
+ issues = [
328
+ _mk_issue(1, "TST-US-001"),
329
+ {"number": 2, "node_id": "N2", "state": "open", "title": "rogue",
330
+ "body": "no marker", "labels": []},
331
+ _mk_issue(3, "TST-US-999"),
332
+ _mk_issue(4, "TST-US-002", state="closed"), # closed: not scope-checked
333
+ ]
334
+ data = ops.status(plan, FakeGh(issues), "o/r")
335
+ assert data["repo"] == "o/r"
336
+ assert [i["number"] for i in data["scope"]["unverified"]] == [2]
337
+ assert [i["doc"] for i in data["scope"]["orphaned"]] == ["TST-US-999"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes