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
cortex/cli/main.py ADDED
@@ -0,0 +1,636 @@
1
+ """CODEC-CORTEX CLI entry point.
2
+
3
+ Usage (canonical commands):
4
+ cortex new brain --out brain.cortex
5
+ cortex render brain.cortex --format hcortex --mode edit --out brain.hcortex.edit.md
6
+ cortex compile brain.hcortex.edit.md --out brain.cortex
7
+ cortex verify brain.cortex --roundtrip hcortex-edit
8
+ cortex list brain.cortex
9
+ cortex get brain.cortex FCS:primary --format json
10
+ cortex add brain.cortex --section $2 --sigil FCS --name primary --value 'what:"x"'
11
+ cortex update brain.cortex FCS:primary --set what="new"
12
+ cortex delete brain.cortex FCS:secondary
13
+ cortex move brain.cortex FCS:primary --to-section $3
14
+ cortex glossary list brain.cortex
15
+ cortex micro list brain.cortex
16
+ cortex doctor brain.cortex
17
+ cortex diff a.cortex b.cortex
18
+ cortex format brain.cortex
19
+ cortex recover legacy.cortex --out legacy.fixed.cortex
20
+ cortex diagram list brain.cortex
21
+ cortex diagram extract brain.cortex --name flow
22
+
23
+ v2 commands (canonical names since v0.3.2; v2-* aliases are deprecated):
24
+ cortex roundtrip <file> # was v2-roundtrip
25
+ cortex convert <file> ... # was v2-convert
26
+ cortex roundtrip-bidir <file> # was v2-roundtrip-bidir
27
+ cortex compare <a> <b> # was v2-compare
28
+ cortex verify-view <file> # was v2-verify-view
29
+ cortex explain-loss <file> # was v2-explain-loss
30
+ cortex canonicalize <file> # was v2-canonicalize (now VIEW-aware)
31
+ cortex inspect <file> # was v2-inspect
32
+
33
+ Aliases (Section 22.2 of SKILL.md):
34
+ cortex decode = cortex render (decode .cortex to a view)
35
+ cortex encode = cortex compile (encode a view back to .cortex)
36
+ cortex patch_add = cortex add
37
+ cortex patch_update = cortex update
38
+ cortex patch_remove = cortex delete
39
+
40
+ Deprecated aliases (still accepted, will be removed in v1.0.0):
41
+ v2-roundtrip, v2-convert, v2-roundtrip-bidir, v2-compare,
42
+ v2-verify-view, v2-explain-loss, v2-canonicalize, v2-inspect
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ import argparse
48
+ import json
49
+ import sys
50
+ from typing import List, Optional
51
+
52
+ from .. import __version__
53
+ from .commands import (
54
+ new as cmd_new,
55
+ render as cmd_render,
56
+ compile as cmd_compile,
57
+ verify as cmd_verify,
58
+ get as cmd_get,
59
+ list as cmd_list,
60
+ add as cmd_add,
61
+ update as cmd_update,
62
+ delete as cmd_delete,
63
+ move as cmd_move,
64
+ glossary as cmd_glossary,
65
+ micro as cmd_micro,
66
+ doctor as cmd_doctor,
67
+ diff as cmd_diff,
68
+ format as cmd_format,
69
+ recover as cmd_recover,
70
+ diagram as cmd_diagram,
71
+ v2_roundtrip as cmd_v2_roundtrip,
72
+ v2_convert as cmd_v2_convert,
73
+ v2_roundtrip_bidir as cmd_v2_roundtrip_bidir,
74
+ v2_compare as cmd_v2_compare,
75
+ v2_verify_view as cmd_v2_verify_view,
76
+ v2_explain_loss as cmd_v2_explain_loss,
77
+ v2_canonicalize as cmd_v2_canonicalize,
78
+ v2_inspect as cmd_v2_inspect,
79
+ )
80
+
81
+
82
+ def build_parser() -> argparse.ArgumentParser:
83
+ p = argparse.ArgumentParser(
84
+ prog="cortex",
85
+ description=(
86
+ "CODEC-CORTEX CLI — modular deterministic processor for .cortex files. "
87
+ f"v{__version__}"
88
+ ),
89
+ )
90
+ p.add_argument("--version", action="version", version=f"cortex {__version__}")
91
+ p.add_argument(
92
+ "--json", action="store_true",
93
+ help="emit machine-readable JSON where supported",
94
+ )
95
+ sub = p.add_subparsers(dest="command", required=True)
96
+
97
+ # ------------------------------------------------------------------
98
+ # new
99
+ # ------------------------------------------------------------------
100
+ sp = sub.add_parser("new", help="create a new .cortex file from a template")
101
+ sp.add_argument("kind", choices=["brain", "skill", "package", "generic"])
102
+ sp.add_argument("--out", required=True, help="output file path")
103
+ sp.add_argument("--name", default=None)
104
+ sp.add_argument("--version", dest="doc_version", default=None)
105
+ sp.add_argument("--domain", default=None)
106
+ sp.add_argument("--owner", default=None)
107
+ sp.add_argument("--language", default="en")
108
+ sp.add_argument("--template", choices=["minimal", "standard", "enterprise"], default="standard")
109
+ sp.add_argument("--with-diagrams", action="store_true")
110
+ sp.add_argument("--force", action="store_true")
111
+ sp.set_defaults(func=cmd_new.run)
112
+
113
+ # ------------------------------------------------------------------
114
+ # render (alias: decode)
115
+ # ------------------------------------------------------------------
116
+ # v1.1.3 P2-8: `decode <file>` (alias) defaults to --mode readable
117
+ # so the planned `cortex decode <file>` UX works without flags.
118
+ sp = sub.add_parser("render", help="render .cortex to HCORTEX markdown", aliases=["decode"])
119
+ sp.add_argument("input")
120
+ sp.add_argument("--format", choices=["hcortex"], default="hcortex")
121
+ sp.add_argument(
122
+ "--mode", choices=["read", "edit", "readable", "audit", "recovery", "full"],
123
+ default=None,
124
+ help="read=readable, edit=editable; recovery/full are profile aliases; "
125
+ "default=readable (v1.1.3 P2-8)",
126
+ )
127
+ sp.add_argument("--profile", choices=["min", "recovery", "work", "full"], default=None,
128
+ help="HCORTEX profile (MIN/RECOVERY/WORK/FULL)")
129
+ sp.add_argument(
130
+ "--layout", choices=["priority", "section"], default=None,
131
+ help="priority=global P0→P5 order; section=preserve section grouping; "
132
+ "auto-selects based on profile/mode",
133
+ )
134
+ sp.add_argument("--out", default=None)
135
+ sp.add_argument("--with-source", action="store_true",
136
+ help="include source column in READ mode (audit mode)")
137
+ sp.add_argument("--force", action="store_true")
138
+ sp.set_defaults(func=cmd_render.run)
139
+
140
+ # ------------------------------------------------------------------
141
+ # compile (alias: encode)
142
+ # ------------------------------------------------------------------
143
+ sp = sub.add_parser("compile", help="compile HCORTEX-EDIT markdown back to .cortex",
144
+ aliases=["encode"])
145
+ sp.add_argument("input")
146
+ sp.add_argument("--out", required=True)
147
+ sp.add_argument("--force", action="store_true")
148
+ sp.set_defaults(func=cmd_compile.run)
149
+
150
+ # ------------------------------------------------------------------
151
+ # verify (with --strict and --kind)
152
+ # ------------------------------------------------------------------
153
+ sp = sub.add_parser("verify", help="validate a .cortex file (optionally with roundtrip)")
154
+ sp.add_argument("input")
155
+ sp.add_argument("--roundtrip", choices=["hcortex-edit", "cortex"], default=None)
156
+ sp.add_argument("--strict", action="store_true",
157
+ help="promote warnings to errors (cognitive governance)")
158
+ sp.add_argument("--kind", choices=["brain", "skill", "package", "generic"], default=None,
159
+ help="explicit document kind for level-policy checks")
160
+ sp.set_defaults(func=cmd_verify.run)
161
+
162
+ # ------------------------------------------------------------------
163
+ # get
164
+ # ------------------------------------------------------------------
165
+ sp = sub.add_parser("get", help="get a single entry by selector")
166
+ sp.add_argument("input")
167
+ sp.add_argument("selector")
168
+ sp.add_argument("--format", choices=["text", "json"], default="text")
169
+ sp.set_defaults(func=cmd_get.run)
170
+
171
+ # ------------------------------------------------------------------
172
+ # list
173
+ # ------------------------------------------------------------------
174
+ sp = sub.add_parser("list", help="list entries in a .cortex file")
175
+ sp.add_argument("input")
176
+ sp.add_argument("--section", default=None)
177
+ sp.add_argument("--sigil", default=None)
178
+ sp.add_argument("--format", choices=["text", "json"], default="text")
179
+ sp.set_defaults(func=cmd_list.run)
180
+
181
+ # ------------------------------------------------------------------
182
+ # add (alias: patch_add)
183
+ # ------------------------------------------------------------------
184
+ sp = sub.add_parser("add", help="add a new entry to a .cortex file",
185
+ aliases=["patch_add"])
186
+ sp.add_argument("input")
187
+ sp.add_argument("--section", required=True)
188
+ sp.add_argument("--sigil", required=True)
189
+ sp.add_argument("--name", required=True)
190
+ sp.add_argument("--value", required=True, help='attrs body e.g. \'what:"x", priority:"high"\'')
191
+ sp.add_argument("--create-section", action="store_true")
192
+ sp.add_argument("--allow-duplicate", action="store_true")
193
+ sp.add_argument(
194
+ "--allow-unknown-sigil", action="store_true",
195
+ help="permit adding entries with sigils not declared in $0 (recovery/debug)",
196
+ )
197
+ sp.add_argument(
198
+ "--no-validate-write", action="store_true",
199
+ help="skip post-mutation validation before persisting (default: validate)",
200
+ )
201
+ sp.add_argument(
202
+ "--strict-write", action="store_true",
203
+ help="require strict validation (warnings also block write)",
204
+ )
205
+ sp.add_argument(
206
+ "--unsafe-allow-secret-forensics", action="store_true",
207
+ help="FORENSIC ONLY: bypass E031_SECRET_NOT_BYPASSABLE for secret "
208
+ "scanning. Use with extreme caution; --force alone cannot "
209
+ "override the no-clear-text-secrets rule (SKILL.md §16.1).",
210
+ )
211
+ sp.add_argument("--dry-run", action="store_true")
212
+ sp.add_argument("--force", action="store_true")
213
+ sp.set_defaults(func=cmd_add.run)
214
+
215
+ # ------------------------------------------------------------------
216
+ # update (alias: patch_update)
217
+ # ------------------------------------------------------------------
218
+ sp = sub.add_parser("update", help="update an existing entry",
219
+ aliases=["patch_update"])
220
+ sp.add_argument("input")
221
+ sp.add_argument("selector")
222
+ sp.add_argument("--set", dest="set_pairs", action="append", default=[],
223
+ help='key="value" (can repeat)')
224
+ sp.add_argument("--body", default=None, help="replace cuerpo/bloque body")
225
+ sp.add_argument("--append", action="store_true")
226
+ sp.add_argument(
227
+ "--no-validate-write", action="store_true",
228
+ help="skip post-mutation validation before persisting (default: validate)",
229
+ )
230
+ sp.add_argument(
231
+ "--strict-write", action="store_true",
232
+ help="require strict validation (warnings also block write)",
233
+ )
234
+ sp.add_argument("--dry-run", action="store_true")
235
+ sp.add_argument("--force", action="store_true")
236
+ sp.set_defaults(func=cmd_update.run)
237
+
238
+ # ------------------------------------------------------------------
239
+ # delete (alias: patch_remove)
240
+ # ------------------------------------------------------------------
241
+ sp = sub.add_parser("delete", help="delete an entry",
242
+ aliases=["patch_remove"])
243
+ sp.add_argument("input")
244
+ sp.add_argument("selector")
245
+ sp.add_argument(
246
+ "--no-validate-write", action="store_true",
247
+ help="skip post-mutation validation before persisting (default: validate)",
248
+ )
249
+ sp.add_argument(
250
+ "--strict-write", action="store_true",
251
+ help="require strict validation (warnings also block write)",
252
+ )
253
+ sp.add_argument("--force", action="store_true")
254
+ sp.add_argument("--dry-run", action="store_true")
255
+ sp.set_defaults(func=cmd_delete.run)
256
+
257
+ # ------------------------------------------------------------------
258
+ # move
259
+ # ------------------------------------------------------------------
260
+ sp = sub.add_parser("move", help="move an entry to another section")
261
+ sp.add_argument("input")
262
+ sp.add_argument("selector")
263
+ sp.add_argument("--to-section", required=True)
264
+ sp.add_argument(
265
+ "--no-validate-write", action="store_true",
266
+ help="skip post-mutation validation before persisting (default: validate)",
267
+ )
268
+ sp.add_argument(
269
+ "--strict-write", action="store_true",
270
+ help="require strict validation (warnings also block write)",
271
+ )
272
+ sp.add_argument("--dry-run", action="store_true")
273
+ sp.add_argument("--force", action="store_true")
274
+ sp.set_defaults(func=cmd_move.run)
275
+
276
+ # ------------------------------------------------------------------
277
+ # glossary
278
+ # ------------------------------------------------------------------
279
+ sp = sub.add_parser("glossary", help="glossary CRUD")
280
+ g_sub = sp.add_subparsers(dest="glossary_command", required=True)
281
+
282
+ gsp = g_sub.add_parser("list", help="list sigils in $0")
283
+ gsp.add_argument("input")
284
+ gsp.add_argument("--format", choices=["text", "json"], default="text")
285
+ gsp.set_defaults(func=cmd_glossary.run_list)
286
+
287
+ gsp = g_sub.add_parser("add", help="add a sigil to $0")
288
+ gsp.add_argument("input")
289
+ gsp.add_argument("--sigil", required=True)
290
+ gsp.add_argument("--name", required=True)
291
+ gsp.add_argument("--type", required=True)
292
+ gsp.add_argument("--risk", default="M")
293
+ gsp.add_argument("--layer", default="Semantic")
294
+ gsp.add_argument("--description", required=True)
295
+ gsp.add_argument("--force-governance", action="store_true")
296
+ gsp.add_argument("--dry-run", action="store_true")
297
+ gsp.set_defaults(func=cmd_glossary.run_add)
298
+
299
+ gsp = g_sub.add_parser("update", help="update a sigil's metadata")
300
+ gsp.add_argument("input")
301
+ gsp.add_argument("--sigil", required=True)
302
+ gsp.add_argument("--description", default=None)
303
+ gsp.add_argument("--risk", default=None)
304
+ gsp.add_argument("--layer", default=None)
305
+ gsp.add_argument("--force-governance", action="store_true")
306
+ gsp.add_argument("--dry-run", action="store_true")
307
+ gsp.set_defaults(func=cmd_glossary.run_update)
308
+
309
+ gsp = g_sub.add_parser("delete", help="remove a sigil from $0")
310
+ gsp.add_argument("input")
311
+ gsp.add_argument("--sigil", required=True)
312
+ gsp.add_argument("--force", action="store_true")
313
+ gsp.add_argument("--dry-run", action="store_true")
314
+ gsp.set_defaults(func=cmd_glossary.run_delete)
315
+
316
+ # ------------------------------------------------------------------
317
+ # micro
318
+ # ------------------------------------------------------------------
319
+ sp = sub.add_parser("micro", help="micro-token CRUD")
320
+ m_sub = sp.add_subparsers(dest="micro_command", required=True)
321
+
322
+ msp = m_sub.add_parser("list", help="list micro-tokens")
323
+ msp.add_argument("input")
324
+ msp.add_argument("--format", choices=["text", "json"], default="text")
325
+ msp.set_defaults(func=cmd_micro.run_list)
326
+
327
+ msp = m_sub.add_parser("add", help="add a micro-token")
328
+ msp.add_argument("input")
329
+ msp.add_argument("--token", required=True)
330
+ msp.add_argument("--value", required=True)
331
+ msp.add_argument("--dry-run", action="store_true")
332
+ msp.set_defaults(func=cmd_micro.run_add)
333
+
334
+ msp = m_sub.add_parser("update", help="update a micro-token")
335
+ msp.add_argument("input")
336
+ msp.add_argument("--token", required=True)
337
+ msp.add_argument("--value", required=True)
338
+ msp.add_argument("--dry-run", action="store_true")
339
+ msp.set_defaults(func=cmd_micro.run_update)
340
+
341
+ msp = m_sub.add_parser("delete", help="remove a micro-token")
342
+ msp.add_argument("input")
343
+ msp.add_argument("--token", required=True)
344
+ msp.add_argument("--force", action="store_true")
345
+ msp.add_argument("--dry-run", action="store_true")
346
+ msp.set_defaults(func=cmd_micro.run_delete)
347
+
348
+ # ------------------------------------------------------------------
349
+ # doctor (with --strict and --kind)
350
+ # ------------------------------------------------------------------
351
+ sp = sub.add_parser("doctor", help="deep diagnostic of a .cortex file")
352
+ sp.add_argument("input")
353
+ sp.add_argument("--format", choices=["text", "json"], default="text")
354
+ sp.add_argument("--strict", action="store_true",
355
+ help="promote warnings to errors (cognitive governance)")
356
+ sp.add_argument("--kind", choices=["brain", "skill", "package", "generic"], default=None,
357
+ help="explicit document kind for level-policy checks")
358
+ sp.set_defaults(func=cmd_doctor.run)
359
+
360
+ # ------------------------------------------------------------------
361
+ # diff (with --profile)
362
+ # ------------------------------------------------------------------
363
+ sp = sub.add_parser("diff", help="structural diff between two .cortex files")
364
+ sp.add_argument("left")
365
+ sp.add_argument("right")
366
+ sp.add_argument("--format", choices=["text", "json"], default="text")
367
+ sp.add_argument(
368
+ "--profile", choices=["structural", "semantic", "governance"], default="structural",
369
+ help="structural=AST diff, semantic=values+glossary, governance=+level policy",
370
+ )
371
+ sp.set_defaults(func=cmd_diff.run)
372
+
373
+ # ------------------------------------------------------------------
374
+ # format
375
+ # ------------------------------------------------------------------
376
+ sp = sub.add_parser("format", help="re-serialize a .cortex file canonically")
377
+ sp.add_argument("input")
378
+ sp.add_argument("--out", default=None)
379
+ sp.add_argument("--dry-run", action="store_true")
380
+ sp.add_argument("--force", action="store_true")
381
+ sp.set_defaults(func=cmd_format.run)
382
+
383
+ # ------------------------------------------------------------------
384
+ # recover (new — audit gap H-06)
385
+ # ------------------------------------------------------------------
386
+ sp = sub.add_parser("recover", help="recover a legacy or non-conforming .cortex file")
387
+ sp.add_argument("input")
388
+ sp.add_argument("--out", default=None,
389
+ help="write the recovered .cortex to this path (default: stdout)")
390
+ sp.add_argument("--strict", action="store_true")
391
+ sp.add_argument(
392
+ "--embed-aud-rsk", action="store_true",
393
+ help="embed AUD and RSK entries in the recovered .cortex so the "
394
+ "artefact itself carries the recovery trace (re-audit M-RA-03)",
395
+ )
396
+ sp.add_argument("--format", choices=["text", "json"], default="text")
397
+ sp.set_defaults(func=cmd_recover.run)
398
+
399
+ # ------------------------------------------------------------------
400
+ # diagram (new — audit gap B-010)
401
+ # ------------------------------------------------------------------
402
+ sp = sub.add_parser("diagram", help="diagram operations (DIAG sigil)")
403
+ d_sub = sp.add_subparsers(dest="diagram_command", required=True)
404
+
405
+ dsp = d_sub.add_parser("list", help="list DIAG entries")
406
+ dsp.add_argument("input")
407
+ dsp.add_argument("--format", choices=["text", "json"], default="text")
408
+ dsp.set_defaults(func=cmd_diagram.run_list)
409
+
410
+ dsp = d_sub.add_parser("extract", help="extract a DIAG bloque verbatim")
411
+ dsp.add_argument("input")
412
+ dsp.add_argument("--name", required=True, help="DIAG entry name")
413
+ dsp.add_argument("--out", default=None, help="output file (default: stdout)")
414
+ dsp.add_argument(
415
+ "--print-newline", action="store_true",
416
+ help="add a trailing newline on stdout output (terminal-friendly; "
417
+ "default: write exactly the bytes)",
418
+ )
419
+ dsp.set_defaults(func=cmd_diagram.run_extract)
420
+
421
+ dsp = d_sub.add_parser("validate", help="validate DIAG bloque integrity")
422
+ dsp.add_argument("input")
423
+ dsp.add_argument("--name", default=None, help="specific DIAG name (default: all)")
424
+ dsp.add_argument("--format", choices=["text", "json"], default="text")
425
+ dsp.set_defaults(func=cmd_diagram.run_validate)
426
+
427
+ # ------------------------------------------------------------------
428
+ # roundtrip (canonical; alias: v2-roundtrip — deprecated)
429
+ # v2.0.0: CORTEX v2 byte-identical roundtrip.
430
+ # v0.3.2: rename v2-roundtrip → roundtrip (canonical).
431
+ # ------------------------------------------------------------------
432
+ sp = sub.add_parser(
433
+ "roundtrip",
434
+ help="verify CORTEX v2 roundtrip fidelity (byte-identical)",
435
+ aliases=["v2-roundtrip"],
436
+ )
437
+ sp.add_argument("input", help="CORTEX v2 file to test")
438
+ sp.add_argument("--format", choices=["text", "json"], default="text")
439
+ sp.set_defaults(func=cmd_v2_roundtrip.run)
440
+
441
+ # ------------------------------------------------------------------
442
+ # convert (canonical; alias: v2-convert — deprecated)
443
+ # v2.1.0: CORTEX ⇄ HCORTEX conversion.
444
+ # v0.3.2: rename v2-convert → convert (canonical).
445
+ # ------------------------------------------------------------------
446
+ sp = sub.add_parser(
447
+ "convert",
448
+ help="convert between CORTEX v2 and HCORTEX v2",
449
+ aliases=["v2-convert"],
450
+ )
451
+ sp.add_argument("input", help="input file")
452
+ sp.add_argument("--from", dest="from_format", choices=["cortex", "hcortex", "hcortex-r"], required=True,
453
+ help="source format")
454
+ sp.add_argument("--to", dest="to_format", choices=["cortex", "hcortex", "hcortex-r"], required=True,
455
+ help="target format")
456
+ sp.add_argument("--out", default=None, help="output file (default: stdout)")
457
+ sp.add_argument(
458
+ "--force-write-on-error", action="store_true",
459
+ help="v2.2.2: write --out even when E_VIEW_* errors occur. "
460
+ "Default: skip writing --out if any E_VIEW_* error is present "
461
+ "(prevents invalid artefacts on disk).",
462
+ )
463
+ sp.add_argument(
464
+ "--strict", action="store_true",
465
+ help="v2.2.2: promote W_VIEW_* warnings to errors (rc=1).",
466
+ )
467
+ sp.add_argument(
468
+ "--mode", choices=["normal", "strict", "audit", "recovery", "display"], default="normal",
469
+ help="v2.2.3 PRE-05: operating mode. 'display' produces Markdown without "
470
+ "reversible contract (not canonical HCORTEX).",
471
+ )
472
+ sp.set_defaults(func=cmd_v2_convert.run)
473
+
474
+ # ------------------------------------------------------------------
475
+ # roundtrip-bidir (canonical; alias: v2-roundtrip-bidir — deprecated)
476
+ # v2.3.0: CORTEX ⇄ HCORTEX bidirectional roundtrip.
477
+ # v0.3.2: rename v2-roundtrip-bidir → roundtrip-bidir (canonical).
478
+ # ------------------------------------------------------------------
479
+ sp = sub.add_parser(
480
+ "roundtrip-bidir",
481
+ help="validate CORTEX ⇄ HCORTEX bidirectional roundtrip",
482
+ aliases=["v2-roundtrip-bidir"],
483
+ )
484
+ sp.add_argument("input", help="CORTEX or HCORTEX file")
485
+ sp.add_argument("--format", choices=["text", "json"], default="text")
486
+ sp.set_defaults(func=cmd_v2_roundtrip_bidir.run)
487
+
488
+ # ------------------------------------------------------------------
489
+ # compare (canonical; alias: v2-compare — deprecated)
490
+ # v2.3.0: compare two CORTEX/HCORTEX artefacts.
491
+ # v0.3.2: rename v2-compare → compare (canonical).
492
+ # ------------------------------------------------------------------
493
+ sp = sub.add_parser(
494
+ "compare",
495
+ help="compare two CORTEX/HCORTEX artefacts (byte/AST/semantic/content)",
496
+ aliases=["v2-compare"],
497
+ )
498
+ sp.add_argument("left", help="left file")
499
+ sp.add_argument("right", help="right file")
500
+ sp.add_argument("--format", choices=["text", "json"], default="text")
501
+ sp.add_argument("--verbose", action="store_true", help="show first 20 diffs in detail")
502
+ sp.set_defaults(func=cmd_v2_compare.run)
503
+
504
+ # ------------------------------------------------------------------
505
+ # verify-view (canonical; alias: v2-verify-view — deprecated)
506
+ # v2.3.0: validate VIEW coverage, reversibility, consistency.
507
+ # v0.3.2: rename v2-verify-view → verify-view (canonical).
508
+ # ------------------------------------------------------------------
509
+ sp = sub.add_parser(
510
+ "verify-view",
511
+ help="validate VIEW coverage, reversibility, consistency",
512
+ aliases=["v2-verify-view"],
513
+ )
514
+ sp.add_argument("input", help="CORTEX file")
515
+ sp.add_argument("--format", choices=["text", "json"], default="text")
516
+ sp.add_argument("--strict", action="store_true", help="warnings also cause non-zero rc")
517
+ sp.set_defaults(func=cmd_v2_verify_view.run)
518
+
519
+ # ------------------------------------------------------------------
520
+ # explain-loss (canonical; alias: v2-explain-loss — deprecated)
521
+ # v2.3.0: explain loss, omission, or non-reversible content.
522
+ # v0.3.2: rename v2-explain-loss → explain-loss (canonical).
523
+ # ------------------------------------------------------------------
524
+ sp = sub.add_parser(
525
+ "explain-loss",
526
+ help="explain loss, omission, or non-reversible content",
527
+ aliases=["v2-explain-loss"],
528
+ )
529
+ sp.add_argument("input", help="CORTEX or HCORTEX file")
530
+ sp.add_argument("--format", choices=["text", "json"], default="text")
531
+ sp.set_defaults(func=cmd_v2_explain_loss.run)
532
+
533
+ # ------------------------------------------------------------------
534
+ # canonicalize (canonical; alias: v2-canonicalize — deprecated)
535
+ # v2.3.0: normalize artefact without changing semantics.
536
+ # v0.3.2: rename v2-canonicalize → canonicalize (canonical).
537
+ # Adds --preserve flag and VIEW-aware behavior (B-01/B-05 fix):
538
+ # - If the artefact has no VIEW directives, emit a warning and
539
+ # only normalize whitespace/section ordering (no structural
540
+ # rewrite). This preserves v1-render compatibility.
541
+ # - --preserve forces this behavior even when VIEW directives
542
+ # are present.
543
+ # ------------------------------------------------------------------
544
+ sp = sub.add_parser(
545
+ "canonicalize",
546
+ help="normalize artefact without changing semantics (VIEW-aware)",
547
+ aliases=["v2-canonicalize"],
548
+ )
549
+ sp.add_argument("input", help="CORTEX or HCORTEX file")
550
+ sp.add_argument("--out", default=None, help="output file (default: stdout)")
551
+ sp.add_argument(
552
+ "--preserve", action="store_true",
553
+ help="v0.3.2: preserve original structure even if VIEW directives "
554
+ "are present. Only normalize whitespace and section ordering. "
555
+ "Forces v1-render compatibility.",
556
+ )
557
+ sp.set_defaults(func=cmd_v2_canonicalize.run)
558
+
559
+ # ------------------------------------------------------------------
560
+ # inspect (canonical; alias: v2-inspect — deprecated)
561
+ # v2.3.0: inspect AST, sections, sigils, VIEW coverage, errors.
562
+ # v0.3.2: rename v2-inspect → inspect (canonical).
563
+ # ------------------------------------------------------------------
564
+ sp = sub.add_parser(
565
+ "inspect",
566
+ help="inspect AST, sections, sigils, VIEW coverage, errors",
567
+ aliases=["v2-inspect"],
568
+ )
569
+ sp.add_argument("input", help="CORTEX or HCORTEX file")
570
+ sp.add_argument("--format", choices=["text", "json"], default="text")
571
+ sp.set_defaults(func=cmd_v2_inspect.run)
572
+
573
+ return p
574
+
575
+
576
+ def main(argv: Optional[List[str]] = None) -> int:
577
+ parser = build_parser()
578
+ args = parser.parse_args(argv)
579
+ if not getattr(args, "func", None):
580
+ parser.print_help()
581
+ return 2
582
+
583
+ # v0.3.2 — Deprecation warning for v2-* aliases.
584
+ # The canonical names (roundtrip, convert, ...) should be used instead.
585
+ # The v2-* forms are still accepted for backward compatibility but will
586
+ # be removed in v1.0.0.
587
+ _DEPRECATED_ALIASES = {
588
+ "v2-roundtrip", "v2-convert", "v2-roundtrip-bidir", "v2-compare",
589
+ "v2-verify-view", "v2-explain-loss", "v2-canonicalize", "v2-inspect",
590
+ }
591
+ invoked_command = None
592
+ if argv:
593
+ # Skip global flags before the subcommand name
594
+ for token in argv:
595
+ if token.startswith("-"):
596
+ continue
597
+ invoked_command = token
598
+ break
599
+ elif sys.argv and len(sys.argv) > 1:
600
+ for token in sys.argv[1:]:
601
+ if token.startswith("-"):
602
+ continue
603
+ invoked_command = token
604
+ break
605
+ if invoked_command in _DEPRECATED_ALIASES:
606
+ canonical = invoked_command.removeprefix("v2-")
607
+ print(
608
+ f"WARNING: `cortex {invoked_command}` is deprecated since v0.3.2; "
609
+ f"use `cortex {canonical}` instead. The `v2-` prefix will be "
610
+ f"removed in v1.0.0.",
611
+ file=sys.stderr,
612
+ )
613
+
614
+ # Normalise --json: stash it on args so subcommands can read it (audit L-03)
615
+ json_mode = getattr(args, "json", False) or ("--json" in (argv or sys.argv))
616
+ args._json_mode = json_mode # type: ignore[attr-defined]
617
+
618
+ try:
619
+ rc = args.func(args)
620
+ return int(rc or 0)
621
+ except Exception as e:
622
+ if json_mode:
623
+ print(json.dumps({
624
+ "ok": False,
625
+ "error": {
626
+ "code": getattr(e, "code", "E000_UNKNOWN"),
627
+ "message": str(e),
628
+ },
629
+ }, indent=2, ensure_ascii=False))
630
+ else:
631
+ print(f"error: {e}", file=sys.stderr)
632
+ return 1
633
+
634
+
635
+ if __name__ == "__main__":
636
+ sys.exit(main())