docassert 0.4.0__tar.gz → 0.5.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 (109) hide show
  1. {docassert-0.4.0/docassert.egg-info → docassert-0.5.0}/PKG-INFO +14 -3
  2. {docassert-0.4.0 → docassert-0.5.0}/README.md +13 -2
  3. {docassert-0.4.0 → docassert-0.5.0}/docassert/__init__.py +1 -1
  4. {docassert-0.4.0 → docassert-0.5.0}/docassert/cli.py +6 -0
  5. {docassert-0.4.0 → docassert-0.5.0}/docassert/report.py +27 -1
  6. {docassert-0.4.0 → docassert-0.5.0/docassert.egg-info}/PKG-INFO +14 -3
  7. {docassert-0.4.0 → docassert-0.5.0}/docassert.egg-info/SOURCES.txt +1 -0
  8. {docassert-0.4.0 → docassert-0.5.0}/tests/test_extract.py +32 -0
  9. docassert-0.5.0/tests/test_json_report.py +43 -0
  10. {docassert-0.4.0 → docassert-0.5.0}/LICENSE +0 -0
  11. {docassert-0.4.0 → docassert-0.5.0}/NOTICE +0 -0
  12. {docassert-0.4.0 → docassert-0.5.0}/docassert/__main__.py +0 -0
  13. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/consistency.yaml +0 -0
  14. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/adr.criteria.yaml +0 -0
  15. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/benefits-realization.criteria.yaml +0 -0
  16. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/brd.criteria.yaml +0 -0
  17. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/business-case.criteria.yaml +0 -0
  18. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/charter.criteria.yaml +0 -0
  19. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/data-migration-plan.criteria.yaml +0 -0
  20. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/frnfr.criteria.yaml +0 -0
  21. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/hypercare-plan.criteria.yaml +0 -0
  22. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/post-implementation-review.criteria.yaml +0 -0
  23. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/prd.criteria.yaml +0 -0
  24. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/project.criteria.yaml +0 -0
  25. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/qa-test-plan.criteria.yaml +0 -0
  26. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/raci-stakeholder.criteria.yaml +0 -0
  27. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/release-cutover-plan.criteria.yaml +0 -0
  28. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/risk-register.criteria.yaml +0 -0
  29. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/rollback-plan.criteria.yaml +0 -0
  30. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/runbook.criteria.yaml +0 -0
  31. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/status-report.criteria.yaml +0 -0
  32. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/test-cases.criteria.yaml +0 -0
  33. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/criteria/user-story.criteria.yaml +0 -0
  34. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/profiles/agile-delivery.yaml +0 -0
  35. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/profiles/lean-startup.yaml +0 -0
  36. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/profiles/regulated-industry.yaml +0 -0
  37. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/adr.schema.json +0 -0
  38. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/benefits-realization.schema.json +0 -0
  39. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/brd.schema.json +0 -0
  40. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/business-case.schema.json +0 -0
  41. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/charter.schema.json +0 -0
  42. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/data-migration-plan.schema.json +0 -0
  43. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/frnfr.schema.json +0 -0
  44. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/hypercare-plan.schema.json +0 -0
  45. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/post-implementation-review.schema.json +0 -0
  46. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/prd.schema.json +0 -0
  47. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/project.schema.json +0 -0
  48. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/qa-test-plan.schema.json +0 -0
  49. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/raci-stakeholder.schema.json +0 -0
  50. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/release-cutover-plan.schema.json +0 -0
  51. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/risk-register.schema.json +0 -0
  52. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/rollback-plan.schema.json +0 -0
  53. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/runbook.schema.json +0 -0
  54. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/status-report.schema.json +0 -0
  55. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/test-cases.schema.json +0 -0
  56. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/schema/user-story.schema.json +0 -0
  57. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/skills/doc-to-pmo/SKILL.md +0 -0
  58. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/adr.template.md +0 -0
  59. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/benefits-realization.template.md +0 -0
  60. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/brd.template.md +0 -0
  61. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/business-case.template.md +0 -0
  62. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/charter.template.md +0 -0
  63. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/data-migration-plan.template.md +0 -0
  64. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/frnfr.template.md +0 -0
  65. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/hypercare-plan.template.md +0 -0
  66. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/post-implementation-review.template.md +0 -0
  67. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/prd.template.md +0 -0
  68. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/project.template.md +0 -0
  69. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/qa-test-plan.template.md +0 -0
  70. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/raci-stakeholder.template.md +0 -0
  71. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/release-cutover-plan.template.md +0 -0
  72. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/risk-register.template.md +0 -0
  73. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/rollback-plan.template.md +0 -0
  74. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/runbook.template.md +0 -0
  75. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/status-report.template.md +0 -0
  76. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/test-cases.template.md +0 -0
  77. {docassert-0.4.0 → docassert-0.5.0}/docassert/_data/templates/user-story.template.md +0 -0
  78. {docassert-0.4.0 → docassert-0.5.0}/docassert/config.py +0 -0
  79. {docassert-0.4.0 → docassert-0.5.0}/docassert/consistency.py +0 -0
  80. {docassert-0.4.0 → docassert-0.5.0}/docassert/extract.py +0 -0
  81. {docassert-0.4.0 → docassert-0.5.0}/docassert/graph.py +0 -0
  82. {docassert-0.4.0 → docassert-0.5.0}/docassert/loader.py +0 -0
  83. {docassert-0.4.0 → docassert-0.5.0}/docassert/models.py +0 -0
  84. {docassert-0.4.0 → docassert-0.5.0}/docassert/profiles.py +0 -0
  85. {docassert-0.4.0 → docassert-0.5.0}/docassert/projects.py +0 -0
  86. {docassert-0.4.0 → docassert-0.5.0}/docassert/rtm.py +0 -0
  87. {docassert-0.4.0 → docassert-0.5.0}/docassert/scaffold.py +0 -0
  88. {docassert-0.4.0 → docassert-0.5.0}/docassert/semantic.py +0 -0
  89. {docassert-0.4.0 → docassert-0.5.0}/docassert/status.py +0 -0
  90. {docassert-0.4.0 → docassert-0.5.0}/docassert/structural.py +0 -0
  91. {docassert-0.4.0 → docassert-0.5.0}/docassert.egg-info/dependency_links.txt +0 -0
  92. {docassert-0.4.0 → docassert-0.5.0}/docassert.egg-info/entry_points.txt +0 -0
  93. {docassert-0.4.0 → docassert-0.5.0}/docassert.egg-info/requires.txt +0 -0
  94. {docassert-0.4.0 → docassert-0.5.0}/docassert.egg-info/top_level.txt +0 -0
  95. {docassert-0.4.0 → docassert-0.5.0}/pyproject.toml +0 -0
  96. {docassert-0.4.0 → docassert-0.5.0}/setup.cfg +0 -0
  97. {docassert-0.4.0 → docassert-0.5.0}/tests/test_config.py +0 -0
  98. {docassert-0.4.0 → docassert-0.5.0}/tests/test_consistency.py +0 -0
  99. {docassert-0.4.0 → docassert-0.5.0}/tests/test_defects.py +0 -0
  100. {docassert-0.4.0 → docassert-0.5.0}/tests/test_graph.py +0 -0
  101. {docassert-0.4.0 → docassert-0.5.0}/tests/test_kinds_delivery.py +0 -0
  102. {docassert-0.4.0 → docassert-0.5.0}/tests/test_kinds_governance.py +0 -0
  103. {docassert-0.4.0 → docassert-0.5.0}/tests/test_kinds_operate.py +0 -0
  104. {docassert-0.4.0 → docassert-0.5.0}/tests/test_kinds_reporting.py +0 -0
  105. {docassert-0.4.0 → docassert-0.5.0}/tests/test_profiles.py +0 -0
  106. {docassert-0.4.0 → docassert-0.5.0}/tests/test_projects.py +0 -0
  107. {docassert-0.4.0 → docassert-0.5.0}/tests/test_scaffold.py +0 -0
  108. {docassert-0.4.0 → docassert-0.5.0}/tests/test_status.py +0 -0
  109. {docassert-0.4.0 → docassert-0.5.0}/tests/test_structural.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.4.0
