framesdkpy 0.3.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.
- framesdkpy/__init__.py +33 -0
- framesdkpy/loaders/__init__.py +79 -0
- framesdkpy/loaders/assembler.py +217 -0
- framesdkpy/loaders/yaml_reader.py +57 -0
- framesdkpy/models/__init__.py +95 -0
- framesdkpy/models/acts_model.py +139 -0
- framesdkpy/models/base.py +64 -0
- framesdkpy/models/expect_model.py +127 -0
- framesdkpy/models/facts_model.py +163 -0
- framesdkpy/models/frame_model.py +42 -0
- framesdkpy/models/map_model.py +157 -0
- framesdkpy/models/rules_model.py +181 -0
- framesdkpy/schemas/acts.schema.json +116 -0
- framesdkpy/schemas/expect.schema.json +105 -0
- framesdkpy/schemas/facts.schema.json +256 -0
- framesdkpy/schemas/frame.schema.json +114 -0
- framesdkpy/schemas/map.schema.json +119 -0
- framesdkpy/schemas/rules.schema.json +140 -0
- framesdkpy/translators/__init__.py +24 -0
- framesdkpy/translators/normalizer.py +106 -0
- framesdkpy/translators/yaml_to_json.py +88 -0
- framesdkpy/validators/__init__.py +74 -0
- framesdkpy/validators/cross_file_validator.py +85 -0
- framesdkpy/validators/limits_validator.py +169 -0
- framesdkpy/validators/result.py +100 -0
- framesdkpy/validators/schema_validator.py +152 -0
- framesdkpy-0.3.0.dist-info/METADATA +89 -0
- framesdkpy-0.3.0.dist-info/RECORD +29 -0
- framesdkpy-0.3.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""FrameExpect model and sub-models -- what must pass.
|
|
2
|
+
|
|
3
|
+
Mirrors schemas/json/expect.schema.json exactly.
|
|
4
|
+
The expect block feeds directly into the mechanical validator.
|
|
5
|
+
checks connect to rules.commands via command_ref.
|
|
6
|
+
pass_condition defines machine-parseable success criteria.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
from framesdkpy.models.base import FrameBaseModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Sub-models
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class MustHold(FrameBaseModel):
|
|
23
|
+
"""Invariant that must remain true. Has an id for cross-referencing."""
|
|
24
|
+
|
|
25
|
+
id: str
|
|
26
|
+
"""Stable identifier. maxLength: 100."""
|
|
27
|
+
|
|
28
|
+
statement: str
|
|
29
|
+
"""The invariant statement. maxLength: 300."""
|
|
30
|
+
|
|
31
|
+
links: list[dict] = field(default_factory=list)
|
|
32
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(slots=True)
|
|
36
|
+
class Check(FrameBaseModel):
|
|
37
|
+
"""Verification check that feeds the mechanical validator.
|
|
38
|
+
|
|
39
|
+
command_ref points to rules.commands.<name> -- the shell command to execute.
|
|
40
|
+
pass_condition is machine-parseable: 'exit_code == 0', 'stdout contains X',
|
|
41
|
+
'stdout matches REGEX', or 'file_exists PATH'.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
name: str
|
|
45
|
+
"""Short check name displayed in reports. maxLength: 100."""
|
|
46
|
+
|
|
47
|
+
what: str
|
|
48
|
+
"""What is being checked -- human-readable explanation. maxLength: 300."""
|
|
49
|
+
|
|
50
|
+
how: str | None = None
|
|
51
|
+
"""How the check works: test, build, lint, grep, manual. maxLength: 200."""
|
|
52
|
+
|
|
53
|
+
command_ref: str | None = None
|
|
54
|
+
"""Ref to rules.commands.<name> for executable verification. maxLength: 200."""
|
|
55
|
+
|
|
56
|
+
pass_condition: str | None = None
|
|
57
|
+
"""Machine-parseable success criteria. Examples:
|
|
58
|
+
'exit_code == 0'
|
|
59
|
+
'stdout contains BUILD SUCCESS'
|
|
60
|
+
'stdout matches ^[a-f0-9]{12}_'
|
|
61
|
+
'file_exists dist/index.html'
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
links: list[dict] = field(default_factory=list)
|
|
65
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(slots=True)
|
|
69
|
+
class Proof(FrameBaseModel):
|
|
70
|
+
"""Required evidence type. Has an id for cross-referencing.
|
|
71
|
+
|
|
72
|
+
type: review (human review needed), smoke_test (quick sanity check),
|
|
73
|
+
static_check (linter/type checker), unavailable (can't verify yet).
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
id: str
|
|
77
|
+
"""Stable identifier. maxLength: 100."""
|
|
78
|
+
|
|
79
|
+
type: str
|
|
80
|
+
"""Fixed enum: review, smoke_test, static_check, unavailable."""
|
|
81
|
+
|
|
82
|
+
description: str
|
|
83
|
+
"""What evidence is needed. maxLength: 300."""
|
|
84
|
+
|
|
85
|
+
links: list[dict] = field(default_factory=list)
|
|
86
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Main model
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(slots=True)
|
|
95
|
+
class FrameExpect(FrameBaseModel):
|
|
96
|
+
"""What must pass -- outcomes, invariants, checks, proof requirements.
|
|
97
|
+
|
|
98
|
+
Populated from expect.yaml. This is the contract the mechanical validator
|
|
99
|
+
enforces. checks connect to rules.commands via command_ref.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
frame: dict = field(default_factory=dict)
|
|
103
|
+
"""Shared FRAME header block. Required by every FRAME file."""
|
|
104
|
+
|
|
105
|
+
outcomes: dict | None = None
|
|
106
|
+
"""Named expected results of work. Keys are outcome names."""
|
|
107
|
+
|
|
108
|
+
must_hold: list[MustHold] = field(default_factory=list)
|
|
109
|
+
"""Invariants that must stay true. Each has a stable id."""
|
|
110
|
+
|
|
111
|
+
checks: dict[str, Check] = field(default_factory=dict)
|
|
112
|
+
"""Verification checks. Key is the check name -- used as command_ref target."""
|
|
113
|
+
|
|
114
|
+
done_when: dict | None = None
|
|
115
|
+
"""Completion conditions. Free-form keys."""
|
|
116
|
+
|
|
117
|
+
proof: list[Proof] = field(default_factory=list)
|
|
118
|
+
"""Required evidence types. Each has a stable id."""
|
|
119
|
+
|
|
120
|
+
handoff: dict | None = None
|
|
121
|
+
"""State to pass to the next session. Free-form."""
|
|
122
|
+
|
|
123
|
+
evidence: list[dict] = field(default_factory=list)
|
|
124
|
+
"""Evidence entries supporting Expect claims."""
|
|
125
|
+
|
|
126
|
+
links: list[dict] = field(default_factory=list)
|
|
127
|
+
"""Typed links from this file to other FRAME refs."""
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""FrameFacts model and sub-models -- stable project truth.
|
|
2
|
+
|
|
3
|
+
Mirrors schemas/json/facts.schema.json exactly. Required fields are
|
|
4
|
+
non-nullable (str, not str | None). The loader guarantees these are
|
|
5
|
+
populated before model construction. Optional fields use | None.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from framesdkpy.models.base import FrameBaseModel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Sub-models -- typed representations of Facts blocks
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(slots=True)
|
|
21
|
+
class Profile(FrameBaseModel):
|
|
22
|
+
"""Project identity. name and summary are always present."""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
"""Project name. maxLength: 100."""
|
|
26
|
+
|
|
27
|
+
summary: str
|
|
28
|
+
"""One-paragraph project description. maxLength: 300."""
|
|
29
|
+
|
|
30
|
+
repo_shape: str | None = None
|
|
31
|
+
"""Enum: split-backend-frontend, monorepo, single-package, monolith, microservices."""
|
|
32
|
+
|
|
33
|
+
delivery_family: str | None = None
|
|
34
|
+
"""Enum: cli, web-app, mobile-app, sdk, infra-tooling, data-pipeline, etc."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(slots=True)
|
|
38
|
+
class Architecture(FrameBaseModel):
|
|
39
|
+
"""System layout. summary is always present."""
|
|
40
|
+
|
|
41
|
+
summary: str
|
|
42
|
+
"""Human-readable system layout overview. maxLength: 500."""
|
|
43
|
+
|
|
44
|
+
backend_layers: list[str] | None = None
|
|
45
|
+
"""Ordered list of backend architectural layers, e.g. ['routes', 'services', 'models']."""
|
|
46
|
+
|
|
47
|
+
frontend_layers: list[str] | None = None
|
|
48
|
+
"""Ordered list of frontend layers, e.g. ['views', 'stores', 'services']."""
|
|
49
|
+
|
|
50
|
+
data_flow: str | None = None
|
|
51
|
+
"""Description of how data moves through the system. maxLength: 500."""
|
|
52
|
+
|
|
53
|
+
deployment_topology: str | None = None
|
|
54
|
+
"""Where and how the system is deployed. maxLength: 500."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(slots=True)
|
|
58
|
+
class Technology(FrameBaseModel):
|
|
59
|
+
"""Structured technology stack. Required fields are present when this block exists."""
|
|
60
|
+
|
|
61
|
+
language: str
|
|
62
|
+
"""Primary programming language. maxLength: 100."""
|
|
63
|
+
|
|
64
|
+
framework: str
|
|
65
|
+
"""Primary framework (FastAPI, Next.js, etc.). maxLength: 100."""
|
|
66
|
+
|
|
67
|
+
database: str
|
|
68
|
+
"""Primary database (PostgreSQL, SQLite, etc.). maxLength: 100."""
|
|
69
|
+
|
|
70
|
+
extensions: dict | None = None
|
|
71
|
+
"""Optional free-form extensions for additional tech details."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(slots=True)
|
|
75
|
+
class Source(FrameBaseModel):
|
|
76
|
+
"""Trusted source-of-truth file. Has an id for cross-referencing."""
|
|
77
|
+
|
|
78
|
+
id: str
|
|
79
|
+
"""Stable identifier -- other files reference this via 'facts.sources.<id>'. maxLength: 100."""
|
|
80
|
+
|
|
81
|
+
path: str
|
|
82
|
+
"""Filesystem path to the source file. maxLength: 200."""
|
|
83
|
+
|
|
84
|
+
purpose: str
|
|
85
|
+
"""Why this file is a source of truth. maxLength: 300."""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(slots=True)
|
|
89
|
+
class Quirk(FrameBaseModel):
|
|
90
|
+
"""Weird project-specific thing agents must understand. Has an id for cross-referencing."""
|
|
91
|
+
|
|
92
|
+
id: str
|
|
93
|
+
"""Stable identifier. maxLength: 100."""
|
|
94
|
+
|
|
95
|
+
description: str
|
|
96
|
+
"""What the quirk is. maxLength: 200."""
|
|
97
|
+
|
|
98
|
+
why: str
|
|
99
|
+
"""Why this quirk exists -- prevents agents from 'fixing' it. maxLength: 300."""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(slots=True)
|
|
103
|
+
class OpenQuestion(FrameBaseModel):
|
|
104
|
+
"""Thing nobody has decided yet. Has an id for cross-referencing."""
|
|
105
|
+
|
|
106
|
+
id: str
|
|
107
|
+
"""Stable identifier. maxLength: 100."""
|
|
108
|
+
|
|
109
|
+
question: str
|
|
110
|
+
"""The unresolved question. maxLength: 300."""
|
|
111
|
+
|
|
112
|
+
context: str
|
|
113
|
+
"""Background needed to understand the question. maxLength: 300."""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# Main model
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass(slots=True)
|
|
122
|
+
class FrameFacts(FrameBaseModel):
|
|
123
|
+
"""Stable project truth -- what the project is, how it's built, its quirks.
|
|
124
|
+
|
|
125
|
+
Populated by the loader from facts.yaml. Required fields are non-nullable.
|
|
126
|
+
Optional blocks (environments, persistence, classification) use | None.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
frame: dict
|
|
130
|
+
"""Shared FRAME header block. Required by every FRAME file."""
|
|
131
|
+
|
|
132
|
+
profile: Profile
|
|
133
|
+
"""Required. Every project has a name and summary."""
|
|
134
|
+
|
|
135
|
+
architecture: Architecture
|
|
136
|
+
"""Required. Every project has a system layout."""
|
|
137
|
+
|
|
138
|
+
sources: list[Source] = field(default_factory=list)
|
|
139
|
+
"""Trusted source-of-truth files. Each has a stable id for cross-referencing."""
|
|
140
|
+
|
|
141
|
+
quirks: list[Quirk] = field(default_factory=list)
|
|
142
|
+
"""Project-specific oddities agents must understand. Each has a stable id."""
|
|
143
|
+
|
|
144
|
+
open_questions: list[OpenQuestion] = field(default_factory=list)
|
|
145
|
+
"""Unresolved questions. Each has a stable id."""
|
|
146
|
+
|
|
147
|
+
classification: dict | None = None
|
|
148
|
+
"""Project classification details. Free-form -- varies per project."""
|
|
149
|
+
|
|
150
|
+
technology: Technology | None = None
|
|
151
|
+
"""Structured technology stack. Optional at first."""
|
|
152
|
+
|
|
153
|
+
environments: dict | None = None
|
|
154
|
+
"""Per-environment configuration. Free-form keys (local, production, staging)."""
|
|
155
|
+
|
|
156
|
+
persistence: dict | None = None
|
|
157
|
+
"""Database, ORM, migration, and storage details. Free-form -- varies by ORM/storage."""
|
|
158
|
+
|
|
159
|
+
evidence: list[dict] = field(default_factory=list)
|
|
160
|
+
"""Evidence entries supporting Facts claims."""
|
|
161
|
+
|
|
162
|
+
links: list[dict] = field(default_factory=list)
|
|
163
|
+
"""Typed links from this file to other FRAME refs."""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""FRAME -- the assembled whole.
|
|
2
|
+
|
|
3
|
+
Composes all five typed parts into one model. All 5 files must be present
|
|
4
|
+
in the .haxaml/ directory at all times (D3: strict single-directory discovery).
|
|
5
|
+
Empty files are valid -- content depth is Haxaml's concern, not the SDK's.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from framesdkpy.models.base import FrameBaseModel
|
|
13
|
+
from framesdkpy.models.facts_model import FrameFacts
|
|
14
|
+
from framesdkpy.models.rules_model import FrameRules
|
|
15
|
+
from framesdkpy.models.map_model import FrameMap
|
|
16
|
+
from framesdkpy.models.expect_model import FrameExpect
|
|
17
|
+
from framesdkpy.models.acts_model import FrameActs
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(slots=True)
|
|
21
|
+
class FRAME(FrameBaseModel):
|
|
22
|
+
"""The assembled FRAME object -- all five parts in one typed model.
|
|
23
|
+
|
|
24
|
+
All 5 files are required by the loader (D3). Content depth varies --
|
|
25
|
+
a new project has empty Expect and Acts. A mature project fills them.
|
|
26
|
+
The SDK validates structure and schema. Haxaml enforces content.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
facts: FrameFacts
|
|
30
|
+
"""Required. Stable project truth -- identity, stack, architecture, quirks."""
|
|
31
|
+
|
|
32
|
+
rules: FrameRules
|
|
33
|
+
"""Required. Governance constraints, commands, policies."""
|
|
34
|
+
|
|
35
|
+
map: FrameMap
|
|
36
|
+
"""Required. Where things live -- at minimum structure + roots."""
|
|
37
|
+
|
|
38
|
+
expect: FrameExpect
|
|
39
|
+
"""Required. What must pass -- may be empty in early projects."""
|
|
40
|
+
|
|
41
|
+
acts: FrameActs
|
|
42
|
+
"""Required. Run history -- starts empty, grows over time."""
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""FrameMap model and sub-models -- where things live in the repo.
|
|
2
|
+
|
|
3
|
+
Mirrors schemas/json/map.schema.json exactly.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
|
|
10
|
+
from framesdkpy.models.base import FrameBaseModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Sub-models
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class Group(FrameBaseModel):
|
|
20
|
+
"""Logical grouping of paths. Supports wildcards for flexible coverage.
|
|
21
|
+
|
|
22
|
+
Groups let the map cover growing repos without requiring every new file
|
|
23
|
+
to be individually registered.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
id: str
|
|
27
|
+
"""Stable identifier for cross-referencing. maxLength: 100."""
|
|
28
|
+
|
|
29
|
+
label: str
|
|
30
|
+
"""Human-readable group name. maxLength: 150."""
|
|
31
|
+
|
|
32
|
+
paths: list[str] = field(default_factory=list)
|
|
33
|
+
"""File/directory paths in this group. Wildcards allowed (e.g. 'Backend/app/**/*.py')."""
|
|
34
|
+
|
|
35
|
+
links: list[dict] = field(default_factory=list)
|
|
36
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(slots=True)
|
|
40
|
+
class PathEntry(FrameBaseModel):
|
|
41
|
+
"""Critical individual file. Explicit path only -- no wildcards.
|
|
42
|
+
|
|
43
|
+
Optional id for cross-referencing from expect.checks or acts.runs.touched.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
path: str
|
|
47
|
+
"""Filesystem path. maxLength: 200."""
|
|
48
|
+
|
|
49
|
+
purpose: str
|
|
50
|
+
"""Why this file is important. maxLength: 300."""
|
|
51
|
+
|
|
52
|
+
id: str | None = None
|
|
53
|
+
"""Optional stable identifier. Only needed when this path is referenced from another file."""
|
|
54
|
+
|
|
55
|
+
links: list[dict] = field(default_factory=list)
|
|
56
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(slots=True)
|
|
60
|
+
class Entrypoint(FrameBaseModel):
|
|
61
|
+
"""CLI, API, web, or script entry point. Always has an id for cross-referencing."""
|
|
62
|
+
|
|
63
|
+
id: str
|
|
64
|
+
"""Stable identifier. maxLength: 100."""
|
|
65
|
+
|
|
66
|
+
path: str
|
|
67
|
+
"""Filesystem path to the entry point. maxLength: 200."""
|
|
68
|
+
|
|
69
|
+
kind: str
|
|
70
|
+
"""Fixed enum: cli, api, web, script."""
|
|
71
|
+
|
|
72
|
+
description: str | None = None
|
|
73
|
+
"""What this entry point does. maxLength: 300."""
|
|
74
|
+
|
|
75
|
+
links: list[dict] = field(default_factory=list)
|
|
76
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(slots=True)
|
|
80
|
+
class ManagedPath(FrameBaseModel):
|
|
81
|
+
"""Path under special rule. Supports wildcards.
|
|
82
|
+
|
|
83
|
+
rule: generated (auto-generated files), config (.env, settings files),
|
|
84
|
+
immutable (migration files, lock files).
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
path: str
|
|
88
|
+
"""Filesystem path or wildcard pattern. maxLength: 200."""
|
|
89
|
+
|
|
90
|
+
rule: str
|
|
91
|
+
"""Fixed enum: generated, config, immutable."""
|
|
92
|
+
|
|
93
|
+
id: str | None = None
|
|
94
|
+
"""Optional stable identifier for cross-referencing from rules.donts or expect.checks."""
|
|
95
|
+
|
|
96
|
+
links: list[dict] = field(default_factory=list)
|
|
97
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(slots=True)
|
|
101
|
+
class UnmappedPath(FrameBaseModel):
|
|
102
|
+
"""Path not yet placed in the map. Honest about gaps -- invites improvement."""
|
|
103
|
+
|
|
104
|
+
path: str
|
|
105
|
+
"""Filesystem path that needs mapping. maxLength: 200."""
|
|
106
|
+
|
|
107
|
+
reason: str
|
|
108
|
+
"""Why this path hasn't been mapped yet. maxLength: 200."""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Main model
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(slots=True)
|
|
117
|
+
class FrameMap(FrameBaseModel):
|
|
118
|
+
"""Where things live in the repo. Populated from map.yaml.
|
|
119
|
+
|
|
120
|
+
structure provides a quick visual overview (top block).
|
|
121
|
+
roots describe top-level directories.
|
|
122
|
+
groups provide logical groupings with wildcard support.
|
|
123
|
+
paths list critical individual files.
|
|
124
|
+
entrypoints show CLI/API/web start points.
|
|
125
|
+
managed_paths declare files under special rules.
|
|
126
|
+
unmapped_paths acknowledge gaps.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
frame: dict = field(default_factory=dict)
|
|
130
|
+
"""Shared FRAME header block. Required by every FRAME file."""
|
|
131
|
+
|
|
132
|
+
structure: str | None = None
|
|
133
|
+
"""Quick visual overview of repo layout. Top block, maxLength: 800."""
|
|
134
|
+
|
|
135
|
+
roots: dict | None = None
|
|
136
|
+
"""Top-level directory purposes. Free-form keys."""
|
|
137
|
+
|
|
138
|
+
groups: list[Group] = field(default_factory=list)
|
|
139
|
+
"""Logical groupings of paths. Supports wildcards."""
|
|
140
|
+
|
|
141
|
+
paths: list[PathEntry] = field(default_factory=list)
|
|
142
|
+
"""Critical individual files. Explicit paths only."""
|
|
143
|
+
|
|
144
|
+
entrypoints: list[Entrypoint] = field(default_factory=list)
|
|
145
|
+
"""CLI/API/web entry points. Each has a stable id."""
|
|
146
|
+
|
|
147
|
+
managed_paths: list[ManagedPath] = field(default_factory=list)
|
|
148
|
+
"""Paths under special rules. Supports wildcards."""
|
|
149
|
+
|
|
150
|
+
unmapped_paths: list[UnmappedPath] = field(default_factory=list)
|
|
151
|
+
"""Paths not yet mapped. Honest about gaps."""
|
|
152
|
+
|
|
153
|
+
evidence: list[dict] = field(default_factory=list)
|
|
154
|
+
"""Evidence entries supporting Map claims."""
|
|
155
|
+
|
|
156
|
+
links: list[dict] = field(default_factory=list)
|
|
157
|
+
"""Typed links from this file to other FRAME refs."""
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""FrameRules model and sub-models -- how to work safely in this repo.
|
|
2
|
+
|
|
3
|
+
Mirrors schemas/json/rules.schema.json exactly.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
|
|
10
|
+
from framesdkpy.models.base import FrameBaseModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Sub-models
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class Policy(FrameBaseModel):
|
|
20
|
+
"""Durable project policy (role access, lifecycle, audit, auth).
|
|
21
|
+
|
|
22
|
+
Moved from Facts to Rules -- policies are behavioral constraints,
|
|
23
|
+
not current project truth.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
id: str
|
|
27
|
+
"""Stable identifier for cross-referencing. maxLength: 100."""
|
|
28
|
+
|
|
29
|
+
name: str
|
|
30
|
+
"""Short policy name. maxLength: 150."""
|
|
31
|
+
|
|
32
|
+
rule: str
|
|
33
|
+
"""The policy rule text. maxLength: 500."""
|
|
34
|
+
|
|
35
|
+
links: list[dict] = field(default_factory=list)
|
|
36
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(slots=True)
|
|
40
|
+
class CoreRule(FrameBaseModel):
|
|
41
|
+
"""Core behavioral constraint. Has an id for cross-referencing."""
|
|
42
|
+
|
|
43
|
+
id: str
|
|
44
|
+
"""Stable identifier. maxLength: 100."""
|
|
45
|
+
|
|
46
|
+
rule: str
|
|
47
|
+
"""The constraint text. maxLength: 500."""
|
|
48
|
+
|
|
49
|
+
links: list[dict] = field(default_factory=list)
|
|
50
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(slots=True)
|
|
54
|
+
class Command(FrameBaseModel):
|
|
55
|
+
"""Named shell command with a fixed schema.
|
|
56
|
+
|
|
57
|
+
kind: setup (install deps), verify (validation check), or run (server/interactive).
|
|
58
|
+
The mechanical validator uses kind to decide which commands to execute.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
run: str
|
|
62
|
+
"""Shell command to execute. maxLength: 500."""
|
|
63
|
+
|
|
64
|
+
kind: str
|
|
65
|
+
"""Fixed enum: setup, verify, run."""
|
|
66
|
+
|
|
67
|
+
purpose: str
|
|
68
|
+
"""Why this command exists. maxLength: 300."""
|
|
69
|
+
|
|
70
|
+
links: list[dict] = field(default_factory=list)
|
|
71
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(slots=True)
|
|
75
|
+
class Dont(FrameBaseModel):
|
|
76
|
+
"""Thing you must never do. severity: critical (blocks) or warning (flags)."""
|
|
77
|
+
|
|
78
|
+
id: str
|
|
79
|
+
"""Stable identifier. maxLength: 100."""
|
|
80
|
+
|
|
81
|
+
rule: str
|
|
82
|
+
"""The forbidden action. maxLength: 300."""
|
|
83
|
+
|
|
84
|
+
severity: str = "critical"
|
|
85
|
+
"""Enum: critical, warning. critical blocks the agent; warning flags it."""
|
|
86
|
+
|
|
87
|
+
links: list[dict] = field(default_factory=list)
|
|
88
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(slots=True)
|
|
92
|
+
class AskFirst(FrameBaseModel):
|
|
93
|
+
"""Trigger needing human approval before the agent proceeds.
|
|
94
|
+
|
|
95
|
+
trigger_type: file_pattern (matches against files the agent will touch)
|
|
96
|
+
or task_pattern (matches against the agent's stated task description).
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
id: str
|
|
100
|
+
"""Stable identifier. maxLength: 100."""
|
|
101
|
+
|
|
102
|
+
trigger_type: str
|
|
103
|
+
"""Fixed enum: file_pattern, task_pattern."""
|
|
104
|
+
|
|
105
|
+
trigger: str
|
|
106
|
+
"""The pattern to match against. maxLength: 300."""
|
|
107
|
+
|
|
108
|
+
reason: str
|
|
109
|
+
"""Why approval is needed. maxLength: 300."""
|
|
110
|
+
|
|
111
|
+
links: list[dict] = field(default_factory=list)
|
|
112
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass(slots=True)
|
|
116
|
+
class Hint(FrameBaseModel):
|
|
117
|
+
"""Skill reference, known gotcha, or task-specific guidance.
|
|
118
|
+
|
|
119
|
+
Hints are not enforced -- they help the agent work faster.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
id: str
|
|
123
|
+
"""Stable identifier. maxLength: 100."""
|
|
124
|
+
|
|
125
|
+
hint: str
|
|
126
|
+
"""The guidance text. maxLength: 300."""
|
|
127
|
+
|
|
128
|
+
links: list[dict] = field(default_factory=list)
|
|
129
|
+
"""Typed links from this entry to other FRAME refs."""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
# Main model
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass(slots=True)
|
|
138
|
+
class FrameRules(FrameBaseModel):
|
|
139
|
+
"""How to work safely in this repo. Populated from rules.yaml.
|
|
140
|
+
|
|
141
|
+
governance_level controls enforcement strictness:
|
|
142
|
+
relaxed → agents proceed freely, ask_first is advisory
|
|
143
|
+
normal → ask_first warns but doesn't block
|
|
144
|
+
strict → ask_first blocks, donts enforced, validator must pass
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
frame: dict = field(default_factory=dict)
|
|
148
|
+
"""Shared FRAME header block. Required by every FRAME file."""
|
|
149
|
+
|
|
150
|
+
governance_level: str = "normal"
|
|
151
|
+
"""Enum: relaxed, normal, strict. Controls Haxaml enforcement strictness."""
|
|
152
|
+
|
|
153
|
+
rules: list[CoreRule] = field(default_factory=list)
|
|
154
|
+
"""Core behavioral constraints."""
|
|
155
|
+
|
|
156
|
+
policies: list[Policy] = field(default_factory=list)
|
|
157
|
+
"""Durable project policies (role access, lifecycle, audit)."""
|
|
158
|
+
|
|
159
|
+
commands: dict[str, Command] = field(default_factory=dict)
|
|
160
|
+
"""Named shell commands. Key is the command name used by expect.checks.command_ref."""
|
|
161
|
+
|
|
162
|
+
donts: list[Dont] = field(default_factory=list)
|
|
163
|
+
"""Things you must never do. severity determines enforcement."""
|
|
164
|
+
|
|
165
|
+
ask_first: list[AskFirst] = field(default_factory=list)
|
|
166
|
+
"""Triggers needing human approval. Enforcement depends on governance_level."""
|
|
167
|
+
|
|
168
|
+
hints: list[Hint] = field(default_factory=list)
|
|
169
|
+
"""Skill references, gotchas, task-specific guidance."""
|
|
170
|
+
|
|
171
|
+
code_style: dict | None = None
|
|
172
|
+
"""Formatting, naming, conventions. Free-form -- varies per language. Advisory limit 1000 chars."""
|
|
173
|
+
|
|
174
|
+
git: dict | None = None
|
|
175
|
+
"""Branch strategy, commit style, PR rules. Free-form -- varies per team. Advisory limit 1000 chars."""
|
|
176
|
+
|
|
177
|
+
evidence: list[dict] = field(default_factory=list)
|
|
178
|
+
"""Evidence entries supporting Rules claims."""
|
|
179
|
+
|
|
180
|
+
links: list[dict] = field(default_factory=list)
|
|
181
|
+
"""Typed links from this file to other FRAME refs."""
|