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.
Files changed (109) hide show
  1. fushinryu_model-0.1.0/.aifred-tk/logs/general_20260605T210215.log +0 -0
  2. fushinryu_model-0.1.0/.aifred-tk/logs/general_20260605T210215.log.jsonl +0 -0
  3. fushinryu_model-0.1.0/.aifred-tk/logs/general_20260606T013140.log +0 -0
  4. fushinryu_model-0.1.0/.aifred-tk/logs/general_20260606T013140.log.jsonl +0 -0
  5. fushinryu_model-0.1.0/.codegraph/.gitignore +16 -0
  6. fushinryu_model-0.1.0/.codegraph/codegraph.db +0 -0
  7. fushinryu_model-0.1.0/.codegraph/codegraph.db-shm +0 -0
  8. fushinryu_model-0.1.0/.codegraph/codegraph.db-wal +0 -0
  9. fushinryu_model-0.1.0/.daimyo/rules/fushinryu_model/metadata.yml +4 -0
  10. fushinryu_model-0.1.0/.daimyo/rules/fushinryu_model/tenka.yml +229 -0
  11. fushinryu_model-0.1.0/.gitignore +39 -0
  12. fushinryu_model-0.1.0/.pre-commit-config.yaml +17 -0
  13. fushinryu_model-0.1.0/.project-metadata.yml +2 -0
  14. fushinryu_model-0.1.0/.python-version +1 -0
  15. fushinryu_model-0.1.0/.readthedocs.yaml +16 -0
  16. fushinryu_model-0.1.0/LICENSE.md +21 -0
  17. fushinryu_model-0.1.0/PKG-INFO +61 -0
  18. fushinryu_model-0.1.0/README.md +27 -0
  19. fushinryu_model-0.1.0/docs/concepts/domain-model.md +59 -0
  20. fushinryu_model-0.1.0/docs/concepts/merging.md +63 -0
  21. fushinryu_model-0.1.0/docs/concepts/scope-hierarchy.md +67 -0
  22. fushinryu_model-0.1.0/docs/getting-started.md +76 -0
  23. fushinryu_model-0.1.0/docs/index.md +21 -0
  24. fushinryu_model-0.1.0/docs/reference/acceptance-criterion.md +7 -0
  25. fushinryu_model-0.1.0/docs/reference/scope.md +7 -0
  26. fushinryu_model-0.1.0/docs/reference/user-story.md +13 -0
  27. fushinryu_model-0.1.0/docs/reference/validations.md +28 -0
  28. fushinryu_model-0.1.0/docs/requirements.txt +2 -0
  29. fushinryu_model-0.1.0/fushinryu_model/__init__.py +25 -0
  30. fushinryu_model-0.1.0/fushinryu_model/_merge_utils.py +31 -0
  31. fushinryu_model-0.1.0/fushinryu_model/acceptance_criterion.py +69 -0
  32. fushinryu_model-0.1.0/fushinryu_model/acceptance_criterion_validation.py +61 -0
  33. fushinryu_model-0.1.0/fushinryu_model/py.typed +0 -0
  34. fushinryu_model-0.1.0/fushinryu_model/scope.py +114 -0
  35. fushinryu_model-0.1.0/fushinryu_model/user_story.py +73 -0
  36. fushinryu_model-0.1.0/logs/daimyo.jsonl +488 -0
  37. fushinryu_model-0.1.0/logs/daimyo.log +488 -0
  38. fushinryu_model-0.1.0/mkdocs.yml +66 -0
  39. fushinryu_model-0.1.0/pyproject.toml +69 -0
  40. fushinryu_model-0.1.0/reports/junit.xml +1 -0
  41. fushinryu_model-0.1.0/reports/test-report.xml +1 -0
  42. fushinryu_model-0.1.0/reports/test_user_story.xml +1 -0
  43. fushinryu_model-0.1.0/site/404.html +786 -0
  44. fushinryu_model-0.1.0/site/assets/_mkdocstrings.css +237 -0
  45. fushinryu_model-0.1.0/site/assets/images/favicon.png +0 -0
  46. fushinryu_model-0.1.0/site/assets/javascripts/bundle.79ae519e.min.js +16 -0
  47. fushinryu_model-0.1.0/site/assets/javascripts/bundle.79ae519e.min.js.map +7 -0
  48. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
  49. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
  50. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
  51. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
  52. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
  53. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
  54. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
  55. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
  56. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
  57. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
  58. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
  59. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
  60. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
  61. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
  62. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
  63. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
  64. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
  65. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
  66. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
  67. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
  68. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
  69. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
  70. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
  71. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
  72. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
  73. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
  74. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
  75. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
  76. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
  77. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
  78. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
  79. fushinryu_model-0.1.0/site/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
  80. fushinryu_model-0.1.0/site/assets/javascripts/lunr/tinyseg.js +206 -0
  81. fushinryu_model-0.1.0/site/assets/javascripts/lunr/wordcut.js +6708 -0
  82. fushinryu_model-0.1.0/site/assets/javascripts/workers/search.2c215733.min.js +42 -0
  83. fushinryu_model-0.1.0/site/assets/javascripts/workers/search.2c215733.min.js.map +7 -0
  84. fushinryu_model-0.1.0/site/assets/stylesheets/main.484c7ddc.min.css +1 -0
  85. fushinryu_model-0.1.0/site/assets/stylesheets/main.484c7ddc.min.css.map +1 -0
  86. fushinryu_model-0.1.0/site/assets/stylesheets/palette.ab4e12ef.min.css +1 -0
  87. fushinryu_model-0.1.0/site/assets/stylesheets/palette.ab4e12ef.min.css.map +1 -0
  88. fushinryu_model-0.1.0/site/concepts/domain-model/index.html +1109 -0
  89. fushinryu_model-0.1.0/site/concepts/merging/index.html +1028 -0
  90. fushinryu_model-0.1.0/site/concepts/scope-hierarchy/index.html +1047 -0
  91. fushinryu_model-0.1.0/site/getting-started/index.html +1029 -0
  92. fushinryu_model-0.1.0/site/index.html +935 -0
  93. fushinryu_model-0.1.0/site/objects.inv +6 -0
  94. fushinryu_model-0.1.0/site/reference/acceptance-criterion/index.html +1117 -0
  95. fushinryu_model-0.1.0/site/reference/scope/index.html +1147 -0
  96. fushinryu_model-0.1.0/site/reference/user-story/index.html +1249 -0
  97. fushinryu_model-0.1.0/site/reference/validations/index.html +1185 -0
  98. fushinryu_model-0.1.0/site/requirements.txt +2 -0
  99. fushinryu_model-0.1.0/site/search/search_index.json +1 -0
  100. fushinryu_model-0.1.0/site/sitemap.xml +39 -0
  101. fushinryu_model-0.1.0/site/sitemap.xml.gz +0 -0
  102. fushinryu_model-0.1.0/tests/__init__.py +0 -0
  103. fushinryu_model-0.1.0/tests/conftest.py +3 -0
  104. fushinryu_model-0.1.0/tests/test_acceptance_criterion.py +163 -0
  105. fushinryu_model-0.1.0/tests/test_acceptance_criterion_validation.py +58 -0
  106. fushinryu_model-0.1.0/tests/test_scope.py +233 -0
  107. fushinryu_model-0.1.0/tests/test_structure.py +91 -0
  108. fushinryu_model-0.1.0/tests/test_user_story.py +135 -0
  109. fushinryu_model-0.1.0/uv.lock +1265 -0
@@ -0,0 +1,16 @@
1
+ # CodeGraph data files
2
+ # These are local to each machine and should not be committed
3
+
4
+ # Database
5
+ *.db
6
+ *.db-wal
7
+ *.db-shm
8
+
9
+ # Cache
10
+ cache/
11
+
12
+ # Logs
13
+ *.log
14
+
15
+ # Hook markers
16
+ .dirty
@@ -0,0 +1,4 @@
1
+ name: fushinryu_model
2
+ description: Model of the Fushinryu development projects management methodology.
3
+ parents:
4
+ - kencho
@@ -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,2 @@
1
+ daimyo:
2
+ scope: fushinryu_model
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,16 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.11"
7
+
8
+ mkdocs:
9
+ configuration: mkdocs.yml
10
+
11
+ python:
12
+ install:
13
+ - method: pip
14
+ path: .
15
+ extra_requirements:
16
+ - docs
@@ -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
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org)
40
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE.md)
41
+ [![Documentation](https://readthedocs.org/projects/fushinryu-model/badge/?version=latest)](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
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE.md)
7
+ [![Documentation](https://readthedocs.org/projects/fushinryu-model/badge/?version=latest)](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
+ ```