gabion 0.1.0__py3-none-any.whl → 0.1.5__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.
- gabion/__init__.py +1 -1
- gabion/analysis/dataflow_audit.py +315 -88
- gabion/analysis/visitors.py +80 -0
- gabion/cli.py +367 -144
- gabion/config.py +8 -0
- gabion/lsp_client.py +3 -2
- gabion/refactor/engine.py +214 -23
- gabion/refactor/model.py +1 -0
- gabion/schema.py +2 -0
- gabion/server.py +10 -7
- gabion/synthesis/merge.py +0 -2
- gabion/synthesis/model.py +1 -0
- {gabion-0.1.0.dist-info → gabion-0.1.5.dist-info}/METADATA +21 -4
- gabion-0.1.5.dist-info/RECORD +26 -0
- gabion-0.1.0.dist-info/RECORD +0 -26
- {gabion-0.1.0.dist-info → gabion-0.1.5.dist-info}/WHEEL +0 -0
- {gabion-0.1.0.dist-info → gabion-0.1.5.dist-info}/entry_points.txt +0 -0
- {gabion-0.1.0.dist-info → gabion-0.1.5.dist-info}/licenses/LICENSE +0 -0
gabion/refactor/engine.py
CHANGED
|
@@ -115,10 +115,14 @@ class RefactorEngine:
|
|
|
115
115
|
bundle_fields = [spec.name for spec in field_specs]
|
|
116
116
|
protocol_hint = protocol
|
|
117
117
|
if targets:
|
|
118
|
+
compat_shim = bool(request.compatibility_shim)
|
|
119
|
+
if compat_shim:
|
|
120
|
+
new_module = _ensure_compat_imports(new_module)
|
|
118
121
|
transformer = _RefactorTransformer(
|
|
119
122
|
targets=targets,
|
|
120
123
|
bundle_fields=bundle_fields,
|
|
121
124
|
protocol_hint=protocol_hint,
|
|
125
|
+
compat_shim=compat_shim,
|
|
122
126
|
)
|
|
123
127
|
new_module = new_module.visit(transformer)
|
|
124
128
|
warnings.extend(transformer.warnings)
|
|
@@ -141,9 +145,9 @@ class RefactorEngine:
|
|
|
141
145
|
new_source = new_module.code
|
|
142
146
|
else:
|
|
143
147
|
new_source = new_module.code
|
|
144
|
-
if new_source == source:
|
|
145
|
-
warnings.append("No changes generated for protocol extraction.")
|
|
146
|
-
return RefactorPlan(warnings=warnings)
|
|
148
|
+
if new_source == source: # pragma: no cover
|
|
149
|
+
warnings.append("No changes generated for protocol extraction.") # pragma: no cover
|
|
150
|
+
return RefactorPlan(warnings=warnings) # pragma: no cover
|
|
147
151
|
end_line = len(source.splitlines())
|
|
148
152
|
edits = [
|
|
149
153
|
TextEdit(
|
|
@@ -219,7 +223,7 @@ def _module_expr_to_str(expr: cst.BaseExpression | None) -> str | None:
|
|
|
219
223
|
parts.append(current.value)
|
|
220
224
|
if parts:
|
|
221
225
|
return ".".join(reversed(parts))
|
|
222
|
-
return None
|
|
226
|
+
return None # pragma: no cover
|
|
223
227
|
|
|
224
228
|
|
|
225
229
|
def _has_typing_import(body: list[cst.CSTNode]) -> bool:
|
|
@@ -236,7 +240,7 @@ def _has_typing_import(body: list[cst.CSTNode]) -> bool:
|
|
|
236
240
|
alias.name, cst.Attribute
|
|
237
241
|
):
|
|
238
242
|
if _module_expr_to_str(alias.name) == "typing":
|
|
239
|
-
return True
|
|
243
|
+
return True # pragma: no cover
|
|
240
244
|
return False
|
|
241
245
|
|
|
242
246
|
|
|
@@ -257,6 +261,63 @@ def _has_typing_protocol_import(body: list[cst.CSTNode]) -> bool:
|
|
|
257
261
|
return False
|
|
258
262
|
|
|
259
263
|
|
|
264
|
+
def _has_typing_overload_import(body: list[cst.CSTNode]) -> bool:
|
|
265
|
+
for stmt in body:
|
|
266
|
+
if not isinstance(stmt, cst.SimpleStatementLine):
|
|
267
|
+
continue
|
|
268
|
+
for item in stmt.body:
|
|
269
|
+
if not isinstance(item, cst.ImportFrom):
|
|
270
|
+
continue
|
|
271
|
+
module = _module_expr_to_str(item.module)
|
|
272
|
+
if module != "typing":
|
|
273
|
+
continue
|
|
274
|
+
for alias in item.names:
|
|
275
|
+
if isinstance(alias, cst.ImportAlias) and isinstance(alias.name, cst.Name):
|
|
276
|
+
if alias.name.value == "overload":
|
|
277
|
+
return True
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _has_warnings_import(body: list[cst.CSTNode]) -> bool:
|
|
282
|
+
for stmt in body:
|
|
283
|
+
if not isinstance(stmt, cst.SimpleStatementLine):
|
|
284
|
+
continue
|
|
285
|
+
for item in stmt.body:
|
|
286
|
+
if isinstance(item, cst.Import):
|
|
287
|
+
for alias in item.names:
|
|
288
|
+
if isinstance(alias, cst.ImportAlias) and isinstance(alias.name, cst.Name):
|
|
289
|
+
if alias.name.value == "warnings":
|
|
290
|
+
return True
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _ensure_compat_imports(module: cst.Module) -> cst.Module:
|
|
295
|
+
body = list(module.body)
|
|
296
|
+
insert_idx = _find_import_insert_index(body)
|
|
297
|
+
if not _has_warnings_import(body):
|
|
298
|
+
body.insert(
|
|
299
|
+
insert_idx,
|
|
300
|
+
cst.SimpleStatementLine(
|
|
301
|
+
[cst.Import(names=[cst.ImportAlias(name=cst.Name("warnings"))])]
|
|
302
|
+
),
|
|
303
|
+
)
|
|
304
|
+
insert_idx += 1
|
|
305
|
+
if not _has_typing_overload_import(body):
|
|
306
|
+
body.insert(
|
|
307
|
+
insert_idx,
|
|
308
|
+
cst.SimpleStatementLine(
|
|
309
|
+
[
|
|
310
|
+
cst.ImportFrom(
|
|
311
|
+
module=cst.Name("typing"),
|
|
312
|
+
names=[cst.ImportAlias(name=cst.Name("overload"))],
|
|
313
|
+
)
|
|
314
|
+
]
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
insert_idx += 1
|
|
318
|
+
return module.with_changes(body=body)
|
|
319
|
+
|
|
320
|
+
|
|
260
321
|
def _collect_import_context(
|
|
261
322
|
module: cst.Module,
|
|
262
323
|
*,
|
|
@@ -272,10 +333,10 @@ def _collect_import_context(
|
|
|
272
333
|
for item in stmt.body:
|
|
273
334
|
if isinstance(item, cst.Import):
|
|
274
335
|
for alias in item.names:
|
|
275
|
-
if not isinstance(alias, cst.ImportAlias):
|
|
336
|
+
if not isinstance(alias, cst.ImportAlias): # pragma: no cover
|
|
276
337
|
continue
|
|
277
338
|
module_name = _module_expr_to_str(alias.name)
|
|
278
|
-
if not module_name:
|
|
339
|
+
if not module_name: # pragma: no cover
|
|
279
340
|
continue
|
|
280
341
|
if module_name != target_module:
|
|
281
342
|
continue
|
|
@@ -363,10 +424,10 @@ def _rewrite_call_sites(
|
|
|
363
424
|
insert_idx = _find_import_insert_index(body)
|
|
364
425
|
try:
|
|
365
426
|
module_expr = cst.parse_expression(target_module)
|
|
366
|
-
except Exception:
|
|
367
|
-
module_expr = cst.Name(target_module.split(".")[0])
|
|
427
|
+
except Exception: # pragma: no cover
|
|
428
|
+
module_expr = cst.Name(target_module.split(".")[0]) # pragma: no cover
|
|
368
429
|
if not isinstance(module_expr, (cst.Name, cst.Attribute)):
|
|
369
|
-
module_expr = cst.Name(target_module.split(".")[0])
|
|
430
|
+
module_expr = cst.Name(target_module.split(".")[0]) # pragma: no cover
|
|
370
431
|
import_stmt = cst.SimpleStatementLine(
|
|
371
432
|
[
|
|
372
433
|
cst.ImportFrom(
|
|
@@ -420,7 +481,7 @@ def _rewrite_call_sites_in_project(
|
|
|
420
481
|
if updated_module is None:
|
|
421
482
|
continue
|
|
422
483
|
new_source = updated_module.code
|
|
423
|
-
if new_source == source:
|
|
484
|
+
if new_source == source: # pragma: no cover
|
|
424
485
|
continue
|
|
425
486
|
end_line = len(source.splitlines())
|
|
426
487
|
edits.append(
|
|
@@ -441,11 +502,13 @@ class _RefactorTransformer(cst.CSTTransformer):
|
|
|
441
502
|
targets: set[str],
|
|
442
503
|
bundle_fields: list[str],
|
|
443
504
|
protocol_hint: str,
|
|
505
|
+
compat_shim: bool = False,
|
|
444
506
|
) -> None:
|
|
445
|
-
# dataflow-bundle: bundle_fields, protocol_hint, targets
|
|
507
|
+
# dataflow-bundle: bundle_fields, compat_shim, protocol_hint, targets
|
|
446
508
|
self.targets = targets
|
|
447
509
|
self.bundle_fields = bundle_fields
|
|
448
510
|
self.protocol_hint = protocol_hint
|
|
511
|
+
self.compat_shim = compat_shim
|
|
449
512
|
self.warnings: list[str] = []
|
|
450
513
|
self._stack: list[str] = []
|
|
451
514
|
|
|
@@ -472,21 +535,21 @@ class _RefactorTransformer(cst.CSTTransformer):
|
|
|
472
535
|
self._stack.pop()
|
|
473
536
|
return updated
|
|
474
537
|
|
|
475
|
-
def visit_AsyncFunctionDef(self, node: cst.AsyncFunctionDef) -> bool:
|
|
476
|
-
self._stack.append(node.name.value)
|
|
477
|
-
return True
|
|
538
|
+
def visit_AsyncFunctionDef(self, node: cst.AsyncFunctionDef) -> bool: # pragma: no cover
|
|
539
|
+
self._stack.append(node.name.value) # pragma: no cover
|
|
540
|
+
return True # pragma: no cover
|
|
478
541
|
|
|
479
|
-
def leave_AsyncFunctionDef(
|
|
542
|
+
def leave_AsyncFunctionDef( # pragma: no cover
|
|
480
543
|
self, original_node: cst.AsyncFunctionDef, updated_node: cst.AsyncFunctionDef
|
|
481
544
|
) -> cst.CSTNode:
|
|
482
|
-
updated = self._maybe_rewrite_function(original_node, updated_node)
|
|
483
|
-
if self._stack:
|
|
484
|
-
self._stack.pop()
|
|
485
|
-
return updated
|
|
545
|
+
updated = self._maybe_rewrite_function(original_node, updated_node) # pragma: no cover
|
|
546
|
+
if self._stack: # pragma: no cover
|
|
547
|
+
self._stack.pop() # pragma: no cover
|
|
548
|
+
return updated # pragma: no cover
|
|
486
549
|
|
|
487
550
|
def _maybe_rewrite_function(
|
|
488
551
|
self,
|
|
489
|
-
original_node: cst.FunctionDef
|
|
552
|
+
original_node: cst.FunctionDef,
|
|
490
553
|
updated_node: cst.FunctionDef | cst.AsyncFunctionDef,
|
|
491
554
|
) -> cst.CSTNode:
|
|
492
555
|
qualname = ".".join(self._stack)
|
|
@@ -515,7 +578,135 @@ class _RefactorTransformer(cst.CSTTransformer):
|
|
|
515
578
|
new_body = self._inject_preamble(
|
|
516
579
|
updated_node.body, bundle_param_name, target_fields
|
|
517
580
|
)
|
|
518
|
-
|
|
581
|
+
updated_impl = updated_node.with_changes(params=new_params, body=new_body)
|
|
582
|
+
if not self.compat_shim:
|
|
583
|
+
return updated_impl
|
|
584
|
+
impl_name = f"_{name}_bundle"
|
|
585
|
+
impl_node = updated_impl.with_changes(
|
|
586
|
+
name=cst.Name(impl_name),
|
|
587
|
+
decorators=[],
|
|
588
|
+
)
|
|
589
|
+
shim_nodes = self._build_compat_shim(
|
|
590
|
+
original_node=original_node,
|
|
591
|
+
impl_name=impl_name,
|
|
592
|
+
bundle_param=bundle_param_name,
|
|
593
|
+
self_param=self_param,
|
|
594
|
+
)
|
|
595
|
+
return cst.FlattenSentinel([*shim_nodes, impl_node])
|
|
596
|
+
|
|
597
|
+
def _build_compat_shim(
|
|
598
|
+
self,
|
|
599
|
+
*,
|
|
600
|
+
original_node: cst.FunctionDef | cst.AsyncFunctionDef,
|
|
601
|
+
impl_name: str,
|
|
602
|
+
bundle_param: str,
|
|
603
|
+
self_param: cst.Param | None,
|
|
604
|
+
) -> list[cst.CSTNode]:
|
|
605
|
+
decorators = list(original_node.decorators)
|
|
606
|
+
overload_decorators = [cst.Decorator(cst.Name("overload")), *decorators]
|
|
607
|
+
bundle_params = self._build_parameters(self_param, bundle_param)
|
|
608
|
+
legacy_params = original_node.params
|
|
609
|
+
bundle_stub = self._build_overload_stub(
|
|
610
|
+
original_node=original_node,
|
|
611
|
+
params=bundle_params,
|
|
612
|
+
decorators=overload_decorators,
|
|
613
|
+
)
|
|
614
|
+
legacy_stub = self._build_overload_stub(
|
|
615
|
+
original_node=original_node,
|
|
616
|
+
params=legacy_params,
|
|
617
|
+
decorators=overload_decorators,
|
|
618
|
+
)
|
|
619
|
+
wrapper_params = self._build_shim_parameters(self_param)
|
|
620
|
+
wrapper_body = self._build_shim_body(
|
|
621
|
+
impl_name=impl_name,
|
|
622
|
+
bundle_type=self.protocol_hint,
|
|
623
|
+
self_param=self_param,
|
|
624
|
+
is_async=bool(original_node.asynchronous),
|
|
625
|
+
public_name=original_node.name.value,
|
|
626
|
+
)
|
|
627
|
+
wrapper = self._build_wrapper(
|
|
628
|
+
original_node=original_node,
|
|
629
|
+
params=wrapper_params,
|
|
630
|
+
body=wrapper_body,
|
|
631
|
+
decorators=decorators,
|
|
632
|
+
)
|
|
633
|
+
return [bundle_stub, legacy_stub, wrapper]
|
|
634
|
+
|
|
635
|
+
def _build_overload_stub(
|
|
636
|
+
self,
|
|
637
|
+
*,
|
|
638
|
+
original_node: cst.FunctionDef,
|
|
639
|
+
params: cst.Parameters,
|
|
640
|
+
decorators: list[cst.Decorator],
|
|
641
|
+
) -> cst.CSTNode:
|
|
642
|
+
body = cst.IndentedBlock(
|
|
643
|
+
body=[cst.SimpleStatementLine([cst.Expr(cst.Ellipsis())])]
|
|
644
|
+
)
|
|
645
|
+
return original_node.with_changes(
|
|
646
|
+
params=params,
|
|
647
|
+
body=body,
|
|
648
|
+
decorators=decorators,
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
def _build_wrapper(
|
|
652
|
+
self,
|
|
653
|
+
*,
|
|
654
|
+
original_node: cst.FunctionDef,
|
|
655
|
+
params: cst.Parameters,
|
|
656
|
+
body: cst.BaseSuite,
|
|
657
|
+
decorators: list[cst.Decorator],
|
|
658
|
+
) -> cst.CSTNode:
|
|
659
|
+
return original_node.with_changes(
|
|
660
|
+
params=params,
|
|
661
|
+
body=body,
|
|
662
|
+
decorators=decorators,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
def _build_shim_parameters(self, self_param: cst.Param | None) -> cst.Parameters:
|
|
666
|
+
params: list[cst.Param] = []
|
|
667
|
+
if self_param is not None:
|
|
668
|
+
params.append(self_param)
|
|
669
|
+
return cst.Parameters(
|
|
670
|
+
params=params,
|
|
671
|
+
star_arg=cst.Param(name=cst.Name("args")),
|
|
672
|
+
kwonly_params=[],
|
|
673
|
+
star_kwarg=cst.Param(name=cst.Name("kwargs")),
|
|
674
|
+
posonly_params=[],
|
|
675
|
+
posonly_ind=cst.MaybeSentinel.DEFAULT,
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
def _build_shim_body(
|
|
679
|
+
self,
|
|
680
|
+
*,
|
|
681
|
+
impl_name: str,
|
|
682
|
+
bundle_type: str,
|
|
683
|
+
self_param: cst.Param | None,
|
|
684
|
+
is_async: bool,
|
|
685
|
+
public_name: str,
|
|
686
|
+
) -> cst.BaseSuite:
|
|
687
|
+
receiver = self_param.name.value if self_param is not None else None
|
|
688
|
+
impl_call = f"{receiver}.{impl_name}" if receiver else impl_name
|
|
689
|
+
return_prefix = "return await" if is_async else "return"
|
|
690
|
+
guard = (
|
|
691
|
+
f"if args and isinstance(args[0], {bundle_type}):\n"
|
|
692
|
+
f" if len(args) != 1 or kwargs:\n"
|
|
693
|
+
f" raise TypeError(\"{public_name}() bundle call expects a single {bundle_type} argument\")\n"
|
|
694
|
+
f" {return_prefix} {impl_call}(args[0])\n"
|
|
695
|
+
)
|
|
696
|
+
warn = (
|
|
697
|
+
f"warnings.warn(\"{public_name}() is deprecated; use {public_name}({bundle_type}(...))\", "
|
|
698
|
+
"DeprecationWarning, stacklevel=2)"
|
|
699
|
+
)
|
|
700
|
+
build = f"bundle = {bundle_type}(*args, **kwargs)"
|
|
701
|
+
tail = f"{return_prefix} {impl_call}(bundle)"
|
|
702
|
+
return cst.IndentedBlock(
|
|
703
|
+
body=[
|
|
704
|
+
cst.parse_statement(guard),
|
|
705
|
+
cst.parse_statement(warn),
|
|
706
|
+
cst.parse_statement(build),
|
|
707
|
+
cst.parse_statement(tail),
|
|
708
|
+
]
|
|
709
|
+
)
|
|
519
710
|
|
|
520
711
|
def _ordered_param_names(self, params: cst.Parameters) -> list[str]:
|
|
521
712
|
names: list[str] = []
|
|
@@ -664,7 +855,7 @@ class _CallSiteTransformer(cst.CSTTransformer):
|
|
|
664
855
|
return True
|
|
665
856
|
return False
|
|
666
857
|
if isinstance(func, cst.Attribute):
|
|
667
|
-
if not isinstance(func.attr, cst.Name):
|
|
858
|
+
if not isinstance(func.attr, cst.Name): # pragma: no cover
|
|
668
859
|
return False
|
|
669
860
|
attr = func.attr.value
|
|
670
861
|
if self.file_is_target and self._class_stack:
|
gabion/refactor/model.py
CHANGED
gabion/schema.py
CHANGED
|
@@ -35,6 +35,7 @@ class SynthesisRequest(BaseModel):
|
|
|
35
35
|
max_tier: int = 2
|
|
36
36
|
min_bundle_size: int = 2
|
|
37
37
|
allow_singletons: bool = False
|
|
38
|
+
merge_overlap_threshold: float = 0.75
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class SynthesisFieldDTO(BaseModel):
|
|
@@ -68,6 +69,7 @@ class RefactorRequest(BaseModel):
|
|
|
68
69
|
fields: List[RefactorFieldDTO] = []
|
|
69
70
|
target_path: str
|
|
70
71
|
target_functions: List[str] = []
|
|
72
|
+
compatibility_shim: bool = False
|
|
71
73
|
rationale: Optional[str] = None
|
|
72
74
|
|
|
73
75
|
|
gabion/server.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Callable
|
|
5
6
|
from urllib.parse import unquote, urlparse
|
|
6
7
|
|
|
7
8
|
from pygls.lsp.server import LanguageServer
|
|
@@ -99,9 +100,9 @@ def _diagnostics_for_path(path_str: str, project_root: Path | None) -> list[Diag
|
|
|
99
100
|
message = f"Implicit bundle detected: {', '.join(sorted(bundle))}"
|
|
100
101
|
for name in sorted(bundle):
|
|
101
102
|
span = param_spans.get(name)
|
|
102
|
-
if span is None:
|
|
103
|
-
start = Position(line=0, character=0)
|
|
104
|
-
end = Position(line=0, character=1)
|
|
103
|
+
if span is None: # pragma: no cover - spans are derived from parsed params
|
|
104
|
+
start = Position(line=0, character=0) # pragma: no cover
|
|
105
|
+
end = Position(line=0, character=1) # pragma: no cover
|
|
105
106
|
else:
|
|
106
107
|
start_line, start_col, end_line, end_col = span
|
|
107
108
|
start = Position(line=start_line, character=start_col)
|
|
@@ -321,6 +322,7 @@ def execute_synthesis(ls: LanguageServer, payload: dict | None = None) -> dict:
|
|
|
321
322
|
max_tier=request.max_tier,
|
|
322
323
|
min_bundle_size=request.min_bundle_size,
|
|
323
324
|
allow_singletons=request.allow_singletons,
|
|
325
|
+
merge_overlap_threshold=request.merge_overlap_threshold,
|
|
324
326
|
)
|
|
325
327
|
naming_context = NamingContext(
|
|
326
328
|
existing_names=set(request.existing_names),
|
|
@@ -379,6 +381,7 @@ def execute_refactor(ls: LanguageServer, payload: dict | None = None) -> dict:
|
|
|
379
381
|
],
|
|
380
382
|
target_path=request.target_path,
|
|
381
383
|
target_functions=request.target_functions,
|
|
384
|
+
compatibility_shim=request.compatibility_shim,
|
|
382
385
|
rationale=request.rationale,
|
|
383
386
|
)
|
|
384
387
|
)
|
|
@@ -438,10 +441,10 @@ def did_save(ls: LanguageServer, params) -> None:
|
|
|
438
441
|
ls.publish_diagnostics(uri, diagnostics)
|
|
439
442
|
|
|
440
443
|
|
|
441
|
-
def start() -> None:
|
|
444
|
+
def start(start_fn: Callable[[], None] | None = None) -> None:
|
|
442
445
|
"""Start the language server (stub)."""
|
|
443
|
-
server.start_io()
|
|
446
|
+
(start_fn or server.start_io)()
|
|
444
447
|
|
|
445
448
|
|
|
446
|
-
if __name__ == "__main__":
|
|
447
|
-
start()
|
|
449
|
+
if __name__ == "__main__": # pragma: no cover
|
|
450
|
+
start() # pragma: no cover
|
gabion/synthesis/merge.py
CHANGED
gabion/synthesis/model.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gabion
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Architectural linter that stabilizes loose parameters into structural bundles.
|
|
5
5
|
Project-URL: Repository, https://github.com/mikemol/gabion
|
|
6
6
|
Project-URL: Issues, https://github.com/mikemol/gabion/issues
|
|
@@ -23,10 +23,15 @@ Requires-Dist: pydantic>=2.5
|
|
|
23
23
|
Requires-Dist: pygls>=1.3
|
|
24
24
|
Requires-Dist: rich>=13.0
|
|
25
25
|
Requires-Dist: typer>=0.9
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-xdist>=3.6; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
26
31
|
Description-Content-Type: text/markdown
|
|
27
32
|
|
|
28
33
|
---
|
|
29
|
-
doc_revision:
|
|
34
|
+
doc_revision: 58
|
|
30
35
|
reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc."
|
|
31
36
|
doc_id: readme
|
|
32
37
|
doc_role: readme
|
|
@@ -40,6 +45,11 @@ doc_requires:
|
|
|
40
45
|
- glossary.md
|
|
41
46
|
- AGENTS.md
|
|
42
47
|
- CONTRIBUTING.md
|
|
48
|
+
doc_reviewed_as_of:
|
|
49
|
+
POLICY_SEED.md: 28
|
|
50
|
+
glossary.md: 13
|
|
51
|
+
AGENTS.md: 12
|
|
52
|
+
CONTRIBUTING.md: 69
|
|
43
53
|
doc_change_protocol: "POLICY_SEED.md §6"
|
|
44
54
|
doc_erasure:
|
|
45
55
|
- formatting
|
|
@@ -78,10 +88,15 @@ breaking changes; patch releases target fixes. Breaking changes will be called
|
|
|
78
88
|
out in release notes.
|
|
79
89
|
|
|
80
90
|
## Branching model
|
|
81
|
-
- `stage` is the integration branch for routine pushes; CI runs on
|
|
91
|
+
- `stage` is the integration branch for routine pushes; CI runs on `stage` pushes.
|
|
82
92
|
- `main` is protected and receives changes via PRs from `stage`.
|
|
83
|
-
-
|
|
93
|
+
- Merges to `main` are regular merge commits (no squash or rebase).
|
|
84
94
|
- `stage` accumulates changes and may include merge commits from `main` as it stays in sync.
|
|
95
|
+
- `next` mirrors `main` (no unique commits) and is updated after `main` merges.
|
|
96
|
+
- `release` mirrors `next` (no unique commits) and is updated only after `test-v*` succeeds.
|
|
97
|
+
- Tags are cut via the `release-tag` workflow on `next` (test) and `release` (prod).
|
|
98
|
+
- `next` and `release` are automation-only branches; `mirror-next` and
|
|
99
|
+
`promote-release` keep them in sync.
|
|
85
100
|
|
|
86
101
|
## Convergence checklist
|
|
87
102
|
Bottom-up convergence targets live in `docs/sppf_checklist.md`.
|
|
@@ -104,6 +119,8 @@ Install from source (editable):
|
|
|
104
119
|
```
|
|
105
120
|
mise exec -- python -m pip install -e .
|
|
106
121
|
```
|
|
122
|
+
Dependencies are locked in `requirements.lock` (generated via `uv`).
|
|
123
|
+
CI installs from the lockfile inside an explicit venv to prevent drift.
|
|
107
124
|
|
|
108
125
|
Install git hooks (optional):
|
|
109
126
|
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
gabion/__init__.py,sha256=pu9EfVdXzRQaVDWklyukrpQAEMYsCHJi4tMIdma2t7g,77
|
|
2
|
+
gabion/__main__.py,sha256=pZ3SvIhFpELK_ry4qcOI8tt_81PydhzJMS-AQUpfO4Q,129
|
|
3
|
+
gabion/cli.py,sha256=wH_cX_muUDgT7YsDvAfZMLEf1sgsX2gqGmEJH7tfO0o,26299
|
|
4
|
+
gabion/config.py,sha256=jwFABPWYF5kWvm6qeGRCMe1XQyR431VLq9o1Y0N3r_c,1499
|
|
5
|
+
gabion/lsp_client.py,sha256=I_BinuLKpUMLxUN6gY4KpJxh1iVHuCzuO6sIBgA-1L0,3359
|
|
6
|
+
gabion/schema.py,sha256=Yj5EWYczqGOrrkm62GIZ55MU13BkFFOmNPFq4L_XkEo,1804
|
|
7
|
+
gabion/server.py,sha256=16rrNj-ih1MkhRzHjXE7ffErN5EgGtIkuewDyOS7MIs,16430
|
|
8
|
+
gabion/analysis/__init__.py,sha256=jq1C9ie-II8eWZtPH-yBijEApWOCFsszSXwhd1Msk0Q,793
|
|
9
|
+
gabion/analysis/dataflow_audit.py,sha256=BUBINohpkaer6drRG4cL1XllUbsxJsOV41Iux6tYLuE,120737
|
|
10
|
+
gabion/analysis/engine.py,sha256=Ec0DL9eCc_4kedI9eiEZM9qDK79AWzbIio6iD9Yf47o,198
|
|
11
|
+
gabion/analysis/model.py,sha256=d4f3Vb_3a-trbP5L0yHkmniK8mh43lVFgv40QsXYeZo,846
|
|
12
|
+
gabion/analysis/visitors.py,sha256=7gi0c99bGgRC-cvWghze3C9Ra1oqL9JOlrpdXtearRU,19700
|
|
13
|
+
gabion/refactor/__init__.py,sha256=J1TlmgUgl86tSZnh7hZOL9M6pA2cf0cLTsyuOuA1qCc,225
|
|
14
|
+
gabion/refactor/engine.py,sha256=MBOrHz-Efs0WFOIZ5TX5SSUzpwo1S-KsPGMQNlCoh9Q,33967
|
|
15
|
+
gabion/refactor/model.py,sha256=GbH8Qdym3xcUKnrRPfUtW2NLRdGRzuSoyQf1sKT1OM4,857
|
|
16
|
+
gabion/synthesis/__init__.py,sha256=G76-Jp_OX9_UEJmuYi14XrJHlyMtU2k-gH77RMD8PLg,622
|
|
17
|
+
gabion/synthesis/merge.py,sha256=fckP1OenvxCeCVoMn44PXUKsTODt_VJQlOkjXTG4xNU,1156
|
|
18
|
+
gabion/synthesis/model.py,sha256=PFRscauBOAv2F-RgPyyUzzhwejpPbK-2SWoJ9vgxePc,1010
|
|
19
|
+
gabion/synthesis/naming.py,sha256=I7FXFkxoTXEog63CA8FjPNkcp0DzPn-WzncLLlABH4E,1262
|
|
20
|
+
gabion/synthesis/protocols.py,sha256=OdxbVUsj_DmKSNlZeaFgRIu46_on2qV4ge3K8bquXGY,2470
|
|
21
|
+
gabion/synthesis/schedule.py,sha256=_1SBktOR5X5hl1z91tVXht8ZPIrY8wXP3SaxWh3K9xI,2835
|
|
22
|
+
gabion-0.1.5.dist-info/METADATA,sha256=w_1-EbX9ZmRJ_JhPt1oJmCR2_EZxXVGr4OCdHrHwq4I,9275
|
|
23
|
+
gabion-0.1.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
+
gabion-0.1.5.dist-info/entry_points.txt,sha256=Deu--5uPUKZKcsVfXvAj8h_0T7K52PLieJttYCrlRG8,74
|
|
25
|
+
gabion-0.1.5.dist-info/licenses/LICENSE,sha256=ZlxQIqhO3NX4-cv0ineYuyIeQJRuu43qq2p_J2OWvX4,10735
|
|
26
|
+
gabion-0.1.5.dist-info/RECORD,,
|
gabion-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
gabion/__init__.py,sha256=IuUZho6f7uzs3D8wgAQM1OpCgmJ949PXd24oq-GEufE,77
|
|
2
|
-
gabion/__main__.py,sha256=pZ3SvIhFpELK_ry4qcOI8tt_81PydhzJMS-AQUpfO4Q,129
|
|
3
|
-
gabion/cli.py,sha256=ncGtAWwWYY2GZEkxvxVKtOcgAjCFHldzwIQBe5OPAak,20002
|
|
4
|
-
gabion/config.py,sha256=4kg0x03fP2-A8LuXe3QikED6jZbDFfzr9IQaMge7Ua4,1235
|
|
5
|
-
gabion/lsp_client.py,sha256=XA8uuxolRPhcbMHpjUVgrnFw3PMuMGzm5bBCakqDvPk,3277
|
|
6
|
-
gabion/schema.py,sha256=QG1tYTFDqrafvWwrRjyfD44ufoh45vvynJwoMxiMT1w,1725
|
|
7
|
-
gabion/server.py,sha256=n4bVYYwW-cjydfFJCpbgFUqJVMX-nyPj3OKJvbH5LaU,16083
|
|
8
|
-
gabion/analysis/__init__.py,sha256=jq1C9ie-II8eWZtPH-yBijEApWOCFsszSXwhd1Msk0Q,793
|
|
9
|
-
gabion/analysis/dataflow_audit.py,sha256=Nqdacgk6X2x3rdUlk2fKhn-J2bkk1mXHc2ga8X7Slio,111937
|
|
10
|
-
gabion/analysis/engine.py,sha256=Ec0DL9eCc_4kedI9eiEZM9qDK79AWzbIio6iD9Yf47o,198
|
|
11
|
-
gabion/analysis/model.py,sha256=d4f3Vb_3a-trbP5L0yHkmniK8mh43lVFgv40QsXYeZo,846
|
|
12
|
-
gabion/analysis/visitors.py,sha256=8gjLMhP7I_aWgGhCqX7xb9KyVysgfeRTA-hCrPqainE,16460
|
|
13
|
-
gabion/refactor/__init__.py,sha256=J1TlmgUgl86tSZnh7hZOL9M6pA2cf0cLTsyuOuA1qCc,225
|
|
14
|
-
gabion/refactor/engine.py,sha256=1DyoZ8Riln2cA730OZDa4Jm4zT_y76m7merqFuJ60MQ,26860
|
|
15
|
-
gabion/refactor/model.py,sha256=GVdVNUXT9kFpoTvgZ17X4vzSRe8OnqQf6Rji-mWiRL4,820
|
|
16
|
-
gabion/synthesis/__init__.py,sha256=G76-Jp_OX9_UEJmuYi14XrJHlyMtU2k-gH77RMD8PLg,622
|
|
17
|
-
gabion/synthesis/merge.py,sha256=2758qL2xGmB9lXC_jvqrxK6bTD8Vmrt1YTH_5_5jKg0,1193
|
|
18
|
-
gabion/synthesis/model.py,sha256=cBHltcdLmCRneE2fgi3hA36A6UzU9E0YuTmKiCOk17I,968
|
|
19
|
-
gabion/synthesis/naming.py,sha256=I7FXFkxoTXEog63CA8FjPNkcp0DzPn-WzncLLlABH4E,1262
|
|
20
|
-
gabion/synthesis/protocols.py,sha256=OdxbVUsj_DmKSNlZeaFgRIu46_on2qV4ge3K8bquXGY,2470
|
|
21
|
-
gabion/synthesis/schedule.py,sha256=_1SBktOR5X5hl1z91tVXht8ZPIrY8wXP3SaxWh3K9xI,2835
|
|
22
|
-
gabion-0.1.0.dist-info/METADATA,sha256=_eQVSisUWWYIhF4ofE3PnwUfUO-OhlTkB4Y0LlF2n0Q,8479
|
|
23
|
-
gabion-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
-
gabion-0.1.0.dist-info/entry_points.txt,sha256=Deu--5uPUKZKcsVfXvAj8h_0T7K52PLieJttYCrlRG8,74
|
|
25
|
-
gabion-0.1.0.dist-info/licenses/LICENSE,sha256=ZlxQIqhO3NX4-cv0ineYuyIeQJRuu43qq2p_J2OWvX4,10735
|
|
26
|
-
gabion-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|