3
+ Version: 0.5.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
@@ -85,8 +85,8 @@ flagged as TODOs, never invented). The skill's source is
85
85
 
86
86
  | Command | What it does |
87
87
  |---|---|
88
- | `docassert validate <globs>` | Validate documents against their kind's criteria. Exit code = number of blocking failures (capped at 125). |
89
- | `docassert consistency` | Cross-document checks: referential integrity, coverage, required links, profile completeness. |
88
+ | `docassert validate <globs>` | Validate documents against their kind's criteria. Exit code = number of blocking failures (capped at 125). Reports: `--junit` / `--markdown` / `--json`. |
89
+ | `docassert consistency` | Cross-document checks: referential integrity, coverage, required links, profile completeness. Reports: `--junit` / `--markdown` / `--json`. |
90
90
  | `docassert rtm [--project ID]` | Requirements traceability matrix (Markdown or CSV). |
91
91
  | `docassert status [--project ID] [--index]` | Derived project status (md / json / html). |
92
92
  | `docassert pages --out DIR` | Build the portfolio site (index + a page per project). |
@@ -117,6 +117,17 @@ kind is adding a trio — no code for the common cases.
117
117
  - **Semantic — AI-graded, advisory.** Scored via the Anthropic API and posted to
118
118
  the PR — never blocking. Set `ANTHROPIC_API_KEY` to enable; skipped otherwise.
