elspais 0.11.1__tar.gz → 0.11.2__tar.gz
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-0.11.1 → elspais-0.11.2}/CHANGELOG.md +19 -0
- {elspais-0.11.1 → elspais-0.11.2}/PKG-INFO +1 -1
- {elspais-0.11.1 → elspais-0.11.2}/pyproject.toml +1 -1
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/__init__.py +1 -1
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/cli.py +29 -10
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/analyze.py +5 -6
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/changed.py +2 -6
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/config_cmd.py +4 -4
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/edit.py +32 -36
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/hash_cmd.py +24 -18
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/index.py +8 -7
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/init.py +4 -4
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/reformat_cmd.py +32 -43
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/rules_cmd.py +6 -2
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/trace.py +23 -19
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/validate.py +8 -10
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/config/defaults.py +7 -1
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/core/content_rules.py +0 -1
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/core/git.py +4 -10
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/core/parser.py +55 -56
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/core/patterns.py +2 -6
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/core/rules.py +10 -15
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/mcp/__init__.py +2 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/mcp/context.py +1 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/mcp/serializers.py +1 -1
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/mcp/server.py +54 -39
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/reformat/__init__.py +13 -13
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/reformat/detector.py +9 -16
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/reformat/hierarchy.py +8 -7
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/reformat/line_breaks.py +36 -38
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/reformat/prompts.py +22 -12
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/reformat/transformer.py +43 -41
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/sponsors/__init__.py +0 -2
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/testing/__init__.py +1 -1
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/testing/result_parser.py +25 -21
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/__init__.py +4 -3
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/coverage.py +5 -5
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/generators/base.py +17 -12
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/generators/csv.py +2 -6
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/generators/markdown.py +3 -8
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/__init__.py +4 -2
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/generator.py +423 -289
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/models.py +25 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/review/__init__.py +21 -18
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/review/branches.py +114 -121
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/review/models.py +232 -237
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/review/position.py +53 -71
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/review/server.py +264 -288
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/review/status.py +43 -58
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/review/storage.py +48 -72
- {elspais-0.11.1 → elspais-0.11.2}/.gitignore +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/LICENSE +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/README.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/commands.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/configuration.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/multi-repo.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/patterns.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/roadmap/llm-reformatting-integration.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/roadmap/new-format.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/roadmap/plantuml-diagram-support.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/roadmap/requirements-format-enhancements.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/rules.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/docs/trace-view.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/__main__.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/commands/__init__.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/config/__init__.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/config/loader.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/core/__init__.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/core/hasher.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/core/models.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/mcp/__main__.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/testing/config.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/testing/mapper.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/testing/scanner.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/generators/__init__.py +1 -1
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/base.html +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/components/code_viewer_modal.html +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/components/file_picker_modal.html +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/components/legend_modal.html +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/components/review_panel.html +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/help/help-panel.json +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/help/onboarding.json +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/help/tooltips.json +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-comments.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-data.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-help.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-init.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-line-numbers.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-packages.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-position.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-resize.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-status.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review/review-sync.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/review-styles.css +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/scripts.js +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/html/templates/partials/styles.css +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/scanning.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/README.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00001-template-architecture.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00002-css-extraction.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00003-js-extraction.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00004-build-embedding.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00005-test-format.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00010-review-data-models.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00011-review-storage.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00012-position-resolution.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00013-git-branches.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00014-review-api-server.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00015-status-modifier.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-d00016-js-integration.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-p00001-html-generator.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/src/elspais/trace_view/specs/tv-p00002-review-system.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/conftest.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/assertions/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/assertions/spec/dev-impl.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/assertions/spec/prd-sample.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/associated-repo/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/associated-repo/spec/dev-sponsor.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/associated-repo/spec/prd-sponsor.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/fda-style/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/fda-style/spec/dev-impl.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/fda-style/spec/prd-core.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/hht-like/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/hht-like/database/schema.sql +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/hht-like/spec/INDEX.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/hht-like/spec/dev-impl.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/hht-like/spec/ops-deploy.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/hht-like/spec/prd-core.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/invalid/broken-links/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/invalid/broken-links/spec/broken.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/invalid/circular-deps/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/invalid/circular-deps/spec/circular.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/invalid/missing-hash/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/invalid/missing-hash/spec/missing.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/jira-style/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/jira-style/requirements/features.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/named-reqs/.elspais.toml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/fixtures/named-reqs/spec/features.md +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/mcp/__init__.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/mcp/test_context.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/mcp/test_serializers.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_config.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_content_rules.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_doc_sync.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_edit.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_git.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_hash_bugs.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_hasher.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_models.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_parser.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_parser_resilience.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_patterns.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_rules.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_sponsors.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_trace_view/__init__.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_trace_view/test_integration.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/test_validate_json.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/testing/__init__.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/testing/fixtures/junit_results.xml +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/testing/fixtures/pytest_results.json +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/testing/fixtures/sample_test.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/testing/test_config.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/testing/test_mapper.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/testing/test_result_parser.py +0 -0
- {elspais-0.11.1 → elspais-0.11.2}/tests/testing/test_scanner.py +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
<!-- markdownlint-disable MD022 MD032 -->
|
|
4
|
+
<!-- Compact changelog format: no blank lines around headings/lists -->
|
|
5
|
+
|
|
3
6
|
All notable changes to elspais will be documented in this file.
|
|
4
7
|
|
|
5
8
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
@@ -7,6 +10,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
10
|
|
|
8
11
|
## [Unreleased]
|
|
9
12
|
|
|
13
|
+
## [0.11.2] - 2026-01-21
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed `elspais trace --view` crash caused by missing `is_cycle` and `cycle_path` properties in `TraceViewRequirement`
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Comprehensive git hooks (pre-commit, pre-push, commit-msg) with branch protection, linting, secret detection, and commit message format validation
|
|
22
|
+
- Commit message format validation requiring `[TICKET-NUMBER]` prefix (e.g., `[CUR-514]`)
|
|
23
|
+
- Markdownlint configuration (`.markdownlint.json`) disabling line length and duplicate heading rules
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Applied ruff and black formatting fixes across the codebase
|
|
28
|
+
|
|
10
29
|
## [0.11.1] - 2026-01-15
|
|
11
30
|
|
|
12
31
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: elspais
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.2
|
|
4
4
|
Summary: Requirements validation and traceability tools - L-Space connects all libraries
|
|
5
5
|
Project-URL: Homepage, https://github.com/anspar/elspais
|
|
6
6
|
Project-URL: Documentation, https://github.com/anspar/elspais#readme
|
|
@@ -10,7 +10,7 @@ and supports multi-repository requirement management with configurable
|
|
|
10
10
|
ID patterns and validation rules.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from importlib.metadata import
|
|
13
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
14
14
|
|
|
15
15
|
try:
|
|
16
16
|
__version__ = version("elspais")
|
|
@@ -10,7 +10,19 @@ from pathlib import Path
|
|
|
10
10
|
from typing import List, Optional
|
|
11
11
|
|
|
12
12
|
from elspais import __version__
|
|
13
|
-
from elspais.commands import
|
|
13
|
+
from elspais.commands import (
|
|
14
|
+
analyze,
|
|
15
|
+
changed,
|
|
16
|
+
config_cmd,
|
|
17
|
+
edit,
|
|
18
|
+
hash_cmd,
|
|
19
|
+
index,
|
|
20
|
+
init,
|
|
21
|
+
reformat_cmd,
|
|
22
|
+
rules_cmd,
|
|
23
|
+
trace,
|
|
24
|
+
validate,
|
|
25
|
+
)
|
|
14
26
|
|
|
15
27
|
|
|
16
28
|
def create_parser() -> argparse.ArgumentParser:
|
|
@@ -54,12 +66,14 @@ For detailed command help: elspais <command> --help
|
|
|
54
66
|
metavar="PATH",
|
|
55
67
|
)
|
|
56
68
|
parser.add_argument(
|
|
57
|
-
"-v",
|
|
69
|
+
"-v",
|
|
70
|
+
"--verbose",
|
|
58
71
|
action="store_true",
|
|
59
72
|
help="Verbose output",
|
|
60
73
|
)
|
|
61
74
|
parser.add_argument(
|
|
62
|
-
"-q",
|
|
75
|
+
"-q",
|
|
76
|
+
"--quiet",
|
|
63
77
|
action="store_true",
|
|
64
78
|
help="Suppress non-error output",
|
|
65
79
|
)
|
|
@@ -105,7 +119,8 @@ Common rules to skip:
|
|
|
105
119
|
metavar="RULE",
|
|
106
120
|
)
|
|
107
121
|
validate_parser.add_argument(
|
|
108
|
-
"-j",
|
|
122
|
+
"-j",
|
|
123
|
+
"--json",
|
|
109
124
|
action="store_true",
|
|
110
125
|
help="Output requirements as JSON (hht_diary compatible format)",
|
|
111
126
|
)
|
|
@@ -262,12 +277,14 @@ Common rules to skip:
|
|
|
262
277
|
metavar="BRANCH",
|
|
263
278
|
)
|
|
264
279
|
changed_parser.add_argument(
|
|
265
|
-
"-j",
|
|
280
|
+
"-j",
|
|
281
|
+
"--json",
|
|
266
282
|
action="store_true",
|
|
267
283
|
help="Output as JSON",
|
|
268
284
|
)
|
|
269
285
|
changed_parser.add_argument(
|
|
270
|
-
"-a",
|
|
286
|
+
"-a",
|
|
287
|
+
"--all",
|
|
271
288
|
action="store_true",
|
|
272
289
|
help="Include all changed files (not just spec)",
|
|
273
290
|
)
|
|
@@ -374,7 +391,8 @@ JSON batch format:
|
|
|
374
391
|
metavar="SECTION",
|
|
375
392
|
)
|
|
376
393
|
config_show.add_argument(
|
|
377
|
-
"-j",
|
|
394
|
+
"-j",
|
|
395
|
+
"--json",
|
|
378
396
|
action="store_true",
|
|
379
397
|
help="Output as JSON",
|
|
380
398
|
)
|
|
@@ -389,7 +407,8 @@ JSON batch format:
|
|
|
389
407
|
help="Configuration key (dot-notation, e.g., 'patterns.prefix')",
|
|
390
408
|
)
|
|
391
409
|
config_get.add_argument(
|
|
392
|
-
"-j",
|
|
410
|
+
"-j",
|
|
411
|
+
"--json",
|
|
393
412
|
action="store_true",
|
|
394
413
|
help="Output as JSON",
|
|
395
414
|
)
|
|
@@ -405,7 +424,7 @@ JSON batch format:
|
|
|
405
424
|
)
|
|
406
425
|
config_set.add_argument(
|
|
407
426
|
"value",
|
|
408
|
-
help="Value to set (
|
|
427
|
+
help="Value to set (auto-detected: bool, number, JSON array/object, string)",
|
|
409
428
|
)
|
|
410
429
|
|
|
411
430
|
# config unset
|
|
@@ -662,7 +681,7 @@ def mcp_command(args: argparse.Namespace) -> int:
|
|
|
662
681
|
if hasattr(args, "spec_dir") and args.spec_dir:
|
|
663
682
|
working_dir = args.spec_dir.parent
|
|
664
683
|
|
|
665
|
-
print(
|
|
684
|
+
print("Starting elspais MCP server...")
|
|
666
685
|
print(f"Working directory: {working_dir}")
|
|
667
686
|
print(f"Transport: {args.transport}")
|
|
668
687
|
|
|
@@ -41,16 +41,14 @@ def run_hierarchy(args: argparse.Namespace) -> int:
|
|
|
41
41
|
|
|
42
42
|
# Find root requirements (PRD with no implements)
|
|
43
43
|
roots = [
|
|
44
|
-
req
|
|
44
|
+
req
|
|
45
|
+
for req in requirements.values()
|
|
45
46
|
if req.level.upper() in ["PRD", "PRODUCT"] and not req.implements
|
|
46
47
|
]
|
|
47
48
|
|
|
48
49
|
if not roots:
|
|
49
50
|
# Fall back to all PRD requirements
|
|
50
|
-
roots = [
|
|
51
|
-
req for req in requirements.values()
|
|
52
|
-
if req.level.upper() in ["PRD", "PRODUCT"]
|
|
53
|
-
]
|
|
51
|
+
roots = [req for req in requirements.values() if req.level.upper() in ["PRD", "PRODUCT"]]
|
|
54
52
|
|
|
55
53
|
printed = set()
|
|
56
54
|
|
|
@@ -153,7 +151,8 @@ def run_coverage(args: argparse.Namespace) -> int:
|
|
|
153
151
|
|
|
154
152
|
# List unimplemented PRD
|
|
155
153
|
unimplemented = [
|
|
156
|
-
req
|
|
154
|
+
req
|
|
155
|
+
for req in requirements.values()
|
|
157
156
|
if req.level.upper() in ["PRD", "PRODUCT"] and req.id not in implemented_prd
|
|
158
157
|
]
|
|
159
158
|
|
|
@@ -11,13 +11,11 @@ import argparse
|
|
|
11
11
|
import json
|
|
12
12
|
import sys
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Dict,
|
|
14
|
+
from typing import Dict, Optional
|
|
15
15
|
|
|
16
16
|
from elspais.config.defaults import DEFAULT_CONFIG
|
|
17
17
|
from elspais.config.loader import find_config_file, load_config
|
|
18
18
|
from elspais.core.git import (
|
|
19
|
-
GitChangeInfo,
|
|
20
|
-
MovedRequirement,
|
|
21
19
|
detect_moved_requirements,
|
|
22
20
|
filter_spec_files,
|
|
23
21
|
get_current_req_locations,
|
|
@@ -76,9 +74,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
76
74
|
|
|
77
75
|
# Detect moved requirements
|
|
78
76
|
current_locations = get_current_req_locations(repo_root, spec_dir)
|
|
79
|
-
moved = detect_moved_requirements(
|
|
80
|
-
changes.committed_req_locations, current_locations
|
|
81
|
-
)
|
|
77
|
+
moved = detect_moved_requirements(changes.committed_req_locations, current_locations)
|
|
82
78
|
|
|
83
79
|
# Build result
|
|
84
80
|
result = {
|
|
@@ -8,15 +8,14 @@ import argparse
|
|
|
8
8
|
import json
|
|
9
9
|
import sys
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
11
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
12
12
|
|
|
13
|
+
from elspais.config.defaults import DEFAULT_CONFIG
|
|
13
14
|
from elspais.config.loader import (
|
|
14
15
|
find_config_file,
|
|
15
16
|
load_config,
|
|
16
|
-
merge_configs,
|
|
17
17
|
parse_toml,
|
|
18
18
|
)
|
|
19
|
-
from elspais.config.defaults import DEFAULT_CONFIG
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
def run(args: argparse.Namespace) -> int:
|
|
@@ -255,6 +254,7 @@ def cmd_path(args: argparse.Namespace) -> int:
|
|
|
255
254
|
|
|
256
255
|
# Helper functions
|
|
257
256
|
|
|
257
|
+
|
|
258
258
|
def _get_config_path(args: argparse.Namespace) -> Optional[Path]:
|
|
259
259
|
"""Get configuration file path from args or by discovery."""
|
|
260
260
|
if hasattr(args, "config") and args.config:
|
|
@@ -363,7 +363,7 @@ def _print_value(value: Any, prefix: str = "") -> None:
|
|
|
363
363
|
if prefix:
|
|
364
364
|
print(f"{prefix} = {'true' if value else 'false'}")
|
|
365
365
|
else:
|
|
366
|
-
print(
|
|
366
|
+
print("true" if value else "false")
|
|
367
367
|
elif isinstance(value, str):
|
|
368
368
|
if prefix:
|
|
369
369
|
print(f'{prefix} = "{value}"')
|
|
@@ -22,7 +22,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
22
22
|
from elspais.config.loader import find_config_file, get_spec_directories, load_config
|
|
23
23
|
|
|
24
24
|
# Load configuration
|
|
25
|
-
config_path = args.config if hasattr(args,
|
|
25
|
+
config_path = args.config if hasattr(args, "config") else None
|
|
26
26
|
if config_path is None:
|
|
27
27
|
config_path = find_config_file(Path.cwd())
|
|
28
28
|
if config_path and config_path.exists():
|
|
@@ -31,7 +31,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
31
31
|
config = DEFAULT_CONFIG
|
|
32
32
|
|
|
33
33
|
# Get spec directories
|
|
34
|
-
spec_dir = args.spec_dir if hasattr(args,
|
|
34
|
+
spec_dir = args.spec_dir if hasattr(args, "spec_dir") and args.spec_dir else None
|
|
35
35
|
spec_dirs = get_spec_directories(spec_dir, config)
|
|
36
36
|
if not spec_dirs:
|
|
37
37
|
print("Error: No spec directories found", file=sys.stderr)
|
|
@@ -40,16 +40,16 @@ def run(args: argparse.Namespace) -> int:
|
|
|
40
40
|
# Use first spec dir as base
|
|
41
41
|
base_spec_dir = spec_dirs[0]
|
|
42
42
|
|
|
43
|
-
dry_run = getattr(args,
|
|
43
|
+
dry_run = getattr(args, "dry_run", False)
|
|
44
44
|
|
|
45
|
-
validate_refs = getattr(args,
|
|
45
|
+
validate_refs = getattr(args, "validate_refs", False)
|
|
46
46
|
|
|
47
47
|
# Handle batch mode
|
|
48
|
-
if hasattr(args,
|
|
48
|
+
if hasattr(args, "from_json") and args.from_json:
|
|
49
49
|
return run_batch_edit(args.from_json, base_spec_dir, dry_run, validate_refs)
|
|
50
50
|
|
|
51
51
|
# Handle single edit mode
|
|
52
|
-
if hasattr(args,
|
|
52
|
+
if hasattr(args, "req_id") and args.req_id:
|
|
53
53
|
return run_single_edit(args, base_spec_dir, dry_run)
|
|
54
54
|
|
|
55
55
|
print("Error: Must specify --req-id or --from-json", file=sys.stderr)
|
|
@@ -109,18 +109,18 @@ def run_single_edit(args: argparse.Namespace, spec_dir: Path, dry_run: bool) ->
|
|
|
109
109
|
results = []
|
|
110
110
|
|
|
111
111
|
# Apply implements change
|
|
112
|
-
if hasattr(args,
|
|
112
|
+
if hasattr(args, "implements") and args.implements is not None:
|
|
113
113
|
impl_list = [i.strip() for i in args.implements.split(",")]
|
|
114
114
|
result = modify_implements(file_path, req_id, impl_list, dry_run=dry_run)
|
|
115
115
|
results.append(("implements", result))
|
|
116
116
|
|
|
117
117
|
# Apply status change
|
|
118
|
-
if hasattr(args,
|
|
118
|
+
if hasattr(args, "status") and args.status:
|
|
119
119
|
result = modify_status(file_path, req_id, args.status, dry_run=dry_run)
|
|
120
120
|
results.append(("status", result))
|
|
121
121
|
|
|
122
122
|
# Apply move
|
|
123
|
-
if hasattr(args,
|
|
123
|
+
if hasattr(args, "move_to") and args.move_to:
|
|
124
124
|
dest_path = spec_dir / args.move_to
|
|
125
125
|
result = move_requirement(file_path, dest_path, req_id, dry_run=dry_run)
|
|
126
126
|
results.append(("move", result))
|
|
@@ -157,14 +157,14 @@ def find_requirement_in_files(
|
|
|
157
157
|
Dict with file_path, req_id, line_number, or None if not found
|
|
158
158
|
"""
|
|
159
159
|
# Pattern to match requirement header
|
|
160
|
-
pattern = re.compile(rf
|
|
160
|
+
pattern = re.compile(rf"^#\s*{re.escape(req_id)}:", re.MULTILINE)
|
|
161
161
|
|
|
162
162
|
for md_file in spec_dir.rglob("*.md"):
|
|
163
163
|
content = md_file.read_text()
|
|
164
164
|
match = pattern.search(content)
|
|
165
165
|
if match:
|
|
166
166
|
# Count line number
|
|
167
|
-
line_number = content[:match.start()].count(
|
|
167
|
+
line_number = content[: match.start()].count("\n") + 1
|
|
168
168
|
return {
|
|
169
169
|
"file_path": md_file,
|
|
170
170
|
"req_id": req_id,
|
|
@@ -195,7 +195,7 @@ def modify_implements(
|
|
|
195
195
|
content = file_path.read_text()
|
|
196
196
|
|
|
197
197
|
# Find the requirement header
|
|
198
|
-
req_pattern = re.compile(rf
|
|
198
|
+
req_pattern = re.compile(rf"^(#\s*{re.escape(req_id)}:[^\n]*\n)", re.MULTILINE)
|
|
199
199
|
req_match = req_pattern.search(content)
|
|
200
200
|
|
|
201
201
|
if not req_match:
|
|
@@ -203,9 +203,9 @@ def modify_implements(
|
|
|
203
203
|
|
|
204
204
|
# Find the **Implements**: field after the header
|
|
205
205
|
start_pos = req_match.end()
|
|
206
|
-
search_region = content[start_pos:start_pos + 500]
|
|
206
|
+
search_region = content[start_pos : start_pos + 500]
|
|
207
207
|
|
|
208
|
-
impl_pattern = re.compile(r
|
|
208
|
+
impl_pattern = re.compile(r"(\*\*Implements\*\*:\s*)([^|\n]+)")
|
|
209
209
|
impl_match = impl_pattern.search(search_region)
|
|
210
210
|
|
|
211
211
|
if not impl_match:
|
|
@@ -272,7 +272,7 @@ def modify_status(
|
|
|
272
272
|
content = file_path.read_text()
|
|
273
273
|
|
|
274
274
|
# Find the requirement header
|
|
275
|
-
req_pattern = re.compile(rf
|
|
275
|
+
req_pattern = re.compile(rf"^(#\s*{re.escape(req_id)}:[^\n]*\n)", re.MULTILINE)
|
|
276
276
|
req_match = req_pattern.search(content)
|
|
277
277
|
|
|
278
278
|
if not req_match:
|
|
@@ -280,9 +280,9 @@ def modify_status(
|
|
|
280
280
|
|
|
281
281
|
# Find the **Status**: field after the header
|
|
282
282
|
start_pos = req_match.end()
|
|
283
|
-
search_region = content[start_pos:start_pos + 500]
|
|
283
|
+
search_region = content[start_pos : start_pos + 500]
|
|
284
284
|
|
|
285
|
-
status_pattern = re.compile(r
|
|
285
|
+
status_pattern = re.compile(r"(\*\*Status\*\*:\s*)(\w+)")
|
|
286
286
|
status_match = status_pattern.search(search_region)
|
|
287
287
|
|
|
288
288
|
if not status_match:
|
|
@@ -342,11 +342,8 @@ def move_requirement(
|
|
|
342
342
|
# Find the requirement block
|
|
343
343
|
# Pattern: # REQ-xxx: title ... *End* *title* | **Hash**: xxx\n---
|
|
344
344
|
req_pattern = re.compile(
|
|
345
|
-
rf
|
|
346
|
-
|
|
347
|
-
rf'\*End\*[^\n]*\n'
|
|
348
|
-
rf'(?:---\n)?)',
|
|
349
|
-
re.MULTILINE | re.DOTALL
|
|
345
|
+
rf"(^#\s*{re.escape(req_id)}:[^\n]*\n" rf".*?" rf"\*End\*[^\n]*\n" rf"(?:---\n)?)",
|
|
346
|
+
re.MULTILINE | re.DOTALL,
|
|
350
347
|
)
|
|
351
348
|
|
|
352
349
|
req_match = req_pattern.search(source_content)
|
|
@@ -361,9 +358,9 @@ def move_requirement(
|
|
|
361
358
|
req_block = req_block.rstrip() + "\n---\n"
|
|
362
359
|
|
|
363
360
|
# Remove from source
|
|
364
|
-
new_source_content = source_content[:req_match.start()] + source_content[req_match.end():]
|
|
361
|
+
new_source_content = source_content[: req_match.start()] + source_content[req_match.end() :]
|
|
365
362
|
# Clean up extra blank lines
|
|
366
|
-
new_source_content = re.sub(r
|
|
363
|
+
new_source_content = re.sub(r"\n{3,}", "\n\n", new_source_content)
|
|
367
364
|
|
|
368
365
|
# Add to destination
|
|
369
366
|
dest_content = dest_file.read_text() if dest_file.exists() else ""
|
|
@@ -400,8 +397,9 @@ def collect_all_req_ids(spec_dir: Path) -> set:
|
|
|
400
397
|
Set of requirement IDs found (short form, e.g., "p00001")
|
|
401
398
|
"""
|
|
402
399
|
import re
|
|
400
|
+
|
|
403
401
|
req_ids = set()
|
|
404
|
-
pattern = re.compile(r
|
|
402
|
+
pattern = re.compile(r"^#\s*(REQ-[A-Za-z0-9-]+):", re.MULTILINE)
|
|
405
403
|
|
|
406
404
|
for md_file in spec_dir.rglob("*.md"):
|
|
407
405
|
content = md_file.read_text()
|
|
@@ -453,11 +451,13 @@ def batch_edit(
|
|
|
453
451
|
# Find the requirement
|
|
454
452
|
location = find_requirement_in_files(spec_dir, req_id)
|
|
455
453
|
if not location:
|
|
456
|
-
results.append(
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
454
|
+
results.append(
|
|
455
|
+
{
|
|
456
|
+
"success": False,
|
|
457
|
+
"req_id": req_id,
|
|
458
|
+
"error": f"Requirement {req_id} not found",
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
461
|
continue
|
|
462
462
|
|
|
463
463
|
file_path = location["file_path"]
|
|
@@ -493,9 +493,7 @@ def batch_edit(
|
|
|
493
493
|
|
|
494
494
|
# Apply status change
|
|
495
495
|
if "status" in change:
|
|
496
|
-
status_result = modify_status(
|
|
497
|
-
file_path, req_id, change["status"], dry_run=dry_run
|
|
498
|
-
)
|
|
496
|
+
status_result = modify_status(file_path, req_id, change["status"], dry_run=dry_run)
|
|
499
497
|
if not status_result["success"]:
|
|
500
498
|
result = status_result
|
|
501
499
|
result["req_id"] = req_id
|
|
@@ -506,9 +504,7 @@ def batch_edit(
|
|
|
506
504
|
# Apply move (must be last since it changes file location)
|
|
507
505
|
if "move_to" in change:
|
|
508
506
|
dest_path = spec_dir / change["move_to"]
|
|
509
|
-
move_result = move_requirement(
|
|
510
|
-
file_path, dest_path, req_id, dry_run=dry_run
|
|
511
|
-
)
|
|
507
|
+
move_result = move_requirement(file_path, dest_path, req_id, dry_run=dry_run)
|
|
512
508
|
if not move_result["success"]:
|
|
513
509
|
result = move_result
|
|
514
510
|
result["req_id"] = req_id
|
|
@@ -105,15 +105,15 @@ def run_update(args: argparse.Namespace) -> int:
|
|
|
105
105
|
print(f"Updating {len(updates)} hashes...")
|
|
106
106
|
for req_id, req, new_hash in updates:
|
|
107
107
|
result = update_hash_in_file(req, new_hash)
|
|
108
|
-
if result[
|
|
108
|
+
if result["updated"]:
|
|
109
109
|
print(f" ✓ {req_id}")
|
|
110
|
-
old_hash = result[
|
|
110
|
+
old_hash = result["old_hash"] or "(none)"
|
|
111
111
|
print(f" [INFO] Hash: {old_hash} -> {result['new_hash']}")
|
|
112
|
-
if result[
|
|
112
|
+
if result["title_fixed"]:
|
|
113
113
|
print(f" [INFO] Title fixed: \"{result['old_title']}\" -> \"{req.title}\"")
|
|
114
114
|
else:
|
|
115
115
|
print(f" ✗ {req_id}")
|
|
116
|
-
print(
|
|
116
|
+
print(" [WARN] Could not find End marker to update")
|
|
117
117
|
|
|
118
118
|
return 0
|
|
119
119
|
|
|
@@ -168,11 +168,11 @@ def update_hash_in_file(req: Requirement, new_hash: str) -> dict:
|
|
|
168
168
|
import re
|
|
169
169
|
|
|
170
170
|
result = {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
"updated": False,
|
|
172
|
+
"old_hash": req.hash,
|
|
173
|
+
"new_hash": new_hash,
|
|
174
|
+
"title_fixed": False,
|
|
175
|
+
"old_title": None,
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
if not req.file_path:
|
|
@@ -186,35 +186,41 @@ def update_hash_in_file(req: Requirement, new_hash: str) -> dict:
|
|
|
186
186
|
# This handles both: (1) normal case, (2) mismatched title case
|
|
187
187
|
|
|
188
188
|
# First try: match by correct title (handles case where titles match)
|
|
189
|
-
pattern_by_title =
|
|
189
|
+
pattern_by_title = (
|
|
190
|
+
rf"^\*End\*\s+\*{re.escape(req.title)}\*\s*\|\s*\*\*Hash\*\*:\s*[a-fA-F0-9]+\s*$"
|
|
191
|
+
)
|
|
190
192
|
if re.search(pattern_by_title, content, re.MULTILINE):
|
|
191
193
|
content, count = re.subn(pattern_by_title, new_end_line, content, flags=re.MULTILINE)
|
|
192
194
|
if count > 0:
|
|
193
|
-
result[
|
|
195
|
+
result["updated"] = True
|
|
194
196
|
else:
|
|
195
197
|
# Second try: find by hash value (handles mismatched title)
|
|
196
198
|
# Pattern: *End* *AnyTitle* | **Hash**: oldhash
|
|
197
|
-
pattern_by_hash =
|
|
199
|
+
pattern_by_hash = (
|
|
200
|
+
rf"^\*End\*\s+\*([^*]+)\*\s*\|\s*\*\*Hash\*\*:\s*{re.escape(req.hash)}\s*$"
|
|
201
|
+
)
|
|
198
202
|
match = re.search(pattern_by_hash, content, re.MULTILINE)
|
|
199
203
|
|
|
200
204
|
if match:
|
|
201
205
|
old_title = match.group(1)
|
|
202
206
|
if old_title != req.title:
|
|
203
|
-
result[
|
|
204
|
-
result[
|
|
207
|
+
result["title_fixed"] = True
|
|
208
|
+
result["old_title"] = old_title
|
|
205
209
|
|
|
206
210
|
# Replace entire line (only first match to avoid affecting other reqs)
|
|
207
|
-
content = re.sub(
|
|
208
|
-
|
|
211
|
+
content = re.sub(
|
|
212
|
+
pattern_by_hash, new_end_line, content, count=1, flags=re.MULTILINE
|
|
213
|
+
)
|
|
214
|
+
result["updated"] = True
|
|
209
215
|
else:
|
|
210
216
|
# Add hash to end marker (no existing hash)
|
|
211
217
|
# Pattern: *End* *Title* (without hash)
|
|
212
218
|
pattern = rf"^(\*End\*\s+\*{re.escape(req.title)}\*)(?!\s*\|\s*\*\*Hash\*\*)\s*$"
|
|
213
219
|
content, count = re.subn(pattern, new_end_line, content, flags=re.MULTILINE)
|
|
214
220
|
if count > 0:
|
|
215
|
-
result[
|
|
221
|
+
result["updated"] = True
|
|
216
222
|
|
|
217
|
-
if result[
|
|
223
|
+
if result["updated"]:
|
|
218
224
|
req.file_path.write_text(content, encoding="utf-8")
|
|
219
225
|
|
|
220
226
|
return result
|
|
@@ -58,6 +58,7 @@ def run_validate(args: argparse.Namespace) -> int:
|
|
|
58
58
|
indexed_ids = set()
|
|
59
59
|
|
|
60
60
|
import re
|
|
61
|
+
|
|
61
62
|
for match in re.finditer(r"\|\s*([A-Z]+-(?:[A-Z]+-)?[a-zA-Z]?\d+)\s*\|", index_content):
|
|
62
63
|
indexed_ids.add(match.group(1))
|
|
63
64
|
|
|
@@ -155,12 +156,12 @@ def generate_index(requirements: dict, config: dict) -> str:
|
|
|
155
156
|
|
|
156
157
|
lines.append("")
|
|
157
158
|
|
|
158
|
-
lines.extend(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
lines.extend(
|
|
160
|
+
[
|
|
161
|
+
"---",
|
|
162
|
+
"",
|
|
163
|
+
"*Generated by elspais*",
|
|
164
|
+
]
|
|
165
|
+
)
|
|
163
166
|
|
|
164
167
|
return "\n".join(lines)
|
|
165
|
-
|
|
166
|
-
|
|
@@ -50,7 +50,7 @@ def generate_config(project_type: str, associated_prefix: Optional[str] = None)
|
|
|
50
50
|
if project_type == "associated":
|
|
51
51
|
if associated_prefix is None:
|
|
52
52
|
associated_prefix = "XXX" # Placeholder if not provided
|
|
53
|
-
return f
|
|
53
|
+
return f"""# elspais configuration - Associated Repository
|
|
54
54
|
# Generated by: elspais init --type associated
|
|
55
55
|
|
|
56
56
|
[project]
|
|
@@ -102,10 +102,10 @@ allow_orphans = true # More permissive for associated development
|
|
|
102
102
|
[rules.format]
|
|
103
103
|
require_hash = true
|
|
104
104
|
require_assertions = true
|
|
105
|
-
|
|
105
|
+
"""
|
|
106
106
|
|
|
107
107
|
else: # core
|
|
108
|
-
return
|
|
108
|
+
return """# elspais configuration - Core Repository
|
|
109
109
|
# Generated by: elspais init
|
|
110
110
|
|
|
111
111
|
[project]
|
|
@@ -174,4 +174,4 @@ scan_patterns = [
|
|
|
174
174
|
"src/**/*.py",
|
|
175
175
|
"apps/**/*.dart",
|
|
176
176
|
]
|
|
177
|
-
|
|
177
|
+
"""
|