codec-cortex 0.3.2__py3-none-any.whl

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 (81) hide show
  1. codec_cortex-0.3.2.dist-info/METADATA +183 -0
  2. codec_cortex-0.3.2.dist-info/RECORD +81 -0
  3. codec_cortex-0.3.2.dist-info/WHEEL +5 -0
  4. codec_cortex-0.3.2.dist-info/entry_points.txt +2 -0
  5. codec_cortex-0.3.2.dist-info/licenses/LICENSE +21 -0
  6. codec_cortex-0.3.2.dist-info/scm_file_list.json +124 -0
  7. codec_cortex-0.3.2.dist-info/scm_version.json +8 -0
  8. codec_cortex-0.3.2.dist-info/top_level.txt +1 -0
  9. cortex/__init__.py +22 -0
  10. cortex/__main__.py +7 -0
  11. cortex/_version.py +24 -0
  12. cortex/cli/__init__.py +5 -0
  13. cortex/cli/commands/__init__.py +150 -0
  14. cortex/cli/commands/add.py +89 -0
  15. cortex/cli/commands/compile.py +31 -0
  16. cortex/cli/commands/delete.py +44 -0
  17. cortex/cli/commands/diagram.py +130 -0
  18. cortex/cli/commands/diff.py +135 -0
  19. cortex/cli/commands/doctor.py +74 -0
  20. cortex/cli/commands/format.py +29 -0
  21. cortex/cli/commands/get.py +45 -0
  22. cortex/cli/commands/glossary.py +98 -0
  23. cortex/cli/commands/list.py +44 -0
  24. cortex/cli/commands/micro.py +79 -0
  25. cortex/cli/commands/move.py +45 -0
  26. cortex/cli/commands/new.py +80 -0
  27. cortex/cli/commands/recover.py +90 -0
  28. cortex/cli/commands/render.py +93 -0
  29. cortex/cli/commands/update.py +81 -0
  30. cortex/cli/commands/v2_canonicalize.py +162 -0
  31. cortex/cli/commands/v2_compare.py +74 -0
  32. cortex/cli/commands/v2_convert.py +183 -0
  33. cortex/cli/commands/v2_explain_loss.py +97 -0
  34. cortex/cli/commands/v2_inspect.py +113 -0
  35. cortex/cli/commands/v2_roundtrip.py +104 -0
  36. cortex/cli/commands/v2_roundtrip_bidir.py +128 -0
  37. cortex/cli/commands/v2_verify_view.py +65 -0
  38. cortex/cli/commands/verify.py +119 -0
  39. cortex/cli/main.py +636 -0
  40. cortex/core/__init__.py +85 -0
  41. cortex/core/ast.py +313 -0
  42. cortex/core/compare.py +180 -0
  43. cortex/core/document_kind.py +525 -0
  44. cortex/core/errors.py +399 -0
  45. cortex/core/lexer.py +267 -0
  46. cortex/core/parser.py +671 -0
  47. cortex/core/validator.py +268 -0
  48. cortex/core/writer.py +249 -0
  49. cortex/crud/__init__.py +17 -0
  50. cortex/crud/mutations.py +342 -0
  51. cortex/crud/selectors.py +95 -0
  52. cortex/crud/transactions.py +213 -0
  53. cortex/glossary/__init__.py +21 -0
  54. cortex/glossary/contracts.py +37 -0
  55. cortex/glossary/minimal.py +94 -0
  56. cortex/glossary/model.py +77 -0
  57. cortex/glossary/resolver.py +96 -0
  58. cortex/hcortex/__init__.py +35 -0
  59. cortex/hcortex/edit_parser.py +489 -0
  60. cortex/hcortex/edit_renderer.py +158 -0
  61. cortex/hcortex/markdown_model.py +81 -0
  62. cortex/hcortex/profiles.py +166 -0
  63. cortex/hcortex/read_renderer.py +342 -0
  64. cortex/hcortex/recovery.py +782 -0
  65. cortex/py.typed +0 -0
  66. cortex/templates/__init__.py +8 -0
  67. cortex/templates/brain.py +118 -0
  68. cortex/templates/minimal_glossary.py +42 -0
  69. cortex/templates/package.py +91 -0
  70. cortex/templates/skill.py +91 -0
  71. cortex/v2/__init__.py +30 -0
  72. cortex/v2/diagnostics.py +57 -0
  73. cortex/v2/encoder.py +1106 -0
  74. cortex/v2/equivalence.py +323 -0
  75. cortex/v2/hcortex_parser.py +615 -0
  76. cortex/v2/hcortex_renderer.py +450 -0
  77. cortex/v2/ir.py +223 -0
  78. cortex/v2/parser.py +655 -0
  79. cortex/v2/view.py +425 -0
  80. cortex/v2/view_renderer.py +474 -0
  81. cortex/v2/writer.py +301 -0
