elspais 0.9.3__py3-none-any.whl → 0.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- elspais/cli.py +99 -1
- elspais/commands/hash_cmd.py +72 -26
- elspais/commands/reformat_cmd.py +458 -0
- elspais/commands/trace.py +157 -3
- elspais/commands/validate.py +44 -16
- elspais/core/models.py +2 -0
- elspais/core/parser.py +68 -24
- elspais/reformat/__init__.py +50 -0
- elspais/reformat/detector.py +119 -0
- elspais/reformat/hierarchy.py +246 -0
- elspais/reformat/line_breaks.py +220 -0
- elspais/reformat/prompts.py +123 -0
- elspais/reformat/transformer.py +264 -0
- elspais/sponsors/__init__.py +432 -0
- elspais/trace_view/__init__.py +54 -0
- elspais/trace_view/coverage.py +183 -0
- elspais/trace_view/generators/__init__.py +12 -0
- elspais/trace_view/generators/base.py +329 -0
- elspais/trace_view/generators/csv.py +122 -0
- elspais/trace_view/generators/markdown.py +175 -0
- elspais/trace_view/html/__init__.py +31 -0
- elspais/trace_view/html/generator.py +1006 -0
- elspais/trace_view/html/templates/base.html +283 -0
- elspais/trace_view/html/templates/components/code_viewer_modal.html +14 -0
- elspais/trace_view/html/templates/components/file_picker_modal.html +20 -0
- elspais/trace_view/html/templates/components/legend_modal.html +69 -0
- elspais/trace_view/html/templates/components/review_panel.html +118 -0
- elspais/trace_view/html/templates/partials/review/help/help-panel.json +244 -0
- elspais/trace_view/html/templates/partials/review/help/onboarding.json +77 -0
- elspais/trace_view/html/templates/partials/review/help/tooltips.json +237 -0
- elspais/trace_view/html/templates/partials/review/review-comments.js +928 -0
- elspais/trace_view/html/templates/partials/review/review-data.js +961 -0
- elspais/trace_view/html/templates/partials/review/review-help.js +679 -0
- elspais/trace_view/html/templates/partials/review/review-init.js +177 -0
- elspais/trace_view/html/templates/partials/review/review-line-numbers.js +429 -0
- elspais/trace_view/html/templates/partials/review/review-packages.js +1029 -0
- elspais/trace_view/html/templates/partials/review/review-position.js +540 -0
- elspais/trace_view/html/templates/partials/review/review-resize.js +115 -0
- elspais/trace_view/html/templates/partials/review/review-status.js +659 -0
- elspais/trace_view/html/templates/partials/review/review-sync.js +992 -0
- elspais/trace_view/html/templates/partials/review-styles.css +2238 -0
- elspais/trace_view/html/templates/partials/scripts.js +1741 -0
- elspais/trace_view/html/templates/partials/styles.css +1756 -0
- elspais/trace_view/models.py +353 -0
- elspais/trace_view/review/__init__.py +60 -0
- elspais/trace_view/review/branches.py +1149 -0
- elspais/trace_view/review/models.py +1205 -0
- elspais/trace_view/review/position.py +609 -0
- elspais/trace_view/review/server.py +1056 -0
- elspais/trace_view/review/status.py +470 -0
- elspais/trace_view/review/storage.py +1367 -0
- elspais/trace_view/scanning.py +213 -0
- elspais/trace_view/specs/README.md +84 -0
- elspais/trace_view/specs/tv-d00001-template-architecture.md +36 -0
- elspais/trace_view/specs/tv-d00002-css-extraction.md +37 -0
- elspais/trace_view/specs/tv-d00003-js-extraction.md +43 -0
- elspais/trace_view/specs/tv-d00004-build-embedding.md +40 -0
- elspais/trace_view/specs/tv-d00005-test-format.md +78 -0
- elspais/trace_view/specs/tv-d00010-review-data-models.md +33 -0
- elspais/trace_view/specs/tv-d00011-review-storage.md +33 -0
- elspais/trace_view/specs/tv-d00012-position-resolution.md +33 -0
- elspais/trace_view/specs/tv-d00013-git-branches.md +31 -0
- elspais/trace_view/specs/tv-d00014-review-api-server.md +31 -0
- elspais/trace_view/specs/tv-d00015-status-modifier.md +27 -0
- elspais/trace_view/specs/tv-d00016-js-integration.md +33 -0
- elspais/trace_view/specs/tv-p00001-html-generator.md +33 -0
- elspais/trace_view/specs/tv-p00002-review-system.md +29 -0
- {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/METADATA +33 -18
- elspais-0.11.0.dist-info/RECORD +101 -0
- elspais-0.9.3.dist-info/RECORD +0 -40
- {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/WHEEL +0 -0
- {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/entry_points.txt +0 -0
- {elspais-0.9.3.dist-info → elspais-0.11.0.dist-info}/licenses/LICENSE +0 -0
elspais/cli.py
CHANGED
|
@@ -10,7 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
from typing import List, Optional
|
|
11
11
|
|
|
12
12
|
from elspais import __version__
|
|
13
|
-
from elspais.commands import analyze, changed, config_cmd, edit, hash_cmd, index, init, rules_cmd, trace, validate
|
|
13
|
+
from elspais.commands import analyze, changed, config_cmd, edit, hash_cmd, index, init, rules_cmd, trace, validate, reformat_cmd
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def create_parser() -> argparse.ArgumentParser:
|
|
@@ -97,6 +97,12 @@ Examples:
|
|
|
97
97
|
action="store_true",
|
|
98
98
|
help="Skip test scanning",
|
|
99
99
|
)
|
|
100
|
+
validate_parser.add_argument(
|
|
101
|
+
"--mode",
|
|
102
|
+
choices=["core", "combined"],
|
|
103
|
+
default="combined",
|
|
104
|
+
help="core: skip sponsor repos, combined: include all (default: combined)",
|
|
105
|
+
)
|
|
100
106
|
|
|
101
107
|
# trace command
|
|
102
108
|
trace_parser = subparsers.add_parser(
|
|
@@ -115,6 +121,49 @@ Examples:
|
|
|
115
121
|
help="Output file path",
|
|
116
122
|
metavar="PATH",
|
|
117
123
|
)
|
|
124
|
+
# trace-view enhanced options (requires elspais[trace-view])
|
|
125
|
+
trace_parser.add_argument(
|
|
126
|
+
"--view",
|
|
127
|
+
action="store_true",
|
|
128
|
+
help="Generate interactive HTML traceability view (requires trace-view extra)",
|
|
129
|
+
)
|
|
130
|
+
trace_parser.add_argument(
|
|
131
|
+
"--embed-content",
|
|
132
|
+
action="store_true",
|
|
133
|
+
help="Embed full requirement content in HTML output",
|
|
134
|
+
)
|
|
135
|
+
trace_parser.add_argument(
|
|
136
|
+
"--edit-mode",
|
|
137
|
+
action="store_true",
|
|
138
|
+
help="Include edit mode UI in HTML output",
|
|
139
|
+
)
|
|
140
|
+
trace_parser.add_argument(
|
|
141
|
+
"--review-mode",
|
|
142
|
+
action="store_true",
|
|
143
|
+
help="Include review mode UI in HTML output",
|
|
144
|
+
)
|
|
145
|
+
trace_parser.add_argument(
|
|
146
|
+
"--server",
|
|
147
|
+
action="store_true",
|
|
148
|
+
help="Start review server (requires trace-review extra)",
|
|
149
|
+
)
|
|
150
|
+
trace_parser.add_argument(
|
|
151
|
+
"--port",
|
|
152
|
+
type=int,
|
|
153
|
+
default=8080,
|
|
154
|
+
help="Port for review server (default: 8080)",
|
|
155
|
+
)
|
|
156
|
+
trace_parser.add_argument(
|
|
157
|
+
"--mode",
|
|
158
|
+
choices=["core", "sponsor", "combined"],
|
|
159
|
+
default="core",
|
|
160
|
+
help="Report mode: core, sponsor, or combined (default: core)",
|
|
161
|
+
)
|
|
162
|
+
trace_parser.add_argument(
|
|
163
|
+
"--sponsor",
|
|
164
|
+
help="Sponsor name for sponsor-specific reports",
|
|
165
|
+
metavar="NAME",
|
|
166
|
+
)
|
|
118
167
|
|
|
119
168
|
# hash command
|
|
120
169
|
hash_parser = subparsers.add_parser(
|
|
@@ -393,6 +442,53 @@ Examples:
|
|
|
393
442
|
help="Content rule file name (e.g., 'AI-AGENT.md')",
|
|
394
443
|
)
|
|
395
444
|
|
|
445
|
+
# reformat-with-claude command
|
|
446
|
+
reformat_parser = subparsers.add_parser(
|
|
447
|
+
"reformat-with-claude",
|
|
448
|
+
help="Reformat requirements using AI (Acceptance Criteria -> Assertions)",
|
|
449
|
+
)
|
|
450
|
+
reformat_parser.add_argument(
|
|
451
|
+
"--start-req",
|
|
452
|
+
help="Starting requirement ID (default: all PRD requirements)",
|
|
453
|
+
metavar="ID",
|
|
454
|
+
)
|
|
455
|
+
reformat_parser.add_argument(
|
|
456
|
+
"--depth",
|
|
457
|
+
type=int,
|
|
458
|
+
help="Maximum traversal depth (default: unlimited)",
|
|
459
|
+
)
|
|
460
|
+
reformat_parser.add_argument(
|
|
461
|
+
"--dry-run",
|
|
462
|
+
action="store_true",
|
|
463
|
+
help="Preview changes without applying",
|
|
464
|
+
)
|
|
465
|
+
reformat_parser.add_argument(
|
|
466
|
+
"--backup",
|
|
467
|
+
action="store_true",
|
|
468
|
+
help="Create .bak files before editing",
|
|
469
|
+
)
|
|
470
|
+
reformat_parser.add_argument(
|
|
471
|
+
"--force",
|
|
472
|
+
action="store_true",
|
|
473
|
+
help="Reformat even if already in new format",
|
|
474
|
+
)
|
|
475
|
+
reformat_parser.add_argument(
|
|
476
|
+
"--fix-line-breaks",
|
|
477
|
+
action="store_true",
|
|
478
|
+
help="Normalize line breaks (remove extra blank lines)",
|
|
479
|
+
)
|
|
480
|
+
reformat_parser.add_argument(
|
|
481
|
+
"--line-breaks-only",
|
|
482
|
+
action="store_true",
|
|
483
|
+
help="Only fix line breaks, skip AI-based reformatting",
|
|
484
|
+
)
|
|
485
|
+
reformat_parser.add_argument(
|
|
486
|
+
"--mode",
|
|
487
|
+
choices=["combined", "core-only", "local-only"],
|
|
488
|
+
default="combined",
|
|
489
|
+
help="Which repos to include in hierarchy (default: combined)",
|
|
490
|
+
)
|
|
491
|
+
|
|
396
492
|
# mcp command
|
|
397
493
|
mcp_parser = subparsers.add_parser(
|
|
398
494
|
"mcp",
|
|
@@ -489,6 +585,8 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
489
585
|
return config_cmd.run(args)
|
|
490
586
|
elif args.command == "rules":
|
|
491
587
|
return rules_cmd.run(args)
|
|
588
|
+
elif args.command == "reformat-with-claude":
|
|
589
|
+
return reformat_cmd.run(args)
|
|
492
590
|
elif args.command == "mcp":
|
|
493
591
|
return mcp_command(args)
|
|
494
592
|
else:
|
elspais/commands/hash_cmd.py
CHANGED
|
@@ -104,8 +104,16 @@ def run_update(args: argparse.Namespace) -> int:
|
|
|
104
104
|
else:
|
|
105
105
|
print(f"Updating {len(updates)} hashes...")
|
|
106
106
|
for req_id, req, new_hash in updates:
|
|
107
|
-
update_hash_in_file(req, new_hash)
|
|
108
|
-
|
|
107
|
+
result = update_hash_in_file(req, new_hash)
|
|
108
|
+
if result['updated']:
|
|
109
|
+
print(f" ✓ {req_id}")
|
|
110
|
+
old_hash = result['old_hash'] or "(none)"
|
|
111
|
+
print(f" [INFO] Hash: {old_hash} -> {result['new_hash']}")
|
|
112
|
+
if result['title_fixed']:
|
|
113
|
+
print(f" [INFO] Title fixed: \"{result['old_title']}\" -> \"{req.title}\"")
|
|
114
|
+
else:
|
|
115
|
+
print(f" ✗ {req_id}")
|
|
116
|
+
print(f" [WARN] Could not find End marker to update")
|
|
109
117
|
|
|
110
118
|
return 0
|
|
111
119
|
|
|
@@ -138,37 +146,75 @@ def load_requirements(args: argparse.Namespace) -> tuple:
|
|
|
138
146
|
return config, requirements
|
|
139
147
|
|
|
140
148
|
|
|
141
|
-
def update_hash_in_file(req: Requirement, new_hash: str) ->
|
|
149
|
+
def update_hash_in_file(req: Requirement, new_hash: str) -> dict:
|
|
142
150
|
"""Update the hash in the requirement's source file.
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
Finds the End marker by the old hash value, then replaces the entire line
|
|
153
|
+
with the correct title and new hash. This handles cases where the End
|
|
154
|
+
marker title doesn't match the header title.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
req: Requirement object with file_path, title, and hash
|
|
158
|
+
new_hash: New hash value to write
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Dict with change info:
|
|
162
|
+
- 'updated': bool - whether file was modified
|
|
163
|
+
- 'old_hash': str - previous hash (or None)
|
|
164
|
+
- 'new_hash': str - new hash value
|
|
165
|
+
- 'title_fixed': bool - whether title was corrected
|
|
166
|
+
- 'old_title': str - previous title (if different)
|
|
147
167
|
"""
|
|
168
|
+
import re
|
|
169
|
+
|
|
170
|
+
result = {
|
|
171
|
+
'updated': False,
|
|
172
|
+
'old_hash': req.hash,
|
|
173
|
+
'new_hash': new_hash,
|
|
174
|
+
'title_fixed': False,
|
|
175
|
+
'old_title': None,
|
|
176
|
+
}
|
|
177
|
+
|
|
148
178
|
if not req.file_path:
|
|
149
|
-
return
|
|
179
|
+
return result
|
|
150
180
|
|
|
151
181
|
content = req.file_path.read_text(encoding="utf-8")
|
|
152
|
-
|
|
153
|
-
import re
|
|
182
|
+
new_end_line = f"*End* *{req.title}* | **Hash**: {new_hash}"
|
|
154
183
|
|
|
155
184
|
if req.hash:
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
content,
|
|
163
|
-
|
|
185
|
+
# Strategy: Try title first (most specific), then hash if title not found
|
|
186
|
+
# This handles both: (1) normal case, (2) mismatched title case
|
|
187
|
+
|
|
188
|
+
# First try: match by correct title (handles case where titles match)
|
|
189
|
+
pattern_by_title = rf"^\*End\*\s+\*{re.escape(req.title)}\*\s*\|\s*\*\*Hash\*\*:\s*[a-fA-F0-9]+\s*$"
|
|
190
|
+
if re.search(pattern_by_title, content, re.MULTILINE):
|
|
191
|
+
content, count = re.subn(pattern_by_title, new_end_line, content, flags=re.MULTILINE)
|
|
192
|
+
if count > 0:
|
|
193
|
+
result['updated'] = True
|
|
194
|
+
else:
|
|
195
|
+
# Second try: find by hash value (handles mismatched title)
|
|
196
|
+
# Pattern: *End* *AnyTitle* | **Hash**: oldhash
|
|
197
|
+
pattern_by_hash = rf"^\*End\*\s+\*([^*]+)\*\s*\|\s*\*\*Hash\*\*:\s*{re.escape(req.hash)}\s*$"
|
|
198
|
+
match = re.search(pattern_by_hash, content, re.MULTILINE)
|
|
199
|
+
|
|
200
|
+
if match:
|
|
201
|
+
old_title = match.group(1)
|
|
202
|
+
if old_title != req.title:
|
|
203
|
+
result['title_fixed'] = True
|
|
204
|
+
result['old_title'] = old_title
|
|
205
|
+
|
|
206
|
+
# Replace entire line (only first match to avoid affecting other reqs)
|
|
207
|
+
content = re.sub(pattern_by_hash, new_end_line, content, count=1, flags=re.MULTILINE)
|
|
208
|
+
result['updated'] = True
|
|
164
209
|
else:
|
|
165
|
-
# Add hash to end marker
|
|
210
|
+
# Add hash to end marker (no existing hash)
|
|
166
211
|
# Pattern: *End* *Title* (without hash)
|
|
167
|
-
|
|
168
|
-
content = re.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
212
|
+
pattern = rf"^(\*End\*\s+\*{re.escape(req.title)}\*)(?!\s*\|\s*\*\*Hash\*\*)\s*$"
|
|
213
|
+
content, count = re.subn(pattern, new_end_line, content, flags=re.MULTILINE)
|
|
214
|
+
if count > 0:
|
|
215
|
+
result['updated'] = True
|
|
216
|
+
|
|
217
|
+
if result['updated']:
|
|
218
|
+
req.file_path.write_text(content, encoding="utf-8")
|
|
219
|
+
|
|
220
|
+
return result
|