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.
- elspais/__init__.py +1 -10
- elspais/{sponsors/__init__.py → associates.py} +102 -56
- elspais/cli.py +366 -69
- elspais/commands/__init__.py +9 -3
- elspais/commands/analyze.py +118 -169
- elspais/commands/changed.py +12 -23
- elspais/commands/config_cmd.py +10 -13
- elspais/commands/edit.py +33 -13
- elspais/commands/example_cmd.py +319 -0
- elspais/commands/hash_cmd.py +161 -183
- elspais/commands/health.py +1177 -0
- elspais/commands/index.py +98 -115
- elspais/commands/init.py +99 -22
- elspais/commands/reformat_cmd.py +41 -433
- elspais/commands/rules_cmd.py +2 -2
- elspais/commands/trace.py +443 -324
- elspais/commands/validate.py +193 -411
- elspais/config/__init__.py +799 -5
- elspais/{core/content_rules.py → content_rules.py} +20 -2
- elspais/docs/cli/assertions.md +67 -0
- elspais/docs/cli/commands.md +304 -0
- elspais/docs/cli/config.md +262 -0
- elspais/docs/cli/format.md +66 -0
- elspais/docs/cli/git.md +45 -0
- elspais/docs/cli/health.md +190 -0
- elspais/docs/cli/hierarchy.md +60 -0
- elspais/docs/cli/ignore.md +72 -0
- elspais/docs/cli/mcp.md +245 -0
- elspais/docs/cli/quickstart.md +58 -0
- elspais/docs/cli/traceability.md +89 -0
- elspais/docs/cli/validation.md +96 -0
- elspais/graph/GraphNode.py +383 -0
- elspais/graph/__init__.py +40 -0
- elspais/graph/annotators.py +927 -0
- elspais/graph/builder.py +1886 -0
- elspais/graph/deserializer.py +248 -0
- elspais/graph/factory.py +284 -0
- elspais/graph/metrics.py +127 -0
- elspais/graph/mutations.py +161 -0
- elspais/graph/parsers/__init__.py +156 -0
- elspais/graph/parsers/code.py +213 -0
- elspais/graph/parsers/comments.py +112 -0
- elspais/graph/parsers/config_helpers.py +29 -0
- elspais/graph/parsers/heredocs.py +225 -0
- elspais/graph/parsers/journey.py +131 -0
- elspais/graph/parsers/remainder.py +79 -0
- elspais/graph/parsers/requirement.py +347 -0
- elspais/graph/parsers/results/__init__.py +6 -0
- elspais/graph/parsers/results/junit_xml.py +229 -0
- elspais/graph/parsers/results/pytest_json.py +313 -0
- elspais/graph/parsers/test.py +305 -0
- elspais/graph/relations.py +78 -0
- elspais/graph/serialize.py +216 -0
- elspais/html/__init__.py +8 -0
- elspais/html/generator.py +731 -0
- elspais/html/templates/trace_view.html.j2 +2151 -0
- elspais/mcp/__init__.py +45 -29
- elspais/mcp/__main__.py +5 -1
- elspais/mcp/file_mutations.py +138 -0
- elspais/mcp/server.py +1998 -244
- elspais/testing/__init__.py +3 -3
- elspais/testing/config.py +3 -0
- elspais/testing/mapper.py +1 -1
- elspais/testing/scanner.py +301 -12
- elspais/utilities/__init__.py +1 -0
- elspais/utilities/docs_loader.py +115 -0
- elspais/utilities/git.py +607 -0
- elspais/{core → utilities}/hasher.py +8 -22
- elspais/utilities/md_renderer.py +189 -0
- elspais/{core → utilities}/patterns.py +56 -51
- elspais/utilities/reference_config.py +626 -0
- elspais/validation/__init__.py +19 -0
- elspais/validation/format.py +264 -0
- {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/METADATA +7 -4
- elspais-0.43.5.dist-info/RECORD +80 -0
- elspais/config/defaults.py +0 -179
- elspais/config/loader.py +0 -494
- elspais/core/__init__.py +0 -21
- elspais/core/git.py +0 -346
- elspais/core/models.py +0 -320
- elspais/core/parser.py +0 -639
- elspais/core/rules.py +0 -509
- elspais/mcp/context.py +0 -172
- elspais/mcp/serializers.py +0 -112
- elspais/reformat/__init__.py +0 -50
- elspais/reformat/detector.py +0 -112
- elspais/reformat/hierarchy.py +0 -247
- elspais/reformat/line_breaks.py +0 -218
- elspais/reformat/prompts.py +0 -133
- elspais/reformat/transformer.py +0 -266
- elspais/trace_view/__init__.py +0 -55
- elspais/trace_view/coverage.py +0 -183
- elspais/trace_view/generators/__init__.py +0 -12
- elspais/trace_view/generators/base.py +0 -334
- elspais/trace_view/generators/csv.py +0 -118
- elspais/trace_view/generators/markdown.py +0 -170
- elspais/trace_view/html/__init__.py +0 -33
- elspais/trace_view/html/generator.py +0 -1140
- elspais/trace_view/html/templates/base.html +0 -283
- elspais/trace_view/html/templates/components/code_viewer_modal.html +0 -14
- elspais/trace_view/html/templates/components/file_picker_modal.html +0 -20
- elspais/trace_view/html/templates/components/legend_modal.html +0 -69
- elspais/trace_view/html/templates/components/review_panel.html +0 -118
- elspais/trace_view/html/templates/partials/review/help/help-panel.json +0 -244
- elspais/trace_view/html/templates/partials/review/help/onboarding.json +0 -77
- elspais/trace_view/html/templates/partials/review/help/tooltips.json +0 -237
- elspais/trace_view/html/templates/partials/review/review-comments.js +0 -928
- elspais/trace_view/html/templates/partials/review/review-data.js +0 -961
- elspais/trace_view/html/templates/partials/review/review-help.js +0 -679
- elspais/trace_view/html/templates/partials/review/review-init.js +0 -177
- elspais/trace_view/html/templates/partials/review/review-line-numbers.js +0 -429
- elspais/trace_view/html/templates/partials/review/review-packages.js +0 -1029
- elspais/trace_view/html/templates/partials/review/review-position.js +0 -540
- elspais/trace_view/html/templates/partials/review/review-resize.js +0 -115
- elspais/trace_view/html/templates/partials/review/review-status.js +0 -659
- elspais/trace_view/html/templates/partials/review/review-sync.js +0 -992
- elspais/trace_view/html/templates/partials/review-styles.css +0 -2238
- elspais/trace_view/html/templates/partials/scripts.js +0 -1741
- elspais/trace_view/html/templates/partials/styles.css +0 -1756
- elspais/trace_view/models.py +0 -378
- elspais/trace_view/review/__init__.py +0 -63
- elspais/trace_view/review/branches.py +0 -1142
- elspais/trace_view/review/models.py +0 -1200
- elspais/trace_view/review/position.py +0 -591
- elspais/trace_view/review/server.py +0 -1032
- elspais/trace_view/review/status.py +0 -455
- elspais/trace_view/review/storage.py +0 -1343
- elspais/trace_view/scanning.py +0 -213
- elspais/trace_view/specs/README.md +0 -84
- elspais/trace_view/specs/tv-d00001-template-architecture.md +0 -36
- elspais/trace_view/specs/tv-d00002-css-extraction.md +0 -37
- elspais/trace_view/specs/tv-d00003-js-extraction.md +0 -43
- elspais/trace_view/specs/tv-d00004-build-embedding.md +0 -40
- elspais/trace_view/specs/tv-d00005-test-format.md +0 -78
- elspais/trace_view/specs/tv-d00010-review-data-models.md +0 -33
- elspais/trace_view/specs/tv-d00011-review-storage.md +0 -33
- elspais/trace_view/specs/tv-d00012-position-resolution.md +0 -33
- elspais/trace_view/specs/tv-d00013-git-branches.md +0 -31
- elspais/trace_view/specs/tv-d00014-review-api-server.md +0 -31
- elspais/trace_view/specs/tv-d00015-status-modifier.md +0 -27
- elspais/trace_view/specs/tv-d00016-js-integration.md +0 -33
- elspais/trace_view/specs/tv-p00001-html-generator.md +0 -33
- elspais/trace_view/specs/tv-p00002-review-system.md +0 -29
- elspais-0.11.2.dist-info/RECORD +0 -101
- {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/WHEEL +0 -0
- {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/entry_points.txt +0 -0
- {elspais-0.11.2.dist-info → elspais-0.43.5.dist-info}/licenses/LICENSE +0 -0
elspais/mcp/__init__.py
CHANGED
|
@@ -1,44 +1,60 @@
|
|
|
1
|
-
"""
|
|
2
|
-
elspais.mcp - MCP (Model Context Protocol) server for elspais.
|
|
1
|
+
"""elspais.mcp - Model Context Protocol server for elspais.
|
|
3
2
|
|
|
4
3
|
This module provides an MCP server that exposes elspais functionality
|
|
5
|
-
to AI agents
|
|
6
|
-
|
|
7
|
-
pip install elspais[mcp]
|
|
4
|
+
to AI agents. The server is a pure interface layer that consumes
|
|
5
|
+
TraceGraph directly (REQ-p00060-B).
|
|
8
6
|
|
|
9
7
|
Usage:
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
# Check if MCP is available
|
|
9
|
+
from elspais.mcp import MCP_AVAILABLE
|
|
10
|
+
|
|
11
|
+
if MCP_AVAILABLE:
|
|
12
|
+
from elspais.mcp import create_server, run_server
|
|
13
|
+
|
|
14
|
+
# Create server
|
|
15
|
+
server = create_server()
|
|
16
|
+
|
|
17
|
+
# Or run directly
|
|
18
|
+
run_server()
|
|
12
19
|
"""
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
from
|
|
16
|
-
serialize_assertion,
|
|
17
|
-
serialize_content_rule,
|
|
18
|
-
serialize_requirement,
|
|
19
|
-
serialize_requirement_summary,
|
|
20
|
-
serialize_violation,
|
|
21
|
-
)
|
|
21
|
+
try:
|
|
22
|
+
from mcp.server.fastmcp import FastMCP
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"serialize_requirement",
|
|
28
|
-
"serialize_requirement_summary",
|
|
29
|
-
"serialize_violation",
|
|
30
|
-
]
|
|
24
|
+
MCP_AVAILABLE = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
MCP_AVAILABLE = False
|
|
27
|
+
FastMCP = None
|
|
31
28
|
|
|
32
29
|
|
|
33
|
-
def create_server(
|
|
34
|
-
"""Create MCP server
|
|
30
|
+
def create_server(*args, **kwargs):
|
|
31
|
+
"""Create the MCP server.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ImportError: If MCP dependencies are not installed.
|
|
35
|
+
"""
|
|
36
|
+
if not MCP_AVAILABLE:
|
|
37
|
+
raise ImportError("MCP dependencies not installed. Install with: pip install elspais[mcp]")
|
|
35
38
|
from elspais.mcp.server import create_server as _create
|
|
36
39
|
|
|
37
|
-
return _create(
|
|
40
|
+
return _create(*args, **kwargs)
|
|
41
|
+
|
|
38
42
|
|
|
43
|
+
def run_server(*args, **kwargs):
|
|
44
|
+
"""Run the MCP server.
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
Raises:
|
|
47
|
+
ImportError: If MCP dependencies are not installed.
|
|
48
|
+
"""
|
|
49
|
+
if not MCP_AVAILABLE:
|
|
50
|
+
raise ImportError("MCP dependencies not installed. Install with: pip install elspais[mcp]")
|
|
42
51
|
from elspais.mcp.server import run_server as _run
|
|
43
52
|
|
|
44
|
-
return _run(
|
|
53
|
+
return _run(*args, **kwargs)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"MCP_AVAILABLE",
|
|
58
|
+
"create_server",
|
|
59
|
+
"run_server",
|
|
60
|
+
]
|
elspais/mcp/__main__.py
CHANGED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""File mutation helpers for CLI commands.
|
|
2
|
+
|
|
3
|
+
Provides functions to safely modify spec files on disk:
|
|
4
|
+
- update_hash_in_file: Update the hash in a requirement's End marker
|
|
5
|
+
- add_status_to_file: Add a missing Status field to metadata
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def update_hash_in_file(
|
|
15
|
+
file_path: Path,
|
|
16
|
+
req_id: str,
|
|
17
|
+
new_hash: str,
|
|
18
|
+
) -> bool:
|
|
19
|
+
"""Update the hash in a spec file for a requirement.
|
|
20
|
+
|
|
21
|
+
Finds the end marker line: *End* *Title* | **Hash**: old_hash
|
|
22
|
+
And updates to: *End* *Title* | **Hash**: new_hash
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
file_path: Path to the spec file.
|
|
26
|
+
req_id: The requirement ID (e.g., 'REQ-p00001').
|
|
27
|
+
new_hash: The new hash value to set.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
True if the hash was updated, False if the requirement was not found.
|
|
31
|
+
"""
|
|
32
|
+
file_path = Path(file_path)
|
|
33
|
+
content = file_path.read_text(encoding="utf-8")
|
|
34
|
+
|
|
35
|
+
# Find the requirement block to locate its End marker
|
|
36
|
+
# First find the header line: ## REQ-xxx: Title
|
|
37
|
+
header_pattern = re.compile(
|
|
38
|
+
rf"^(##+ {re.escape(req_id)}:\s*(.+?)\s*)$",
|
|
39
|
+
re.MULTILINE,
|
|
40
|
+
)
|
|
41
|
+
header_match = header_pattern.search(content)
|
|
42
|
+
if not header_match:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
# Now find the End marker after this header
|
|
46
|
+
# Pattern: *End* *Title* | **Hash**: xxxxxxxx
|
|
47
|
+
# The title in the End marker should match the requirement's title
|
|
48
|
+
start_pos = header_match.end()
|
|
49
|
+
|
|
50
|
+
# Look for the End marker pattern anywhere after the header
|
|
51
|
+
# It can have various title formats, so we just look for *End* ... **Hash**:
|
|
52
|
+
end_pattern = re.compile(
|
|
53
|
+
r"(\*End\*\s+\*.+?\*\s*\|\s*\*\*Hash\*\*:\s*)([a-fA-F0-9]+)",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Search from the header position
|
|
57
|
+
end_match = end_pattern.search(content, pos=start_pos)
|
|
58
|
+
if not end_match:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
# Check if this End marker is still part of our requirement block
|
|
62
|
+
# (i.e., no new requirement header between our header and this End marker)
|
|
63
|
+
next_header_pattern = re.compile(r"^##+ REQ-", re.MULTILINE)
|
|
64
|
+
next_header_match = next_header_pattern.search(content, pos=start_pos)
|
|
65
|
+
|
|
66
|
+
if next_header_match and next_header_match.start() < end_match.start():
|
|
67
|
+
# This End marker belongs to a different requirement
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
# Replace just the hash value
|
|
71
|
+
new_content = content[: end_match.start(2)] + new_hash + content[end_match.end(2) :]
|
|
72
|
+
|
|
73
|
+
file_path.write_text(new_content, encoding="utf-8")
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def add_status_to_file(
|
|
78
|
+
file_path: Path,
|
|
79
|
+
req_id: str,
|
|
80
|
+
status: str,
|
|
81
|
+
) -> bool:
|
|
82
|
+
"""Add a Status field to a requirement's metadata line.
|
|
83
|
+
|
|
84
|
+
Finds the metadata line: **Level**: XXX | **Implements**: YYY
|
|
85
|
+
And adds Status: **Level**: XXX | **Status**: ZZZ | **Implements**: YYY
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
file_path: Path to the spec file.
|
|
89
|
+
req_id: The requirement ID (e.g., 'REQ-p00001').
|
|
90
|
+
status: The status value to add (e.g., 'Active').
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True if status was added, False if req not found or already has status.
|
|
94
|
+
"""
|
|
95
|
+
file_path = Path(file_path)
|
|
96
|
+
content = file_path.read_text(encoding="utf-8")
|
|
97
|
+
|
|
98
|
+
# Find the requirement header
|
|
99
|
+
header_pattern = re.compile(
|
|
100
|
+
rf"^(##+ {re.escape(req_id)}:\s*.+?)$",
|
|
101
|
+
re.MULTILINE,
|
|
102
|
+
)
|
|
103
|
+
header_match = header_pattern.search(content)
|
|
104
|
+
if not header_match:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
start_pos = header_match.end()
|
|
108
|
+
|
|
109
|
+
# Find the next requirement header or end of file to bound our search
|
|
110
|
+
next_header_pattern = re.compile(r"^##+ REQ-", re.MULTILINE)
|
|
111
|
+
next_header_match = next_header_pattern.search(content, pos=start_pos)
|
|
112
|
+
end_pos = next_header_match.start() if next_header_match else len(content)
|
|
113
|
+
|
|
114
|
+
# Search for metadata line within this requirement block
|
|
115
|
+
block = content[start_pos:end_pos]
|
|
116
|
+
|
|
117
|
+
# Check if status already exists
|
|
118
|
+
if re.search(r"\*\*Status\*\*:", block):
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
# Find the Level line: **Level**: XXX | ...
|
|
122
|
+
level_pattern = re.compile(
|
|
123
|
+
r"(\*\*Level\*\*:\s*\w+)(\s*\|)",
|
|
124
|
+
)
|
|
125
|
+
level_match = level_pattern.search(block)
|
|
126
|
+
if not level_match:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
# Insert status after Level
|
|
130
|
+
new_block = (
|
|
131
|
+
block[: level_match.end(1)] + f" | **Status**: {status}" + block[level_match.start(2) :]
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Rebuild the full content
|
|
135
|
+
new_content = content[:start_pos] + new_block + content[end_pos:]
|
|
136
|
+
|
|
137
|
+
file_path.write_text(new_content, encoding="utf-8")
|
|
138
|
+
return True
|