fushinryu-model 0.1.0__tar.gz
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.
- fushinryu_model-0.1.0/.aifred-tk/logs/general_20260605T210215.log +0 -0
- fushinryu_model-0.1.0/.aifred-tk/logs/general_20260605T210215.log.jsonl +0 -0
- fushinryu_model-0.1.0/.aifred-tk/logs/general_20260606T013140.log +0 -0
- fushinryu_model-0.1.0/.aifred-tk/logs/general_20260606T013140.log.jsonl +0 -0
- fushinryu_model-0.1.0/.codegraph/.gitignore +16 -0
- fushinryu_model-0.1.0/.codegraph/codegraph.db +0 -0
- fushinryu_model-0.1.0/.codegraph/codegraph.db-shm +0 -0
- fushinryu_model-0.1.0/.codegraph/codegraph.db-wal +0 -0
- fushinryu_model-0.1.0/.daimyo/rules/fushinryu_model/metadata.yml +4 -0
- fushinryu_model-0.1.0/.daimyo/rules/fushinryu_model/tenka.yml +229 -0
- fushinryu_model-0.1.0/.gitignore +39 -0
- fushinryu_model-0.1.0/.pre-commit-config.yaml +17 -0
- fushinryu_model-0.1.0/.project-metadata.yml +2 -0
- fushinryu_model-0.1.0/.python-version +1 -0
- fushinryu_model-0.1.0/.readthedocs.yaml +16 -0
- fushinryu_model-0.1.0/LICENSE.md +21 -0
- fushinryu_model-0.1.0/PKG-INFO +61 -0
- fushinryu_model-0.1.0/README.md +27 -0
- fushinryu_model-0.1.0/docs/concepts/domain-model.md +59 -0
- fushinryu_model-0.1.0/docs/concepts/merging.md +63 -0
- fushinryu_model-0.1.0/docs/concepts/scope-hierarchy.md +67 -0
- fushinryu_model-0.1.0/docs/getting-started.md +76 -0
- fushinryu_model-0.1.0/docs/index.md +21 -0
- fushinryu_model-0.1.0/docs/reference/acceptance-criterion.md +7 -0
- fushinryu_model-0.1.0/docs/reference/scope.md +7 -0
- fushinryu_model-0.1.0/docs/reference/user-story.md +13 -0
- fushinryu_model-0.1.0/docs/reference/validations.md +28 -0
- fushinryu_model-0.1.0/docs/requirements.txt +2 -0
- fushinryu_model-0.1.0/fushinryu_model/__init__.py +25 -0
- fushinryu_model-0.1.0/fushinryu_model/_merge_utils.py +31 -0
- fushinryu_model-0.1.0/fushinryu_model/acceptance_criterion.py +69 -0
- fushinryu_model-0.1.0/fushinryu_model/acceptance_criterion_validation.py +61 -0
- fushinryu_model-0.1.0/fushinryu_model/py.typed +0 -0
- fushinryu_model-0.1.0/fushinryu_model/scope.py +114 -0
- fushinryu_model-0.1.0/fushinryu_model/user_story.py +73 -0
- fushinryu_model-0.1.0/logs/daimyo.jsonl +488 -0
- fushinryu_model-0.1.0/logs/daimyo.log +488 -0
- fushinryu_model-0.1.0/mkdocs.yml +66 -0
- fushinryu_model-0.1.0/pyproject.toml +69 -0
- fushinryu_model-0.1.0/reports/junit.xml +1 -0
- fushinryu_model-0.1.0/reports/test-report.xml +1 -0
- fushinryu_model-0.1.0/reports/test_user_story.xml +1 -0
- fushinryu_model-0.1.0/site/404.html +786 -0
- fushinryu_model-0.1.0/site/assets/_mkdocstrings.css +237 -0
- fushinryu_model-0.1.0/site/assets/images/favicon.png +0 -0
- fushinryu_model-0.1.0/site/assets/javascripts/bundle.79ae519e.min.js +16 -0
- fushinryu_model-0.1.0/site/assets/javascripts/bundle.79ae519e.min.js.map +7 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/tinyseg.js +206 -0
- fushinryu_model-0.1.0/site/assets/javascripts/lunr/wordcut.js +6708 -0
- fushinryu_model-0.1.0/site/assets/javascripts/workers/search.2c215733.min.js +42 -0
- fushinryu_model-0.1.0/site/assets/javascripts/workers/search.2c215733.min.js.map +7 -0
- fushinryu_model-0.1.0/site/assets/stylesheets/main.484c7ddc.min.css +1 -0
- fushinryu_model-0.1.0/site/assets/stylesheets/main.484c7ddc.min.css.map +1 -0
- fushinryu_model-0.1.0/site/assets/stylesheets/palette.ab4e12ef.min.css +1 -0
- fushinryu_model-0.1.0/site/assets/stylesheets/palette.ab4e12ef.min.css.map +1 -0
- fushinryu_model-0.1.0/site/concepts/domain-model/index.html +1109 -0
- fushinryu_model-0.1.0/site/concepts/merging/index.html +1028 -0
- fushinryu_model-0.1.0/site/concepts/scope-hierarchy/index.html +1047 -0
- fushinryu_model-0.1.0/site/getting-started/index.html +1029 -0
- fushinryu_model-0.1.0/site/index.html +935 -0
- fushinryu_model-0.1.0/site/objects.inv +6 -0
- fushinryu_model-0.1.0/site/reference/acceptance-criterion/index.html +1117 -0
- fushinryu_model-0.1.0/site/reference/scope/index.html +1147 -0
- fushinryu_model-0.1.0/site/reference/user-story/index.html +1249 -0
- fushinryu_model-0.1.0/site/reference/validations/index.html +1185 -0
- fushinryu_model-0.1.0/site/requirements.txt +2 -0
- fushinryu_model-0.1.0/site/search/search_index.json +1 -0
- fushinryu_model-0.1.0/site/sitemap.xml +39 -0
- fushinryu_model-0.1.0/site/sitemap.xml.gz +0 -0
- fushinryu_model-0.1.0/tests/__init__.py +0 -0
- fushinryu_model-0.1.0/tests/conftest.py +3 -0
- fushinryu_model-0.1.0/tests/test_acceptance_criterion.py +163 -0
- fushinryu_model-0.1.0/tests/test_acceptance_criterion_validation.py +58 -0
- fushinryu_model-0.1.0/tests/test_scope.py +233 -0
- fushinryu_model-0.1.0/tests/test_structure.py +91 -0
- fushinryu_model-0.1.0/tests/test_user_story.py +135 -0
- fushinryu_model-0.1.0/uv.lock +1265 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
tenka:
|
|
2
|
+
US50001:
|
|
3
|
+
who: developer
|
|
4
|
+
what: the generated project to meet its structural constraints
|
|
5
|
+
why: the project baseline is valid and ready to build upon
|
|
6
|
+
acs:
|
|
7
|
+
AC50001.1:
|
|
8
|
+
given: the generated project directory
|
|
9
|
+
when: examining its top-level structure
|
|
10
|
+
then: README.md and .gitignore are present
|
|
11
|
+
AC50001.2:
|
|
12
|
+
given: the generated project
|
|
13
|
+
when: examining the daimyo configuration
|
|
14
|
+
then: .daimyo/rules/<project_slug>/metadata.yml exists and is valid YAML
|
|
15
|
+
AC50001.3:
|
|
16
|
+
given: the generated project directory
|
|
17
|
+
when: examining its top-level structure
|
|
18
|
+
then: LICENSE.md is present and its content is non-empty
|
|
19
|
+
AC50001.4:
|
|
20
|
+
given: the generated project directory
|
|
21
|
+
when: reading pyproject.toml
|
|
22
|
+
then: it is valid TOML containing the rendered project name and version
|
|
23
|
+
AC50001.5:
|
|
24
|
+
given: the generated project directory
|
|
25
|
+
when: reading .project-metadata.yml
|
|
26
|
+
then: it is valid YAML declaring a daimyo scope keyed by the project slug
|
|
27
|
+
|
|
28
|
+
US50003:
|
|
29
|
+
who: developer
|
|
30
|
+
what: a well-structured Python module
|
|
31
|
+
why: the package is importable and typed from the first commit
|
|
32
|
+
acs:
|
|
33
|
+
AC50003.1:
|
|
34
|
+
given: the generated project directory
|
|
35
|
+
when: examining its top-level structure
|
|
36
|
+
then: a directory named after the module_name variable exists at the project root
|
|
37
|
+
AC50003.2:
|
|
38
|
+
given: the module directory
|
|
39
|
+
when: examining its contents
|
|
40
|
+
then: a py.typed marker file is present
|
|
41
|
+
AC50003.3:
|
|
42
|
+
given: the module directory
|
|
43
|
+
when: importing the module
|
|
44
|
+
then: the module is importable without errors
|
|
45
|
+
|
|
46
|
+
US50002:
|
|
47
|
+
active: false
|
|
48
|
+
who: developer
|
|
49
|
+
what: my first feature
|
|
50
|
+
why: I can deliver value incrementally
|
|
51
|
+
acs:
|
|
52
|
+
AC50002.1:
|
|
53
|
+
given: a new feature requirement
|
|
54
|
+
when: I begin implementation
|
|
55
|
+
then: "TODO — replace with a real acceptance criterion"
|
|
56
|
+
|
|
57
|
+
US1:
|
|
58
|
+
who: developer
|
|
59
|
+
what: define AcceptanceCriteria for UserStories
|
|
60
|
+
why: testable conditions for requirements can be specified
|
|
61
|
+
acs:
|
|
62
|
+
AC1.1:
|
|
63
|
+
then: an AcceptanceCriterion can be instantiated with only a mandatory then field
|
|
64
|
+
AC1.2:
|
|
65
|
+
given: an AcceptanceCriterion instantiated without explicit given or when values
|
|
66
|
+
then: given and when are None by default
|
|
67
|
+
AC1.3:
|
|
68
|
+
given: an AcceptanceCriterion instantiated without an explicit active value
|
|
69
|
+
then: active is True by default
|
|
70
|
+
AC1.4:
|
|
71
|
+
given: a parent and a child AcceptanceCriterion sharing the same id
|
|
72
|
+
when: they are merged
|
|
73
|
+
then: the child's non-None field values override the parent's
|
|
74
|
+
AC1.5:
|
|
75
|
+
given: a parent AcceptanceCriterion and a child AcceptanceCriterion with the same id and different active values
|
|
76
|
+
when: they are merged
|
|
77
|
+
then: the child's active value overrides the parent's
|
|
78
|
+
|
|
79
|
+
US2:
|
|
80
|
+
who: developer
|
|
81
|
+
what: define UserStories within a Scope
|
|
82
|
+
why: high-level needs can be described as structured requirements
|
|
83
|
+
acs:
|
|
84
|
+
AC2.1:
|
|
85
|
+
then: a UserStory can be instantiated with the required fields id, who, what, why, and type
|
|
86
|
+
AC2.2:
|
|
87
|
+
then: the type field is an enumeration accepting only functional and technical values
|
|
88
|
+
AC2.3:
|
|
89
|
+
given: a parent and a child UserStory sharing the same id
|
|
90
|
+
when: they are merged
|
|
91
|
+
then: the child's scalar field values override the parent's
|
|
92
|
+
AC2.4:
|
|
93
|
+
given: a parent and a child UserStory sharing the same id, each with an AcceptanceCriterion of the same id
|
|
94
|
+
when: they are merged
|
|
95
|
+
then: the overlapping AcceptanceCriteria are merged with the child's fields overriding the parent's
|
|
96
|
+
AC2.5:
|
|
97
|
+
given: a parent and a child UserStory sharing the same id, each with non-overlapping AcceptanceCriteria ids
|
|
98
|
+
when: they are merged
|
|
99
|
+
then: all AcceptanceCriteria from both UserStories are present in the result
|
|
100
|
+
AC2.6:
|
|
101
|
+
given: a UserStory instantiated without an explicit active value
|
|
102
|
+
then: active is True by default
|
|
103
|
+
|
|
104
|
+
US3:
|
|
105
|
+
who: developer
|
|
106
|
+
what: define Scopes as organizational units
|
|
107
|
+
why: project artifacts can be grouped and organized under a named unit
|
|
108
|
+
acs:
|
|
109
|
+
AC3.1:
|
|
110
|
+
then: a Scope can be instantiated with name, id, and description fields
|
|
111
|
+
AC3.2:
|
|
112
|
+
given: a Scope id that does not follow the identifier pattern (e.g., starts with a digit or contains spaces)
|
|
113
|
+
when: the Scope is instantiated
|
|
114
|
+
then: a validation error is raised
|
|
115
|
+
AC3.3:
|
|
116
|
+
given: a Scope id following the pattern starting with a letter or underscore-then-letter, followed by letters, digits, or underscores
|
|
117
|
+
when: the Scope is instantiated
|
|
118
|
+
then: the id is accepted without error
|
|
119
|
+
AC3.4:
|
|
120
|
+
given: a parent and a child Scope
|
|
121
|
+
when: they are merged
|
|
122
|
+
then: the child's scalar field values override the parent's
|
|
123
|
+
AC3.5:
|
|
124
|
+
given: a parent and a child Scope with non-overlapping UserStory ids
|
|
125
|
+
when: they are merged
|
|
126
|
+
then: all UserStories from both Scopes are present in the result
|
|
127
|
+
AC3.6:
|
|
128
|
+
given: a parent and a child Scope with a shared UserStory id
|
|
129
|
+
when: they are merged
|
|
130
|
+
then: the shared UserStory is merged with the child's scalar fields overriding the parent's, and their AcceptanceCriteria sets merged
|
|
131
|
+
|
|
132
|
+
US4:
|
|
133
|
+
who: developer
|
|
134
|
+
what: attach validation records to AcceptanceCriteria
|
|
135
|
+
why: fulfillment evidence can be stored and queried
|
|
136
|
+
acs:
|
|
137
|
+
AC4.1:
|
|
138
|
+
then: a ManualValidation can be instantiated with passed (defaults to False), timestamp, and verdict fields
|
|
139
|
+
AC4.2:
|
|
140
|
+
then: an AutomatedValidation can be instantiated with passed (defaults to False), timestamp, source, and name fields
|
|
141
|
+
AC4.3:
|
|
142
|
+
given: an AcceptanceCriterion instantiated without explicit validations
|
|
143
|
+
then: its validations collection is empty by default
|
|
144
|
+
AC4.4:
|
|
145
|
+
given: an AcceptanceCriterion with no validations
|
|
146
|
+
then: it is not considered validated
|
|
147
|
+
AC4.5:
|
|
148
|
+
given: an AcceptanceCriterion whose every validation has passed=True
|
|
149
|
+
then: it is considered validated
|
|
150
|
+
AC4.6:
|
|
151
|
+
given: an AcceptanceCriterion with at least one validation having passed=False
|
|
152
|
+
then: it is not considered validated
|
|
153
|
+
AC4.7:
|
|
154
|
+
given: a parent and child AcceptanceCriterion sharing the same id, each with ManualValidations
|
|
155
|
+
when: they are merged
|
|
156
|
+
then: all ManualValidations from both are present in the result
|
|
157
|
+
AC4.8:
|
|
158
|
+
given: a parent and child AcceptanceCriterion sharing the same id, each with an AutomatedValidation with the same source and name but different timestamps
|
|
159
|
+
when: they are merged
|
|
160
|
+
then: only the AutomatedValidation with the most recent timestamp is present in the result
|
|
161
|
+
|
|
162
|
+
US5:
|
|
163
|
+
who: developer
|
|
164
|
+
what: query whether a UserStory is fulfilled
|
|
165
|
+
why: sprint completion can be assessed
|
|
166
|
+
acs:
|
|
167
|
+
AC5.1:
|
|
168
|
+
given: a UserStory with no active AcceptanceCriteria
|
|
169
|
+
then: it is not considered validated
|
|
170
|
+
AC5.2:
|
|
171
|
+
given: a UserStory whose every active AcceptanceCriterion is validated
|
|
172
|
+
then: it is considered validated
|
|
173
|
+
AC5.3:
|
|
174
|
+
given: a UserStory with at least one active AcceptanceCriterion that is not validated
|
|
175
|
+
then: it is not considered validated
|
|
176
|
+
AC5.4:
|
|
177
|
+
given: an inactive UserStory (active=False)
|
|
178
|
+
when: querying whether it is fulfilled
|
|
179
|
+
then: it is not considered validated
|
|
180
|
+
|
|
181
|
+
US6:
|
|
182
|
+
who: developer
|
|
183
|
+
what: declare parent Scopes on a Scope
|
|
184
|
+
why: scopes can be organised into a directed acyclic graph hierarchy
|
|
185
|
+
acs:
|
|
186
|
+
AC6.1:
|
|
187
|
+
when: a Scope is instantiated without an explicit parents value
|
|
188
|
+
then: parents defaults to an empty sequence
|
|
189
|
+
AC6.2:
|
|
190
|
+
when: a Scope is instantiated with one or more parent Scope objects
|
|
191
|
+
then: the instance is created successfully
|
|
192
|
+
AC6.3:
|
|
193
|
+
given: a Scope instantiated with an ordered list of parents
|
|
194
|
+
when: examining parents
|
|
195
|
+
then: the order is preserved as declared
|
|
196
|
+
AC6.4:
|
|
197
|
+
given: a parent and a child Scope sharing the same id, each with a different parents value
|
|
198
|
+
when: they are merged
|
|
199
|
+
then: the child's parents value overrides the parent's
|
|
200
|
+
|
|
201
|
+
US7:
|
|
202
|
+
who: developer
|
|
203
|
+
what: collapse a Scope hierarchy into a single flat Scope
|
|
204
|
+
why: the merged effective scope can be obtained
|
|
205
|
+
acs:
|
|
206
|
+
AC7.1:
|
|
207
|
+
given: a Scope with no parents
|
|
208
|
+
when: it is collapsed
|
|
209
|
+
then: the result has equivalent fields and no parents
|
|
210
|
+
AC7.2:
|
|
211
|
+
given: a Scope with one parent
|
|
212
|
+
when: it is collapsed
|
|
213
|
+
then: the child's fields override the parent's fields
|
|
214
|
+
AC7.3:
|
|
215
|
+
given: a Scope with multiple ordered parents
|
|
216
|
+
when: it is collapsed
|
|
217
|
+
then: the first parent's fields override subsequent parents' fields
|
|
218
|
+
AC7.4:
|
|
219
|
+
given: a three-level hierarchy (grandparent, parent, child)
|
|
220
|
+
when: the child is collapsed
|
|
221
|
+
then: all merges are applied transitively
|
|
222
|
+
AC7.5:
|
|
223
|
+
given: a diamond DAG where two parents share a common ancestor
|
|
224
|
+
when: the child is collapsed
|
|
225
|
+
then: the common ancestor contributes exactly once at the lowest precedence
|
|
226
|
+
AC7.6:
|
|
227
|
+
given: a Scope hierarchy containing a cycle
|
|
228
|
+
when: collapse is called
|
|
229
|
+
then: a ValueError is raised
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Virtual environments
|
|
2
|
+
.venv/
|
|
3
|
+
venv/
|
|
4
|
+
env/
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*.pyo
|
|
10
|
+
*.pyd
|
|
11
|
+
*.egg-info/
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
*.egg
|
|
15
|
+
|
|
16
|
+
# Environment and secrets
|
|
17
|
+
.env
|
|
18
|
+
.env.*
|
|
19
|
+
*.local
|
|
20
|
+
|
|
21
|
+
# Coverage and test artifacts
|
|
22
|
+
.coverage
|
|
23
|
+
.coverage.*
|
|
24
|
+
htmlcov/
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.mypy_cache/
|
|
27
|
+
.ruff_cache/
|
|
28
|
+
|
|
29
|
+
# Editor
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
32
|
+
*.swp
|
|
33
|
+
*~
|
|
34
|
+
|
|
35
|
+
# OS
|
|
36
|
+
.DS_Store
|
|
37
|
+
Thumbs.db
|
|
38
|
+
|
|
39
|
+
tenka.fulfillment.yml
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
|
|
10
|
+
- repo: https://gitlab.com/Kencho1/pre-commit-sdlcgov-hooks
|
|
11
|
+
rev: v1.2.0
|
|
12
|
+
hooks:
|
|
13
|
+
- id: add-ai-assisted-trailer
|
|
14
|
+
- id: add-co-authored-by-ai-trailers
|
|
15
|
+
- id: enforce-ai-assisted-trailer
|
|
16
|
+
- id: enforce-co-authored-by-ai-trailer
|
|
17
|
+
- id: clean-commit-temp
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jesús Alonso Abad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fushinryu-model
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Model of the Fushinryu development projects management methodology.
|
|
5
|
+
Project-URL: Repository, https://gitlab.com/Kencho1/fushinryu-model
|
|
6
|
+
Project-URL: Documentation, https://fushinryu-model.readthedocs.io
|
|
7
|
+
Author: Jesús Alonso Abad
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE.md
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: pleroma>=0.2.0
|
|
21
|
+
Requires-Dist: pydantic>=2.0
|
|
22
|
+
Provides-Extra: docs
|
|
23
|
+
Requires-Dist: mkdocs-material>=9.0; extra == 'docs'
|
|
24
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: mypy~=1.8; extra == 'test'
|
|
27
|
+
Requires-Dist: pytest-cov~=6.0; extra == 'test'
|
|
28
|
+
Requires-Dist: pytest-mark-ac~=1.1; extra == 'test'
|
|
29
|
+
Requires-Dist: pytest~=8.3; extra == 'test'
|
|
30
|
+
Requires-Dist: pyyaml~=6.0; extra == 'test'
|
|
31
|
+
Requires-Dist: ruff~=0.8; extra == 'test'
|
|
32
|
+
Requires-Dist: tox~=4.0; extra == 'test'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# fushinryu-model
|
|
36
|
+
|
|
37
|
+
Model of the Fūshinryū (風心流) development projects management methodology.
|
|
38
|
+
|
|
39
|
+
[](https://www.python.org)
|
|
40
|
+
[](LICENSE.md)
|
|
41
|
+
[](https://fushinryu-model.readthedocs.io)
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
pip install fushinryu-model
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick example
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from datetime import datetime, timezone
|
|
53
|
+
from fushinryu_model import AcceptanceCriterion, ManualValidation
|
|
54
|
+
|
|
55
|
+
ac = AcceptanceCriterion(id=1, then="the system returns HTTP 200")
|
|
56
|
+
validation = ManualValidation(verdict="verified manually", passed=True, timestamp=datetime.now(timezone.utc))
|
|
57
|
+
ac = ac.model_copy(update={"validations": frozenset([validation])})
|
|
58
|
+
assert ac.is_validated
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Full documentation: <https://fushinryu-model.readthedocs.io>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# fushinryu-model
|
|
2
|
+
|
|
3
|
+
Model of the Fūshinryū (風心流) development projects management methodology.
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org)
|
|
6
|
+
[](LICENSE.md)
|
|
7
|
+
[](https://fushinryu-model.readthedocs.io)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install fushinryu-model
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick example
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from fushinryu_model import AcceptanceCriterion, ManualValidation
|
|
20
|
+
|
|
21
|
+
ac = AcceptanceCriterion(id=1, then="the system returns HTTP 200")
|
|
22
|
+
validation = ManualValidation(verdict="verified manually", passed=True, timestamp=datetime.now(timezone.utc))
|
|
23
|
+
ac = ac.model_copy(update={"validations": frozenset([validation])})
|
|
24
|
+
assert ac.is_validated
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Full documentation: <https://fushinryu-model.readthedocs.io>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Domain Model
|
|
2
|
+
|
|
3
|
+
`fushinryu-model` is built around four entity types that form a containment hierarchy.
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
graph TD
|
|
7
|
+
S[Scope] -->|contains| US[UserStory]
|
|
8
|
+
US -->|contains| AC[AcceptanceCriterion]
|
|
9
|
+
AC -->|contains| V[ValidationEntry\nManualValidation · AutomatedValidation]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
A `Scope` is the top-level organisational unit. It groups related `UserStory` instances under a named, identifiable container. Scopes can declare parent scopes, forming a directed acyclic graph (DAG) that supports inheritance — see [Scope Hierarchy](scope-hierarchy.md).
|
|
15
|
+
|
|
16
|
+
**Key constraints**
|
|
17
|
+
|
|
18
|
+
- `id` must follow programming identifier rules: start with a letter or `_letter`, followed by letters, digits, or underscores.
|
|
19
|
+
- User story `id` values must be unique within a scope.
|
|
20
|
+
|
|
21
|
+
## UserStory
|
|
22
|
+
|
|
23
|
+
A `UserStory` captures a high-level requirement from the perspective of a role using the classic *As a / I want / so that* structure, stored as `who`, `what`, and `why` fields.
|
|
24
|
+
|
|
25
|
+
Stories are classified as either `functional` or `technical` via the `UserStoryType` enum.
|
|
26
|
+
|
|
27
|
+
**Key constraints**
|
|
28
|
+
|
|
29
|
+
- Acceptance criterion `id` values must be unique within a story.
|
|
30
|
+
- A story is considered validated (`is_validated = True`) only when it is active, has at least one active acceptance criterion, and all active criteria are validated.
|
|
31
|
+
|
|
32
|
+
## AcceptanceCriterion
|
|
33
|
+
|
|
34
|
+
An `AcceptanceCriterion` encodes the *Given / When / Then* structure of a testable condition.
|
|
35
|
+
|
|
36
|
+
| Field | Required | Description |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `id` | yes | Integer, unique within the enclosing story |
|
|
39
|
+
| `then` | yes | The expected outcome |
|
|
40
|
+
| `given` | no | Initial context or state |
|
|
41
|
+
| `when` | no | Trigger condition |
|
|
42
|
+
|
|
43
|
+
A criterion is validated (`is_validated = True`) when its `validations` set is non-empty and every record has `passed=True`.
|
|
44
|
+
|
|
45
|
+
## ValidationEntry
|
|
46
|
+
|
|
47
|
+
Validation evidence is stored as a discriminated union of two frozen record types.
|
|
48
|
+
|
|
49
|
+
### ManualValidation
|
|
50
|
+
|
|
51
|
+
Recorded by a human reviewer. Requires a `verdict` string explaining why the criterion passed or failed.
|
|
52
|
+
|
|
53
|
+
### AutomatedValidation
|
|
54
|
+
|
|
55
|
+
Linked to an automated test or check. Identified by `source` (the file or module) and `name` (the test name). During merges, automated validations are deduplicated by `(source, name)`, keeping the most recent timestamp.
|
|
56
|
+
|
|
57
|
+
## The `active` flag
|
|
58
|
+
|
|
59
|
+
All three entity types carry an `active: bool` field (default `True`). Setting it to `False` performs a **soft deletion** — the entity is retained in the data but excluded from validation checks. An inactive `UserStory` implicitly deactivates all its acceptance criteria for validation purposes, regardless of their individual `active` values.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Merging
|
|
2
|
+
|
|
3
|
+
All domain entities — `Scope`, `UserStory`, and `AcceptanceCriterion` — inherit from `MergeableModel` (provided by the [`pleroma`](https://pypi.org/project/pleroma/) library). This gives them controlled merge semantics: a *child* entity overrides a *parent* entity field by field.
|
|
4
|
+
|
|
5
|
+
## Scalar field merging
|
|
6
|
+
|
|
7
|
+
For any simple field (`str`, `bool`, `int`, `Enum`), the child's value replaces the parent's:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from fushinryu_model import AcceptanceCriterion
|
|
11
|
+
|
|
12
|
+
parent = AcceptanceCriterion(id=1, given="a user is logged in", then="the dashboard is shown")
|
|
13
|
+
child = AcceptanceCriterion(id=1, given=None, then="the profile is shown")
|
|
14
|
+
|
|
15
|
+
merged = AcceptanceCriterion.merge([parent, child])
|
|
16
|
+
# merged.given == "a user is logged in" ← parent kept (child was None)
|
|
17
|
+
# merged.then == "the profile is shown" ← child wins
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`None` values from the child do **not** override a non-`None` parent value unless `overwrite_none=True` is passed explicitly.
|
|
21
|
+
|
|
22
|
+
## Collection merging with `merge_by_id`
|
|
23
|
+
|
|
24
|
+
Collections of entities (acceptance criteria within a user story, user stories within a scope) are merged using the internal `merge_by_id` utility: entities are keyed by their integer `id`; overlapping ids are merged recursively; non-overlapping entities from both sets are included as-is.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from fushinryu_model import AcceptanceCriterion, UserStory, UserStoryType
|
|
28
|
+
|
|
29
|
+
parent_story = UserStory(
|
|
30
|
+
id=1, who="developer", what="X", why="Y", type=UserStoryType.FUNCTIONAL,
|
|
31
|
+
acceptance_criteria=frozenset([
|
|
32
|
+
AcceptanceCriterion(id=1, then="original condition"),
|
|
33
|
+
AcceptanceCriterion(id=2, then="only in parent"),
|
|
34
|
+
]),
|
|
35
|
+
)
|
|
36
|
+
child_story = UserStory(
|
|
37
|
+
id=1, who="developer", what="X", why="Y", type=UserStoryType.FUNCTIONAL,
|
|
38
|
+
acceptance_criteria=frozenset([
|
|
39
|
+
AcceptanceCriterion(id=1, then="overridden condition"), # same id → merged
|
|
40
|
+
AcceptanceCriterion(id=3, then="only in child"), # new id → added
|
|
41
|
+
]),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
merged = UserStory.merge([parent_story, child_story])
|
|
45
|
+
# AC id=1 → "overridden condition"
|
|
46
|
+
# AC id=2 → "only in parent"
|
|
47
|
+
# AC id=3 → "only in child"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Validation record merging
|
|
51
|
+
|
|
52
|
+
Validation records follow different rules depending on their type.
|
|
53
|
+
|
|
54
|
+
| Type | Merge behaviour |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `ManualValidation` | All records from both parent and child are **accumulated** |
|
|
57
|
+
| `AutomatedValidation` | Deduplicated by `(source, name)`; the record with the **most recent timestamp** wins |
|
|
58
|
+
|
|
59
|
+
This means manual review evidence is never discarded, while automated test results are automatically superseded by their latest run.
|
|
60
|
+
|
|
61
|
+
## When merging is used
|
|
62
|
+
|
|
63
|
+
Merging is the mechanism that powers [scope hierarchy collapse](scope-hierarchy.md). When `Scope.collapse()` is called, every scope in the ancestry is merged from lowest to highest precedence, producing a single flat scope with all inherited stories and criteria resolved.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Scope Hierarchy
|
|
2
|
+
|
|
3
|
+
A `Scope` can declare an ordered list of **parent scopes** via its `parents` field. This forms a directed acyclic graph (DAG) that enables requirement inheritance: child scopes override parent definitions while still inheriting anything not explicitly changed.
|
|
4
|
+
|
|
5
|
+
## Declaring parents
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from fushinryu_model import Scope
|
|
9
|
+
|
|
10
|
+
base = Scope(id="base", name="Base", description="Shared requirements.")
|
|
11
|
+
extended = Scope(id="extended", name="Extended", description="Extended requirements.", parents=(base,))
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The `parents` tuple is **ordered**: the first parent takes precedence over subsequent parents when the hierarchy is collapsed.
|
|
15
|
+
|
|
16
|
+
## Collapsing the hierarchy
|
|
17
|
+
|
|
18
|
+
`Scope.collapse()` flattens the entire ancestry into a single `Scope` with no parents. The returned scope contains the merged result of every ancestor's user stories and acceptance criteria.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
flat = extended.collapse()
|
|
22
|
+
# flat.parents == ()
|
|
23
|
+
# flat.user_stories contains inherited and overriding stories merged
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Linearisation algorithm
|
|
27
|
+
|
|
28
|
+
`collapse()` determines merge order using **DFS pre-order traversal with last-occurrence deduplication**:
|
|
29
|
+
|
|
30
|
+
1. The root scope is visited first (highest precedence).
|
|
31
|
+
2. Parents are visited in order; each scope's entire subtree is traversed before moving to the next sibling.
|
|
32
|
+
3. If a scope appears more than once (shared ancestor — the *diamond pattern*), only its **last** occurrence in the traversal is kept.
|
|
33
|
+
|
|
34
|
+
The result is a list ordered from highest to lowest merge precedence. Scopes are then merged from lowest to highest, so that higher-precedence scopes win.
|
|
35
|
+
|
|
36
|
+
### Diamond example
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
A (grandparent)
|
|
40
|
+
/ \
|
|
41
|
+
B C (parents of D, in that order)
|
|
42
|
+
\ /
|
|
43
|
+
D (root)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
A = Scope(id="a", name="A", description="Grandparent.")
|
|
48
|
+
B = Scope(id="b", name="B", description="Parent B.", parents=(A,))
|
|
49
|
+
C = Scope(id="c", name="C", description="Parent C.", parents=(A,))
|
|
50
|
+
D = Scope(id="d", name="D", description="Root.", parents=(B, C))
|
|
51
|
+
|
|
52
|
+
flat = D.collapse()
|
|
53
|
+
# Linearisation order (highest → lowest precedence): D, B, C, A
|
|
54
|
+
# A appears under both B and C but contributes only once, at the lowest precedence position.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Precedence order: **D > B > C > A**. B takes precedence over C because it is listed first in `D.parents`.
|
|
58
|
+
|
|
59
|
+
## Cycle detection
|
|
60
|
+
|
|
61
|
+
`collapse()` raises `ValueError` with a "Cycle detected" message if the parent graph contains a cycle. Cycles are invalid and cannot be collapsed.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
A = Scope(id="a", name="A", description=".")
|
|
65
|
+
# Constructing a genuine cycle requires object mutation (not possible with frozen Pydantic models),
|
|
66
|
+
# but the guard exists for programmatically constructed graphs.
|
|
67
|
+
```
|