elspais 0.11.1__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 (148) hide show
  1. elspais/__init__.py +2 -11
  2. elspais/{sponsors/__init__.py → associates.py} +102 -58
  3. elspais/cli.py +395 -79
  4. elspais/commands/__init__.py +9 -3
  5. elspais/commands/analyze.py +121 -173
  6. elspais/commands/changed.py +15 -30
  7. elspais/commands/config_cmd.py +13 -16
  8. elspais/commands/edit.py +60 -44
  9. elspais/commands/example_cmd.py +319 -0
  10. elspais/commands/hash_cmd.py +167 -183
  11. elspais/commands/health.py +1177 -0
  12. elspais/commands/index.py +98 -114
  13. elspais/commands/init.py +103 -26
  14. elspais/commands/reformat_cmd.py +41 -444
  15. elspais/commands/rules_cmd.py +7 -3
  16. elspais/commands/trace.py +444 -321
  17. elspais/commands/validate.py +195 -415
  18. elspais/config/__init__.py +799 -5
  19. elspais/{core/content_rules.py → content_rules.py} +20 -3
  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 +47 -29
  58. elspais/mcp/__main__.py +5 -1
  59. elspais/mcp/file_mutations.py +138 -0
  60. elspais/mcp/server.py +2016 -247
  61. elspais/testing/__init__.py +4 -4
  62. elspais/testing/config.py +3 -0
  63. elspais/testing/mapper.py +1 -1
  64. elspais/testing/result_parser.py +25 -21
  65. elspais/testing/scanner.py +301 -12
  66. elspais/utilities/__init__.py +1 -0
  67. elspais/utilities/docs_loader.py +115 -0
  68. elspais/utilities/git.py +607 -0
  69. elspais/{core → utilities}/hasher.py +8 -22
  70. elspais/utilities/md_renderer.py +189 -0
  71. elspais/{core → utilities}/patterns.py +58 -57
  72. elspais/utilities/reference_config.py +626 -0
  73. elspais/validation/__init__.py +19 -0
  74. elspais/validation/format.py +264 -0
  75. {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/METADATA +7 -4
  76. elspais-0.43.5.dist-info/RECORD +80 -0
  77. elspais/config/defaults.py +0 -173
  78. elspais/config/loader.py +0 -494
  79. elspais/core/__init__.py +0 -21
  80. elspais/core/git.py +0 -352
  81. elspais/core/models.py +0 -320
  82. elspais/core/parser.py +0 -640
  83. elspais/core/rules.py +0 -514
  84. elspais/mcp/context.py +0 -171
  85. elspais/mcp/serializers.py +0 -112
  86. elspais/reformat/__init__.py +0 -50
  87. elspais/reformat/detector.py +0 -119
  88. elspais/reformat/hierarchy.py +0 -246
  89. elspais/reformat/line_breaks.py +0 -220
  90. elspais/reformat/prompts.py +0 -123
  91. elspais/reformat/transformer.py +0 -264
  92. elspais/trace_view/__init__.py +0 -54
  93. elspais/trace_view/coverage.py +0 -183
  94. elspais/trace_view/generators/__init__.py +0 -12
  95. elspais/trace_view/generators/base.py +0 -329
  96. elspais/trace_view/generators/csv.py +0 -122
  97. elspais/trace_view/generators/markdown.py +0 -175
  98. elspais/trace_view/html/__init__.py +0 -31
  99. elspais/trace_view/html/generator.py +0 -1006
  100. elspais/trace_view/html/templates/base.html +0 -283
  101. elspais/trace_view/html/templates/components/code_viewer_modal.html +0 -14
  102. elspais/trace_view/html/templates/components/file_picker_modal.html +0 -20
  103. elspais/trace_view/html/templates/components/legend_modal.html +0 -69
  104. elspais/trace_view/html/templates/components/review_panel.html +0 -118
  105. elspais/trace_view/html/templates/partials/review/help/help-panel.json +0 -244
  106. elspais/trace_view/html/templates/partials/review/help/onboarding.json +0 -77
  107. elspais/trace_view/html/templates/partials/review/help/tooltips.json +0 -237
  108. elspais/trace_view/html/templates/partials/review/review-comments.js +0 -928
  109. elspais/trace_view/html/templates/partials/review/review-data.js +0 -961
  110. elspais/trace_view/html/templates/partials/review/review-help.js +0 -679
  111. elspais/trace_view/html/templates/partials/review/review-init.js +0 -177
  112. elspais/trace_view/html/templates/partials/review/review-line-numbers.js +0 -429
  113. elspais/trace_view/html/templates/partials/review/review-packages.js +0 -1029
  114. elspais/trace_view/html/templates/partials/review/review-position.js +0 -540
  115. elspais/trace_view/html/templates/partials/review/review-resize.js +0 -115
  116. elspais/trace_view/html/templates/partials/review/review-status.js +0 -659
  117. elspais/trace_view/html/templates/partials/review/review-sync.js +0 -992
  118. elspais/trace_view/html/templates/partials/review-styles.css +0 -2238
  119. elspais/trace_view/html/templates/partials/scripts.js +0 -1741
  120. elspais/trace_view/html/templates/partials/styles.css +0 -1756
  121. elspais/trace_view/models.py +0 -353
  122. elspais/trace_view/review/__init__.py +0 -60
  123. elspais/trace_view/review/branches.py +0 -1149
  124. elspais/trace_view/review/models.py +0 -1205
  125. elspais/trace_view/review/position.py +0 -609
  126. elspais/trace_view/review/server.py +0 -1056
  127. elspais/trace_view/review/status.py +0 -470
  128. elspais/trace_view/review/storage.py +0 -1367
  129. elspais/trace_view/scanning.py +0 -213
  130. elspais/trace_view/specs/README.md +0 -84
  131. elspais/trace_view/specs/tv-d00001-template-architecture.md +0 -36
  132. elspais/trace_view/specs/tv-d00002-css-extraction.md +0 -37
  133. elspais/trace_view/specs/tv-d00003-js-extraction.md +0 -43
  134. elspais/trace_view/specs/tv-d00004-build-embedding.md +0 -40
  135. elspais/trace_view/specs/tv-d00005-test-format.md +0 -78
  136. elspais/trace_view/specs/tv-d00010-review-data-models.md +0 -33
  137. elspais/trace_view/specs/tv-d00011-review-storage.md +0 -33
  138. elspais/trace_view/specs/tv-d00012-position-resolution.md +0 -33
  139. elspais/trace_view/specs/tv-d00013-git-branches.md +0 -31
  140. elspais/trace_view/specs/tv-d00014-review-api-server.md +0 -31
  141. elspais/trace_view/specs/tv-d00015-status-modifier.md +0 -27
  142. elspais/trace_view/specs/tv-d00016-js-integration.md +0 -33
  143. elspais/trace_view/specs/tv-p00001-html-generator.md +0 -33
  144. elspais/trace_view/specs/tv-p00002-review-system.md +0 -29
  145. elspais-0.11.1.dist-info/RECORD +0 -101
  146. {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/WHEEL +0 -0
  147. {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/entry_points.txt +0 -0
  148. {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/licenses/LICENSE +0 -0
@@ -2,457 +2,54 @@
2
2
  """
3
3
  elspais.commands.reformat_cmd - Reformat requirements using AI.
4
4
 
5
- Transforms requirements from old format (Acceptance Criteria) to new format
6
- (labeled Assertions). Also provides line break normalization.
7
-
8
- REQ-int-d00008-A: Format transformation SHALL be available via
9
- `elspais reformat-with-claude`.
10
- REQ-int-d00008-B: The command SHALL support --dry-run, --backup, --start-req flags.
11
- REQ-int-d00008-C: Line break normalization SHALL be included.
5
+ FUNCTIONALITY (to be reimplemented using Graph):
6
+ - `elspais reformat-with-claude` - Transform requirements format using Claude AI
7
+ - Converts old "Acceptance Criteria" format to new "Assertions" format
8
+ - Uses elspais.reformat module for AI calls and content assembly
9
+
10
+ OPTIONS:
11
+ - --start-req REQ_ID: Start from specific requirement (traverse descendants)
12
+ - --depth N: Maximum traversal depth
13
+ - --dry-run: Preview changes without writing
14
+ - --backup: Create .bak files before modifying
15
+ - --force: Reformat even if already in new format
16
+ - --fix-line-breaks: Normalize line breaks in output
17
+ - --line-breaks-only: Only fix line breaks, no format conversion
18
+ - --mode core|combined|local-only: Which repos to include
19
+
20
+ WORKFLOW:
21
+ 1. Build requirement graph from spec directories
22
+ 2. Identify requirements needing reformat (via validation)
23
+ 3. Traverse from start_req or all PRD requirements
24
+ 4. For each requirement:
25
+ - Call Claude to generate new assertions format
26
+ - Validate the result
27
+ - Replace content in source file (with backup if requested)
28
+
29
+ GRAPH INTEGRATION:
30
+ - Receives TraceGraph from CLI dispatcher
31
+ - Uses graph.find_by_id() to locate start requirement
32
+ - Uses node.children for BFS traversal
33
+ - Uses node.requirement.file_path for file modifications
34
+ - Filters by NodeKind.REQUIREMENT
12
35
  """
13
36
 
14
37
  import argparse
15
- import shutil
16
- import sys
17
- from pathlib import Path
18
- from typing import List, Optional
19
-
20
- from elspais.config.loader import load_config, find_config_file, get_spec_directories
21
- from elspais.core.parser import RequirementParser
22
- from elspais.core.patterns import PatternValidator, PatternConfig
23
- from elspais.core.rules import RuleEngine, RulesConfig
24
38
 
25
39
 
26
40
  def run(args: argparse.Namespace) -> int:
27
41
  """Run the reformat-with-claude command.
28
42
 
29
- This command reformats requirements from the old Acceptance Criteria format
30
- to the new Assertions format using Claude AI.
43
+ This command requires reimplementation using the graph-based system.
31
44
  """
32
- from elspais.reformat import (
33
- get_all_requirements,
34
- build_hierarchy,
35
- traverse_top_down,
36
- normalize_req_id,
37
- reformat_requirement,
38
- assemble_new_format,
39
- validate_reformatted_content,
40
- normalize_line_breaks,
41
- fix_requirement_line_breaks,
42
- )
43
-
44
- print("elspais reformat-with-claude")
45
- print()
46
-
47
- # Handle line-breaks-only mode
48
- if args.line_breaks_only:
49
- return run_line_breaks_only(args)
50
-
51
- # Configuration
52
- start_req = args.start_req
53
- max_depth = args.depth
54
- dry_run = args.dry_run
55
- backup = args.backup
56
- force = args.force
57
- fix_line_breaks = args.fix_line_breaks
58
- verbose = getattr(args, 'verbose', False)
59
- mode = getattr(args, 'mode', 'combined')
60
-
61
- print(f"Options:")
62
- print(f" Start REQ: {start_req or 'All PRD requirements'}")
63
- print(f" Max depth: {max_depth or 'Unlimited'}")
64
- print(f" Mode: {mode}")
65
- print(f" Dry run: {dry_run}")
66
- print(f" Backup: {backup}")
67
- print(f" Force reformat: {force}")
68
- print(f" Fix line breaks: {fix_line_breaks}")
45
+ print("Error: 'reformat-with-claude' command not yet implemented with graph-based system")
69
46
  print()
70
-
71
- if dry_run:
72
- print("DRY RUN MODE - no changes will be made")
73
- print()
74
-
75
- # Create cached validator for ID normalization
76
- config_path = find_config_file(Path.cwd())
77
- config = load_config(config_path) if config_path else {}
78
- pattern_config = PatternConfig.from_dict(config.get("patterns", {}))
79
- validator = PatternValidator(pattern_config)
80
-
81
- # Determine local base path for filtering (only modify local files)
82
- local_base_path = config_path.parent if config_path else Path.cwd()
83
-
84
- # Get all requirements (including cross-repo if mode allows)
85
- print("Loading requirements...", end=" ", flush=True)
86
- requirements = get_all_requirements(mode=mode)
87
- if not requirements:
88
- print("FAILED")
89
- print("Error: Could not load requirements. Run 'elspais validate' first.",
90
- file=sys.stderr)
91
- return 1
92
- print(f"found {len(requirements)} requirements")
93
-
94
- # Build hierarchy
95
- print("Building hierarchy...", end=" ", flush=True)
96
- build_hierarchy(requirements)
97
- print("done", flush=True)
98
-
99
- # Determine which requirements to process
100
- if start_req:
101
- # Normalize and validate start requirement
102
- print(f"Normalizing {start_req}...", end=" ", flush=True)
103
- start_req = normalize_req_id(start_req, validator)
104
- print(f"-> {start_req}", flush=True)
105
- if start_req not in requirements:
106
- print(f"Error: Requirement {start_req} not found", file=sys.stderr)
107
- return 1
108
-
109
- print(f"Traversing from {start_req}...", flush=True)
110
- req_ids = traverse_top_down(requirements, start_req, max_depth)
111
- print(f"Traversal complete", flush=True)
112
- else:
113
- # Process all PRD requirements first, then their descendants
114
- prd_reqs = [
115
- req_id for req_id, node in requirements.items()
116
- if node.level.upper() == 'PRD'
117
- ]
118
- prd_reqs.sort()
119
-
120
- print(f"Processing {len(prd_reqs)} PRD requirements and their descendants...")
121
- req_ids = []
122
- seen = set()
123
- for prd_id in prd_reqs:
124
- for req_id in traverse_top_down(requirements, prd_id, max_depth):
125
- if req_id not in seen:
126
- req_ids.append(req_id)
127
- seen.add(req_id)
128
-
129
- print(f"Found {len(req_ids)} requirements to process", flush=True)
130
-
131
- # Run validation to identify requirements with acceptance_criteria issues
132
- print("Running validation to identify old format...", end=" ", flush=True)
133
- needs_reformat_ids = get_requirements_needing_reformat(config, local_base_path)
134
- print(f"found {len(needs_reformat_ids)} with old format", flush=True)
135
- print(flush=True)
136
-
137
- # Filter to only requirements that need reformatting (unless --force)
138
- if not force:
139
- req_ids = [r for r in req_ids if r in needs_reformat_ids]
140
- print(f"Filtered to {len(req_ids)} requirements needing reformat")
141
- print(flush=True)
142
-
143
- # Process each requirement
144
- reformatted = 0
145
- skipped = 0
146
- errors = 0
147
- line_break_fixes = 0
148
-
149
- for i, req_id in enumerate(req_ids):
150
- if i % 10 == 0 and i > 0:
151
- print(f"Processing {i}/{len(req_ids)}...", flush=True)
152
- node = requirements[req_id]
153
-
154
- # Skip non-local files (from core/associated repos)
155
- if not is_local_file(node.file_path, local_base_path):
156
- skipped += 1
157
- continue
158
-
159
- print(f"[PROC] {req_id}: {node.title[:50]}...")
160
-
161
- # Call Claude to reformat
162
- result, success, error_msg = reformat_requirement(node, verbose=verbose)
163
-
164
- if not success:
165
- print(f" ERROR: {error_msg}")
166
- errors += 1
167
- continue
168
-
169
- # Validate the result
170
- rationale = result.get('rationale', '')
171
- assertions = result.get('assertions', [])
172
-
173
- is_valid, warnings = validate_reformatted_content(node, rationale, assertions)
174
-
175
- if warnings:
176
- for warning in warnings:
177
- print(f" WARNING: {warning}")
178
-
179
- if not is_valid:
180
- print(f" INVALID: Skipping due to validation errors")
181
- errors += 1
182
- continue
183
-
184
- # Assemble the new format
185
- new_content = assemble_new_format(
186
- req_id=node.req_id,
187
- title=node.title,
188
- level=node.level,
189
- status=node.status,
190
- implements=node.implements,
191
- rationale=rationale,
192
- assertions=assertions
193
- )
194
-
195
- # Optionally normalize line breaks
196
- if fix_line_breaks:
197
- new_content = normalize_line_breaks(new_content)
198
- line_break_fixes += 1
199
-
200
- if dry_run:
201
- print(f" Would write to: {node.file_path}")
202
- print(f" Assertions: {len(assertions)}")
203
- reformatted += 1
204
- else:
205
- # Write the reformatted content
206
- try:
207
- file_path = Path(node.file_path)
208
-
209
- if backup:
210
- backup_path = file_path.with_suffix(file_path.suffix + '.bak')
211
- shutil.copy2(file_path, backup_path)
212
- print(f" Backup: {backup_path}")
213
-
214
- # Read the entire file
215
- content = file_path.read_text()
216
-
217
- # Find and replace this requirement's content
218
- # The requirement starts with its header and ends before the next
219
- # requirement or end of file
220
- updated_content = replace_requirement_content(
221
- content, node.req_id, node.title, new_content
222
- )
223
-
224
- if updated_content:
225
- file_path.write_text(updated_content)
226
- print(f" Written: {file_path}")
227
- reformatted += 1
228
- else:
229
- print(f" ERROR: Could not locate requirement in file")
230
- errors += 1
231
-
232
- except Exception as e:
233
- print(f" ERROR: {e}")
234
- errors += 1
235
-
236
- # Summary
237
- print()
238
- print("=" * 60)
239
- print(f"Summary:")
240
- print(f" Reformatted: {reformatted}")
241
- print(f" Skipped: {skipped}")
242
- print(f" Errors: {errors}")
243
- if fix_line_breaks:
244
- print(f" Line breaks: {line_break_fixes} files normalized")
245
-
246
- return 0 if errors == 0 else 1
247
-
248
-
249
- def replace_requirement_content(
250
- file_content: str,
251
- req_id: str,
252
- title: str,
253
- new_content: str
254
- ) -> Optional[str]:
255
- """
256
- Replace a requirement's content in a file.
257
-
258
- Finds the requirement by its header pattern and replaces everything
259
- up to the footer line.
260
-
261
- Args:
262
- file_content: Full file content
263
- req_id: Requirement ID (e.g., 'REQ-d00027')
264
- title: Requirement title
265
- new_content: New requirement content
266
-
267
- Returns:
268
- Updated file content, or None if requirement not found
269
- """
270
- import re
271
-
272
- # Pattern to match the requirement header
273
- # # REQ-d00027: Title
274
- header_pattern = rf'^# {re.escape(req_id)}:\s*'
275
-
276
- # Pattern to match the footer
277
- # *End* *Title* | **Hash**: xxxxxxxx
278
- footer_pattern = rf'^\*End\*\s+\*{re.escape(title)}\*\s+\|\s+\*\*Hash\*\*:\s*[a-fA-F0-9]+'
279
-
280
- lines = file_content.split('\n')
281
- result_lines = []
282
- in_requirement = False
283
- found = False
284
-
285
- i = 0
286
- while i < len(lines):
287
- line = lines[i]
288
-
289
- if not in_requirement:
290
- # Check if this line starts the requirement
291
- if re.match(header_pattern, line, re.IGNORECASE):
292
- in_requirement = True
293
- found = True
294
- # Insert new content (without trailing newline, we'll add it)
295
- new_lines = new_content.rstrip('\n').split('\n')
296
- result_lines.extend(new_lines)
297
- i += 1
298
- continue
299
- else:
300
- result_lines.append(line)
301
- i += 1
302
- else:
303
- # We're inside the requirement, skip until we find the footer
304
- if re.match(footer_pattern, line, re.IGNORECASE):
305
- # Found the footer, we've already added the new content
306
- # with its own footer, so skip this old footer
307
- in_requirement = False
308
- i += 1
309
- # Skip any trailing blank lines after the footer
310
- while i < len(lines) and lines[i].strip() == '':
311
- i += 1
312
- else:
313
- # Skip this line (part of old requirement)
314
- i += 1
315
-
316
- if not found:
317
- return None
318
-
319
- return '\n'.join(result_lines)
320
-
321
-
322
- def run_line_breaks_only(args: argparse.Namespace) -> int:
323
- """Run line break normalization only."""
324
- from elspais.reformat import (
325
- get_all_requirements,
326
- normalize_line_breaks,
327
- detect_line_break_issues,
328
- )
329
-
330
- dry_run = args.dry_run
331
- backup = args.backup
332
-
333
- print("Line break normalization mode")
334
- print(f" Dry run: {dry_run}")
335
- print(f" Backup: {backup}")
336
- print()
337
-
338
- # Get all requirements
339
- print("Loading requirements...", end=" ", flush=True)
340
- requirements = get_all_requirements()
341
- if not requirements:
342
- print("FAILED")
343
- print("Error: Could not load requirements.", file=sys.stderr)
344
- return 1
345
- print(f"found {len(requirements)} requirements")
346
-
347
- # Group by file
348
- files_to_process = {}
349
- for req_id, node in requirements.items():
350
- if node.file_path not in files_to_process:
351
- files_to_process[node.file_path] = []
352
- files_to_process[node.file_path].append(req_id)
353
-
354
- print(f"Processing {len(files_to_process)} files...")
355
- print()
356
-
357
- fixed = 0
358
- unchanged = 0
359
- errors = 0
360
-
361
- for file_path_str, req_ids in sorted(files_to_process.items()):
362
- file_path = Path(file_path_str)
363
-
364
- try:
365
- content = file_path.read_text()
366
- issues = detect_line_break_issues(content)
367
-
368
- if not issues:
369
- unchanged += 1
370
- continue
371
-
372
- print(f"[FIX] {file_path}")
373
- for issue in issues:
374
- print(f" - {issue}")
375
-
376
- if dry_run:
377
- fixed += 1
378
- continue
379
-
380
- # Apply fixes
381
- fixed_content = normalize_line_breaks(content)
382
-
383
- if backup:
384
- backup_path = file_path.with_suffix(file_path.suffix + '.bak')
385
- shutil.copy2(file_path, backup_path)
386
-
387
- file_path.write_text(fixed_content)
388
- fixed += 1
389
-
390
- except Exception as e:
391
- print(f"[ERR] {file_path}: {e}")
392
- errors += 1
393
-
394
- print()
395
- print("=" * 60)
396
- print(f"Summary:")
397
- print(f" Fixed: {fixed}")
398
- print(f" Unchanged: {unchanged}")
399
- print(f" Errors: {errors}")
400
-
401
- return 0 if errors == 0 else 1
402
-
403
-
404
- def get_requirements_needing_reformat(config: dict, base_path: Path) -> set:
405
- """Run validation to identify requirements with old format.
406
-
407
- Args:
408
- config: Configuration dictionary
409
- base_path: Base path of the local repository
410
-
411
- Returns:
412
- Set of requirement IDs that have format.acceptance_criteria violations
413
- """
414
- # Get local spec directories only
415
- spec_dirs = get_spec_directories(None, config, base_path)
416
- if not spec_dirs:
417
- return set()
418
-
419
- # Parse local requirements
420
- pattern_config = PatternConfig.from_dict(config.get("patterns", {}))
421
- spec_config = config.get("spec", {})
422
- no_reference_values = spec_config.get("no_reference_values")
423
- parser = RequirementParser(pattern_config, no_reference_values=no_reference_values)
424
- skip_files = spec_config.get("skip_files", [])
425
-
426
- try:
427
- parse_result = parser.parse_directories(spec_dirs, skip_files=skip_files)
428
- requirements = dict(parse_result)
429
- except Exception:
430
- return set()
431
-
432
- # Run validation
433
- rules_config = RulesConfig.from_dict(config.get("rules", {}))
434
- engine = RuleEngine(rules_config)
435
- violations = engine.validate(requirements)
436
-
437
- # Filter to acceptance_criteria violations
438
- return {
439
- v.requirement_id for v in violations
440
- if v.rule_name == "format.acceptance_criteria"
441
- }
442
-
443
-
444
- def is_local_file(file_path: str, base_path: Path) -> bool:
445
- """Check if file is in the local repo (not core/associated).
446
-
447
- Args:
448
- file_path: Path to the file (string)
449
- base_path: Base path of the local repository
450
-
451
- Returns:
452
- True if file is within the local repo, False otherwise
453
- """
454
- try:
455
- Path(file_path).resolve().relative_to(base_path.resolve())
456
- return True
457
- except ValueError:
458
- return False
47
+ print("Planned features:")
48
+ print(" --start-req REQ_ID")
49
+ print(" --depth N")
50
+ print(" --dry-run")
51
+ print(" --backup")
52
+ print(" --force")
53
+ print(" --fix-line-breaks")
54
+ print(" --line-breaks-only")
55
+ return 1
@@ -9,8 +9,8 @@ import sys
9
9
  from pathlib import Path
10
10
  from typing import Optional
11
11
 
12
- from elspais.config.loader import find_config_file, load_config
13
- from elspais.core.content_rules import load_content_rules, load_content_rule
12
+ from elspais.config import find_config_file, load_config
13
+ from elspais.content_rules import load_content_rule, load_content_rules
14
14
 
15
15
 
16
16
  def run(args: argparse.Namespace) -> int:
@@ -56,7 +56,11 @@ def cmd_list(args: argparse.Namespace) -> int:
56
56
  print("Content Rules:")
57
57
  print("-" * 60)
58
58
  for rule in rules:
59
- rel_path = rule.file_path.relative_to(base_path) if base_path in rule.file_path.parents else rule.file_path
59
+ rel_path = (
60
+ rule.file_path.relative_to(base_path)
61
+ if base_path in rule.file_path.parents
62
+ else rule.file_path
63
+ )
60
64
  print(f" {rel_path}")
61
65
  print(f" Title: {rule.title}")
62
66
  print(f" Type: {rule.type}")