119
119
 
120
+ ## Privacy
121
+
122
+ Structural checks run **entirely locally** — no document content leaves your
123
+ machine or CI runner. Semantic checks are the one exception: when
124
+ `ANTHROPIC_API_KEY` is set, the graded excerpts (section text, linked item
125
+ text) are sent to the **Anthropic API** for scoring. Without the key, semantic
126
+ checks are skipped and nothing is sent anywhere. Alignment grading is capped at
127
+ `alignment_limit` links per run (default 25). If your documents are
128
+ confidential, run without the key or review [Anthropic's data-usage
129
+ policies](https://www.anthropic.com/legal/commercial-terms) first.
130
+
120
131
  ## Development
121
132
 
122
133
  ```bash
@@ -47,8 +47,8 @@ flagged as TODOs, never invented). The skill's source is
47
47
 
48
48
  | Command | What it does |
49
49
  |---|---|
50
- | `docassert validate <globs>` | Validate documents against their kind's criteria. Exit code = number of blocking failures (capped at 125). |
51
- | `docassert consistency` | Cross-document checks: referential integrity, coverage, required links, profile completeness. |
50
+ | `docassert validate <globs>` | Validate documents against their kind's criteria. Exit code = number of blocking failures (capped at 125). Reports: `--junit` / `--markdown` / `--json`. |
51
+ | `docassert consistency` | Cross-document checks: referential integrity, coverage, required links, profile completeness. Reports: `--junit` / `--markdown` / `--json`. |
52
52
  | `docassert rtm [--project ID]` | Requirements traceability matrix (Markdown or CSV). |
53
53
  | `docassert status [--project ID] [--index]` | Derived project status (md / json / html). |
54
54
  | `docassert pages --out DIR` | Build the portfolio site (index + a page per project). |
@@ -79,6 +79,17 @@ kind is adding a trio — no code for the common cases.
79
79
  - **Semantic — AI-graded, advisory.** Scored via the Anthropic API and posted to
80
80
  the PR — never blocking. Set `ANTHROPIC_API_KEY` to enable; skipped otherwise.
81
81
 
82
+ ## Privacy
83
+
84
+ Structural checks run **entirely locally** — no document content leaves your
85
+ machine or CI runner. Semantic checks are the one exception: when
86
+ `ANTHROPIC_API_KEY` is set, the graded excerpts (section text, linked item
87
+ text) are sent to the **Anthropic API** for scoring. Without the key, semantic
88
+ checks are skipped and nothing is sent anywhere. Alignment grading is capped at
89
+ `alignment_limit` links per run (default 25). If your documents are
90
+ confidential, run without the key or review [Anthropic's data-usage
91
+ policies](https://www.anthropic.com/legal/commercial-terms) first.
92
+
82
93
  ## Development
83
94
 
84
95
  ```bash
@@ -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.4.0"
8
+ __version__ = "0.5.0"
@@ -116,6 +116,8 @@ def cmd_validate(args: argparse.Namespace) -> int:
116
116
  Path(args.junit).write_text(report.junit(results_by_doc))
117
117
  if args.markdown:
118
118
  Path(args.markdown).write_text(report.markdown(results_by_doc))
119
+ if args.json:
120
+ Path(args.json).write_text(report.json_report(results_by_doc))
119
121
 
120
122
  return _capped(sum(1 for rs in results_by_doc.values()
121
123
  for r in rs if r.is_blocking_failure))
@@ -133,6 +135,8 @@ def cmd_consistency(args: argparse.Namespace) -> int:
133
135
  if args.markdown:
134
136
  Path(args.markdown).write_text(
135
137
  report.markdown(results_by_doc, title="docassert consistency"))
138
+ if args.json:
139
+ Path(args.json).write_text(report.json_report(results_by_doc))
136
140
 
137
141
  return _capped(sum(1 for r in results if r.is_blocking_failure))
138
142
 
@@ -292,12 +296,14 @@ def main(argv: list[str] | None = None) -> int:
292
296
  v.add_argument("paths", nargs="+", help="Markdown files or globs.")
293
297
  v.add_argument("--junit", help="Write a JUnit XML report to this path.")
294
298
  v.add_argument("--markdown", help="Write a PR-comment markdown report to this path.")
299
+ v.add_argument("--json", help="Write a machine-readable JSON report to this path.")
295
300
  docs_dir_opt(v)
296
301
  v.set_defaults(func=cmd_validate)
297
302
 
298
303
  c = sub.add_parser("consistency", help="Check cross-document traceability.")
299
304
  c.add_argument("--junit", help="Write a JUnit XML report to this path.")
300
305
  c.add_argument("--markdown", help="Write a PR-comment markdown report to this path.")
306
+ c.add_argument("--json", help="Write a machine-readable JSON report to this path.")
301
307
  c.add_argument("--no-semantic", action="store_true",
302
308
  help="Skip AI alignment (structural consistency only).")
303
309
  docs_dir_opt(c)
@@ -1,6 +1,7 @@
1
- """Render check results as console text, PR-comment markdown, or JUnit XML."""
1
+ """Render check results as console text, PR-comment markdown, JUnit XML, or JSON."""
2
2
  from __future__ import annotations
3
3
 
4
+ import json as _json
4
5
  import xml.etree.ElementTree as ET
5
6
  from xml.dom import minidom
6
7
 
@@ -37,6 +38,31 @@ def summary_line(results_by_doc: dict[str, list[CheckResult]]) -> str:
37
38
  return f"{_TICK} All structural checks passed across {docs} document(s) {_DASH} clear to merge."
38
39
 
39
40
 
41
+ def json_report(results_by_doc: dict[str, list[CheckResult]]) -> str:
42
+ """Machine-readable results: one entry per document, plus a summary."""
43
+ documents = {
44
+ path: [{
45
+ "check_id": r.check_id,
46
+ "passed": r.passed,
47
+ "blocking": r.blocking,
48
+ "kind": r.kind,
49
+ "score": r.score,
50
+ "detail": r.detail,
51
+ } for r in results]
52
+ for path, results in results_by_doc.items()
53
+ }
54
+ all_results = [r for rs in results_by_doc.values() for r in rs]
55
+ summary = {
56
+ "documents": len(results_by_doc),
57
+ "checks": len(all_results),
58
+ "blocking_failures": sum(1 for r in all_results if r.is_blocking_failure),
59
+ "advisory_failures": sum(1 for r in all_results
60
+ if not r.passed and not r.blocking),
61
+ "passed": not any(r.is_blocking_failure for r in all_results),
62
+ }
63
+ return _json.dumps({"summary": summary, "documents": documents}, indent=2) + "\n"
64
+
65
+
40
66
  def markdown(results_by_doc: dict[str, list[CheckResult]],
41
67
  title: str = "docassert audit") -> str:
42
68
  """PR-comment body."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.4.0
3
+ Version: 0.5.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
@@ -85,8 +85,8 @@ flagged as TODOs, never invented). The skill's source is
85
85
 
86
86
  | Command | What it does |
87
87
  |---|---|
88
- | `docassert validate <globs>` | Validate documents against their kind's criteria. Exit code = number of blocking failures (capped at 125). |
89
- | `docassert consistency` | Cross-document checks: referential integrity, coverage, required links, profile completeness. |
88
+ | `docassert validate <globs>` | Validate documents against their kind's criteria. Exit code = number of blocking failures (capped at 125). Reports: `--junit` / `--markdown` / `--json`. |
89
+ | `docassert consistency` | Cross-document checks: referential integrity, coverage, required links, profile completeness. Reports: `--junit` / `--markdown` / `--json`. |
90
90
  | `docassert rtm [--project ID]` | Requirements traceability matrix (Markdown or CSV). |
91
91
  | `docassert status [--project ID] [--index]` | Derived project status (md / json / html). |
92
92
  | `docassert pages --out DIR` | Build the portfolio site (index + a page per project). |
@@ -117,6 +117,17 @@ kind is adding a trio — no code for the common cases.
117
117
  - **Semantic — AI-graded, advisory.** Scored via the Anthropic API and posted to
118
118
  the PR — never blocking. Set `ANTHROPIC_API_KEY` to enable; skipped otherwise.
119
119
 
120
+ ## Privacy
121
+
122
+ Structural checks run **entirely locally** — no document content leaves your
123
+ machine or CI runner. Semantic checks are the one exception: when
124
+ `ANTHROPIC_API_KEY` is set, the graded excerpts (section text, linked item
125
+ text) are sent to the **Anthropic API** for scoring. Without the key, semantic
126
+ checks are skipped and nothing is sent anywhere. Alignment grading is capped at
127
+ `alignment_limit` links per run (default 25). If your documents are
128
+ confidential, run without the key or review [Anthropic's data-usage
129
+ policies](https://www.anthropic.com/legal/commercial-terms) first.
130
+
120
131
  ## Development
121
132
 
122
133
  ```bash
@@ -95,6 +95,7 @@ tests/test_consistency.py
95
95
  tests/test_defects.py
96
96
  tests/test_extract.py
97
97
  tests/test_graph.py
98
+ tests/test_json_report.py
98
99
  tests/test_kinds_delivery.py
99
100
  tests/test_kinds_governance.py
100
101
  tests/test_kinds_operate.py
@@ -30,6 +30,38 @@ def test_unsupported_type_raises(tmp_path):
30
30
  E.extract(f)
31
31
 
32
32
 
33
+ def _minimal_pdf(text: str) -> bytes:
34
+ """Assemble a one-page PDF with `text` in a content stream, xref included."""
35
+ stream = f"BT /F1 24 Tf 72 720 Td ({text}) Tj ET".encode()
36
+ objects = [
37
+ b"<< /Type /Catalog /Pages 2 0 R >>",
38
+ b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
39
+ (b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] "
40
+ b"/Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>"),
41
+ b"<< /Length " + str(len(stream)).encode() + b" >>\nstream\n" + stream + b"\nendstream",
42
+ b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
43
+ ]
44
+ out = b"%PDF-1.4\n"
45
+ offsets = []
46
+ for i, body in enumerate(objects, start=1):
47
+ offsets.append(len(out))
48
+ out += f"{i} 0 obj\n".encode() + body + b"\nendobj\n"
49
+ xref_at = len(out)
50
+ out += f"xref\n0 {len(objects) + 1}\n0000000000 65535 f \n".encode()
51
+ for off in offsets:
52
+ out += f"{off:010d} 00000 n \n".encode()
53
+ out += (f"trailer\n<< /Size {len(objects) + 1} /Root 1 0 R >>\n"
54
+ f"startxref\n{xref_at}\n%%EOF\n").encode()
55
+ return out
56
+
57
+
58
+ def test_extract_pdf(tmp_path):
59
+ pytest.importorskip("pypdf") # needs the 'convert' extra
60
+ path = tmp_path / "s.pdf"
61
+ path.write_bytes(_minimal_pdf("Hello docassert PDF"))
62
+ assert "Hello docassert PDF" in E.extract(path)
63
+
64
+
33
65
  def test_extract_docx_paragraphs_and_tables(tmp_path):
34
66
  docx = pytest.importorskip("docx") # needs the 'convert' extra
35
67
  d = docx.Document()
@@ -0,0 +1,43 @@
1
+ """Tests for the machine-readable JSON report (`--json`)."""
2
+ import json
3
+ from pathlib import Path
4
+
5
+ from docassert import report
6
+ from docassert.cli import main
7
+ from docassert.models import CheckResult
8
+
9
+ ROOT = Path(__file__).resolve().parent.parent
10
+
11
+
12
+ def test_json_report_shape():
13
+ results = {
14
+ "a.md": [CheckResult("c1", True, True, "ok"),
15
+ CheckResult("c2", False, True, "bad")],
16
+ "b.md": [CheckResult("c3", False, False, "meh", kind="semantic", score=0.4)],
17
+ }
18
+ data = json.loads(report.json_report(results))
19
+ assert data["summary"] == {"documents": 2, "checks": 3, "blocking_failures": 1,
20
+ "advisory_failures": 1, "passed": False}
21
+ assert data["documents"]["a.md"][1]["check_id"] == "c2"
22
+ assert data["documents"]["b.md"][0]["score"] == 0.4
23
+
24
+
25
+ def test_cli_validate_writes_json(tmp_path, monkeypatch):
26
+ monkeypatch.chdir(ROOT) # criteria/schema resolve; sample documents exist
27
+ out = tmp_path / "r.json"
28
+ code = main(["validate", "documents/PRJ-001-AUR/charter.md", "--json", str(out)])
29
+ assert code == 0
30
+ data = json.loads(out.read_text())
31
+ assert data["summary"]["passed"] is True
32
+ assert "documents/PRJ-001-AUR/charter.md" in data["documents"]
33
+
34
+
35
+ def test_cli_consistency_writes_json(tmp_path, monkeypatch):
36
+ monkeypatch.chdir(ROOT)
37
+ monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
38
+ out = tmp_path / "c.json"
39
+ code = main(["consistency", "--no-semantic", "--json", str(out)])
40
+ assert code == 0
41
+ data = json.loads(out.read_text())
42
+ checks = {c["check_id"] for c in data["documents"]["consistency (cross-document)"]}
43
+ assert {"item-id-uniqueness", "referential-integrity", "coverage"} <= checks
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