@@ -0,0 +1,128 @@
1
+ """``cortex roundtrip-bidir`` — validate CORTEX ⇄ HCORTEX roundtrip.
2
+
3
+ Canonical name: ``roundtrip-bidir`` (since v0.3.2).
4
+ Deprecated alias: ``v2-roundtrip-bidir`` (still accepted).
5
+
6
+ v2.3.1: Fixed Direction 2 logic — was using CORTEX as HCORTEX.
7
+ Now properly tests both directions with correct format detection.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import sys
14
+
15
+ from ...core.errors import CortexError
16
+ from ...v2.parser import parse_cortex_v2
17
+ from ...v2.writer import write_cortex_v2
18
+ from ...v2.view_renderer import render_hcortex
19
+ from ...v2.hcortex_parser import parse_hcortex
20
+ from ...v2.encoder import encode_cortex_from_ast
21
+ from ...v2.equivalence import compare_ast_equivalent, compare_content_equivalent
22
+
23
+
24
+ def run(args) -> int:
25
+ if not os.path.exists(args.input):
26
+ raise CortexError("E013_NOT_FOUND", f"file not found: {args.input}")
27
+
28
+ with open(args.input, "r", encoding="utf-8") as f:
29
+ text = f.read()
30
+
31
+ is_hcortex = "<!-- CODEC-CORTEX" in text and "internal_encoding: HCORTEX" in text
32
+ is_cortex = "<!-- CODEC-CORTEX" in text and "internal_encoding: CORTEX" in text
33
+
34
+ if is_cortex:
35
+ # Input is CORTEX
36
+ # Direction 1: CORTEX → HCORTEX → CORTEX (AST-equivalent)
37
+ print("=== Direction 1: CORTEX → HCORTEX → CORTEX ===")
38
+ rc1 = _roundtrip_cortex_to_hcortex_to_cortex(text)
39
+
40
+ # Direction 2: CORTEX → HCORTEX → CORTEX → HCORTEX (content-equivalent)
41
+ # We use the HCORTEX produced in Direction 1 as input
42
+ print("\n=== Direction 2: HCORTEX → CORTEX → HCORTEX ===")
43
+ # Generate HCORTEX from CORTEX first, then test HCORTEX → CORTEX → HCORTEX
44
+ doc = parse_cortex_v2(text)
45
+ hcortex_md, _ = render_hcortex(doc)
46
+ rc2 = _roundtrip_hcortex_to_cortex_to_hcortex(hcortex_md)
47
+ return rc1 or rc2
48
+
49
+ elif is_hcortex:
50
+ # Input is HCORTEX
51
+ # Direction 1: HCORTEX → CORTEX → HCORTEX (content-equivalent)
52
+ print("=== Direction 1: HCORTEX → CORTEX → HCORTEX ===")
53
+ rc1 = _roundtrip_hcortex_to_cortex_to_hcortex(text)
54
+
55
+ # Direction 2: HCORTEX → CORTEX → HCORTEX → CORTEX (AST-equivalent)
56
+ # Use the CORTEX reconstructed in Direction 1 as input
57
+ print("\n=== Direction 2: CORTEX → HCORTEX → CORTEX ===")
58
+ hdoc = parse_hcortex(text, strict=False)
59
+ doc, _ = encode_cortex_from_ast(hdoc)
60
+ cortex_text = write_cortex_v2(doc)
61
+ rc2 = _roundtrip_cortex_to_hcortex_to_cortex(cortex_text)
62
+ return rc1 or rc2
63
+ else:
64
+ print("ERROR: Could not detect format (no CODEC-CORTEX header)", file=sys.stderr)
65
+ return 1
66
+
67
+
68
+ def _roundtrip_cortex_to_hcortex_to_cortex(cortex_text: str) -> int:
69
+ """CORTEX → HCORTEX → CORTEX: verify AST equivalence."""
70
+
71
+ cortex_text.encode("utf-8")
72
+ doc_orig = parse_cortex_v2(cortex_text)
73
+ sum(len(s.entries) for s in doc_orig.sections)
74
+
75
+ # CORTEX → HCORTEX
76
+ hcortex_md, diags = render_hcortex(doc_orig)
77
+ print(f"CORTEX → HCORTEX: {len(hcortex_md)} chars, {len(diags)} diags")
78
+
79
+ # HCORTEX → CORTEX
80
+ hdoc = parse_hcortex(hcortex_md, strict=False)
81
+ doc_reconstructed, enc_diags = encode_cortex_from_ast(hdoc)
82
+ recon_entry_count = sum(len(s.entries) for s in doc_reconstructed.sections)
83
+ print(f"HCORTEX → CORTEX: {len(doc_reconstructed.sections)} sections, {recon_entry_count} entries")
84
+ if enc_diags:
85
+ for d in enc_diags[:3]:
86
+ print(f" [{d.severity}] {d.code}: {d.message}")
87
+
88
+ # Compare AST
89
+ ast_eq, diffs = compare_ast_equivalent(doc_orig, doc_reconstructed)
90
+ print(f"AST equivalent: {ast_eq} ({len(diffs)} diffs)")
91
+
92
+ if ast_eq:
93
+ print("✓ CORTEX → HCORTEX → CORTEX roundtrip: AST-equivalent")
94
+ return 0
95
+ else:
96
+ print("✗ Roundtrip NOT AST-equivalent")
97
+ for d in diffs[:5]:
98
+ print(f" {d.kind} at {d.location}" + (f".{d.field}" if d.field else ""))
99
+ return 1
100
+
101
+
102
+ def _roundtrip_hcortex_to_cortex_to_hcortex(hcortex_text: str) -> int:
103
+ """HCORTEX → CORTEX → HCORTEX: verify content equivalence."""
104
+
105
+ # HCORTEX → CORTEX
106
+ hdoc_orig = parse_hcortex(hcortex_text, strict=False)
107
+ doc, enc_diags = encode_cortex_from_ast(hdoc_orig)
108
+ print(f"HCORTEX → CORTEX: {len(doc.sections)} sections, {sum(len(s.entries) for s in doc.sections)} entries")
109
+ if enc_diags:
110
+ for d in enc_diags[:3]:
111
+ print(f" [{d.severity}] {d.code}: {d.message}")
112
+
113
+ # CORTEX → HCORTEX
114
+ hcortex_regen, diags = render_hcortex(doc)
115
+ print(f"CORTEX → HCORTEX: {len(hcortex_regen)} chars")
116
+
117
+ # Compare content
118
+ content_eq, diffs = compare_content_equivalent(hcortex_text, hcortex_regen)
119
+ print(f"Content equivalent: {content_eq} ({len(diffs)} diffs)")
120
+
121
+ if content_eq:
122
+ print("✓ HCORTEX → CORTEX → HCORTEX roundtrip: content-equivalent")
123
+ return 0
124
+ else:
125
+ print("✗ Roundtrip NOT content-equivalent")
126
+ for d in diffs[:5]:
127
+ print(f" {d.kind} at {d.location}")
128
+ return 1
@@ -0,0 +1,65 @@
1
+ """``cortex verify-view`` — validate VIEW coverage, reversibility, consistency.
2
+
3
+ Canonical name: ``verify-view`` (since v0.3.2).
4
+ Deprecated alias: ``v2-verify-view`` (still accepted).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+
12
+ from ...core.errors import CortexError
13
+ from ...v2.parser import parse_cortex_v2
14
+ from ...v2.view import parse_view_entries_from_doc, calculate_view_coverage
15
+ from ...v2.view_renderer import render_hcortex
16
+
17
+
18
+ def run(args) -> int:
19
+ if not os.path.exists(args.input):
20
+ raise CortexError("E013_NOT_FOUND", f"file not found: {args.input}")
21
+
22
+ with open(args.input, "r", encoding="utf-8") as f:
23
+ text = f.read()
24
+
25
+ doc = parse_cortex_v2(text)
26
+ directives, view_diags = parse_view_entries_from_doc(doc)
27
+ coverage, uncovered = calculate_view_coverage(doc, directives)
28
+
29
+ # Render to get full diagnostics
30
+ _, render_diags = render_hcortex(doc)
31
+
32
+ all_diags = list(view_diags) + list(render_diags)
33
+ errors = [d for d in all_diags if d.severity == "error"]
34
+ warnings = [d for d in all_diags if d.severity == "warning"]
35
+
36
+ is_reversible = (coverage == 1.0) and (len(errors) == 0)
37
+
38
+ result = {
39
+ "view_count": len(directives),
40
+ "view_coverage_percent": int(coverage * 100),
41
+ "uncovered_count": len(uncovered),
42
+ "uncovered": uncovered[:20],
43
+ "errors": len(errors),
44
+ "warnings": len(warnings),
45
+ "reversible": is_reversible,
46
+ "diagnostics": [d.to_dict() for d in all_diags[:20]],
47
+ }
48
+
49
+ if args.format == "json":
50
+ print(json.dumps(result, indent=2, ensure_ascii=False))
51
+ else:
52
+ print(f"VIEW directives: {len(directives)}")
53
+ print(f"VIEW coverage: {coverage:.1%}")
54
+ print(f"Uncovered entries: {len(uncovered)}")
55
+ print(f"Errors: {len(errors)}")
56
+ print(f"Warnings: {len(warnings)}")
57
+ print(f"Reversible: {is_reversible}")
58
+ for d in all_diags[:10]:
59
+ print(f" [{d.severity}] {d.code}: {d.message}")
60
+
61
+ if errors:
62
+ return 1
63
+ if args.strict and warnings:
64
+ return 1
65
+ return 0
@@ -0,0 +1,119 @@
1
+ """``cortex verify`` — validate a .cortex file and optionally roundtrip."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import tempfile
8
+
9
+ from ...core.compare import compare_ast
10
+ from ...core.document_kind import infer_document_kind
11
+ from ...core.parser import parse_cortex
12
+ from ...core.writer import write_cortex
13
+ from ...core.validator import validate
14
+ from ...hcortex import parse_hcortex_edit, render_hcortex_edit
15
+ from ..commands import load_doc
16
+
17
+
18
+ def run(args) -> int:
19
+ doc = load_doc(args.input)
20
+
21
+ # Apply explicit kind if provided (audit gap H-01/H-02)
22
+ kind = None
23
+ if getattr(args, "kind", None):
24
+ from ...core.document_kind import DocumentKind
25
+ kind = DocumentKind(kind=args.kind, source="cli")
26
+
27
+ strict = getattr(args, "strict", False)
28
+ diagnostics = validate(doc, strict=strict, kind=kind)
29
+ errors = [d for d in diagnostics if d.get("severity") == "error"]
30
+ warnings = [d for d in diagnostics if d.get("severity") == "warning"]
31
+
32
+ # Infer kind for display if not explicit
33
+ if kind is None:
34
+ kind = infer_document_kind(doc, args.input)
35
+
36
+ text_lines = []
37
+ text_lines.append(f"verifying: {args.input}")
38
+ text_lines.append(f" kind: {kind.kind} (inferred via {kind.source})")
39
+ text_lines.append(f" sections: {len(doc.sections)}")
40
+ text_lines.append(f" sigils: {len(doc.glossary.sigils)}")
41
+ total_entries = sum(len(s.entries) for s in doc.sections if s.id != "$0")
42
+ text_lines.append(f" entries: {total_entries}")
43
+ text_lines.append(f" errors: {len(errors)}")
44
+ text_lines.append(f" warnings: {len(warnings)}")
45
+ text_lines.append(f" strict mode: {strict}")
46
+ if errors:
47
+ text_lines.append("")
48
+ text_lines.append("errors:")
49
+ for e in errors:
50
+ text_lines.append(f" [{e.get('code')}] {e.get('message')}")
51
+
52
+ roundtrip_ok = None
53
+ roundtrip_diffs = []
54
+ if args.roundtrip == "hcortex-edit":
55
+ md = render_hcortex_edit(doc, source=args.input)
56
+ with tempfile.NamedTemporaryFile(
57
+ mode="w", suffix=".hcortex.edit.md", delete=False, encoding="utf-8"
58
+ ) as tmp:
59
+ tmp.write(md)
60
+ tmp_path = tmp.name
61
+ try:
62
+ with open(tmp_path, "r", encoding="utf-8") as f:
63
+ md_text = f.read()
64
+ doc2 = parse_hcortex_edit(md_text, source=tmp_path)
65
+ cortex2 = write_cortex(doc2)
66
+ doc3 = parse_cortex(cortex2, path="<roundtrip>")
67
+ diff = compare_ast(doc, doc3)
68
+ roundtrip_ok = diff.equal
69
+ roundtrip_diffs = [d.to_dict() for d in diff.diffs]
70
+ if not roundtrip_ok:
71
+ text_lines.append(f" roundtrip: FAILED ({len(diff.diffs)} diff(s))")
72
+ else:
73
+ text_lines.append(" roundtrip: OK (structural identity preserved)")
74
+ finally:
75
+ try:
76
+ os.unlink(tmp_path)
77
+ except OSError:
78
+ pass
79
+ elif args.roundtrip == "cortex":
80
+ # Plain .cortex → .cortex roundtrip (no HCORTEX step)
81
+ text2 = write_cortex(doc)
82
+ doc3 = parse_cortex(text2, path="<roundtrip>")
83
+ diff = compare_ast(doc, doc3)
84
+ roundtrip_ok = diff.equal
85
+ roundtrip_diffs = [d.to_dict() for d in diff.diffs]
86
+ text_lines.append(
87
+ f" roundtrip: {'OK' if roundtrip_ok else 'FAILED'} "
88
+ f"({len(diff.diffs)} diff(s))"
89
+ )
90
+
91
+ payload = {
92
+ "text": "\n".join(text_lines),
93
+ "input": args.input,
94
+ "kind": kind.kind,
95
+ "sections": len(doc.sections),
96
+ "entries": total_entries,
97
+ "errors": errors,
98
+ "warnings": warnings,
99
+ "strict": strict,
100
+ "roundtrip": {
101
+ "mode": args.roundtrip,
102
+ "ok": roundtrip_ok,
103
+ "diffs": roundtrip_diffs,
104
+ } if args.roundtrip else None,
105
+ }
106
+
107
+ json_mode = getattr(args, "_json_mode", False)
108
+ if json_mode:
109
+ print(json.dumps(payload, indent=2, ensure_ascii=False, default=str))
110
+ else:
111
+ print(payload["text"])
112
+
113
+ if errors:
114
+ return 1
115
+ if strict and warnings:
116
+ return 1
117
+ if args.roundtrip and not roundtrip_ok:
118
+ return 2
119
+ return 0