pycforge 0.15.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 (221) hide show
  1. pycforge/__init__.py +8 -0
  2. pycforge/__main__.py +2 -0
  3. pycforge/_version.py +1 -0
  4. pycforge/converter/__init__.py +0 -0
  5. pycforge/converter/analysis/__init__.py +5 -0
  6. pycforge/converter/analysis/functions.py +723 -0
  7. pycforge/converter/analysis/model.py +158 -0
  8. pycforge/converter/analysis/planning.py +464 -0
  9. pycforge/converter/analysis/stage.py +1976 -0
  10. pycforge/converter/analysis/symbols.py +237 -0
  11. pycforge/converter/analysis/validation.py +2166 -0
  12. pycforge/converter/c_output/__init__.py +2 -0
  13. pycforge/converter/c_output/conformance.py +297 -0
  14. pycforge/converter/c_output/renderer.py +220 -0
  15. pycforge/converter/conditional_regions/__init__.py +71 -0
  16. pycforge/converter/conditional_regions/analysis.py +481 -0
  17. pycforge/converter/conditional_regions/lowering.py +579 -0
  18. pycforge/converter/conditional_regions/model.py +243 -0
  19. pycforge/converter/conditional_regions/validation.py +690 -0
  20. pycforge/converter/containers/__init__.py +19 -0
  21. pycforge/converter/containers/analysis.py +542 -0
  22. pycforge/converter/containers/lowering.py +290 -0
  23. pycforge/converter/containers/model.py +146 -0
  24. pycforge/converter/contracts/__init__.py +0 -0
  25. pycforge/converter/contracts/configuration.py +178 -0
  26. pycforge/converter/contracts/identifiers.py +68 -0
  27. pycforge/converter/contracts/versions.py +71 -0
  28. pycforge/converter/core/__init__.py +0 -0
  29. pycforge/converter/core/artifact_io.py +87 -0
  30. pycforge/converter/core/cancellation.py +7 -0
  31. pycforge/converter/core/canonicalization.py +254 -0
  32. pycforge/converter/core/context.py +13 -0
  33. pycforge/converter/core/diagnostics.py +95 -0
  34. pycforge/converter/core/enums.py +23 -0
  35. pycforge/converter/core/fingerprint.py +22 -0
  36. pycforge/converter/core/pipeline.py +8 -0
  37. pycforge/converter/core/progress.py +25 -0
  38. pycforge/converter/core/request.py +50 -0
  39. pycforge/converter/core/resource_policy.py +31 -0
  40. pycforge/converter/core/result.py +23 -0
  41. pycforge/converter/core/serialization.py +36 -0
  42. pycforge/converter/core/stage.py +10 -0
  43. pycforge/converter/core/stage_artifact.py +80 -0
  44. pycforge/converter/core/stage_outcome.py +15 -0
  45. pycforge/converter/decision_trace/__init__.py +0 -0
  46. pycforge/converter/decision_trace/recorder.py +52 -0
  47. pycforge/converter/facade.py +459 -0
  48. pycforge/converter/frontend/__init__.py +4 -0
  49. pycforge/converter/frontend/normalizer.py +59 -0
  50. pycforge/converter/frontend/parser.py +20 -0
  51. pycforge/converter/frontend/source_document.py +93 -0
  52. pycforge/converter/frontend/stages.py +518 -0
  53. pycforge/converter/frontend/validation.py +76 -0
  54. pycforge/converter/io/__init__.py +0 -0
  55. pycforge/converter/io/atomic_writer.py +33 -0
  56. pycforge/converter/ir/__init__.py +1 -0
  57. pycforge/converter/ir/c_ir/__init__.py +4 -0
  58. pycforge/converter/ir/c_ir/builder.py +26 -0
  59. pycforge/converter/ir/c_ir/model.py +318 -0
  60. pycforge/converter/ir/c_ir/serialization.py +88 -0
  61. pycforge/converter/ir/c_ir/validation.py +834 -0
  62. pycforge/converter/ir/python_ir/__init__.py +17 -0
  63. pycforge/converter/ir/python_ir/nodes.py +86 -0
  64. pycforge/converter/keyword_calls/__init__.py +68 -0
  65. pycforge/converter/keyword_calls/analysis.py +458 -0
  66. pycforge/converter/keyword_calls/lowering.py +271 -0
  67. pycforge/converter/keyword_calls/model.py +304 -0
  68. pycforge/converter/keyword_calls/validation.py +802 -0
  69. pycforge/converter/keyword_only_calls/__init__.py +68 -0
  70. pycforge/converter/keyword_only_calls/analysis.py +640 -0
  71. pycforge/converter/keyword_only_calls/lowering.py +376 -0
  72. pycforge/converter/keyword_only_calls/model.py +328 -0
  73. pycforge/converter/keyword_only_calls/validation.py +1357 -0
  74. pycforge/converter/lowering.py +1000 -0
  75. pycforge/converter/modules/__init__.py +13 -0
  76. pycforge/converter/modules/analysis.py +1250 -0
  77. pycforge/converter/modules/lowering.py +200 -0
  78. pycforge/converter/modules/model.py +73 -0
  79. pycforge/converter/modules/stage.py +192 -0
  80. pycforge/converter/numeric_semantics/__init__.py +23 -0
  81. pycforge/converter/numeric_semantics/analysis.py +420 -0
  82. pycforge/converter/numeric_semantics/lowering.py +153 -0
  83. pycforge/converter/numeric_semantics/model.py +123 -0
  84. pycforge/converter/records/__init__.py +34 -0
  85. pycforge/converter/records/analysis.py +1394 -0
  86. pycforge/converter/records/lowering.py +393 -0
  87. pycforge/converter/records/model.py +319 -0
  88. pycforge/converter/rules/__init__.py +3 -0
  89. pycforge/converter/support_templates/__init__.py +49 -0
  90. pycforge/converter/support_templates/assembly.py +106 -0
  91. pycforge/converter/support_templates/factories.py +348 -0
  92. pycforge/converter/support_templates/model.py +159 -0
  93. pycforge/converter/support_templates/registry.py +388 -0
  94. pycforge/converter/telemetry/__init__.py +0 -0
  95. pycforge/converter/telemetry/sink.py +39 -0
  96. pycforge/ide/__init__.py +19 -0
  97. pycforge/ide/__main__.py +5 -0
  98. pycforge/ide/_worker_event_codec.py +276 -0
  99. pycforge/ide/_worker_protocol_json.py +197 -0
  100. pycforge/ide/_worker_protocol_types.py +90 -0
  101. pycforge/ide/_worker_request_codec.py +200 -0
  102. pycforge/ide/_worker_request_data.py +260 -0
  103. pycforge/ide/_worker_result_data.py +343 -0
  104. pycforge/ide/action_contract.py +469 -0
  105. pycforge/ide/action_validation.py +229 -0
  106. pycforge/ide/command_palette.py +223 -0
  107. pycforge/ide/controller.py +904 -0
  108. pycforge/ide/controller_conversion.py +546 -0
  109. pycforge/ide/controller_io.py +205 -0
  110. pycforge/ide/editor.py +951 -0
  111. pycforge/ide/editor_commands_qt.py +274 -0
  112. pycforge/ide/editor_lexical.py +43 -0
  113. pycforge/ide/editor_sidebars.py +236 -0
  114. pycforge/ide/editor_syntax.py +243 -0
  115. pycforge/ide/find_replace.py +535 -0
  116. pycforge/ide/icons.py +91 -0
  117. pycforge/ide/io_service.py +196 -0
  118. pycforge/ide/model.py +164 -0
  119. pycforge/ide/panels.py +903 -0
  120. pycforge/ide/positions.py +220 -0
  121. pycforge/ide/process_worker.py +130 -0
  122. pycforge/ide/qt.py +230 -0
  123. pycforge/ide/qt_actions.py +418 -0
  124. pycforge/ide/qt_close.py +169 -0
  125. pycforge/ide/qt_command_palette.py +297 -0
  126. pycforge/ide/qt_contract.py +220 -0
  127. pycforge/ide/qt_documents.py +590 -0
  128. pycforge/ide/qt_editor_buffers.py +113 -0
  129. pycforge/ide/qt_editor_surfaces.py +112 -0
  130. pycforge/ide/qt_menus.py +324 -0
  131. pycforge/ide/qt_projection.py +500 -0
  132. pycforge/ide/qt_shell.py +348 -0
  133. pycforge/ide/qt_shell_interactions.py +320 -0
  134. pycforge/ide/qt_state.py +393 -0
  135. pycforge/ide/qt_workspace_features.py +398 -0
  136. pycforge/ide/qt_workspace_navigation.py +208 -0
  137. pycforge/ide/qt_workspace_observers.py +173 -0
  138. pycforge/ide/qt_workspace_panels.py +545 -0
  139. pycforge/ide/qt_workspace_widgets.py +228 -0
  140. pycforge/ide/resources/icons/about.svg +1 -0
  141. pycforge/ide/resources/icons/add-document.svg +1 -0
  142. pycforge/ide/resources/icons/brand-mark.svg +1 -0
  143. pycforge/ide/resources/icons/bundle-search.svg +1 -0
  144. pycforge/ide/resources/icons/cancel.svg +1 -0
  145. pycforge/ide/resources/icons/check.svg +1 -0
  146. pycforge/ide/resources/icons/chevron-right.svg +1 -0
  147. pycforge/ide/resources/icons/close.svg +1 -0
  148. pycforge/ide/resources/icons/collapse-all.svg +1 -0
  149. pycforge/ide/resources/icons/command-palette.svg +1 -0
  150. pycforge/ide/resources/icons/convert.svg +1 -0
  151. pycforge/ide/resources/icons/copy.svg +1 -0
  152. pycforge/ide/resources/icons/cut.svg +1 -0
  153. pycforge/ide/resources/icons/decision-trace.svg +1 -0
  154. pycforge/ide/resources/icons/details.svg +1 -0
  155. pycforge/ide/resources/icons/diagnostics.svg +1 -0
  156. pycforge/ide/resources/icons/duplicate-line.svg +1 -0
  157. pycforge/ide/resources/icons/exit.svg +1 -0
  158. pycforge/ide/resources/icons/expand-all.svg +1 -0
  159. pycforge/ide/resources/icons/export.svg +1 -0
  160. pycforge/ide/resources/icons/find.svg +1 -0
  161. pycforge/ide/resources/icons/go-to-line.svg +1 -0
  162. pycforge/ide/resources/icons/go-to-output.svg +1 -0
  163. pycforge/ide/resources/icons/go-to-source.svg +1 -0
  164. pycforge/ide/resources/icons/history.svg +1 -0
  165. pycforge/ide/resources/icons/indent.svg +1 -0
  166. pycforge/ide/resources/icons/link-c.svg +1 -0
  167. pycforge/ide/resources/icons/mappings.svg +1 -0
  168. pycforge/ide/resources/icons/module.svg +1 -0
  169. pycforge/ide/resources/icons/move-down.svg +1 -0
  170. pycforge/ide/resources/icons/move-line-down.svg +1 -0
  171. pycforge/ide/resources/icons/move-line-up.svg +1 -0
  172. pycforge/ide/resources/icons/move-up.svg +1 -0
  173. pycforge/ide/resources/icons/next.svg +1 -0
  174. pycforge/ide/resources/icons/open.svg +1 -0
  175. pycforge/ide/resources/icons/outdent.svg +1 -0
  176. pycforge/ide/resources/icons/outline.svg +1 -0
  177. pycforge/ide/resources/icons/paste.svg +1 -0
  178. pycforge/ide/resources/icons/previous.svg +1 -0
  179. pycforge/ide/resources/icons/primary-module.svg +1 -0
  180. pycforge/ide/resources/icons/redo.svg +1 -0
  181. pycforge/ide/resources/icons/remove-document.svg +1 -0
  182. pycforge/ide/resources/icons/replace.svg +1 -0
  183. pycforge/ide/resources/icons/save-as.svg +1 -0
  184. pycforge/ide/resources/icons/save.svg +1 -0
  185. pycforge/ide/resources/icons/select-all.svg +1 -0
  186. pycforge/ide/resources/icons/settings.svg +1 -0
  187. pycforge/ide/resources/icons/show-c.svg +1 -0
  188. pycforge/ide/resources/icons/split-view.svg +1 -0
  189. pycforge/ide/resources/icons/summary.svg +1 -0
  190. pycforge/ide/resources/icons/telemetry.svg +1 -0
  191. pycforge/ide/resources/icons/toggle-comment.svg +1 -0
  192. pycforge/ide/resources/icons/toggle-fold.svg +1 -0
  193. pycforge/ide/resources/icons/undo.svg +1 -0
  194. pycforge/ide/resources/icons/whitespace.svg +1 -0
  195. pycforge/ide/revisions.py +400 -0
  196. pycforge/ide/search_service.py +280 -0
  197. pycforge/ide/session_history.py +150 -0
  198. pycforge/ide/source_editing.py +417 -0
  199. pycforge/ide/source_structure.py +558 -0
  200. pycforge/ide/source_structure_async.py +191 -0
  201. pycforge/ide/supervisor.py +503 -0
  202. pycforge/ide/theme.py +119 -0
  203. pycforge/ide/theme_stylesheet.py +598 -0
  204. pycforge/ide/theme_workspace_stylesheet.py +155 -0
  205. pycforge/ide/visual_tokens.py +142 -0
  206. pycforge/ide/worker_protocol.py +80 -0
  207. pycforge/ide/workspace_search.py +462 -0
  208. pycforge/ide/workspace_session.py +256 -0
  209. pycforge/laboratory/__init__.py +6 -0
  210. pycforge/laboratory/audits.py +2244 -0
  211. pycforge/laboratory/checkpoint_e.py +2685 -0
  212. pycforge/laboratory/cli.py +119 -0
  213. pycforge/laboratory/exit_codes.py +11 -0
  214. pycforge/laboratory/keyword_audit.py +661 -0
  215. pycforge/laboratory/keyword_only_audit.py +481 -0
  216. pycforge-0.15.2.dist-info/METADATA +209 -0
  217. pycforge-0.15.2.dist-info/RECORD +221 -0
  218. pycforge-0.15.2.dist-info/WHEEL +5 -0
  219. pycforge-0.15.2.dist-info/entry_points.txt +5 -0
  220. pycforge-0.15.2.dist-info/licenses/LICENSE +674 -0
  221. pycforge-0.15.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+ import argparse, json, sys
