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.
- codec_cortex-0.3.2.dist-info/METADATA +183 -0
- codec_cortex-0.3.2.dist-info/RECORD +81 -0
- codec_cortex-0.3.2.dist-info/WHEEL +5 -0
- codec_cortex-0.3.2.dist-info/entry_points.txt +2 -0
- codec_cortex-0.3.2.dist-info/licenses/LICENSE +21 -0
- codec_cortex-0.3.2.dist-info/scm_file_list.json +124 -0
- codec_cortex-0.3.2.dist-info/scm_version.json +8 -0
- codec_cortex-0.3.2.dist-info/top_level.txt +1 -0
- cortex/__init__.py +22 -0
- cortex/__main__.py +7 -0
- cortex/_version.py +24 -0
- cortex/cli/__init__.py +5 -0
- cortex/cli/commands/__init__.py +150 -0
- cortex/cli/commands/add.py +89 -0
- cortex/cli/commands/compile.py +31 -0
- cortex/cli/commands/delete.py +44 -0
- cortex/cli/commands/diagram.py +130 -0
- cortex/cli/commands/diff.py +135 -0
- cortex/cli/commands/doctor.py +74 -0
- cortex/cli/commands/format.py +29 -0
- cortex/cli/commands/get.py +45 -0
- cortex/cli/commands/glossary.py +98 -0
- cortex/cli/commands/list.py +44 -0
- cortex/cli/commands/micro.py +79 -0
- cortex/cli/commands/move.py +45 -0
- cortex/cli/commands/new.py +80 -0
- cortex/cli/commands/recover.py +90 -0
- cortex/cli/commands/render.py +93 -0
- cortex/cli/commands/update.py +81 -0
- cortex/cli/commands/v2_canonicalize.py +162 -0
- cortex/cli/commands/v2_compare.py +74 -0
- cortex/cli/commands/v2_convert.py +183 -0
- cortex/cli/commands/v2_explain_loss.py +97 -0
- cortex/cli/commands/v2_inspect.py +113 -0
- cortex/cli/commands/v2_roundtrip.py +104 -0
- cortex/cli/commands/v2_roundtrip_bidir.py +128 -0
- cortex/cli/commands/v2_verify_view.py +65 -0
- cortex/cli/commands/verify.py +119 -0
- cortex/cli/main.py +636 -0
- cortex/core/__init__.py +85 -0
- cortex/core/ast.py +313 -0
- cortex/core/compare.py +180 -0
- cortex/core/document_kind.py +525 -0
- cortex/core/errors.py +399 -0
- cortex/core/lexer.py +267 -0
- cortex/core/parser.py +671 -0
- cortex/core/validator.py +268 -0
- cortex/core/writer.py +249 -0
- cortex/crud/__init__.py +17 -0
- cortex/crud/mutations.py +342 -0
- cortex/crud/selectors.py +95 -0
- cortex/crud/transactions.py +213 -0
- cortex/glossary/__init__.py +21 -0
- cortex/glossary/contracts.py +37 -0
- cortex/glossary/minimal.py +94 -0
- cortex/glossary/model.py +77 -0
- cortex/glossary/resolver.py +96 -0
- cortex/hcortex/__init__.py +35 -0
- cortex/hcortex/edit_parser.py +489 -0
- cortex/hcortex/edit_renderer.py +158 -0
- cortex/hcortex/markdown_model.py +81 -0
- cortex/hcortex/profiles.py +166 -0
- cortex/hcortex/read_renderer.py +342 -0
- cortex/hcortex/recovery.py +782 -0
- cortex/py.typed +0 -0
- cortex/templates/__init__.py +8 -0
- cortex/templates/brain.py +118 -0
- cortex/templates/minimal_glossary.py +42 -0
- cortex/templates/package.py +91 -0
- cortex/templates/skill.py +91 -0
- cortex/v2/__init__.py +30 -0
- cortex/v2/diagnostics.py +57 -0
- cortex/v2/encoder.py +1106 -0
- cortex/v2/equivalence.py +323 -0
- cortex/v2/hcortex_parser.py +615 -0
- cortex/v2/hcortex_renderer.py +450 -0
- cortex/v2/ir.py +223 -0
- cortex/v2/parser.py +655 -0
- cortex/v2/view.py +425 -0
- cortex/v2/view_renderer.py +474 -0
- 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
|