docassert 0.17.0__tar.gz → 0.19.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 (126) hide show
  1. {docassert-0.17.0/docassert.egg-info → docassert-0.19.0}/PKG-INFO +1 -1
  2. {docassert-0.17.0 → docassert-0.19.0}/docassert/__init__.py +1 -1
  3. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/consistency.yaml +6 -0
  4. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/charter.criteria.yaml +5 -0
  5. {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/ops.py +17 -0
  6. {docassert-0.17.0 → docassert-0.19.0}/docassert/cli.py +20 -0
  7. {docassert-0.17.0 → docassert-0.19.0}/docassert/status/derive.py +68 -1
  8. docassert-0.19.0/docassert/status/render.py +865 -0
  9. {docassert-0.17.0 → docassert-0.19.0}/docassert/structural.py +16 -0
  10. {docassert-0.17.0 → docassert-0.19.0/docassert.egg-info}/PKG-INFO +1 -1
  11. {docassert-0.17.0 → docassert-0.19.0}/tests/test_badge.py +2 -2
  12. {docassert-0.17.0 → docassert-0.19.0}/tests/test_status.py +39 -11
  13. docassert-0.17.0/docassert/status/render.py +0 -397
  14. {docassert-0.17.0 → docassert-0.19.0}/LICENSE +0 -0
  15. {docassert-0.17.0 → docassert-0.19.0}/NOTICE +0 -0
  16. {docassert-0.17.0 → docassert-0.19.0}/README.md +0 -0
  17. {docassert-0.17.0 → docassert-0.19.0}/docassert/__main__.py +0 -0
  18. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/adr.criteria.yaml +0 -0
  19. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/benefits-realization.criteria.yaml +0 -0
  20. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/brd.criteria.yaml +0 -0
  21. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/business-case.criteria.yaml +0 -0
  22. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/data-migration-plan.criteria.yaml +0 -0
  23. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/frnfr.criteria.yaml +0 -0
  24. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/hypercare-plan.criteria.yaml +0 -0
  25. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/operations.criteria.yaml +0 -0
  26. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/post-implementation-review.criteria.yaml +0 -0
  27. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/prd.criteria.yaml +0 -0
  28. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/project.criteria.yaml +0 -0
  29. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/qa-test-plan.criteria.yaml +0 -0
  30. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/raci-stakeholder.criteria.yaml +0 -0
  31. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/release-cutover-plan.criteria.yaml +0 -0
  32. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/risk-register.criteria.yaml +0 -0
  33. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/rollback-plan.criteria.yaml +0 -0
  34. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/runbook.criteria.yaml +0 -0
  35. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/status-report.criteria.yaml +0 -0
  36. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/test-cases.criteria.yaml +0 -0
  37. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/user-story.criteria.yaml +0 -0
  38. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/profiles/agile-delivery.yaml +0 -0
  39. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/profiles/lean-startup.yaml +0 -0
  40. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/profiles/operations.yaml +0 -0
  41. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/profiles/regulated-industry.yaml +0 -0
  42. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/adr.schema.json +0 -0
  43. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/benefits-realization.schema.json +0 -0
  44. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/brd.schema.json +0 -0
  45. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/business-case.schema.json +0 -0
  46. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/charter.schema.json +0 -0
  47. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/data-migration-plan.schema.json +0 -0
  48. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/frnfr.schema.json +0 -0
  49. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/hypercare-plan.schema.json +0 -0
  50. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/operations.schema.json +0 -0
  51. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/post-implementation-review.schema.json +0 -0
  52. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/prd.schema.json +0 -0
  53. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/project.schema.json +0 -0
  54. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/qa-test-plan.schema.json +0 -0
  55. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/raci-stakeholder.schema.json +0 -0
  56. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/release-cutover-plan.schema.json +0 -0
  57. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/risk-register.schema.json +0 -0
  58. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/rollback-plan.schema.json +0 -0
  59. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/runbook.schema.json +0 -0
  60. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/status-report.schema.json +0 -0
  61. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/test-cases.schema.json +0 -0
  62. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/user-story.schema.json +0 -0
  63. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/skills/doc-to-pmo/SKILL.md +0 -0
  64. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/adr.template.md +0 -0
  65. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/benefits-realization.template.md +0 -0
  66. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/brd.template.md +0 -0
  67. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/business-case.template.md +0 -0
  68. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/charter.template.md +0 -0
  69. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/data-migration-plan.template.md +0 -0
  70. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/frnfr.template.md +0 -0
  71. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/hypercare-plan.template.md +0 -0
  72. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/operations.template.md +0 -0
  73. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/post-implementation-review.template.md +0 -0
  74. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/prd.template.md +0 -0
  75. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/project.template.md +0 -0
  76. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/qa-test-plan.template.md +0 -0
  77. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/raci-stakeholder.template.md +0 -0
  78. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/release-cutover-plan.template.md +0 -0
  79. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/risk-register.template.md +0 -0
  80. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/rollback-plan.template.md +0 -0
  81. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/runbook.template.md +0 -0
  82. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/status-report.template.md +0 -0
  83. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/test-cases.template.md +0 -0
  84. {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/user-story.template.md +0 -0
  85. {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/__init__.py +0 -0
  86. {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/board.py +0 -0
  87. {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/gh.py +0 -0
  88. {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/plan.py +0 -0
  89. {docassert-0.17.0 → docassert-0.19.0}/docassert/config.py +0 -0
  90. {docassert-0.17.0 → docassert-0.19.0}/docassert/consistency.py +0 -0
  91. {docassert-0.17.0 → docassert-0.19.0}/docassert/extract.py +0 -0
  92. {docassert-0.17.0 → docassert-0.19.0}/docassert/graph.py +0 -0
  93. {docassert-0.17.0 → docassert-0.19.0}/docassert/loader.py +0 -0
  94. {docassert-0.17.0 → docassert-0.19.0}/docassert/models.py +0 -0
  95. {docassert-0.17.0 → docassert-0.19.0}/docassert/profiles.py +0 -0
  96. {docassert-0.17.0 → docassert-0.19.0}/docassert/projects.py +0 -0
  97. {docassert-0.17.0 → docassert-0.19.0}/docassert/report.py +0 -0
  98. {docassert-0.17.0 → docassert-0.19.0}/docassert/rtm.py +0 -0
  99. {docassert-0.17.0 → docassert-0.19.0}/docassert/scaffold.py +0 -0
  100. {docassert-0.17.0 → docassert-0.19.0}/docassert/semantic.py +0 -0
  101. {docassert-0.17.0 → docassert-0.19.0}/docassert/status/__init__.py +0 -0
  102. {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/SOURCES.txt +0 -0
  103. {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/dependency_links.txt +0 -0
  104. {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/entry_points.txt +0 -0
  105. {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/requires.txt +0 -0
  106. {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/top_level.txt +0 -0
  107. {docassert-0.17.0 → docassert-0.19.0}/pyproject.toml +0 -0
  108. {docassert-0.17.0 → docassert-0.19.0}/setup.cfg +0 -0
  109. {docassert-0.17.0 → docassert-0.19.0}/tests/test_bridge.py +0 -0
  110. {docassert-0.17.0 → docassert-0.19.0}/tests/test_config.py +0 -0
  111. {docassert-0.17.0 → docassert-0.19.0}/tests/test_consistency.py +0 -0
  112. {docassert-0.17.0 → docassert-0.19.0}/tests/test_defects.py +0 -0
  113. {docassert-0.17.0 → docassert-0.19.0}/tests/test_draft_relaxation.py +0 -0
  114. {docassert-0.17.0 → docassert-0.19.0}/tests/test_extract.py +0 -0
  115. {docassert-0.17.0 → docassert-0.19.0}/tests/test_graph.py +0 -0
  116. {docassert-0.17.0 → docassert-0.19.0}/tests/test_json_report.py +0 -0
  117. {docassert-0.17.0 → docassert-0.19.0}/tests/test_kinds_delivery.py +0 -0
  118. {docassert-0.17.0 → docassert-0.19.0}/tests/test_kinds_governance.py +0 -0
  119. {docassert-0.17.0 → docassert-0.19.0}/tests/test_kinds_operate.py +0 -0
  120. {docassert-0.17.0 → docassert-0.19.0}/tests/test_kinds_reporting.py +0 -0
  121. {docassert-0.17.0 → docassert-0.19.0}/tests/test_profiles.py +0 -0
  122. {docassert-0.17.0 → docassert-0.19.0}/tests/test_projects.py +0 -0
  123. {docassert-0.17.0 → docassert-0.19.0}/tests/test_report_extract.py +0 -0
  124. {docassert-0.17.0 → docassert-0.19.0}/tests/test_scaffold.py +0 -0
  125. {docassert-0.17.0 → docassert-0.19.0}/tests/test_semantic.py +0 -0
  126. {docassert-0.17.0 → docassert-0.19.0}/tests/test_structural.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.17.0
3
+ Version: 0.19.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.17.0"
8
+ __version__ = "0.19.0"
@@ -39,6 +39,12 @@ coverage:
39
39
  # (0 = no cap).
40
40
  alignment_limit: 25
41
41
 
42
+ # Open risks move derived status to amber only at or above this
43
+ # probability x impact score (low=1 medium=2 high/critical=3). 0 = any open
44
+ # risk ambers (the strict pre-0.19 behavior). Exposure below the threshold
45
+ # stays fully reported; it just doesn't flatten the headline.
46
+ risk_amber_score: 6
47
+
42
48
  # Rules match in order; the first matching rule grades a link. Typed rules
43
49
  # (child_type/parent_type) come before the generic ones.
44
50
  alignment:
@@ -21,6 +21,11 @@ required_sections:
21
21
  - Approval
22
22
 
23
23
  checks:
24
+ - id: milestones-dated
25
+ type: structural
26
+ blocking: false
27
+ description: "At least one Milestones bullet ends with an ISO date (- <label>: YYYY-MM-DD) so timelines can draw it. Advisory only."
28
+
24
29
  - id: frontmatter-schema
25
30
  type: structural
26
31
  blocking: true
@@ -222,6 +222,19 @@ def status(plan: BridgePlan, gh: G.GhRunner, repo: str) -> dict:
222
222
  "doc": key})
223
223
  out: dict = {"repo": repo, "projects": [],
224
224
  "scope": {"unverified": unverified, "orphaned": orphaned}}
225
+ def facts(key):
226
+ """Real, presentational issue facts (never fed back into derivation)."""
227
+ issue = by_key.get(key) or {}
228
+ return {
229
+ "title": issue.get("title"),
230
+ "state": issue.get("state"),
231
+ "created_at": (issue.get("created_at") or "")[:10] or None,
232
+ "closed_at": (issue.get("closed_at") or "")[:10] or None,
233
+ "url": issue.get("html_url"),
234
+ "assignee": (issue.get("assignee") or {}).get("login"),
235
+ "labels": [lb.get("name") for lb in issue.get("labels") or []],
236
+ }
237
+
225
238
  for p in plan.projects:
226
239
  feats = []
227
240
  for f in p["features"]:
@@ -229,6 +242,10 @@ def status(plan: BridgePlan, gh: G.GhRunner, repo: str) -> dict:
229
242
  feats.append({
230
243
  "id": f.id,
231
244
  "issue": (by_key.get(f.id) or {}).get("number"),
245
+ "facts": facts(f.id),
246
+ "stories": [{"id": s.id,
247
+ "issue": (by_key.get(s.id) or {}).get("number"),
248
+ "facts": facts(s.id)} for s in f.stories],
232
249
  "stories_total": len(f.stories),
233
250
  "stories_closed": sum(1 for s in states if s == "closed"),
234
251
  "closed": (by_key.get(f.id) or {}).get("state") == "closed",
@@ -240,9 +240,29 @@ def cmd_pages(args: argparse.Namespace) -> int:
240
240
  execution[proj["id"]] = {**proj, "scope": data.get("scope"),
241
241
  "repo": proj.get("repo") or data.get("repo")}
242
242
 
243
+ def _activity(project_id: str) -> list[dict]:
244
+ """Recent documents/ commits for one project (best-effort, git only)."""
245
+ import subprocess
246
+ try:
247
+ out = subprocess.run(
248
+ ["git", "log", "-n", "6", "--date=short",
249
+ "--pretty=%ad\x1f%s", "--", f"{docs_dir}/{project_id}"],
250
+ capture_output=True, text=True, timeout=10)
251
+ if out.returncode != 0:
252
+ return []
253
+ rows = []
254
+ for line in out.stdout.strip().splitlines():
255
+ when, _, text = line.partition("\x1f")
256
+ if text:
257
+ rows.append({"when": when, "text": text})
258
+ return rows
259
+ except Exception:
260
+ return []
261
+
243
262
  plist = projects_mod.load_projects(docs_dir)
244
263
  for p in plist:
245
264
  model = status_mod.build_status(docs_dir, project=p["id"])
265
+ model["activity"] = _activity(p["id"])
246
266
  if p["id"] in execution:
247
267
  model["execution"] = execution[p["id"]]
248
268
  (out / f"{p['id']}.html").write_text(status_mod.render_html(model))
@@ -113,6 +113,51 @@ def _broken_references(graph, code=None):
113
113
  return broken
114
114
 
115
115
 
116
+ _MS_RE = re.compile(r"^(?P<label>.+?)[:\u2014\u2013-]\s*(?P<date>\d{4}-\d{2}-\d{2})\.?\s*$")
117
+
118
+
119
+ def _milestones(docs):
120
+ """Dated charter milestones as temporal facts (never completion claims)."""
121
+ import datetime
122
+ today = datetime.date.today()
123
+ out = []
124
+ for d in docs:
125
+ if d.kind != "charter":
126
+ continue
127
+ section = d.section("Milestones")
128
+ if section is None:
129
+ continue
130
+ for bullet in section.list_items:
131
+ m = _MS_RE.match(bullet.strip())
132
+ if not m:
133
+ continue
134
+ try:
135
+ due = datetime.date.fromisoformat(m.group("date"))
136
+ except ValueError:
137
+ continue
138
+ out.append(_ms_entry(m.group("label").strip(), due, today))
139
+ for d in docs:
140
+ if d.kind != "charter":
141
+ continue
142
+ target = (d.frontmatter.get("dates") or {}).get("target")
143
+ if target:
144
+ try:
145
+ due = (target if isinstance(target, datetime.date)
146
+ else datetime.date.fromisoformat(str(target)))
147
+ if not any(x["date"] == due.isoformat() for x in out):
148
+ out.append(_ms_entry("Charter target", due, today))
149
+ except (TypeError, ValueError):
150
+ pass
151
+ return sorted(out, key=lambda x: x["date"])
152
+
153
+
154
+ def _ms_entry(label, due, today):
155
+ delta = (due - today).days
156
+ return {"label": label, "date": due.isoformat(), "days": delta,
157
+ "when": ("today" if delta == 0 else
158
+ "elapsed" if delta < 0 else "upcoming")}
159
+
160
+
116
161
  def _operations(docs):
117
162
  """Operations documents with their review state."""
118
163
  import datetime
@@ -199,9 +244,11 @@ def build_status(documents_dir=DOCUMENTS_DIR, project: str | None = None) -> dic
199
244
  "coverage": _coverage(graph, cfg, code),
200
245
  "risks": _risks(graph, code),
201
246
  "operations": _operations(docs),
247
+ "milestones": _milestones(docs),
202
248
  "broken_references": _broken_references(graph, code),
203
249
  "latest_report": _latest_report(docs),
204
250
  "completeness": completeness,
251
+ "risk_amber_score": int(cfg.get("risk_amber_score", 6) or 0),
205
252
  }
206
253
  model["rag"] = derive_rag(model)
207
254
  return model
@@ -231,6 +278,19 @@ def completeness_report(documents_dir=DOCUMENTS_DIR) -> list[dict]:
231
278
  return out
232
279
 
233
280
 
281
+ def _next_dated(m) -> str | None:
282
+ """The nearest upcoming dated marker (milestone or operations review)."""
283
+ import datetime
284
+ cands = [x["date"] for x in m.get("milestones", []) if x["days"] >= 0]
285
+ for o in m.get("operations", []):
286
+ try:
287
+ if datetime.date.fromisoformat(o["review_by"]) >= datetime.date.today():
288
+ cands.append(o["review_by"])
289
+ except ValueError:
290
+ pass
291
+ return min(cands) if cands else None
292
+
293
+
234
294
  def build_index(documents_dir=DOCUMENTS_DIR) -> dict:
235
295
  """The multi-project view: each project's derived RAG + headline signals,
236
296
  plus the whole-repo rollup."""
@@ -246,6 +306,10 @@ def build_index(documents_dir=DOCUMENTS_DIR) -> dict:
246
306
  "failing": m["counts"]["failing"],
247
307
  "risks": len([r for r in m["risks"] if r.get("disposition", "open") == "open"]),
248
308
  "coverage_gaps": sum(len(c["gaps"]) for c in m["coverage"]),
309
+ "coverage_pct": (round(100 * sum(c["covered"] for c in m["coverage"])
310
+ / max(1, sum(c["total"] for c in m["coverage"])))
311
+ if any(c["total"] for c in m["coverage"]) else 100),
312
+ "next": _next_dated(m),
249
313
  "broken": len(m["broken_references"]),
250
314
  "completeness": m.get("completeness"),
251
315
  })
@@ -264,7 +328,10 @@ def derive_rag(model) -> str:
264
328
  reported = (model["latest_report"] or {}).get("rag")
265
329
  completeness_gap = bool(comp and (comp["missing_required"] or comp["incomplete_required"]
266
330
  or comp["recommended_gaps"] or comp.get("unknown")))
267
- open_risks = [r for r in model["risks"] if r.get("disposition", "open") == "open"]
331
+ threshold = model.get("risk_amber_score", 6)
332
+ open_risks = [r for r in model["risks"]
333
+ if r.get("disposition", "open") == "open"
334
+ and (threshold == 0 or r.get("score", 0) >= threshold)]
268
335
  stale_ops = [o for o in model.get("operations", []) if not o["fresh"]]
269
336
  if (coverage_gap or open_risks or stale_ops
270
337
  or reported in {"amber", "red"} or completeness_gap):