3
+ from pathlib import Path
4
+ from typing import Any
5
+ from pycforge import ConversionRequest, PythonToCConverter, ResultStatus
6
+ from pycforge.converter.contracts.configuration import DEFAULT_RENDERER, DEFAULT_RULE_SET
7
+ from pycforge.converter.core.artifact_io import ArtifactCompatibilityError, load_artifact, save_artifact
8
+ from pycforge.converter.core.request import ObservationOptions
9
+ from pycforge.converter.core.serialization import result_to_dict, result_to_json, result_to_text
10
+ from pycforge.converter.core.stage_artifact import StageArtifact
11
+ from pycforge.converter.io.atomic_writer import AtomicWriteError, AtomicWriter
12
+ from .audits import audit_architecture, audit_conditional, audit_containers, audit_determinism, audit_helpers, audit_keyword, audit_keyword_only, audit_modules, audit_numeric, audit_records, audit_rules, audit_transition
13
+ from .exit_codes import ExitCode
14
+
15
+ ROOT=Path(__file__).resolve().parents[2]
16
+
17
+ def _developer_tree_available() -> bool:
18
+ """Return whether repository-only audit and suite resources are present."""
19
+ return (ROOT / "tests").is_dir() and (ROOT / "transition").is_dir()
20
+
21
+ def _emit(value: Any, fmt: str) -> None:
22
+ if fmt == "json": print(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False))
23
+ elif isinstance(value, str): print(value, end="" if value.endswith("\n") else "\n")
24
+ else:
25
+ for key in sorted(value): print(f"{key}: {value[key]}")
26
+
27
+ def _request_from_path(path: Path, args: argparse.Namespace) -> ConversionRequest:
28
+ text=path.read_text(encoding="utf-8")
29
+ return ConversionRequest.from_source(text, logical_name=path.name, rule_set_version=DEFAULT_RULE_SET, renderer_version=DEFAULT_RENDERER)
30
+
31
+ def _status_exit(status: ResultStatus) -> ExitCode:
32
+ return {ResultStatus.CONVERTED:ExitCode.OK,ResultStatus.CONVERTED_WITH_WARNINGS:ExitCode.OK,ResultStatus.CONVERTED_WITH_APPROXIMATIONS:ExitCode.OK,ResultStatus.REJECTED:ExitCode.REJECTED,ResultStatus.CANCELED:ExitCode.CANCELED,ResultStatus.INTERNAL_FAILURE:ExitCode.INTERNAL_FAILURE}[status]
33
+
34
+ def cmd_convert(args: argparse.Namespace) -> int:
35
+ try: request=_request_from_path(Path(args.source),args)
36
+ except (OSError,UnicodeError) as exc:
37
+ _emit({"error":{"code":"PYC3001","category":"io","message":str(exc)}},args.format); return ExitCode.IO_FAILURE
38
+ observation=ObservationOptions(args.trace_level,args.telemetry)
39
+ result=PythonToCConverter().convert(request,observation=observation)
40
+ if args.output:
41
+ if result.generated_c is not None and result.status in {ResultStatus.CONVERTED,ResultStatus.CONVERTED_WITH_WARNINGS,ResultStatus.CONVERTED_WITH_APPROXIMATIONS}:
42
+ try: AtomicWriter().write_text(Path(args.output),result.generated_c)
43
+ except AtomicWriteError as exc:
44
+ _emit({"error":{"code":"PYC3002","category":"io","message":str(exc)}},args.format); return ExitCode.IO_FAILURE
45
+ else:
46
+ # An explicit output path must not make a semantic rejection
47
+ # silent; leave any preceding file untouched and report why.
48
+ _emit(result_to_dict(result) if args.format=="json" else result_to_text(result),args.format)
49
+ else: _emit(result_to_dict(result) if args.format=="json" else result_to_text(result),args.format)
50
+ if args.save_stage or args.save_final_stage:
51
+ canonical_id=result.request_fingerprint.value if result.request_fingerprint else "rejected"
52
+ artifact=(result.stage_artifact or StageArtifact.initial(canonical_id)) if args.save_final_stage else StageArtifact.initial(canonical_id)
53
+ destination=args.save_final_stage or args.save_stage
54
+ try: save_artifact(Path(destination),artifact,AtomicWriter())
55
+ except AtomicWriteError as exc:
56
+ _emit({"error":{"code":"PYC3002","category":"io","message":str(exc)}},args.format); return ExitCode.IO_FAILURE
57
+ return _status_exit(result.status)
58
+
59
+ def cmd_inspect(args: argparse.Namespace) -> int:
60
+ try: artifact=load_artifact(Path(args.artifact),accepted={("initial","0.1"),("empty","0.1"),("source_document","0.3"),("python_ast","0.3"),("python_ir","0.3"),("python_ir","0.4"),("conversion_plan","0.5"),("conversion_plan","0.9"),("conversion_plan","0.11"),("conversion_plan","0.12"),("conversion_plan","0.13"),("conversion_plan","0.14"),("conversion_plan","0.14.1"),("conversion_plan","0.14.2"),("conversion_plan","0.14.3"),("generated_c","0.6"),("generated_c","0.8"),("generated_c","0.9"),("generated_c","0.10"),("generated_c","0.11"),("generated_c","0.12"),("generated_c","0.13"),("generated_c","0.14"),("generated_c","0.14.1"),("generated_c","0.14.2"),("generated_c","0.14.3")})
61
+ except ArtifactCompatibilityError as exc:
62
+ _emit({"error":{"code":str(exc).split()[0],"category":"artifact-incompatible","message":str(exc)}},args.format); return ExitCode.ARTIFACT_INCOMPATIBLE
63
+ value={"kind":artifact.kind,"schema_version":artifact.schema_version,"conversion_id":artifact.conversion_id,"artifact_fingerprint":artifact.artifact_fingerprint.to_dict(),"parent_fingerprint":None if artifact.parent_fingerprint is None else artifact.parent_fingerprint.to_dict(),"payload":{k:list(v) if isinstance(v,tuple) else v for k,v in artifact.payload.items()}}
64
+ _emit(value,args.format); return ExitCode.OK
65
+
66
+ def cmd_validate(args: argparse.Namespace) -> int:
67
+ if args.artifact:
68
+ return cmd_inspect(argparse.Namespace(artifact=args.artifact,format=args.format))
69
+ try: request=_request_from_path(Path(args.source),args)
70
+ except (OSError,UnicodeError) as exc:
71
+ _emit({"valid":False,"diagnostics":[{"code":"PYC3001","message":str(exc)}]},args.format); return ExitCode.IO_FAILURE
72
+ result=PythonToCConverter().convert(request)
73
+ value={"valid":result.status in {ResultStatus.CONVERTED,ResultStatus.CONVERTED_WITH_WARNINGS,ResultStatus.CONVERTED_WITH_APPROXIMATIONS},"result":result_to_dict(result,include_observers=False)}
74
+ _emit(value,args.format); return _status_exit(result.status)
75
+
76
+ def cmd_suite(args: argparse.Namespace) -> int:
77
+ import unittest
78
+ suite=unittest.defaultTestLoader.discover(str(ROOT/"tests"))
79
+ result=unittest.TextTestRunner(stream=sys.stderr,verbosity=1).run(suite)
80
+ value={"tests_run":result.testsRun,"failures":len(result.failures),"errors":len(result.errors),"passed":result.wasSuccessful()}
81
+ _emit(value,args.format); return ExitCode.OK if result.wasSuccessful() else ExitCode.AUDIT_FAILURE
82
+
83
+ def cmd_audit(args: argparse.Namespace) -> int:
84
+ fn={"architecture":lambda:audit_architecture(ROOT),"rules":lambda:audit_rules(ROOT),"helpers":lambda:audit_helpers(ROOT),"containers":lambda:audit_containers(ROOT),"modules":lambda:audit_modules(ROOT),"records":lambda:audit_records(ROOT),"numeric":lambda:audit_numeric(ROOT),"conditional":lambda:audit_conditional(ROOT),"keyword":lambda:audit_keyword(ROOT),"keyword-only":lambda:audit_keyword_only(ROOT),"determinism":lambda:audit_determinism(ROOT),"transition":lambda:audit_transition(ROOT,args.phase)}[args.audit_kind]
85
+ value=fn(); _emit(value,args.format); return ExitCode.OK if value["passed"] else ExitCode.AUDIT_FAILURE
86
+
87
+ def cmd_diff(args: argparse.Namespace) -> int:
88
+ try:
89
+ left=json.loads(Path(args.left).read_text(encoding="utf-8")); right=json.loads(Path(args.right).read_text(encoding="utf-8"))
90
+ except (OSError,UnicodeError,json.JSONDecodeError) as exc:
91
+ _emit({"error":{"code":"PYC3201","category":"io","message":str(exc)}},args.format); return ExitCode.IO_FAILURE
92
+ fields=("status","stage_order","diagnostics","output_fingerprint")
93
+ changes={field:{"left":left.get(field),"right":right.get(field)} for field in fields if left.get(field)!=right.get(field)}
94
+ value={"schema_version":"0.2","semantic_changes":changes,"telemetry_compared":False,"equal":not changes}
95
+ _emit(value,args.format); return ExitCode.OK
96
+
97
+ def build_parser() -> argparse.ArgumentParser:
98
+ parser=argparse.ArgumentParser(prog="pycforge",description="Headless PyCForge conversion laboratory")
99
+ parser.add_argument("--format",choices=("text","json"),default="text")
100
+ sub=parser.add_subparsers(dest="command",required=True)
101
+ p=sub.add_parser("convert"); p.add_argument("source"); p.add_argument("--output"); p.add_argument("--save-stage"); p.add_argument("--save-final-stage"); p.add_argument("--trace-level",choices=("None","Summary","Decisions","Full"),default="None"); p.add_argument("--telemetry",action="store_true"); p.set_defaults(func=cmd_convert)
102
+ p=sub.add_parser("inspect"); p.add_argument("artifact"); p.set_defaults(func=cmd_inspect)
103
+ p=sub.add_parser("validate"); group=p.add_mutually_exclusive_group(required=True); group.add_argument("--source"); group.add_argument("--artifact"); p.set_defaults(func=cmd_validate)
104
+ if _developer_tree_available():
105
+ p=sub.add_parser("suite"); p.set_defaults(func=cmd_suite)
106
+ p=sub.add_parser("audit"); p.add_argument("audit_kind",choices=("architecture","rules","helpers","containers","modules","records","numeric","conditional","keyword","keyword-only","determinism","transition")); p.add_argument("--phase",default="phase_2"); p.set_defaults(func=cmd_audit)
107
+ p=sub.add_parser("diff"); p.add_argument("left"); p.add_argument("right"); p.set_defaults(func=cmd_diff)
108
+ return parser
109
+
110
+ def main(argv: list[str] | None=None) -> int:
111
+ parser=build_parser()
112
+ try:
113
+ args=parser.parse_args(argv)
114
+ return int(args.func(args))
115
+ except KeyboardInterrupt:
116
+ _emit({"error":{"code":"PYC3901","category":"canceled","message":"Command interrupted"}},getattr(locals().get('args',None),'format','text'))
117
+ return int(ExitCode.CANCELED)
118
+
119
+ if __name__ == "__main__": raise SystemExit(main())
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+ from enum import IntEnum
3
+ class ExitCode(IntEnum):
4
+ OK = 0
5
+ REJECTED = 2
6
+ CANCELED = 3
7
+ INTERNAL_FAILURE = 4
8
+ USAGE = 64
9
+ ARTIFACT_INCOMPATIBLE = 65
10
+ IO_FAILURE = 74
11
+ AUDIT_FAILURE = 78