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/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.
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
return 1
|
|
26
|
+
from elspais.config import get_config, get_spec_directories
|
|
27
|
+
from elspais.graph.factory import build_graph
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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"
|
|
83
|
+
print(f" {req_id}")
|
|
74
84
|
|
|
75
85
|
if extra:
|
|
76
|
-
print(f"
|
|
86
|
+
print(f"Extra in INDEX.md ({len(extra)}):")
|
|
77
87
|
for req_id in sorted(extra):
|
|
78
|
-
print(f"
|
|
88
|
+
print(f" {req_id}")
|
|
79
89
|
|
|
80
90
|
if not missing and not extra:
|
|
81
|
-
print(f"
|
|
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
|
|
88
|
-
"""Regenerate INDEX.md from requirements."""
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
index_file.write_text(content, encoding="utf-8")
|
|
109
|
+
# Generate markdown
|
|
110
|
+
lines = ["# Requirements Index", ""]
|
|
118
111
|
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
[
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
[
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
"""
|