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
@@ -2,446 +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 Optional
19
-
20
- from elspais.config.loader import find_config_file, get_spec_directories, load_config
21
- from elspais.core.parser import RequirementParser
22
- from elspais.core.patterns import PatternConfig, PatternValidator
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
- assemble_new_format,
34
- build_hierarchy,
35
- get_all_requirements,
36
- normalize_line_breaks,
37
- normalize_req_id,
38
- reformat_requirement,
39
- traverse_top_down,
40
- validate_reformatted_content,
41
- )
42
-
43
- print("elspais reformat-with-claude")
44
- print()
45
-
46
- # Handle line-breaks-only mode
47
- if args.line_breaks_only:
48
- return run_line_breaks_only(args)
49
-
50
- # Configuration
51
- start_req = args.start_req
52
- max_depth = args.depth
53
- dry_run = args.dry_run
54
- backup = args.backup
55
- force = args.force
56
- fix_line_breaks = args.fix_line_breaks
57
- verbose = getattr(args, "verbose", False)
58
- mode = getattr(args, "mode", "combined")
59
-
60
- print("Options:")
61
- print(f" Start REQ: {start_req or 'All PRD requirements'}")
62
- print(f" Max depth: {max_depth or 'Unlimited'}")
63
- print(f" Mode: {mode}")
64
- print(f" Dry run: {dry_run}")
65
- print(f" Backup: {backup}")
66
- print(f" Force reformat: {force}")
67
- print(f" Fix line breaks: {fix_line_breaks}")
45
+ print("Error: 'reformat-with-claude' command not yet implemented with graph-based system")
68
46
  print()
