code-factory-2-forge 0.6.0__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.
forgeline/ssat.py ADDED
@@ -0,0 +1,93 @@
1
+ """Semantic Software Architecture Tree — architecture as code, not a doc.
2
+
3
+ An SSAT is a YAML contract: modules, their public signatures, allowed
4
+ dependency edges, and invariants. It is BOTH the scaffold source AND the
5
+ CI gate — the same artifact that generates the skeleton also detects
6
+ 'structural erosion' when filled code violates the declared boundaries.
7
+ """
8
+ from __future__ import annotations
9
+ import ast, re
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+ import yaml
13
+
14
+ @dataclass
15
+ class ArchViolation:
16
+ code: str
17
+ message: str
18
+ where: str = ""
19
+
20
+ def load_ssat(path: Path) -> dict:
21
+ return yaml.safe_load(Path(path).read_text())
22
+
23
+ def scaffold_from_ssat(ssat: dict, out_dir: Path) -> list[Path]:
24
+ """Generate signature-only files with valid imports from the SSAT."""
25
+ out_dir = Path(out_dir); created = []
26
+ for mod in ssat.get("modules", []):
27
+ p = out_dir/mod["path"]
28
+ p.parent.mkdir(parents=True, exist_ok=True)
29
+ lines = ['"""AUTO-SCAFFOLD from SSAT. Fill bodies only; do not change signatures."""']
30
+ for imp in mod.get("imports", []):
31
+ lines.append(f"import {imp}" if "." not in imp else f"from {imp.rsplit('.',1)[0]} import {imp.rsplit('.',1)[1]}")
32
+ lines.append("")
33
+ for fn in mod.get("functions", []):
34
+ args = ", ".join(fn.get("args", []))
35
+ ret = fn.get("returns", "None")
36
+ lines.append(f"def {fn['name']}({args}) -> {ret}:")
37
+ lines.append(f' """{fn.get("doc","TODO")}"""')
38
+ lines.append(" raise NotImplementedError # FILL")
39
+ lines.append("")
40
+ p.write_text("\n".join(lines)); created.append(p)
41
+ return created
42
+
43
+ def _module_of(path: str, ssat: dict) -> str | None:
44
+ for mod in ssat.get("modules", []):
45
+ if path.endswith(mod["path"]):
46
+ return mod["name"]
47
+ return None
48
+
49
+ def check_erosion(ssat: dict, src_dir: Path) -> list[ArchViolation]:
50
+ """Architecture-as-CI-gate: detect signature drift + illegal dependency edges."""
51
+ src_dir = Path(src_dir); violations = []
52
+ allowed = {(e["from"], e["to"]) for e in ssat.get("dependencies", [])}
53
+ names = {m["name"]: m for m in ssat.get("modules", [])}
54
+ path_to_name = {m["path"]: m["name"] for m in ssat.get("modules", [])}
55
+ for mod in ssat.get("modules", []):
56
+ p = src_dir/mod["path"]
57
+ if not p.exists():
58
+ violations.append(ArchViolation("E_MISSING_MODULE", f"{mod['path']} declared in SSAT but absent"))
59
+ continue
60
+ try:
61
+ tree = ast.parse(p.read_text())
62
+ except SyntaxError as e:
63
+ violations.append(ArchViolation("E_SYNTAX", str(e), mod["path"])); continue
64
+ # signature drift
65
+ declared = {f["name"]: f for f in mod.get("functions", [])}
66
+ found = {n.name: n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)}
67
+ for fname, fspec in declared.items():
68
+ if fname not in found:
69
+ violations.append(ArchViolation("E_SIG_MISSING", f"{fname} declared but not implemented", mod["path"]))
70
+ continue
71
+ got_args = [a.arg for a in found[fname].args.args]
72
+ want_args = [a.split(":")[0].strip() for a in fspec.get("args", [])]
73
+ if got_args != want_args:
74
+ violations.append(ArchViolation("E_SIG_DRIFT",
75
+ f"{fname}({got_args}) != SSAT ({want_args})", mod["path"]))
76
+ # illegal dependency edges
77
+ this = mod["name"]
78
+ for node in ast.walk(tree):
79
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
80
+ mods = [a.name for a in node.names] if isinstance(node, ast.Import) else [node.module or ""]
81
+ for m in mods:
82
+ target = names.get(m.split(".")[-1]) or names.get(m)
83
+ tname = target["name"] if target else None
84
+ if tname and tname != this and (this, tname) not in allowed:
85
+ violations.append(ArchViolation("E_ILLEGAL_DEP",
86
+ f"{this} -> {tname} not in SSAT dependency allowlist", mod["path"]))
87
+ for inv in ssat.get("invariants", []):
88
+ pat = inv["forbid_pattern"]
89
+ for mod in ssat.get("modules", []):
90
+ p = src_dir/mod["path"]
91
+ if p.exists() and re.search(pat, p.read_text()):
92
+ violations.append(ArchViolation("E_INVARIANT", f"{inv['name']}: forbidden pattern {pat!r}", mod["path"]))
93
+ return violations
forgeline/states.py ADDED
@@ -0,0 +1,41 @@
1
+ """The Agentic SDLC state machine. States are explicit, transitions are
2
+ gated, and every transition is a receipt. Humans own confidence gates;
3
+ agents own execution between them."""
4
+ from __future__ import annotations
5
+ from enum import Enum
6
+
7
+ class State(str, Enum):
8
+ INTENT = "intent" # vague human intent captured
9
+ EXPANDED = "expanded" # use cases drafted (human confidence gate)
10
+ ARCHITECTED = "architected" # SSAT / architecture-as-code produced
11
+ SCAFFOLDED = "scaffolded" # signatures + files, valid imports
12
+ FILLED = "filled" # function bodies implemented
13
+ REVIEWED = "reviewed" # judge + adversary passed
14
+ ARCH_GATED = "arch_gated" # architecture CI gate passed
15
+ TESTS_VERIFIED = "tests_verified" # smoke checks proven non-hollow
16
+ SMOKED = "smoked" # runtime behavior verified (smoke gate)
17
+ SHIPPED = "shipped"
18
+ BLOCKED = "blocked" # a gate failed; refine loop owns it
19
+
20
+ # allowed forward transitions; anything else is E_ILLEGAL_TRANSITION
21
+ TRANSITIONS = {
22
+ State.INTENT: {State.EXPANDED},
23
+ State.EXPANDED: {State.ARCHITECTED, State.BLOCKED},
24
+ State.ARCHITECTED: {State.SCAFFOLDED, State.BLOCKED},
25
+ State.SCAFFOLDED: {State.FILLED, State.BLOCKED},
26
+ State.FILLED: {State.REVIEWED, State.BLOCKED},
27
+ State.REVIEWED: {State.ARCH_GATED, State.BLOCKED},
28
+ State.ARCH_GATED: {State.TESTS_VERIFIED, State.BLOCKED},
29
+ State.TESTS_VERIFIED: {State.SMOKED, State.BLOCKED},
30
+ State.SMOKED: {State.SHIPPED, State.BLOCKED},
31
+ State.BLOCKED: {State.SCAFFOLDED, State.FILLED, State.REVIEWED, State.ARCH_GATED, State.TESTS_VERIFIED, State.SMOKED},
32
+ State.SHIPPED: set(),
33
+ }
34
+
35
+ HUMAN_GATES = {State.EXPANDED, State.ARCHITECTED} # confidence gates requiring signoff
36
+
37
+ class IllegalTransition(RuntimeError):
38
+ pass
39
+
40
+ def can_transition(frm: State, to: State) -> bool:
41
+ return to in TRANSITIONS.get(frm, set())