elspais 0.11.2__py3-none-any.whl → 0.43.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. elspais/__init__.py +1 -10
  2. elspais/{sponsors/__init__.py → associates.py} +102 -56
  3. elspais/cli.py +366 -69
  4. elspais/commands/__init__.py +9 -3
  5. elspais/commands/analyze.py +118 -169
  6. elspais/commands/changed.py +12 -23
  7. elspais/commands/config_cmd.py +10 -13
  8. elspais/commands/edit.py +33 -13
  9. elspais/commands/example_cmd.py +319 -0
  10. elspais/commands/hash_cmd.py +161 -183
  11. elspais/commands/health.py +1177 -0
  12. elspais/commands/index.py +98 -115
  13. elspais/commands/init.py +99 -22
  14. elspais/commands/reformat_cmd.py +41 -433
  15. elspais/commands/rules_cmd.py +2 -2
  16. elspais/commands/trace.py +443 -324
  17. elspais/commands/validate.py +193 -411
  18. elspais/config/__init__.py +799 -5
  19. elspais/{core/content_rules.py → content_rules.py} +20 -2
  20. elspais/docs/cli/assertions.md +67 -0
  21. elspais/docs/cli/commands.md +304 -0
  22. elspais/docs/cli/config.md +262 -0
  23. elspais/docs/cli/format.md +66 -0
  24. elspais/docs/cli/git.md +45 -0
  25. elspais/docs/cli/health.md +190 -0
  26. elspais/docs/cli/hierarchy.md +60 -0
  27. elspais/docs/cli/ignore.md +72 -0
  28. elspais/docs/cli/mcp.md +245 -0
  29. elspais/docs/cli/quickstart.md +58 -0
  30. elspais/docs/cli/traceability.md +89 -0
  31. elspais/docs/cli/validation.md +96 -0
  32. elspais/graph/GraphNode.py +383 -0
  33. elspais/graph/__init__.py +40 -0
  34. elspais/graph/annotators.py +927 -0
  35. elspais/graph/builder.py +1886 -0
  36. elspais/graph/deserializer.py +248 -0
  37. elspais/graph/factory.py +284 -0
  38. elspais/graph/metrics.py +127 -0
  39. elspais/graph/mutations.py +161 -0
  40. elspais/graph/parsers/__init__.py +156 -0
  41. elspais/graph/parsers/code.py +213 -0
  42. elspais/graph/parsers/comments.py +112 -0
  43. elspais/graph/parsers/config_helpers.py +29 -0
  44. elspais/graph/parsers/heredocs.py +225 -0
  45. elspais/graph/parsers/journey.py +131 -0
  46. elspais/graph/parsers/remainder.py +79 -0
  47. elspais/graph/parsers/requirement.py +347 -0
  48. elspais/graph/parsers/results/__init__.py +6 -0
  49. elspais/graph/parsers/results/junit_xml.py +229 -0
  50. elspais/graph/parsers/results/pytest_json.py +313 -0
  51. elspais/graph/parsers/test.py +305 -0
  52. elspais/graph/relations.py +78 -0
  53. elspais/graph/serialize.py +216 -0
  54. elspais/html/__init__.py +8 -0
  55. elspais/html/generator.py +731 -0
  56. elspais/html/templates/trace_view.html.j2 +2151 -0
  57. elspais/mcp/__init__.py +45 -29
  58. elspais/mcp/__main__.py +5 -1
  59. elspais/mcp/file_mutations.py +138 -0
  60. elspais/mcp/server.py +1998 -244
  61. elspais/testing/__init__.py +3 -3
  62. elspais/testing/config.py +3 -0
  63. elspais/testing/mapper.py +1 -1
  64. elspais/testing/scanner.py +301 -12
  65. elspais/utilities/__init__.py +1 -0
  66. elspais/utilities/docs_loader.py +115 -0
  67. elspais/utilities/git.py +607 -0
  68. elspais/{core → utilities}/hasher.py +8 -22
  69. elspais/utilities/md_renderer.py +189 -0
  70. elspais/{core → utilities}/patterns.py +56 -51
  71. elspais/utilities/reference_config.py +626 -0
  72. elspais/validation/__init__.py +19 -0
  73. elspais/validation/format.py +264 -0
  74. {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/METADATA +7 -4
  75. elspais-0.43.5.dist-info/RECORD +80 -0
  76. elspais/config/defaults.py +0 -179
  77. elspais/config/loader.py +0 -494
  78. elspais/core/__init__.py +0 -21
  79. elspais/core/git.py +0 -346
  80. elspais/core/models.py +0 -320
  81. elspais/core/parser.py +0 -639
  82. elspais/core/rules.py +0 -509
  83. elspais/mcp/context.py +0 -172
  84. elspais/mcp/serializers.py +0 -112
  85. elspais/reformat/__init__.py +0 -50
  86. elspais/reformat/detector.py +0 -112
  87. elspais/reformat/hierarchy.py +0 -247
  88. elspais/reformat/line_breaks.py +0 -218
  89. elspais/reformat/prompts.py +0 -133
  90. elspais/reformat/transformer.py +0 -266
  91. elspais/trace_view/__init__.py +0 -55
  92. elspais/trace_view/coverage.py +0 -183
  93. elspais/trace_view/generators/__init__.py +0 -12
  94. elspais/trace_view/generators/base.py +0 -334
  95. elspais/trace_view/generators/csv.py +0 -118
  96. elspais/trace_view/generators/markdown.py +0 -170
  97. elspais/trace_view/html/__init__.py +0 -33
  98. elspais/trace_view/html/generator.py +0 -1140
  99. elspais/trace_view/html/templates/base.html +0 -283
  100. elspais/trace_view/html/templates/components/code_viewer_modal.html +0 -14
  101. elspais/trace_view/html/templates/components/file_picker_modal.html +0 -20
  102. elspais/trace_view/html/templates/components/legend_modal.html +0 -69
  103. elspais/trace_view/html/templates/components/review_panel.html +0 -118
  104. elspais/trace_view/html/templates/partials/review/help/help-panel.json +0 -244
  105. elspais/trace_view/html/templates/partials/review/help/onboarding.json +0 -77
  106. elspais/trace_view/html/templates/partials/review/help/tooltips.json +0 -237
  107. elspais/trace_view/html/templates/partials/review/review-comments.js +0 -928
  108. elspais/trace_view/html/templates/partials/review/review-data.js +0 -961
  109. elspais/trace_view/html/templates/partials/review/review-help.js +0 -679
  110. elspais/trace_view/html/templates/partials/review/review-init.js +0 -177
  111. elspais/trace_view/html/templates/partials/review/review-line-numbers.js +0 -429
  112. elspais/trace_view/html/templates/partials/review/review-packages.js +0 -1029
  113. elspais/trace_view/html/templates/partials/review/review-position.js +0 -540
  114. elspais/trace_view/html/templates/partials/review/review-resize.js +0 -115
  115. elspais/trace_view/html/templates/partials/review/review-status.js +0 -659
  116. elspais/trace_view/html/templates/partials/review/review-sync.js +0 -992
  117. elspais/trace_view/html/templates/partials/review-styles.css +0 -2238
  118. elspais/trace_view/html/templates/partials/scripts.js +0 -1741
  119. elspais/trace_view/html/templates/partials/styles.css +0 -1756
  120. elspais/trace_view/models.py +0 -378
  121. elspais/trace_view/review/__init__.py +0 -63
  122. elspais/trace_view/review/branches.py +0 -1142
  123. elspais/trace_view/review/models.py +0 -1200
  124. elspais/trace_view/review/position.py +0 -591
  125. elspais/trace_view/review/server.py +0 -1032
  126. elspais/trace_view/review/status.py +0 -455
  127. elspais/trace_view/review/storage.py +0 -1343
  128. elspais/trace_view/scanning.py +0 -213
  129. elspais/trace_view/specs/README.md +0 -84
  130. elspais/trace_view/specs/tv-d00001-template-architecture.md +0 -36
  131. elspais/trace_view/specs/tv-d00002-css-extraction.md +0 -37
  132. elspais/trace_view/specs/tv-d00003-js-extraction.md +0 -43
  133. elspais/trace_view/specs/tv-d00004-build-embedding.md +0 -40
  134. elspais/trace_view/specs/tv-d00005-test-format.md +0 -78
  135. elspais/trace_view/specs/tv-d00010-review-data-models.md +0 -33
  136. elspais/trace_view/specs/tv-d00011-review-storage.md +0 -33
  137. elspais/trace_view/specs/tv-d00012-position-resolution.md +0 -33
  138. elspais/trace_view/specs/tv-d00013-git-branches.md +0 -31
  139. elspais/trace_view/specs/tv-d00014-review-api-server.md +0 -31
  140. elspais/trace_view/specs/tv-d00015-status-modifier.md +0 -27
  141. elspais/trace_view/specs/tv-d00016-js-integration.md +0 -33
  142. elspais/trace_view/specs/tv-p00001-html-generator.md +0 -33
  143. elspais/trace_view/specs/tv-p00002-review-system.md +0 -29
  144. elspais-0.11.2.dist-info/RECORD +0 -101
  145. {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/WHEEL +0 -0
  146. {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/entry_points.txt +0 -0
  147. {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/licenses/LICENSE +0 -0
@@ -1,378 +0,0 @@
1
- # Implements: REQ-int-d00004 (Model Adapter)
2
- """
3
- elspais.trace_view.models - Data models for trace-view.
4
-
5
- Provides TraceViewRequirement which wraps core Requirement with:
6
- - Git state tracking (uncommitted, modified, moved)
7
- - Test coverage information
8
- - Implementation file references
9
- """
10
-
11
- from dataclasses import dataclass, field
12
- from pathlib import Path
13
- from typing import Dict, List, Optional, Set, Tuple
14
-
15
- from elspais.core.models import Requirement as CoreRequirement
16
-
17
-
18
- @dataclass
19
- class TestInfo:
20
- """Represents test coverage for a requirement.
21
-
22
- Attributes:
23
- test_count: Number of automated tests
24
- manual_test_count: Number of manual tests
25
- test_status: Status ('not_tested', 'passed', 'failed', 'error', 'skipped')
26
- test_details: List of test result details
27
- notes: Additional notes about testing
28
- """
29
-
30
- test_count: int = 0
31
- manual_test_count: int = 0
32
- test_status: str = "not_tested"
33
- test_details: List[Dict] = field(default_factory=list)
34
- notes: str = ""
35
-
36
-
37
- @dataclass
38
- class GitChangeInfo:
39
- """Container for git change state.
40
-
41
- Injected into TraceViewRequirement rather than using a global singleton.
42
- This allows for better testing and explicit dependency management.
43
-
44
- Attributes:
45
- uncommitted_files: Set of spec-relative paths with uncommitted changes
46
- untracked_files: Set of spec-relative paths that are untracked (new)
47
- branch_changed_files: Set of spec-relative paths changed vs main branch
48
- committed_req_locations: Map of requirement ID to committed file path
49
- """
50
-
51
- uncommitted_files: Set[str] = field(default_factory=set)
52
- untracked_files: Set[str] = field(default_factory=set)
53
- branch_changed_files: Set[str] = field(default_factory=set)
54
- committed_req_locations: Dict[str, str] = field(default_factory=dict)
55
-
56
-
57
- @dataclass
58
- class TraceViewRequirement:
59
- """Requirement enriched with trace-view data.
60
-
61
- Wraps a core Requirement and adds:
62
- - Git state properties (is_uncommitted, is_branch_changed, is_moved)
63
- - Test coverage information
64
- - Implementation file references
65
-
66
- Implements: REQ-int-d00004-A (wraps elspais.core.models.Requirement)
67
- Implements: REQ-int-d00004-B (git state injected, not global)
68
- Implements: REQ-int-d00004-C (implementation files stored per-requirement)
69
- """
70
-
71
- core: CoreRequirement
72
- test_info: Optional[TestInfo] = None
73
- implementation_files: List[Tuple[str, int]] = field(default_factory=list)
74
- git_info: Optional[GitChangeInfo] = None
75
- external_spec_path: Optional[str] = None
76
-
77
- # --- Delegated core properties ---
78
-
79
- @property
80
- def id(self) -> str:
81
- """Requirement ID without REQ- prefix for display."""
82
- return self.core.id.replace("REQ-", "")
83
-
84
- @property
85
- def full_id(self) -> str:
86
- """Full requirement ID including REQ- prefix."""
87
- return self.core.id
88
-
89
- @property
90
- def title(self) -> str:
91
- return self.core.title
92
-
93
- @property
94
- def level(self) -> str:
95
- """Level normalized to uppercase (PRD, OPS, DEV)."""
96
- return self.core.level.upper()
97
-
98
- @property
99
- def status(self) -> str:
100
- return self.core.status
101
-
102
- @property
103
- def implements(self) -> List[str]:
104
- return self.core.implements
105
-
106
- @property
107
- def file_path(self) -> Path:
108
- return self.core.file_path or Path("unknown")
109
-
110
- @property
111
- def line_number(self) -> int:
112
- return self.core.line_number or 0
113
-
114
- @property
115
- def hash(self) -> str:
116
- return self.core.hash or ""
117
-
118
- @property
119
- def body(self) -> str:
120
- return self.core.body
121
-
122
- @property
123
- def rationale(self) -> str:
124
- return self.core.rationale or ""
125
-
126
- @property
127
- def is_roadmap(self) -> bool:
128
- return self.core.is_roadmap
129
-
130
- @property
131
- def is_conflict(self) -> bool:
132
- return self.core.is_conflict
133
-
134
- @property
135
- def conflict_with(self) -> str:
136
- return self.core.conflict_with
137
-
138
- @property
139
- def is_cycle(self) -> bool:
140
- """Check if requirement is part of a circular dependency.
141
-
142
- Note: Cycle detection happens during validation. This property
143
- defaults to False unless explicitly set via cycle_info.
144
- """
145
- return getattr(self, "_is_cycle", False)
146
-
147
- @is_cycle.setter
148
- def is_cycle(self, value: bool) -> None:
149
- self._is_cycle = value
150
-
151
- @property
152
- def cycle_path(self) -> str:
153
- """Get the cycle path if this requirement is part of a cycle.
154
-
155
- Returns empty string if not in a cycle.
156
- """
157
- return getattr(self, "_cycle_path", "")
158
-
159
- @cycle_path.setter
160
- def cycle_path(self, value: str) -> None:
161
- self._cycle_path = value
162
-
163
- @property
164
- def subdir(self) -> str:
165
- return self.core.subdir
166
-
167
- # --- Computed properties ---
168
-
169
- @property
170
- def repo_prefix(self) -> Optional[str]:
171
- """Extract repo prefix from ID (e.g., 'CAL-d00005' → 'CAL').
172
-
173
- Associated repo requirements have format PREFIX-{level}{number}.
174
- Core repo requirements have format {level}{number} (returns None).
175
- """
176
- import re
177
-
178
- # Match: optional prefix (uppercase letters), then level (p/o/d) and 5 digits
179
- match = re.match(r"^([A-Z]+-)?([pod]\d{5})$", self.id, re.IGNORECASE)
180
- if match and match.group(1):
181
- return match.group(1).rstrip("-")
182
- return None
183
-
184
- @property
185
- def display_filename(self) -> str:
186
- """Get displayable filename with repo prefix for external repos.
187
-
188
- Returns 'CAL/filename.md' for external repos, 'filename.md' for core.
189
- """
190
- prefix = self.repo_prefix
191
- if prefix:
192
- return f"{prefix}/{self.file_path.name}"
193
- return self.file_path.name
194
-
195
- # --- Git state properties ---
196
-
197
- def _get_spec_relative_path(self) -> str:
198
- """Get the spec-relative path for this requirement's file."""
199
- if self.is_roadmap:
200
- return f"spec/roadmap/{self.file_path.name}"
201
- if self.subdir:
202
- return f"spec/{self.subdir}/{self.file_path.name}"
203
- return f"spec/{self.file_path.name}"
204
-
205
- def _is_in_untracked_file(self) -> bool:
206
- """Check if requirement is in an untracked (new) file."""
207
- if not self.git_info:
208
- return False
209
- rel_path = self._get_spec_relative_path()
210
- return rel_path in self.git_info.untracked_files
211
-
212
- def _check_modified_in_fileset(self, file_set: Set[str]) -> bool:
213
- """Check if requirement is modified based on a set of changed files."""
214
- if not self.git_info:
215
- return False
216
-
217
- rel_path = self._get_spec_relative_path()
218
-
219
- # Check if file is untracked (new) - all REQs in new files are considered modified
220
- if rel_path in self.git_info.untracked_files:
221
- return True
222
-
223
- # Check if file is in the modified set
224
- return rel_path in file_set
225
-
226
- @property
227
- def is_uncommitted(self) -> bool:
228
- """Check if requirement has uncommitted changes."""
229
- if not self.git_info:
230
- return False
231
- return self._check_modified_in_fileset(self.git_info.uncommitted_files)
232
-
233
- @property
234
- def is_branch_changed(self) -> bool:
235
- """Check if requirement changed vs main branch."""
236
- if not self.git_info:
237
- return False
238
- return self._check_modified_in_fileset(self.git_info.branch_changed_files)
239
-
240
- @property
241
- def is_new(self) -> bool:
242
- """Check if requirement is in a new (untracked) file."""
243
- return self._is_in_untracked_file()
244
-
245
- @property
246
- def is_modified(self) -> bool:
247
- """Check if requirement has modified content but is not in a new file."""
248
- if self._is_in_untracked_file():
249
- return False # New files are "new", not "modified"
250
- return self.is_uncommitted
251
-
252
- @property
253
- def is_moved(self) -> bool:
254
- """Check if requirement was moved from a different file.
255
-
256
- A requirement is considered moved if:
257
- - It exists in the committed state in a different file, OR
258
- - It's in a new file but has a non-TBD hash (suggesting it was copied/moved)
259
- """
260
- if not self.git_info:
261
- return False
262
-
263
- current_path = self._get_spec_relative_path()
264
- committed_path = self.git_info.committed_req_locations.get(self.id)
265
-
266
- if committed_path is not None:
267
- # REQ existed in committed state - check if path changed
268
- return committed_path != current_path
269
-
270
- # REQ doesn't exist in committed state
271
- # If it's in a new file but has a real hash, it was likely moved/copied
272
- if self._is_in_untracked_file() and self.hash and self.hash != "TBD":
273
- return True
274
-
275
- return False
276
-
277
- # --- Factory methods ---
278
-
279
- @classmethod
280
- def from_core(
281
- cls,
282
- core_req: CoreRequirement,
283
- git_info: Optional[GitChangeInfo] = None,
284
- ) -> "TraceViewRequirement":
285
- """Create TraceViewRequirement from core Requirement.
286
-
287
- Args:
288
- core_req: The core Requirement to wrap
289
- git_info: Optional git change state (inject for testability)
290
-
291
- Returns:
292
- TraceViewRequirement instance
293
- """
294
- # Detect external repo paths (absolute paths)
295
- external_spec_path = None
296
- if core_req.file_path and core_req.file_path.is_absolute():
297
- external_spec_path = str(core_req.file_path)
298
-
299
- return cls(
300
- core=core_req,
301
- git_info=git_info,
302
- external_spec_path=external_spec_path,
303
- )
304
-
305
- @classmethod
306
- def from_dict(
307
- cls,
308
- req_id: str,
309
- data: Dict,
310
- git_info: Optional[GitChangeInfo] = None,
311
- ) -> "TraceViewRequirement":
312
- """Create TraceViewRequirement from elspais validate --json output.
313
-
314
- This provides backward compatibility with code that expects to create
315
- requirements from JSON data.
316
-
317
- Args:
318
- req_id: Full requirement ID (e.g., 'REQ-d00027')
319
- data: Dict from elspais JSON output
320
- git_info: Optional git change state
321
-
322
- Returns:
323
- TraceViewRequirement instance
324
- """
325
- # Map level to uppercase for consistency
326
- level_map = {
327
- "PRD": "PRD",
328
- "Ops": "OPS",
329
- "Dev": "DEV",
330
- "prd": "PRD",
331
- "ops": "OPS",
332
- "dev": "DEV",
333
- }
334
- level = data.get("level", "")
335
- subdir = data.get("subdir", "")
336
-
337
- # Create core requirement
338
- file_path_str = data.get("filePath", data.get("file", ""))
339
- file_path = Path(file_path_str) if file_path_str else None
340
-
341
- core_req = CoreRequirement(
342
- id=req_id,
343
- title=data.get("title", ""),
344
- level=level_map.get(level, level.upper()),
345
- status=data.get("status", "Active"),
346
- body=data.get("body", ""),
347
- implements=data.get("implements", []),
348
- rationale=data.get("rationale"),
349
- hash=data.get("hash"),
350
- file_path=file_path,
351
- line_number=data.get("line", 0),
352
- subdir=subdir,
353
- is_conflict=data.get("isConflict", False),
354
- conflict_with=data.get("conflictWith", "") or "",
355
- )
356
-
357
- # Create trace-view requirement
358
- tv_req = cls.from_core(core_req, git_info=git_info)
359
-
360
- # Add test info if provided
361
- test_count = data.get("test_count", 0)
362
- if test_count > 0:
363
- test_passed = data.get("test_passed", 0)
364
- test_status = "passed" if test_passed == test_count else "failed"
365
- tv_req.test_info = TestInfo(
366
- test_count=test_count,
367
- manual_test_count=0,
368
- test_status=test_status,
369
- test_details=data.get("test_result_files", []),
370
- notes="",
371
- )
372
-
373
- return tv_req
374
-
375
-
376
- # Backward compatibility aliases
377
- Requirement = TraceViewRequirement
378
- TraceabilityRequirement = TraceViewRequirement
@@ -1,63 +0,0 @@
1
- # Implements: REQ-int-d00002-C (Review server requires flask)
2
- """
3
- elspais.trace_view.review - Collaborative review system.
4
-
5
- Requires: pip install elspais[trace-review]
6
- """
7
-
8
- # Models are always available (no flask dependency)
9
- from elspais.trace_view.review.models import (
10
- Approval,
11
- ApprovalDecision,
12
- Comment,
13
- CommentPosition,
14
- PositionType,
15
- RequestState,
16
- ReviewConfig,
17
- ReviewFlag,
18
- ReviewPackage,
19
- ReviewSession,
20
- StatusRequest,
21
- Thread,
22
- )
23
-
24
-
25
- def _check_flask():
26
- try:
27
- import flask # noqa: F401
28
-
29
- return True
30
- except ImportError:
31
- return False
32
-
33
-
34
- FLASK_AVAILABLE = _check_flask()
35
-
36
- __all__ = [
37
- "FLASK_AVAILABLE",
38
- # Models (always available)
39
- "Comment",
40
- "CommentPosition",
41
- "Thread",
42
- "ReviewFlag",
43
- "StatusRequest",
44
- "Approval",
45
- "ReviewSession",
46
- "ReviewConfig",
47
- "ReviewPackage",
48
- "PositionType",
49
- "RequestState",
50
- "ApprovalDecision",
51
- ]
52
-
53
- if FLASK_AVAILABLE:
54
- from elspais.trace_view.review.server import create_app
55
-
56
- __all__.append("create_app")
57
- else:
58
-
59
- def create_app(*args, **kwargs):
60
- """Placeholder when flask is not installed."""
61
- raise ImportError(
62
- "Review server requires Flask. " "Install with: pip install elspais[trace-review]"
63
- )