69
-
70
- if dry_run:
71
- print("DRY RUN MODE - no changes will be made")
72
- print()
73
-
74
- # Create cached validator for ID normalization
75
- config_path = find_config_file(Path.cwd())
76
- config = load_config(config_path) if config_path else {}
77
- pattern_config = PatternConfig.from_dict(config.get("patterns", {}))
78
- validator = PatternValidator(pattern_config)
79
-
80
- # Determine local base path for filtering (only modify local files)
81
- local_base_path = config_path.parent if config_path else Path.cwd()
82
-
83
- # Get all requirements (including cross-repo if mode allows)
84
- print("Loading requirements...", end=" ", flush=True)
85
- requirements = get_all_requirements(mode=mode)
86
- if not requirements:
87
- print("FAILED")
88
- print("Error: Could not load requirements. Run 'elspais validate' first.", file=sys.stderr)
89
- return 1
90
- print(f"found {len(requirements)} requirements")
91
-
92
- # Build hierarchy
93
- print("Building hierarchy...", end=" ", flush=True)
94
- build_hierarchy(requirements)
95
- print("done", flush=True)
96
-
97
- # Determine which requirements to process
98
- if start_req:
99
- # Normalize and validate start requirement
100
- print(f"Normalizing {start_req}...", end=" ", flush=True)
101
- start_req = normalize_req_id(start_req, validator)
102
- print(f"-> {start_req}", flush=True)
103
- if start_req not in requirements:
104
- print(f"Error: Requirement {start_req} not found", file=sys.stderr)
105
- return 1
106
-
107
- print(f"Traversing from {start_req}...", flush=True)
108
- req_ids = traverse_top_down(requirements, start_req, max_depth)
109
- print("Traversal complete", flush=True)
110
- else:
111
- # Process all PRD requirements first, then their descendants
112
- prd_reqs = [req_id for req_id, node in requirements.items() if node.level.upper() == "PRD"]
113
- prd_reqs.sort()
114
-
115
- print(f"Processing {len(prd_reqs)} PRD requirements and their descendants...")
116
- req_ids = []
117
- seen = set()
118
- for prd_id in prd_reqs:
119
- for req_id in traverse_top_down(requirements, prd_id, max_depth):
120
- if req_id not in seen:
121
- req_ids.append(req_id)
122
- seen.add(req_id)
123
-
124
- print(f"Found {len(req_ids)} requirements to process", flush=True)
125
-
126
- # Run validation to identify requirements with acceptance_criteria issues
127
- print("Running validation to identify old format...", end=" ", flush=True)
128
- needs_reformat_ids = get_requirements_needing_reformat(config, local_base_path)
129
- print(f"found {len(needs_reformat_ids)} with old format", flush=True)
130
- print(flush=True)
131
-
132
- # Filter to only requirements that need reformatting (unless --force)
133
- if not force:
134
- req_ids = [r for r in req_ids if r in needs_reformat_ids]
135
- print(f"Filtered to {len(req_ids)} requirements needing reformat")
136
- print(flush=True)
137
-
138
- # Process each requirement
139
- reformatted = 0
140
- skipped = 0
141
- errors = 0
142
- line_break_fixes = 0
143
-
144
- for i, req_id in enumerate(req_ids):
145
- if i % 10 == 0 and i > 0:
146
- print(f"Processing {i}/{len(req_ids)}...", flush=True)
147
- node = requirements[req_id]
148
-
149
- # Skip non-local files (from core/associated repos)
150
- if not is_local_file(node.file_path, local_base_path):
151
- skipped += 1
152
- continue
153
-
154
- print(f"[PROC] {req_id}: {node.title[:50]}...")
155
-
156
- # Call Claude to reformat
157
- result, success, error_msg = reformat_requirement(node, verbose=verbose)
158
-
159
- if not success:
160
- print(f" ERROR: {error_msg}")
161
- errors += 1
162
- continue
163
-
164
- # Validate the result
165
- rationale = result.get("rationale", "")
166
- assertions = result.get("assertions", [])
167
-
168
- is_valid, warnings = validate_reformatted_content(node, rationale, assertions)
169
-
170
- if warnings:
171
- for warning in warnings:
172
- print(f" WARNING: {warning}")
173
-
174
- if not is_valid:
175
- print(" INVALID: Skipping due to validation errors")
176
- errors += 1
177
- continue
178
-
179
- # Assemble the new format
180
- new_content = assemble_new_format(
181
- req_id=node.req_id,
182
- title=node.title,
183
- level=node.level,
184
- status=node.status,
185
- implements=node.implements,
186
- rationale=rationale,
187
- assertions=assertions,
188
- )
189
-
190
- # Optionally normalize line breaks
191
- if fix_line_breaks:
192
- new_content = normalize_line_breaks(new_content)
193
- line_break_fixes += 1
194
-
195
- if dry_run:
196
- print(f" Would write to: {node.file_path}")
197
- print(f" Assertions: {len(assertions)}")
198
- reformatted += 1
199
- else:
200
- # Write the reformatted content
201
- try:
202
- file_path = Path(node.file_path)
203
-
204
- if backup:
205
- backup_path = file_path.with_suffix(file_path.suffix + ".bak")
206
- shutil.copy2(file_path, backup_path)
207
- print(f" Backup: {backup_path}")
208
-
209
- # Read the entire file
210
- content = file_path.read_text()
211
-
212
- # Find and replace this requirement's content
213
- # The requirement starts with its header and ends before the next
214
- # requirement or end of file
215
- updated_content = replace_requirement_content(
216
- content, node.req_id, node.title, new_content
217
- )
218
-
219
- if updated_content:
220
- file_path.write_text(updated_content)
221
- print(f" Written: {file_path}")
222
- reformatted += 1
223
- else:
224
- print(" ERROR: Could not locate requirement in file")
225
- errors += 1
226
-
227
- except Exception as e:
228
- print(f" ERROR: {e}")
229
- errors += 1
230
-
231
- # Summary
232
- print()
233
- print("=" * 60)
234
- print("Summary:")
235
- print(f" Reformatted: {reformatted}")
236
- print(f" Skipped: {skipped}")
237
- print(f" Errors: {errors}")
238
- if fix_line_breaks:
239
- print(f" Line breaks: {line_break_fixes} files normalized")
240
-
241
- return 0 if errors == 0 else 1
242
-
243
-
244
- def replace_requirement_content(
245
- file_content: str, req_id: str, title: str, new_content: str
246
- ) -> Optional[str]:
247
- """
248
- Replace a requirement's content in a file.
249
-
250
- Finds the requirement by its header pattern and replaces everything
251
- up to the footer line.
252
-
253
- Args:
254
- file_content: Full file content
255
- req_id: Requirement ID (e.g., 'REQ-d00027')
256
- title: Requirement title
257
- new_content: New requirement content
258
-
259
- Returns:
260
- Updated file content, or None if requirement not found
261
- """
262
- import re
263
-
264
- # Pattern to match the requirement header
265
- # # REQ-d00027: Title
266
- header_pattern = rf"^# {re.escape(req_id)}:\s*"
267
-
268
- # Pattern to match the footer
269
- # *End* *Title* | **Hash**: xxxxxxxx
270
- footer_pattern = rf"^\*End\*\s+\*{re.escape(title)}\*\s+\|\s+\*\*Hash\*\*:\s*[a-fA-F0-9]+"
271
-
272
- lines = file_content.split("\n")
273
- result_lines = []
274
- in_requirement = False
275
- found = False
276
-
277
- i = 0
278
- while i < len(lines):
279
- line = lines[i]
280
-
281
- if not in_requirement:
282
- # Check if this line starts the requirement
283
- if re.match(header_pattern, line, re.IGNORECASE):
284
- in_requirement = True
285
- found = True
286
- # Insert new content (without trailing newline, we'll add it)
287
- new_lines = new_content.rstrip("\n").split("\n")
288
- result_lines.extend(new_lines)
289
- i += 1
290
- continue
291
- else:
292
- result_lines.append(line)
293
- i += 1
294
- else:
295
- # We're inside the requirement, skip until we find the footer
296
- if re.match(footer_pattern, line, re.IGNORECASE):
297
- # Found the footer, we've already added the new content
298
- # with its own footer, so skip this old footer
299
- in_requirement = False
300
- i += 1
301
- # Skip any trailing blank lines after the footer
302
- while i < len(lines) and lines[i].strip() == "":
303
- i += 1
304
- else:
305
- # Skip this line (part of old requirement)
306
- i += 1
307
-
308
- if not found:
309
- return None
310
-
311
- return "\n".join(result_lines)
312
-
313
-
314
- def run_line_breaks_only(args: argparse.Namespace) -> int:
315
- """Run line break normalization only."""
316
- from elspais.reformat import (
317
- detect_line_break_issues,
318
- get_all_requirements,
319
- normalize_line_breaks,
320
- )
321
-
322
- dry_run = args.dry_run
323
- backup = args.backup
324
-
325
- print("Line break normalization mode")
326
- print(f" Dry run: {dry_run}")
327
- print(f" Backup: {backup}")
328
- print()
329
-
330
- # Get all requirements
331
- print("Loading requirements...", end=" ", flush=True)
332
- requirements = get_all_requirements()
333
- if not requirements:
334
- print("FAILED")
335
- print("Error: Could not load requirements.", file=sys.stderr)
336
- return 1
337
- print(f"found {len(requirements)} requirements")
338
-
339
- # Group by file
340
- files_to_process = {}
341
- for req_id, node in requirements.items():
342
- if node.file_path not in files_to_process:
343
- files_to_process[node.file_path] = []
344
- files_to_process[node.file_path].append(req_id)
345
-
346
- print(f"Processing {len(files_to_process)} files...")
347
- print()
348
-
349
- fixed = 0
350
- unchanged = 0
351
- errors = 0
352
-
353
- for file_path_str, _req_ids in sorted(files_to_process.items()):
354
- file_path = Path(file_path_str)
355
-
356
- try:
357
- content = file_path.read_text()
358
- issues = detect_line_break_issues(content)
359
-
360
- if not issues:
361
- unchanged += 1
362
- continue
363
-
364
- print(f"[FIX] {file_path}")
365
- for issue in issues:
366
- print(f" - {issue}")
367
-
368
- if dry_run:
369
- fixed += 1
370
- continue
371
-
372
- # Apply fixes
373
- fixed_content = normalize_line_breaks(content)
374
-
375
- if backup:
376
- backup_path = file_path.with_suffix(file_path.suffix + ".bak")
377
- shutil.copy2(file_path, backup_path)
378
-
379
- file_path.write_text(fixed_content)
380
- fixed += 1
381
-
382
- except Exception as e:
383
- print(f"[ERR] {file_path}: {e}")
384
- errors += 1
385
-
386
- print()
387
- print("=" * 60)
388
- print("Summary:")
389
- print(f" Fixed: {fixed}")
390
- print(f" Unchanged: {unchanged}")
391
- print(f" Errors: {errors}")
392
-
393
- return 0 if errors == 0 else 1
394
-
395
-
396
- def get_requirements_needing_reformat(config: dict, base_path: Path) -> set:
397
- """Run validation to identify requirements with old format.
398
-
399
- Args:
400
- config: Configuration dictionary
401
- base_path: Base path of the local repository
402
-
403
- Returns:
404
- Set of requirement IDs that have format.acceptance_criteria violations
405
- """
406
- # Get local spec directories only
407
- spec_dirs = get_spec_directories(None, config, base_path)
408
- if not spec_dirs:
409
- return set()
410
-
411
- # Parse local requirements
412
- pattern_config = PatternConfig.from_dict(config.get("patterns", {}))
413
- spec_config = config.get("spec", {})
414
- no_reference_values = spec_config.get("no_reference_values")
415
- parser = RequirementParser(pattern_config, no_reference_values=no_reference_values)
416
- skip_files = spec_config.get("skip_files", [])
417
-
418
- try:
419
- parse_result = parser.parse_directories(spec_dirs, skip_files=skip_files)
420
- requirements = dict(parse_result)
421
- except Exception:
422
- return set()
423
-
424
- # Run validation
425
- rules_config = RulesConfig.from_dict(config.get("rules", {}))
426
- engine = RuleEngine(rules_config)
427
- violations = engine.validate(requirements)
428
-
429
- # Filter to acceptance_criteria violations
430
- return {v.requirement_id for v in violations if v.rule_name == "format.acceptance_criteria"}
431
-
432
-
433
- def is_local_file(file_path: str, base_path: Path) -> bool:
434
- """Check if file is in the local repo (not core/associated).
435
-
436
- Args:
437
- file_path: Path to the file (string)
438
- base_path: Base path of the local repository
439
-
440
- Returns:
441
- True if file is within the local repo, False otherwise
442
- """
443
- try:
444
- Path(file_path).resolve().relative_to(base_path.resolve())
445
- return True
446
- except ValueError:
447
- 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_rule, load_content_rules
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: