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
elspais/commands/index.py CHANGED
@@ -1,147 +1,124 @@
1
+ # Implements: REQ-int-d00003 (CLI Extension)
1
2
  """
2
3
  elspais.commands.index - INDEX.md management command.
4
+
5
+ Uses graph-based system:
6
+ - `elspais index validate` - Validate INDEX.md accuracy
7
+ - `elspais index regenerate` - Regenerate INDEX.md from requirements
3
8
  """
4
9
 
10
+ from __future__ import annotations
11
+
5
12
  import argparse
13
+ import re
14
+ import sys
6
15
  from pathlib import Path
16
+ from typing import TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING:
19
+ from elspais.graph.builder import TraceGraph
7
20
 
8
- from elspais.config.defaults import DEFAULT_CONFIG
9
- from elspais.config.loader import find_config_file, get_spec_directories, load_config
10
- from elspais.core.parser import RequirementParser
11
- from elspais.core.patterns import PatternConfig
21
+ from elspais.graph import NodeKind
12
22
 
13
23
 
14
24
  def run(args: argparse.Namespace) -> int:
15
25
  """Run the index command."""
16
- if not args.index_action:
17
- print("Usage: elspais index {validate|regenerate}")
18
- return 1
26
+ from elspais.config import get_config, get_spec_directories
27
+ from elspais.graph.factory import build_graph
19
28
 
20
- if args.index_action == "validate":
21
- return run_validate(args)
22
- elif args.index_action == "regenerate":
23
- return run_regenerate(args)
29
+ spec_dir = getattr(args, "spec_dir", None)
30
+ config_path = getattr(args, "config", None)
24
31
 
25
- return 1
32
+ config = get_config(config_path)
33
+ spec_dirs = get_spec_directories(spec_dir, config)
26
34
 
35
+ graph = build_graph(
36
+ config=config,
37
+ spec_dirs=spec_dirs if spec_dir else None,
38
+ config_path=config_path,
39
+ )
27
40
 
28
- def run_validate(args: argparse.Namespace) -> int:
29
- """Validate INDEX.md accuracy."""
30
- config_path = args.config or find_config_file(Path.cwd())
31
- if config_path and config_path.exists():
32
- config = load_config(config_path)
33
- else:
34
- config = DEFAULT_CONFIG
41
+ action = getattr(args, "index_action", None)
35
42
 
36
- spec_dirs = get_spec_directories(args.spec_dir, config)
37
- if not spec_dirs:
38
- print("Error: No spec directories found")
43
+ if action == "validate":
44
+ return _validate_index(graph, spec_dirs, args)
45
+ elif action == "regenerate":
46
+ return _regenerate_index(graph, spec_dirs, args)
47
+ else:
48
+ print("Usage: elspais index <validate|regenerate>", file=sys.stderr)
39
49
  return 1
40
50
 
41
- spec_config = config.get("spec", {})
42
- # Use first spec directory for INDEX.md location
43
- index_file = spec_dirs[0] / spec_config.get("index_file", "INDEX.md")
44
-
45
- if not index_file.exists():
46
- print(f"INDEX.md not found: {index_file}")
47
- return 1
48
51
 
49
- # Parse all requirements
50
- pattern_config = PatternConfig.from_dict(config.get("patterns", {}))
51
- no_reference_values = spec_config.get("no_reference_values")
52
- skip_files = spec_config.get("skip_files", [])
53
- parser = RequirementParser(pattern_config, no_reference_values=no_reference_values)
54
- requirements = parser.parse_directories(spec_dirs, skip_files=skip_files)
52
+ def _validate_index(graph: TraceGraph, spec_dirs: list[Path], args: argparse.Namespace) -> int:
53
+ """Validate INDEX.md against graph requirements."""
54
+ # Find INDEX.md
55
+ index_path = None
56
+ for spec_dir in spec_dirs:
57
+ candidate = spec_dir / "INDEX.md"
58
+ if candidate.exists():
59
+ index_path = candidate
60
+ break
55
61
 
56
- # Parse INDEX.md to find listed requirements
57
- index_content = index_file.read_text(encoding="utf-8")
58
- indexed_ids = set()
62
+ if not index_path:
63
+ print("No INDEX.md found in spec directories.")
64
+ print("Run 'elspais index regenerate' to create one.")
65
+ return 1
59
66
 
60
- import re
67
+ # Parse IDs from INDEX.md
68
+ content = index_path.read_text()
69
+ index_ids = set(re.findall(r"REQ-[a-z0-9-]+", content, re.IGNORECASE))
61
70
 
62
- for match in re.finditer(r"\|\s*([A-Z]+-(?:[A-Z]+-)?[a-zA-Z]?\d+)\s*\|", index_content):
63
- indexed_ids.add(match.group(1))
71
+ # Get IDs from graph
72
+ graph_ids = set()
73
+ for node in graph.nodes_by_kind(NodeKind.REQUIREMENT):
74
+ graph_ids.add(node.id)
64
75
 
65
76
  # Compare
66
- actual_ids = set(requirements.keys())
67
- missing = actual_ids - indexed_ids
68
- extra = indexed_ids - actual_ids
77
+ missing = graph_ids - index_ids
78
+ extra = index_ids - graph_ids
69
79
 
70
80
  if missing:
71
81
  print(f"Missing from INDEX.md ({len(missing)}):")
72
82
  for req_id in sorted(missing):
73
- print(f" - {req_id}")
83
+ print(f" {req_id}")
74
84
 
75
85
  if extra:
76
- print(f"\nExtra in INDEX.md ({len(extra)}):")
86
+ print(f"Extra in INDEX.md ({len(extra)}):")
77
87
  for req_id in sorted(extra):
78
- print(f" - {req_id}")
88
+ print(f" {req_id}")
79
89
 
80
90
  if not missing and not extra:
81
- print(f"INDEX.md is accurate ({len(actual_ids)} requirements)")
91
+ print(f"INDEX.md is up to date ({len(graph_ids)} requirements)")
82
92
  return 0
83
93
 
84
- return 1
94
+ return 1 if missing or extra else 0
85
95
 
86
96
 
87
- def run_regenerate(args: argparse.Namespace) -> int:
88
- """Regenerate INDEX.md from requirements."""
89
- config_path = args.config or find_config_file(Path.cwd())
90
- if config_path and config_path.exists():
91
- config = load_config(config_path)
92
- else:
93
- config = DEFAULT_CONFIG
97
+ def _regenerate_index(graph: TraceGraph, spec_dirs: list[Path], args: argparse.Namespace) -> int:
98
+ """Regenerate INDEX.md from graph requirements."""
99
+ # Group by level
100
+ by_level = {"PRD": [], "OPS": [], "DEV": [], "other": []}
94
101
 
95
- spec_dirs = get_spec_directories(args.spec_dir, config)
96
- if not spec_dirs:
97
- print("Error: No spec directories found")
98
- return 1
99
-
100
- spec_config = config.get("spec", {})
101
- # Use first spec directory for INDEX.md location
102
- index_file = spec_dirs[0] / spec_config.get("index_file", "INDEX.md")
103
-
104
- # Parse all requirements
105
- pattern_config = PatternConfig.from_dict(config.get("patterns", {}))
106
- no_reference_values = spec_config.get("no_reference_values")
107
- skip_files = spec_config.get("skip_files", [])
108
- parser = RequirementParser(pattern_config, no_reference_values=no_reference_values)
109
- requirements = parser.parse_directories(spec_dirs, skip_files=skip_files)
110
-
111
- if not requirements:
112
- print("No requirements found")
113
- return 1
102
+ for node in graph.nodes_by_kind(NodeKind.REQUIREMENT):
103
+ level = (node.level or "").upper()
104
+ if level in by_level:
105
+ by_level[level].append(node)
106
+ else:
107
+ by_level["other"].append(node)
114
108
 
115
- # Generate INDEX.md
116
- content = generate_index(requirements, config)
117
- index_file.write_text(content, encoding="utf-8")
109
+ # Generate markdown
110
+ lines = ["# Requirements Index", ""]
118
111
 
119
- print(f"Regenerated: {index_file}")
120
- print(f" {len(requirements)} requirements indexed")
112
+ level_names = {
113
+ "PRD": "Product Requirements (PRD)",
114
+ "OPS": "Operations Requirements (OPS)",
115
+ "DEV": "Development Requirements (DEV)",
116
+ "other": "Other Requirements",
117
+ }
121
118
 
122
- return 0
123
-
124
-
125
- def generate_index(requirements: dict, config: dict) -> str:
126
- """Generate INDEX.md content."""
127
- lines = [
128
- "# Requirements Index",
129
- "",
130
- "This file provides a complete index of all requirements.",
131
- "",
132
- ]
133
-
134
- # Group by type
135
- prd_reqs = {k: v for k, v in requirements.items() if v.level.upper() in ["PRD", "PRODUCT"]}
136
- ops_reqs = {k: v for k, v in requirements.items() if v.level.upper() in ["OPS", "OPERATIONS"]}
137
- dev_reqs = {k: v for k, v in requirements.items() if v.level.upper() in ["DEV", "DEVELOPMENT"]}
138
-
139
- for title, reqs in [
140
- ("Product Requirements (PRD)", prd_reqs),
141
- ("Operations Requirements (OPS)", ops_reqs),
142
- ("Development Requirements (DEV)", dev_reqs),
143
- ]:
144
- if not reqs:
119
+ for level, title in level_names.items():
120
+ nodes = by_level[level]
121
+ if not nodes:
145
122
  continue
146
123
 
147
124
  lines.append(f"## {title}")
@@ -149,19 +126,25 @@ def generate_index(requirements: dict, config: dict) -> str:
149
126
  lines.append("| ID | Title | File | Hash |")
150
127
  lines.append("|---|---|---|---|")
151
128
 
152
- for req_id, req in sorted(reqs.items()):
153
- file_name = req.file_path.name if req.file_path else "-"
154
- hash_val = req.hash or "-"
155
- lines.append(f"| {req_id} | {req.title} | {file_name} | {hash_val} |")
129
+ for node in sorted(nodes, key=lambda n: n.id):
130
+ file_path = node.source.path if node.source else ""
131
+ # Make path relative
132
+ if file_path:
133
+ for spec_dir in spec_dirs:
134
+ try:
135
+ file_path = Path(file_path).relative_to(spec_dir)
136
+ break
137
+ except ValueError:
138
+ pass
139
+ hash_val = node.hash or ""
140
+ lines.append(f"| {node.id} | {node.get_label()} | {file_path} | {hash_val} |")
156
141
 
157
142
  lines.append("")
158
143
 
159
- lines.extend(
160
- [
161
- "---",
162
- "",
163
- "*Generated by elspais*",
164
- ]
165
- )
144
+ # Write to first spec dir
145
+ output_path = spec_dirs[0] / "INDEX.md" if spec_dirs else Path("spec/INDEX.md")
146
+ output_path.write_text("\n".join(lines))
166
147
 
167
- return "\n".join(lines)
148
+ req_count = sum(len(nodes) for nodes in by_level.values())
149
+ print(f"Generated {output_path} ({req_count} requirements)")
150
+ return 0
elspais/commands/init.py CHANGED
@@ -8,6 +8,36 @@ import argparse
8
8
  from pathlib import Path
9
9
  from typing import Optional
10
10
 
11
+ # Example requirement template for --template flag
12
+ EXAMPLE_REQUIREMENT = """# REQ-d00001: Example Requirement Title
13
+
14
+ **Level**: Dev | **Status**: Draft | **Implements**: -
15
+
16
+ ## Assertions
17
+
18
+ A. The system SHALL demonstrate the assertion format.
19
+ B. The system SHALL show proper use of SHALL language.
20
+
21
+ ## Rationale
22
+
23
+ This is an example requirement demonstrating the proper format.
24
+ Delete this file after reviewing the structure.
25
+
26
+ ---
27
+
28
+ **Format Notes** (delete this section):
29
+
30
+ - **Title line**: `# REQ-{type}{id}: Title` where type is p/o/d for PRD/OPS/DEV
31
+ - **Metadata line**: Level, Status, and Implements (use `-` for top-level reqs)
32
+ - **Assertions**: Labeled A-Z, each using SHALL for required behavior
33
+ - **Rationale**: Optional explanation section (non-normative)
34
+ - **Footer**: `*End* *Title* | **Hash**: XXXXXXXX` - hash computed by `elspais hash update`
35
+
36
+ Run `elspais format` for more templates and `elspais validate` to check this file.
37
+
38
+ *End* *Example Requirement Title* | **Hash**: 00000000
39
+ """
40
+
11
41
 
12
42
  def run(args: argparse.Namespace) -> int:
13
43
  """
@@ -19,6 +49,10 @@ def run(args: argparse.Namespace) -> int:
19
49
  Returns:
20
50
  Exit code (0 for success)
21
51
  """
52
+ # Handle --template flag separately
53
+ if getattr(args, "template", False):
54
+ return create_template_requirement(args)
55
+
22
56
  config_path = Path.cwd() / ".elspais.toml"
23
57
 
24
58
  if config_path.exists() and not args.force:
@@ -44,6 +78,44 @@ def run(args: argparse.Namespace) -> int:
44
78
  return 0
45
79
 
46
80
 
81
+ def create_template_requirement(args: argparse.Namespace) -> int:
82
+ """Create an example requirement file in the spec directory."""
83
+ from elspais.config import load_config
84
+
85
+ # Try to load config to find spec directory
86
+ try:
87
+ config = load_config(args.config if hasattr(args, "config") else None)
88
+ spec_dir_name = config.get("directories", {}).get("spec", "spec")
89
+ except Exception:
90
+ spec_dir_name = "spec"
91
+
92
+ spec_dir = Path.cwd() / spec_dir_name
93
+
94
+ # Create spec directory if it doesn't exist
95
+ if not spec_dir.exists():
96
+ spec_dir.mkdir(parents=True)
97
+ print(f"Created directory: {spec_dir}")
98
+
99
+ # Create example file
100
+ example_path = spec_dir / "EXAMPLE-requirement.md"
101
+
102
+ if example_path.exists() and not getattr(args, "force", False):
103
+ print(f"Example file already exists: {example_path}")
104
+ print("Use --force to overwrite.")
105
+ return 1
106
+
107
+ example_path.write_text(EXAMPLE_REQUIREMENT)
108
+ print(f"Created example requirement: {example_path}")
109
+ print()
110
+ print("Next steps:")
111
+ print(" 1. Review the example to understand the format")
112
+ print(" 2. Delete or rename it when creating real requirements")
113
+ print(" 3. Run `elspais validate` to check format compliance")
114
+ print(" 4. Run `elspais hash update` to compute content hashes")
115
+
116
+ return 0
117
+
118
+
47
119
  def generate_config(project_type: str, associated_prefix: Optional[str] = None) -> str:
48
120
  """Generate configuration file content."""
49
121
 
@@ -91,11 +163,9 @@ format = "uppercase"
91
163
  separator = "-"
92
164
 
93
165
  [rules.hierarchy]
94
- allowed_implements = [
95
- "dev -> ops, prd",
96
- "ops -> prd",
97
- "prd -> prd",
98
- ]
166
+ dev = ["dev", "ops", "prd"]
167
+ ops = ["ops", "prd"]
168
+ prd = ["prd"]
99
169
  cross_repo_implements = true
100
170
  allow_orphans = true # More permissive for associated development
101
171
 
@@ -115,7 +185,6 @@ type = "core"
115
185
  [directories]
116
186
  spec = "spec"
117
187
  docs = "docs"
118
- database = "database"
119
188
  code = ["src", "apps", "packages"]
120
189
 
121
190
  [patterns]
@@ -138,6 +207,11 @@ length = 3
138
207
  format = "uppercase"
139
208
  separator = "-"
140
209
 
210
+ [patterns.assertions]
211
+ label_style = "uppercase" # "uppercase" | "numeric" | "alphanumeric" | "numeric_1based"
212
+ # max_count = 26 # Defaults to style maximum (26 for uppercase, 100 for numeric)
213
+ # zero_pad = false # For numeric styles: "01" vs "1"
214
+
141
215
  [spec]
142
216
  index_file = "INDEX.md"
143
217
  skip_files = ["README.md", "requirements-format.md", "INDEX.md"]
@@ -148,11 +222,9 @@ skip_files = ["README.md", "requirements-format.md", "INDEX.md"]
148
222
  "dev-*.md" = "dev"
149
223
 
150
224
  [rules.hierarchy]
151
- allowed_implements = [
152
- "dev -> ops, prd",
153
- "ops -> prd",
154
- "prd -> prd",
155
- ]
225
+ dev = ["dev", "ops", "prd"]
226
+ ops = ["ops", "prd"]
227
+ prd = ["prd"]
156
228
  allow_circular = false
157
229
  allow_orphans = false
158
230
 
@@ -163,15 +235,20 @@ require_assertions = true
163
235
  require_status = true
164
236
  allowed_statuses = ["Active", "Draft", "Deprecated", "Superseded"]
165
237
 
166
- [validation]
167
- hash_algorithm = "sha256"
168
- hash_length = 8
169
-
170
- [traceability]
171
- output_formats = ["markdown", "html"]
172
- scan_patterns = [
173
- "database/**/*.sql",
174
- "src/**/*.py",
175
- "apps/**/*.dart",
176
- ]
238
+ [testing]
239
+ enabled = false
240
+ test_dirs = ["tests"]
241
+ patterns = ["test_*.py", "*_test.py"]
242
+ # result_files = ["test-results.xml"] # Uncomment to enable test result parsing
243
+ reference_keyword = "Validates"
244
+
245
+ [ignore]
246
+ # Global patterns applied everywhere
247
+ global = ["node_modules", ".git", "__pycache__", "*.pyc", ".venv", ".env"]
248
+ # Additional patterns for spec file scanning
249
+ spec = ["README.md", "INDEX.md", "drafts/**"]
250
+ # Additional patterns for code scanning
251
+ code = ["*_test.py", "conftest.py", "test_*.py"]
252
+ # Additional patterns for test scanning
253
+ test = ["fixtures/**", "__snapshots__"]
177
254
  """